All Posts

  • 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

    void 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;
    }
    

    Read More
  • UE4 LightMap Directionality

    …

    UE4 LightMap Directionality

    alt text

    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:

    alt text

    Without directionality, Unreal uses 0.6 as an empirical value:

    alt text

    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 normal to interact with world normal to 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:

    alt text

    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
  • Analysis Android Game Crash

    …

    Read More
  • Use Scene to Monitor Android Power Consumption

    …

    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
  • Graphics Quality Configuration for Android

    …

    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