在BasePass阶段使用
DefferredShadingRenderer:Render()
if (bRenderDeferredLighting)
{
RDG_GPU_STAT_SCOPE(GraphBuilder, RenderDeferredLighting);
RDG_CSV_STAT_EXCLUSIVE_SCOPE(GraphBuilder, RenderLighting);
SCOPE_CYCLE_COUNTER(STAT_FDeferredShadingSceneRenderer_Lighting);
FRDGTextureRef DynamicBentNormalAOTexture = nullptr;
RenderDiffuseIndirectAndAmbientOcclusion(
GraphBuilder,
SceneTextures,
LumenFrameTemporaries,
LightingChannelsTexture,
bHasLumenLights,
/* bCompositeRegularLumenOnly = */ false,
/* bIsVisualizePass = */ false,
AsyncLumenIndirectLightingOutputs);
// These modulate the scenecolor output from the basepass, which is assumed to be indirect lighting
if (bAllowStaticLighting)
{
RenderIndirectCapsuleShadows(GraphBuilder, SceneTextures);
}
// These modulate the scene color output from the base pass, which is assumed to be indirect lighting
RenderDFAOAsIndirectShadowing(GraphBuilder, SceneTextures, DynamicBentNormalAOTexture);
// Clear the translucent lighting volumes before we accumulate
if ((GbEnableAsyncComputeTranslucencyLightingVolumeClear && GSupportsEfficientAsyncCompute) == false)
{
TranslucencyLightingVolumeTextures.Init(GraphBuilder, Views, ERDGPassFlags::Compute);
}
#if RHI_RAYTRACING
if (IsRayTracingEnabled())
{
RenderDitheredLODFadingOutMask(GraphBuilder, Views[0], SceneTextures.Depth.Target);
}
#endif
GraphBuilder.SetCommandListStat(GET_STATID(STAT_CLM_Lighting));
RenderLights(GraphBuilder, SceneTextures, TranslucencyLightingVolumeTextures, LightingChannelsTexture, SortedLightSet);
GraphBuilder.SetCommandListStat(GET_STATID(STAT_CLM_AfterLighting));
InjectTranslucencyLightingVolumeAmbientCubemap(GraphBuilder, Views, TranslucencyLightingVolumeTextures);
FilterTranslucencyLightingVolume(GraphBuilder, Views, TranslucencyLightingVolumeTextures);
// Do DiffuseIndirectComposite after Lights so that async Lumen work can overlap
RenderDiffuseIndirectAndAmbientOcclusion(
GraphBuilder,
SceneTextures,
LumenFrameTemporaries,
LightingChannelsTexture,
bHasLumenLights,
/* bCompositeRegularLumenOnly = */ true,
/* bIsVisualizePass = */ false,
AsyncLumenIndirectLightingOutputs);
// Render diffuse sky lighting and reflections that only operate on opaque pixels
RenderDeferredReflectionsAndSkyLighting(GraphBuilder, SceneTextures, DynamicBentNormalAOTexture);
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
// Renders debug visualizations for global illumination plugins
RenderGlobalIlluminationPluginVisualizations(GraphBuilder, LightingChannelsTexture);
#endif
AddSubsurfacePass(GraphBuilder, SceneTextures, Views);
Strata::AddStrataOpaqueRoughRefractionPasses(GraphBuilder, SceneTextures, Views);
{
RenderHairStrandsSceneColorScattering(GraphBuilder, SceneTextures.Color.Target, Scene, Views);
}
#if RHI_RAYTRACING
if (ShouldRenderRayTracingSkyLight(Scene->SkyLight)
//@todo - integrate RenderRayTracingSkyLight into RenderDiffuseIndirectAndAmbientOcclusion
&& GetViewPipelineState(Views[0]).DiffuseIndirectMethod != EDiffuseIndirectMethod::Lumen
&& ViewFamily.EngineShowFlags.GlobalIllumination)
{
FRDGTextureRef SkyLightTexture = nullptr;
FRDGTextureRef SkyLightHitDistanceTexture = nullptr;
RenderRayTracingSkyLight(GraphBuilder, SceneTextures.Color.Target, SkyLightTexture, SkyLightHitDistanceTexture);
CompositeRayTracingSkyLight(GraphBuilder, SceneTextures, SkyLightTexture, SkyLightHitDistanceTexture);
}
#endif
}
RenderDiffuseIndirectAndAmbientOcclusion
RenderDeferredReflectionsAndSkyLighting
Final Image :
基础结构
struct VTPageTableResult
{
float2 UV;
float2 dUVdx;
float2 dUVdy;
uint4 PageTableValue[2];
uint PackedRequest;
};
struct FLightmapSceneData
{
float4 StaticShadowMapMasks;
float4 InvUniformPenumbraSizes;
float4 LightMapCoordinateScaleBias;
float4 ShadowMapCoordinateScaleBias;
float4 LightMapScale[2];
float4 LightMapAdd[2];
uint4 LightmapVTPackedPageTableUniform[2];
uint4 LightmapVTPackedUniform[5];
};
Shader测具体过程:
BasePathPixelShader.usf
void GetPrecomputedIndirectLightingAndSkyLight(
FMaterialPixelParameters MaterialParameters,
FVertexFactoryInterpolantsVSToPS Interpolants,
FBasePassInterpolantsVSToPS BasePassInterpolants,
VTPageTableResult LightmapVTPageTableResult,
bool bEvaluateBackface,
float3 DiffuseDir,
float3 VolumetricLightmapBrickTextureUVs,
out float3 OutDiffuseLighting,
out float3 OutSubsurfaceLighting,
out float OutIndirectIrradiance)
{
OutIndirectIrradiance = 0;
OutDiffuseLighting = 0;
OutSubsurfaceLighting = 0;
LightmapUVType SkyOcclusionUV = (LightmapUVType)0;
uint SkyOcclusionDataIndex = 0u;
// High quality texture lightmaps
#elif HQ_TEXTURE_LIGHTMAP
LightmapUVType LightmapUV0, LightmapUV1;
uint LightmapDataIndex;
GetLightMapCoordinates(Interpolants, LightmapUV0, LightmapUV1, LightmapDataIndex);
SkyOcclusionUV = LightmapUV0;
SkyOcclusionDataIndex = LightmapDataIndex;
GetLightMapColorHQ(LightmapVTPageTableResult, LightmapUV0, LightmapUV1, LightmapDataIndex, DiffuseDir, MaterialParameters.SvPosition.xy, bEvaluateBackface, OutDiffuseLighting, OutSubsurfaceLighting);
// Low quality texture lightmaps
#elif LQ_TEXTURE_LIGHTMAP
LightmapUVType LightmapUV0, LightmapUV1;
uint LightmapDataIndex;
GetLightMapCoordinates(Interpolants, LightmapUV0, LightmapUV1, LightmapDataIndex);
GetLightMapColorLQ(LightmapVTPageTableResult, LightmapUV0, LightmapUV1, LightmapDataIndex, DiffuseDir, bEvaluateBackface, OutDiffuseLighting, OutSubsurfaceLighting, OutIndirectIrradiance);
#endif
// Apply indirect lighting scale while we have only accumulated lightmaps
OutDiffuseLighting *= View.PrecomputedIndirectLightingColorScale;
OutSubsurfaceLighting *= View.PrecomputedIndirectLightingColorScale;
float3 SkyDiffuseLighting;
float3 SkySubsurfaceLighting;
GetSkyLighting(MaterialParameters, LightmapVTPageTableResult, bEvaluateBackface, DiffuseDir, SkyOcclusionUV, SkyOcclusionDataIndex, VolumetricLightmapBrickTextureUVs, SkyDiffuseLighting, SkySubsurfaceLighting);
OutSubsurfaceLighting += SkySubsurfaceLighting;
// Sky lighting must contribute to IndirectIrradiance for ReflectionEnvironment lightmap mixing
OutDiffuseLighting += SkyDiffuseLighting;
#if HQ_TEXTURE_LIGHTMAP || LQ_TEXTURE_LIGHTMAP || CACHED_VOLUME_INDIRECT_LIGHTING || CACHED_POINT_INDIRECT_LIGHTING || PRECOMPUTED_IRRADIANCE_VOLUME_LIGHTING
OutIndirectIrradiance = Luminance(OutDiffuseLighting);
#endif
}
void GetLightMapColorHQ( VTPageTableResult LightmapVTPageTableResult, LightmapUVType LightmapUV0, LightmapUVType LightmapUV1, uint LightmapDataIndex, half3 WorldNormal, float2 SvPositionXY, bool bEvaluateBackface, out half3 OutDiffuseLighting, out half3 OutSubsurfaceLighting )
{
OutSubsurfaceLighting = 0;
half4 Lightmap0;
half4 Lightmap1;
#if LIGHTMAP_VT_ENABLED
Lightmap0 = SampleLightmapVT( LightmapVTPageTableResult, 0u, LightmapDataIndex, LightmapResourceCluster.VTLightMapTexture, LightmapResourceCluster.LightMapSampler);
Lightmap1 = SampleLightmapVT( LightmapVTPageTableResult, 1u, LightmapDataIndex, LightmapResourceCluster.VTLightMapTexture_1, LightmapResourceCluster.LightMapSampler);
#else
Lightmap0 = Texture2DSample( LightmapResourceCluster.LightMapTexture, LightmapResourceCluster.LightMapSampler, LightmapUV0 );
Lightmap1 = Texture2DSample( LightmapResourceCluster.LightMapTexture, LightmapResourceCluster.LightMapSampler, LightmapUV1 );
#endif
half LogL = Lightmap0.w;
// Add residual
LogL += Lightmap1.w * (1.0 / 255) - (0.5 / 255);
// Range scale LogL
LogL = LogL * GetLightmapData(LightmapDataIndex).LightMapScale[0].w + GetLightmapData(LightmapDataIndex).LightMapAdd[0].w;
// Range scale UVW
half3 UVW = Lightmap0.rgb * Lightmap0.rgb * GetLightmapData(LightmapDataIndex).LightMapScale[0].rgb + GetLightmapData(LightmapDataIndex).LightMapAdd[0].rgb;
// LogL -> L
const half LogBlackPoint = 0.01858136;
half L = exp2( LogL ) - LogBlackPoint;
#if USE_LM_DIRECTIONALITY
// Range scale SH. Alpha doesn't matter, will scale with zero
float4 SH = Lightmap1 * GetLightmapData(LightmapDataIndex).LightMapScale[1] + GetLightmapData(LightmapDataIndex).LightMapAdd[1];
// Sample SH with normal
half Directionality = max( 0.0, dot( SH, float4(WorldNormal.yzx, 1) ) );
#if SHADINGMODEL_REQUIRES_BACKFACE_LIGHTING
if (bEvaluateBackface)
{
half SubsurfaceDirectionality = max(0.0, dot(SH, float4(-WorldNormal.yzx, 1)));
OutSubsurfaceLighting = L * SubsurfaceDirectionality * UVW;
}
#endif
#else
half Directionality = 0.6;
#if SHADINGMODEL_REQUIRES_BACKFACE_LIGHTING
if (bEvaluateBackface)
{
OutSubsurfaceLighting = L * Directionality * UVW;
}
#endif
#endif
half Luma = L * Directionality;
half3 Color = Luma * UVW;
OutDiffuseLighting = Color;
}
VTPageTableResult LightmapVTPageTableResult = (VTPageTableResult)0.0f;
#if LIGHTMAP_VT_ENABLED
{
LightmapUVType LightmapUV0, LightmapUV1;
uint LightmapDataIndex;
GetLightMapCoordinates(Interpolants, LightmapUV0, LightmapUV1, LightmapDataIndex);
LightmapVTPageTableResult = LightmapGetVTSampleInfo(LightmapUV0, LightmapDataIndex, In.SvPosition.xy);
}
#endif
#if NEEDS_LIGHTMAP_COORDINATE
void GetLightMapCoordinates(FVertexFactoryInterpolantsVSToPS Interpolants, out float2 LightmapUV0, out float2 LightmapUV1, out uint LightmapDataIndex)
{
LightmapUV0 = Interpolants.LightMapCoordinate.xy * float2( 1, 0.5 );
LightmapUV1 = LightmapUV0 + float2( 0, 0.5 );
#if VF_USE_PRIMITIVE_SCENE_DATA && NEEDS_LIGHTMAP_COORDINATE
LightmapDataIndex = Interpolants.LightmapDataIndex;
#else
LightmapDataIndex = 0;
#endif
}
VTPageTableResult LightmapGetVTSampleInfo(float2 UV, uint LightmapDataIndex, float2 SvPositionXY)
{
UV = ScaleLightmapUV(UV, float2(1.0f, 2.0f)); // Undo transform used to pack 2 lightmap coeffs in 1 texture for the non-VT default case
return TextureLoadVirtualPageTable(LightmapResourceCluster.LightmapVirtualTexturePageTable0, LightmapResourceCluster.LightmapVirtualTexturePageTable1,
VTPageTableUniform_Unpack(GetLightmapData(LightmapDataIndex).LightmapVTPackedPageTableUniform[0], GetLightmapData(LightmapDataIndex).LightmapVTPackedPageTableUniform[1]),
UV, LIGHTMAP_VTADDRESSMODE, LIGHTMAP_VTADDRESSMODE, 0, SvPositionXY);
}
VTPageTableResult TextureLoadVirtualPageTable(
Texture2D<uint4> PageTable0, Texture2D<uint4> PageTable1,
VTPageTableUniform PageTableUniform,
float2 UV, uint AddressU, uint AddressV,
MaterialFloat MipBias, float2 SvPositionXY)
{
VTPageTableResult Result = (VTPageTableResult)0;
UV = UV * PageTableUniform.UVScale;
int vLevel = GetGlobalVirtualTextureMipBias();
#if PIXELSHADER
vLevel = TextureComputeVirtualMipLevel(Result, ddx(UV), ddy(UV), MipBias, SvPositionXY, PageTableUniform);
#endif // PIXELSHADER
UV = ApplyAddressMode(UV, AddressU, AddressV);
TextureLoadVirtualPageTableInternal(Result, PageTable0, PageTable1, PageTableUniform, UV, vLevel);
return Result;
}
void TextureLoadVirtualPageTableInternal(
in out VTPageTableResult OutResult,
Texture2D<uint4> PageTable0, Texture2D<uint4> PageTable1,
VTPageTableUniform PageTableUniform,
float2 UV, int vLevel)
{
OutResult.UV = UV * PageTableUniform.SizeInPages;
const uint vLevelClamped = clamp(vLevel, 0, int(PageTableUniform.MaxLevel));
const uint vPageX = (uint(OutResult.UV.x) + PageTableUniform.XOffsetInPages) >> vLevelClamped;
const uint vPageY = (uint(OutResult.UV.y) + PageTableUniform.YOffsetInPages) >> vLevelClamped;
OutResult.PageTableValue[0] = PageTable0.Load(int3(vPageX, vPageY, vLevelClamped));
OutResult.PageTableValue[1] = PageTable1.Load(int3(vPageX, vPageY, vLevelClamped));
// PageTableID packed in upper 4 bits of 'PackedPageTableUniform', which is the bit position we want it in for PackedRequest as well, just need to mask off extra bits
OutResult.PackedRequest = PageTableUniform.ShiftedPageTableID;
OutResult.PackedRequest |= vPageX;
OutResult.PackedRequest |= vPageY << 12;
// Feedback always encodes vLevel+1, and subtracts 1 on the CPU side.
// This allows the CPU code to know when we requested a negative vLevel which indicates that we don't have sufficient virtual texture resolution.
const uint vLevelPlusOneClamped = clamp(vLevel + 1, 0, int(PageTableUniform.MaxLevel + 1));
OutResult.PackedRequest |= vLevelPlusOneClamped << 24;
}
FLightmapSceneData GetLightmapData(uint LightmapDataIndex)
{
// Note: layout must match FLightmapSceneShaderData in C++
// Relying on optimizer to remove unused loads
FLightmapSceneData LightmapData;
uint LightmapDataBaseOffset = LightmapDataIndex * LIGHTMAP_SCENE_DATA_STRIDE;
LightmapData.StaticShadowMapMasks = LoadLightmapDataElement(LightmapDataBaseOffset + 0);
LightmapData.InvUniformPenumbraSizes = LoadLightmapDataElement(LightmapDataBaseOffset + 1);
LightmapData.LightMapCoordinateScaleBias = LoadLightmapDataElement(LightmapDataBaseOffset + 2);
LightmapData.ShadowMapCoordinateScaleBias = LoadLightmapDataElement(LightmapDataBaseOffset + 3);
LightmapData.LightMapScale[0] = LoadLightmapDataElement(LightmapDataBaseOffset + 4);
LightmapData.LightMapScale[1] = LoadLightmapDataElement(LightmapDataBaseOffset + 5);
LightmapData.LightMapAdd[0] = LoadLightmapDataElement(LightmapDataBaseOffset + 6);
LightmapData.LightMapAdd[1] = LoadLightmapDataElement(LightmapDataBaseOffset + 7);
LightmapData.LightmapVTPackedPageTableUniform[0] = asuint(LoadLightmapDataElement(LightmapDataBaseOffset + 8));
LightmapData.LightmapVTPackedPageTableUniform[1] = asuint(LoadLightmapDataElement(LightmapDataBaseOffset + 9));
UNROLL
for (uint i = 0u; i < 5u; ++i)
{
LightmapData.LightmapVTPackedUniform[i] = asuint(LoadLightmapDataElement(LightmapDataBaseOffset + 10 + i));
}
return LightmapData;
}
#define LIGHTMAP_SCENE_DATA_STRIDE 15
#if USE_GLOBAL_GPU_LIGHTMAP_DATA
StructuredBuffer<float4> GPUSceneLightmapData;
#endif
float4 LoadLightmapDataElement(uint Index)
{
#if USE_GLOBAL_GPU_LIGHTMAP_DATA
checkStructuredBufferAccessSlow(GPUSceneLightmapData, Index);
return GPUSceneLightmapData[Index];
#else
checkStructuredBufferAccessSlow(View.LightmapSceneData, Index);
return View.LightmapSceneData[Index];
#endif
}
C++测具体过程:
GPUScene.cpp
template<typename FUploadDataSourceAdapter>
void FGPUScene::UpdateBufferState(FRDGBuilder& GraphBuilder, FScene& Scene, const FUploadDataSourceAdapter& UploadDataSourceAdapter)
{
LLM_SCOPE_BYTAG(GPUScene);
ensure(bInBeginEndBlock);
ensure(bIsEnabled == UseGPUScene(GMaxRHIShaderPlatform, Scene.GetFeatureLevel()));
ensure(NumScenePrimitives == Scene.Primitives.Num());
// Multi-GPU support : Updating on all GPUs is inefficient for AFR. Work is wasted
// for any primitives that update on consecutive frames.
RDG_GPU_MASK_SCOPE(GraphBuilder, FRHIGPUMask::All());
constexpr int32 InitialBufferSize = 256;
const uint32 LightMapDataBufferSize = FMath::RoundUpToPowerOfTwo(FMath::Max(LightmapDataAllocator.GetMaxSize(), InitialBufferSize));
BufferState.LightmapDataBuffer = ResizeStructuredBufferIfNeeded(GraphBuilder, LightmapDataBuffer, LightMapDataBufferSize * sizeof(FLightmapSceneShaderData::Data), TEXT("GPUScene.LightmapData"));
BufferState.LightMapDataBufferSize = LightMapDataBufferSize;
ShaderParameters.GPUSceneInstanceSceneData = GraphBuilder.CreateSRV(BufferState.InstanceSceneDataBuffer);
ShaderParameters.GPUSceneInstancePayloadData = GraphBuilder.CreateSRV(BufferState.InstancePayloadDataBuffer);
ShaderParameters.GPUScenePrimitiveSceneData = GraphBuilder.CreateSRV(BufferState.PrimitiveBuffer);
ShaderParameters.GPUSceneLightmapData = GraphBuilder.CreateSRV(BufferState.LightmapDataBuffer);
ShaderParameters.InstanceDataSOAStride = InstanceSceneDataSOAStride;
ShaderParameters.NumScenePrimitives = NumScenePrimitives;
ShaderParameters.NumInstances = InstanceSceneDataAllocator.GetMaxSize();
ShaderParameters.GPUSceneFrameNumber = GetSceneFrameNumber();
}
GPUScene.h
template<typename FUploadDataSourceAdapter>
void FGPUScene::UploadGeneral(FRDGBuilder& GraphBuilder, FScene& Scene, FRDGExternalAccessQueue& ExternalAccessQueue, const FUploadDataSourceAdapter& UploadDataSourceAdapter)
{
LLM_SCOPE_BYTAG(GPUScene);
ensure(bIsEnabled == UseGPUScene(GMaxRHIShaderPlatform, Scene.GetFeatureLevel()));
ensure(NumScenePrimitives == Scene.Primitives.Num());
const int32 NumPrimitiveDataUploads = UploadDataSourceAdapter.NumPrimitivesToUpload();
if (!NumPrimitiveDataUploads)
{
return;
}
const bool bExecuteInParallel = GGPUSceneParallelUpdate != 0 && FApp::ShouldUseThreadingForPerformance();
const bool bNaniteEnabled = DoesPlatformSupportNanite(GMaxRHIShaderPlatform);
SCOPED_NAMED_EVENT(UpdateGPUScene, FColor::Green);
// Multi-GPU support : Updating on all GPUs is inefficient for AFR. Work is wasted
// for any primitives that update on consecutive frames.
RDG_GPU_MASK_SCOPE(GraphBuilder, FRHIGPUMask::All());
RDG_EVENT_SCOPE(GraphBuilder, "UpdateGPUScene NumPrimitiveDataUploads %u", NumPrimitiveDataUploads);
struct FTaskContext
{
TArray<FPrimitiveUploadInfoHeader, FSceneRenderingArrayAllocator> PrimitiveUploadInfos;
FRDGScatterUploader* PrimitiveUploader = nullptr;
FRDGScatterUploader* InstancePayloadUploader = nullptr;
FRDGScatterUploader* InstanceSceneUploader = nullptr;
FRDGScatterUploader* InstanceBVHUploader = nullptr;
FRDGScatterUploader* LightmapUploader = nullptr;
TStaticArray<FNaniteMaterialCommands::FUploader*, ENaniteMeshPass::Num> NaniteMaterialUploaders{ InPlace, nullptr };
int32 NumPrimitiveDataUploads = 0;
int32 NumLightmapDataUploads = 0;
int32 NumInstanceSceneDataUploads = 0;
int32 NumInstancePayloadDataUploads = 0; // Count of float4s
uint32 InstanceSceneDataSOAStride = 1;
bool bUseNaniteMaterialUploaders = false;
};
FTaskContext& TaskContext = *GraphBuilder.AllocObject<FTaskContext>();
TaskContext.NumPrimitiveDataUploads = NumPrimitiveDataUploads;
TaskContext.InstanceSceneDataSOAStride = BufferState.InstanceSceneDataSOAStride;
TaskContext.PrimitiveUploadInfos.SetNumUninitialized(NumPrimitiveDataUploads);
for (int32 ItemIndex = 0; ItemIndex < NumPrimitiveDataUploads; ++ItemIndex)
{
FPrimitiveUploadInfoHeader& UploadInfo = TaskContext.PrimitiveUploadInfos[ItemIndex];
UploadDataSourceAdapter.GetPrimitiveInfoHeader(ItemIndex, UploadInfo);
TaskContext.NumLightmapDataUploads += UploadInfo.LightmapUploadCount; // Not thread safe
TaskContext.NumInstanceSceneDataUploads += UploadInfo.NumInstanceUploads; // Not thread safe
TaskContext.NumInstancePayloadDataUploads += UploadInfo.NumInstancePayloadDataUploads; // Not thread safe
}
TaskContext.PrimitiveUploader = PrimitiveUploadBuffer.Begin(GraphBuilder, BufferState.PrimitiveBuffer, UploadDataSourceAdapter.GetItemPrimitiveIds().Num(), sizeof(FPrimitiveSceneShaderData::Data), TEXT("PrimitiveUploadBuffer"));
if (TaskContext.NumLightmapDataUploads > 0)
{
TaskContext.LightmapUploader = LightmapUploadBuffer.Begin(GraphBuilder, BufferState.LightmapDataBuffer, TaskContext.NumLightmapDataUploads, sizeof(FLightmapSceneShaderData::Data), TEXT("LightmapUploadBuffer"));
}
if (UploadDataSourceAdapter.bUpdateNaniteMaterialTables && bNaniteEnabled)
{
for (int32 NaniteMeshPassIndex = 0; NaniteMeshPassIndex < ENaniteMeshPass::Num; ++NaniteMeshPassIndex)
{
TaskContext.NaniteMaterialUploaders[NaniteMeshPassIndex] = Scene.NaniteMaterials[NaniteMeshPassIndex].Begin(GraphBuilder, Scene.Primitives.Num(), NumPrimitiveDataUploads);
}
TaskContext.bUseNaniteMaterialUploaders = true;
}
}