- DistanceField Generation of Unreal
…
DistanceField Generation of Unreal
// Runtime\Engine\Private\StaticMesh.cpp void UStaticMesh::Serialize(FArchive& Ar)then
// Runtime\Engine\Private\StaticMesh.cpp FStaticMeshRenderData::Cache { ... static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.GenerateMeshDistanceFields")); if (CVar->GetValueOnAnyThread(true) != 0 || Owner->bGenerateMeshDistanceField) { if (LODResources.IsValidIndex(0)) { if (!LODResources[0].DistanceFieldData) { LODResources[0].DistanceFieldData = new FDistanceFieldVolumeData(); LODResources[0].DistanceFieldData->AssetName = Owner->GetFName(); } // Only generate distance fields and card representations for the base render data, not platform render data. if (this == Owner->GetRenderData()) { const FMeshBuildSettings& BuildSettings = Owner->GetSourceModel(0).BuildSettings; UStaticMesh* MeshToGenerateFrom = BuildSettings.DistanceFieldReplacementMesh ? ToRawPtr(BuildSettings.DistanceFieldReplacementMesh) : Owner; if (BuildSettings.DistanceFieldReplacementMesh) { // Make sure dependency is postloaded BuildSettings.DistanceFieldReplacementMesh->ConditionalPostLoad(); } LODResources[0].DistanceFieldData->CacheDerivedData(Owner, MeshToGenerateFrom, BuildSettings.DistanceFieldResolutionScale, BuildSettings.bGenerateDistanceFieldAsIfTwoSided); } ... } ... } ... }only build for lod0
then
// Runtime\Engine\Private\DistanceFieldAtlas.cpp void FDistanceFieldAsyncQueue::Build(FAsyncDistanceFieldTask* Task, FQueuedThreadPool& BuildThreadPool) { ... GenerateSignedDistanceFieldVolumeData() ... }then
https://github.com/RenderKit/embree
it generates sparse distance field data with mips
// Developer\MeshUtilities\Private\MeshDistanceFieldUtilities.cpp void FMeshUtilities::GenerateSignedDistanceFieldVolumeData() { ... SetupEmbreeScene() AddMeshDataToEmbreeScene() BuildSignedDistanceField() DeleteEmbreeScene() ... }then
static void BuildSignedDistanceField() { ... for (int32 MipIndex = 0; MipIndex < DistanceField::NumMips; MipIndex++) { ... TArray<FSparseMeshDistanceFieldAsyncTask> AsyncTasks; AsyncTasks.Reserve(IndirectionDimensions.X * IndirectionDimensions.Y * IndirectionDimensions.Z); for (int32 ZIndex = 0; ZIndex < IndirectionDimensions.Z; ZIndex++) { for (int32 YIndex = 0; YIndex < IndirectionDimensions.Y; YIndex++) { for (int32 XIndex = 0; XIndex < IndirectionDimensions.X; XIndex++) { AsyncTasks.Emplace( EmbreeScene, &SampleDirections, LocalSpaceTraceDistance, DistanceFieldVolumeBounds, LocalToVolumeScale, DistanceFieldToVolumeScaleBias, FInt32Vector(XIndex, YIndex, ZIndex), IndirectionDimensions, bUsePointQuery); } } } ... } }find closets point of each voxel
Read Morevoid FSparseMeshDistanceFieldAsyncTask::DoWork() { ... for (int32 ZIndex = 0; ZIndex < DistanceField::BrickSize; ZIndex++) { for (int32 YIndex = 0; YIndex < DistanceField::BrickSize; YIndex++) { for (int32 XIndex = 0; XIndex < DistanceField::BrickSize; XIndex++) { ... if (bUsePointQuery) { RTCPointQuery PointQuery; PointQuery.x = VoxelPosition.X; PointQuery.y = VoxelPosition.Y; PointQuery.z = VoxelPosition.Z; PointQuery.time = 0; PointQuery.radius = LocalSpaceTraceDistance; FEmbreePointQueryContext QueryContext; rtcInitPointQueryContext(&QueryContext); QueryContext.Scene = &EmbreeScene; float ClosestUnsignedDistanceSq = (LocalSpaceTraceDistance * 2.0f) * (LocalSpaceTraceDistance * 2.0f); rtcPointQuery(EmbreeScene.Scene, &PointQuery, &QueryContext, EmbreePointQueryFunction, &ClosestUnsignedDistanceSq); const float ClosestDistance = FMath::Sqrt(ClosestUnsignedDistanceSq); bTraceRays = ClosestDistance <= LocalSpaceTraceDistance; MinLocalSpaceDistance = FMath::Min(MinLocalSpaceDistance, ClosestDistance); } ... } } } } bool EmbreePointQueryFunction(RTCPointQueryFunctionArguments* args) { const FEmbreePointQueryContext* Context = (const FEmbreePointQueryContext*)args->context; check(args->userPtr); float& ClosestDistanceSq = *(float*)(args->userPtr); int32 GeometryIndex = args->geomID; if (Context->instID[0] != RTC_INVALID_GEOMETRY_ID) { // when testing against a geometry instance use instID to index into Scene->Geometries GeometryIndex = Context->instID[0]; } const FEmbreeGeometryAsset* GeometryAsset = Context->Scene->Geometries[GeometryIndex].Asset; const int32 NumTriangles = GeometryAsset->NumTriangles; const int32 TriangleIndex = args->primID; check(TriangleIndex < NumTriangles); const FVector3f* VertexBuffer = (const FVector3f*)GeometryAsset->VertexArray.GetData(); const uint32* IndexBuffer = (const uint32*)GeometryAsset->IndexArray.GetData(); const uint32 I0 = IndexBuffer[TriangleIndex * 3 + 0]; const uint32 I1 = IndexBuffer[TriangleIndex * 3 + 1]; const uint32 I2 = IndexBuffer[TriangleIndex * 3 + 2]; FVector3f V0 = VertexBuffer[I0]; FVector3f V1 = VertexBuffer[I1]; FVector3f V2 = VertexBuffer[I2]; if (Context->instID[0] != RTC_INVALID_GEOMETRY_ID) { // when testing against a geometry instance need to transform vertices to world space FMatrix44f* InstToWorld = (FMatrix44f*)Context->inst2world[0]; V0 = InstToWorld->TransformPosition(V0); V1 = InstToWorld->TransformPosition(V1); V2 = InstToWorld->TransformPosition(V2); } const FVector3f QueryPosition(args->query->x, args->query->y, args->query->z); const FVector3f ClosestPoint = (FVector3f)FMath::ClosestPointOnTriangleToPoint((FVector)QueryPosition, (FVector)V0, (FVector)V1, (FVector)V2); const float QueryDistanceSq = (ClosestPoint - QueryPosition).SizeSquared(); if (QueryDistanceSq < ClosestDistanceSq) { ClosestDistanceSq = QueryDistanceSq; bool bShrinkQuery = true; if (bShrinkQuery) { args->query->radius = FMath::Sqrt(ClosestDistanceSq); // Return true to indicate that the query radius has shrunk return true; } } // Return false to indicate that the query radius hasn't changed return false; }
- UE4 LightMap Directionality
…
UE4 LightMap Directionality

From UE4 Lightmap Format Analysis, Unreal encodes a direction channel in the lower half of the lightmap to interact with the pixel normal.
With directionality:

Without directionality, Unreal uses 0.6 as an empirical value:

Some mobile games discard the lower half to reduce lightmap size, resulting in very flat lighting with a normal map. If your game uses a forward pipeline, you can utilize
geometry normalto interact withworld normalto achieve better results with the same lightmap size.The code is as follows:
// old directionality // float4 SH = Lightmap1 * GetLightmapData(LightmapDataIndex).LightMapScale[1] + GetLightmapData(LightmapDataIndex).LightMapAdd[1]; // 1 vmad // half Directionality = max( 0.0, dot( SH, float4(WorldNormal.yzx, 1) ) ); // 1 dot, 1 smax // faked directionality half Directionality = 0.6 * max( 0.0, dot( normalize(VertexNormal), normalize(WorldNormal) ) );Result:

Our lightmap now has more detail. Even though we discard the directionality, we still use the empirical value of 0.6 to adjust luminance, resulting in slight differences compared to the original.
Read More
- (WIP)Unreal Diffuse Indirect Light Explain
…
LightMap
SH
ILC
VLM
IRRADIANCE VOLUME
Read More
- penantumbrathegame
generate by gemini
Read More
- (WIP) Compute Shader cheatsheet
…
Compute Shader Basics
GPU are designed to execute paralled works, we can divide paralled graphics or non-graphics works into group to utilize gpu.
Sync
Shared Memory
WorkGroupSize
CS vs PS
Mobile
Read More
- Shadow Lod/Proxy Trap
some optimizations techs are not silver bullet
Read More
- Debug Metal GPU Crash
open Edit Scheme and open Shader Validation
Read More
- Compare Shadow Map Atlas and Shadow Map Texture Array
some notes on shadowmap rt format
Read More
- Explain XXXView in DirectX
what is RenderTargetView/DepthStencilView/ShaderResourceView means
Read More
- Shadow Compression
some method to compress shadow map
Read More
- VRS The Invisble Scenes
Tier 2 VRS provides a way to specify a shading rate texture for rasterization. Content rendered behind the UI can use a lower shading rate to reduce pixel shader workload.
Read More
- Reduce unreal shader permutation
some ways to reduce ue4 shader permutation count
Read More
- Shader Optimization Cheatsheet
my shader programming cheatsheet
Read More
- How to correctly add engine feature
When developing a game engine, adding new features requires careful consideration of how they are enabled, configured, and controlled. This guide outlines several common methods for managing engine features, helping you choose the most appropriate approach for your use case.
Read More