Calculate lightmap resolution to match the ideal lightmap density for a mesh

Given the ideal lightmap density (0.2), what are the formulas/steps to calculate the ideal lightmap resolution for a mesh?

I tried :

  • Calculating the area of the mesh
  • Calculating the volume of the mesh
  • Calculating the perimeter of the mesh
    And then multiplicating the resulting value with the ideal lightmap density value, but none of these solutions works (approaching the max density).

It would be LightmapDensityMeshSurfaceAreaAdjustmentFactor, where Adjustment factor is a ratio between occupied and free lightmap UV unwrap area of the mesh. That works, assuming that lightmap unwrap has uniform texel density.

You can get UVdensities from static mesh asset. This is calculated for texture streaming but can be also used to calculate lightmap size. You may need to run this script couple times becase MinLigthMap resolution affect how much padding is needed which cause Lightmap uv’s to be rebuild which affect uv density. Keep in mind that lightmap sizes does not need to be power of two but it has to be multiply of 4.(dxt block size).

This is simplified version of system that we use. I didn’t test this version but it should work just fine. I call this c++ code from Blutility script that can be started from editor.



void AutoLightmapSize()
{
    auto ObjectLibrary = UObjectLibrary::CreateLibrary(UStaticMesh::StaticClass(), false, false);
    ObjectLibrary->LoadAssetDataFromPath(TEXT("/Meshes"));
    TArray<FAssetData> AssetDatas;
    ObjectLibrary->GetAssetDataList(AssetDatas);

    for (int32 i = 0; i < AssetDatas.Num(); ++i)
    {
        UObject* object = AssetDatas*.GetAsset();
        UStaticMesh* m = Cast<UStaticMesh>(object);
        if (m == nullptr)
            continue;

        const int CompressionBlockSize = 4;
        const in MinSize = 16;
        const in MaxSize = 256;
        const float AdjustmentMagic 0.2f;
        int32 newLightMapRes = FMath::Clamp(FMath::RoundToInt(m->LightmapUVDensity * AdjustmentMagic), MinSize, MaxSize);

        int32 mod = newLightMapRes % CompressionBlockSize;
        if (mod != 0)
        {
            newLightMapRes += CompressionBlockSize - mod;
        }

        if (source.BuildSettings.MinLightmapResolution > newLightMapRes || source.BuildSettings.MinLightmapResolution % CompressionBlockSize != 0)
        {

            lightmapsize += m->LightMapResolution*m->LightMapResolution - newLightMapRes * newLightMapRes;
            dirty = true;
            source.BuildSettings.MinLightmapResolution = newLightMapRes;
        }

        if (m->LightMapResolution != m->SourceModels[0].BuildSettings.MinLightmapResolution)
        {
            dirty = true;
            m->LightMapResolution = m->SourceModels[0].BuildSettings.MinLightmapResolution;
        }

        if (dirty)
        {
            m->Build();
            m->MarkPackageDirty();
        }
    }
}


The script way is currently a potential working solution (all of my meshes turned green now), but is there a more elegant way to do it? How is the occupied UV area calculated (I assume free UV area is ‘1 - occupied’)?

I’m calculating the texel area like this :



    const int32 NumTriangles = RawMesh.WedgeIndices.Num() / 3;
    for (int32 TriangeIdx = 0; TriangeIdx < NumTriangles; TriangeIdx += 3)
    {
        const int32 Indice1 = RawMesh.WedgeIndices[TriangeIdx];
        const int32 Indice2 = RawMesh.WedgeIndices[TriangeIdx + 1];
        const int32 Indice3 = RawMesh.WedgeIndices[TriangeIdx + 2];

        const FVector2D& UV1 = RawMesh.WedgeTexCoords[1][Indice1];
        const FVector2D& UV2 = RawMesh.WedgeTexCoords[1][Indice2];
        const FVector2D& UV3 = RawMesh.WedgeTexCoords[1][Indice3];

        // See Engine/Shaders/Private/LightMapDensityShader.usf:193
        const FVector2D A = (UV2 - UV1) * (SrcModel->BuildSettings.MinLightmapResolution * 2.f);
        const FVector2D B = (UV3 - UV1) * (SrcModel->BuildSettings.MinLightmapResolution * 2.f);
        const FVector2D C = FVector2D(A.X * B.Y, A.Y * B.X);
        TexelArea += FMath::Abs(C.X - C.Y);
    }

    TexelArea *= 0.5f;


Can anyone please provide me with a full script? I’ve tried to get the script supplied by Kalle_H working with no success - it also appears incomplete. I’m new to C++ so I’m probably missing something otherwise obvious. If anyone can point me in the right direction I’d be extremely grateful

I cannot just copy whole script because its doing all the asset checking at once. In latest version there is also bug that crash editor if I don’t run the script from mobile preview. What you got now and what is the problem?