vault backup: 2024-09-27 19:44:52
This commit is contained in:
parent
083d49a79c
commit
4ba40d3c18
@ -22,7 +22,7 @@ FDeferredShadingSceneRenderer::RenderBasePassInternal() =>
|
|||||||
FBasePassMeshProcessor::TryAddMeshBatch =>
|
FBasePassMeshProcessor::TryAddMeshBatch =>
|
||||||
|
|
||||||
## 大致流程
|
## 大致流程
|
||||||
1. 绑定GBuffer与Depth。
|
1. 创建MRT并绑定、取得深度缓存。
|
||||||
```c++
|
```c++
|
||||||
const FExclusiveDepthStencil ExclusiveDepthStencil(BasePassDepthStencilAccess);
|
const FExclusiveDepthStencil ExclusiveDepthStencil(BasePassDepthStencilAccess);
|
||||||
|
|
||||||
@ -58,10 +58,173 @@ GraphBuilder.AddPass(RDG_EVENT_NAME("GBufferClear"), PassParameters, ERDGPassFla
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
3.
|
3. RenderTargetBindingSlots
|
||||||
|
```c++
|
||||||
|
// Render targets bindings should remain constant at this point.
|
||||||
|
FRenderTargetBindingSlots BasePassRenderTargets = GetRenderTargetBindings(ERenderTargetLoadAction::ELoad, BasePassTexturesView);
|
||||||
|
BasePassRenderTargets.DepthStencil = FDepthStencilBinding(BasePassDepthTexture, ERenderTargetLoadAction::ELoad, ERenderTargetLoadAction::ELoad, ExclusiveDepthStencil);
|
||||||
|
```
|
||||||
|
4. RenderBasePassInternal()
|
||||||
|
5. RenderAnisotropyPass()
|
||||||
|
|
||||||
|
# MeshDraw
|
||||||
|
## RenderBasePassInternal()
|
||||||
|
RenderNaniteBasePass()为一个Lambda,最终调用**Nanite::DrawBasePass()** 渲染Nanite物体的BasePass。其他相关渲染代码如下:
|
||||||
|
```c++
|
||||||
|
SCOPE_CYCLE_COUNTER(STAT_BasePassDrawTime);
|
||||||
|
RDG_EVENT_SCOPE(GraphBuilder, "BasePass");
|
||||||
|
RDG_GPU_STAT_SCOPE(GraphBuilder, Basepass);
|
||||||
|
|
||||||
# SetDepthStencilStateForBasePass()
|
const bool bDrawSceneViewsInOneNanitePass = Views.Num() > 1 && Nanite::ShouldDrawSceneViewsInOneNanitePass(Views[0]);
|
||||||
|
if (bParallelBasePass)//并行渲染模式
|
||||||
|
{
|
||||||
|
RDG_WAIT_FOR_TASKS_CONDITIONAL(GraphBuilder, IsBasePassWaitForTasksEnabled());
|
||||||
|
|
||||||
|
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex)
|
||||||
|
{
|
||||||
|
FViewInfo& View = Views[ViewIndex];
|
||||||
|
RDG_GPU_MASK_SCOPE(GraphBuilder, View.GPUMask);
|
||||||
|
RDG_EVENT_SCOPE_CONDITIONAL(GraphBuilder, Views.Num() > 1, "View%d", ViewIndex);
|
||||||
|
View.BeginRenderView();
|
||||||
|
|
||||||
|
const bool bLumenGIEnabled = GetViewPipelineState(View).DiffuseIndirectMethod == EDiffuseIndirectMethod::Lumen;
|
||||||
|
|
||||||
|
FMeshPassProcessorRenderState DrawRenderState;
|
||||||
|
SetupBasePassState(BasePassDepthStencilAccess, ViewFamily.EngineShowFlags.ShaderComplexity, DrawRenderState);
|
||||||
|
|
||||||
|
FOpaqueBasePassParameters* PassParameters = GraphBuilder.AllocParameters<FOpaqueBasePassParameters>();
|
||||||
|
PassParameters->View = View.GetShaderParameters();
|
||||||
|
PassParameters->ReflectionCapture = View.ReflectionCaptureUniformBuffer;
|
||||||
|
PassParameters->BasePass = CreateOpaqueBasePassUniformBuffer(GraphBuilder, View, ViewIndex, ForwardBasePassTextures, DBufferTextures, bLumenGIEnabled);
|
||||||
|
PassParameters->RenderTargets = BasePassRenderTargets;
|
||||||
|
PassParameters->RenderTargets.ShadingRateTexture = GVRSImageManager.GetVariableRateShadingImage(GraphBuilder, View, FVariableRateShadingImageManager::EVRSPassType::BasePass);
|
||||||
|
|
||||||
|
const bool bShouldRenderView = View.ShouldRenderView();
|
||||||
|
if (bShouldRenderView)
|
||||||
|
{
|
||||||
|
View.ParallelMeshDrawCommandPasses[EMeshPass::BasePass].BuildRenderingCommands(GraphBuilder, Scene->GPUScene, PassParameters->InstanceCullingDrawParams);
|
||||||
|
|
||||||
|
GraphBuilder.AddPass(
|
||||||
|
RDG_EVENT_NAME("BasePassParallel"),
|
||||||
|
PassParameters,
|
||||||
|
ERDGPassFlags::Raster | ERDGPassFlags::SkipRenderPass,
|
||||||
|
[this, &View, PassParameters](const FRDGPass* InPass, FRHICommandListImmediate& RHICmdList)
|
||||||
|
{
|
||||||
|
FRDGParallelCommandListSet ParallelCommandListSet(InPass, RHICmdList, GET_STATID(STAT_CLP_BasePass), View, FParallelCommandListBindings(PassParameters));
|
||||||
|
View.ParallelMeshDrawCommandPasses[EMeshPass::BasePass].DispatchDraw(&ParallelCommandListSet, RHICmdList, &PassParameters->InstanceCullingDrawParams);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool bShouldRenderViewForNanite = bNaniteEnabled && (!bDrawSceneViewsInOneNanitePass || ViewIndex == 0); // when bDrawSceneViewsInOneNanitePass, the first view should cover all the other atlased ones
|
||||||
|
if (bShouldRenderViewForNanite)
|
||||||
|
{
|
||||||
|
// Should always have a full Z prepass with Nanite
|
||||||
|
check(ShouldRenderPrePass());
|
||||||
|
//渲染Nanite物体BasePass
|
||||||
|
RenderNaniteBasePass(View, ViewIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
//渲染编辑器相关的图元物体
|
||||||
|
RenderEditorPrimitives(GraphBuilder, PassParameters, View, DrawRenderState, InstanceCullingManager);
|
||||||
|
|
||||||
|
//渲染大气
|
||||||
|
if (bShouldRenderView && View.Family->EngineShowFlags.Atmosphere)
|
||||||
|
{
|
||||||
|
FOpaqueBasePassParameters* SkyPassPassParameters = GraphBuilder.AllocParameters<FOpaqueBasePassParameters>();
|
||||||
|
SkyPassPassParameters->BasePass = PassParameters->BasePass;
|
||||||
|
SkyPassPassParameters->RenderTargets = BasePassRenderTargets;
|
||||||
|
SkyPassPassParameters->View = View.GetShaderParameters();
|
||||||
|
SkyPassPassParameters->ReflectionCapture = View.ReflectionCaptureUniformBuffer;
|
||||||
|
|
||||||
|
View.ParallelMeshDrawCommandPasses[EMeshPass::SkyPass].BuildRenderingCommands(GraphBuilder, Scene->GPUScene, SkyPassPassParameters->InstanceCullingDrawParams);
|
||||||
|
|
||||||
|
GraphBuilder.AddPass(
|
||||||
|
RDG_EVENT_NAME("SkyPassParallel"),
|
||||||
|
SkyPassPassParameters,
|
||||||
|
ERDGPassFlags::Raster | ERDGPassFlags::SkipRenderPass,
|
||||||
|
[this, &View, SkyPassPassParameters](const FRDGPass* InPass, FRHICommandListImmediate& RHICmdList)
|
||||||
|
{
|
||||||
|
FRDGParallelCommandListSet ParallelCommandListSet(InPass, RHICmdList, GET_STATID(STAT_CLP_BasePass), View, FParallelCommandListBindings(SkyPassPassParameters));
|
||||||
|
View.ParallelMeshDrawCommandPasses[EMeshPass::SkyPass].DispatchDraw(&ParallelCommandListSet, RHICmdList, &SkyPassPassParameters->InstanceCullingDrawParams);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex)
|
||||||
|
{
|
||||||
|
FViewInfo& View = Views[ViewIndex];
|
||||||
|
RDG_GPU_MASK_SCOPE(GraphBuilder, View.GPUMask);
|
||||||
|
RDG_EVENT_SCOPE_CONDITIONAL(GraphBuilder, Views.Num() > 1, "View%d", ViewIndex);
|
||||||
|
View.BeginRenderView();
|
||||||
|
|
||||||
|
const bool bLumenGIEnabled = GetViewPipelineState(View).DiffuseIndirectMethod == EDiffuseIndirectMethod::Lumen;
|
||||||
|
|
||||||
|
FMeshPassProcessorRenderState DrawRenderState;
|
||||||
|
SetupBasePassState(BasePassDepthStencilAccess, ViewFamily.EngineShowFlags.ShaderComplexity, DrawRenderState);
|
||||||
|
|
||||||
|
FOpaqueBasePassParameters* PassParameters = GraphBuilder.AllocParameters<FOpaqueBasePassParameters>();
|
||||||
|
PassParameters->View = View.GetShaderParameters();
|
||||||
|
PassParameters->ReflectionCapture = View.ReflectionCaptureUniformBuffer;
|
||||||
|
PassParameters->BasePass = CreateOpaqueBasePassUniformBuffer(GraphBuilder, View, ViewIndex, ForwardBasePassTextures, DBufferTextures, bLumenGIEnabled);
|
||||||
|
PassParameters->RenderTargets = BasePassRenderTargets;
|
||||||
|
PassParameters->RenderTargets.ShadingRateTexture = GVRSImageManager.GetVariableRateShadingImage(GraphBuilder, View, FVariableRateShadingImageManager::EVRSPassType::BasePass);
|
||||||
|
|
||||||
|
const bool bShouldRenderView = View.ShouldRenderView();
|
||||||
|
if (bShouldRenderView)
|
||||||
|
{
|
||||||
|
View.ParallelMeshDrawCommandPasses[EMeshPass::BasePass].BuildRenderingCommands(GraphBuilder, Scene->GPUScene, PassParameters->InstanceCullingDrawParams);
|
||||||
|
|
||||||
|
GraphBuilder.AddPass(
|
||||||
|
RDG_EVENT_NAME("BasePass"),
|
||||||
|
PassParameters,
|
||||||
|
ERDGPassFlags::Raster,
|
||||||
|
[this, &View, PassParameters](FRHICommandList& RHICmdList)
|
||||||
|
{
|
||||||
|
SetStereoViewport(RHICmdList, View, 1.0f);
|
||||||
|
View.ParallelMeshDrawCommandPasses[EMeshPass::BasePass].DispatchDraw(nullptr, RHICmdList, &PassParameters->InstanceCullingDrawParams);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool bShouldRenderViewForNanite = bNaniteEnabled && (!bDrawSceneViewsInOneNanitePass || ViewIndex == 0); // when bDrawSceneViewsInOneNanitePass, the first view should cover all the other atlased ones
|
||||||
|
if (bShouldRenderViewForNanite)
|
||||||
|
{
|
||||||
|
// Should always have a full Z prepass with Nanite
|
||||||
|
check(ShouldRenderPrePass());
|
||||||
|
|
||||||
|
RenderNaniteBasePass(View, ViewIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderEditorPrimitives(GraphBuilder, PassParameters, View, DrawRenderState, InstanceCullingManager);
|
||||||
|
|
||||||
|
if (bShouldRenderView && View.Family->EngineShowFlags.Atmosphere)
|
||||||
|
{
|
||||||
|
FOpaqueBasePassParameters* SkyPassParameters = GraphBuilder.AllocParameters<FOpaqueBasePassParameters>();
|
||||||
|
SkyPassParameters->BasePass = PassParameters->BasePass;
|
||||||
|
SkyPassParameters->RenderTargets = BasePassRenderTargets;
|
||||||
|
SkyPassParameters->View = View.GetShaderParameters();
|
||||||
|
SkyPassParameters->ReflectionCapture = View.ReflectionCaptureUniformBuffer;
|
||||||
|
|
||||||
|
View.ParallelMeshDrawCommandPasses[EMeshPass::SkyPass].BuildRenderingCommands(GraphBuilder, Scene->GPUScene, SkyPassParameters->InstanceCullingDrawParams);
|
||||||
|
|
||||||
|
GraphBuilder.AddPass(
|
||||||
|
RDG_EVENT_NAME("SkyPass"),
|
||||||
|
SkyPassParameters,
|
||||||
|
ERDGPassFlags::Raster,
|
||||||
|
[this, &View, SkyPassParameters](FRHICommandList& RHICmdList)
|
||||||
|
{
|
||||||
|
SetStereoViewport(RHICmdList, View, 1.0f);
|
||||||
|
View.ParallelMeshDrawCommandPasses[EMeshPass::SkyPass].DispatchDraw(nullptr, RHICmdList, &SkyPassParameters->InstanceCullingDrawParams);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### SetDepthStencilStateForBasePass()
|
||||||
```c++
|
```c++
|
||||||
void SetDepthStencilStateForBasePass(
|
void SetDepthStencilStateForBasePass(
|
||||||
FMeshPassProcessorRenderState& DrawRenderState,
|
FMeshPassProcessorRenderState& DrawRenderState,
|
||||||
@ -98,4 +261,118 @@ void SetDepthStencilStateForBasePass(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
#
|
## AnisotropyPass
|
||||||
|
Anisotropy的RT设置:
|
||||||
|
- RenderTarget:SceneTextures.GBufferF。
|
||||||
|
- DepthStencil:SceneTextures.Depth.Target。**ERenderTargetLoadAction::ELoad**、**FExclusiveDepthStencil::DepthRead_StencilNop**
|
||||||
|
|
||||||
|
### 管线状态
|
||||||
|
在FAnisotropyMeshProcessor::CollectPSOInitializers()中:
|
||||||
|
```c++
|
||||||
|
ETextureCreateFlags GBufferFCreateFlags;
|
||||||
|
EPixelFormat GBufferFPixelFormat = FSceneTextures::GetGBufferFFormatAndCreateFlags(GBufferFCreateFlags);
|
||||||
|
AddRenderTargetInfo(GBufferFPixelFormat, GBufferFCreateFlags, RenderTargetsInfo);
|
||||||
|
SetupDepthStencilInfo(PF_DepthStencil, SceneTexturesConfig.DepthCreateFlags, ERenderTargetLoadAction::ELoad,
|
||||||
|
ERenderTargetLoadAction::ELoad, FExclusiveDepthStencil::DepthRead_StencilNop, RenderTargetsInfo);
|
||||||
|
```
|
||||||
|
|
||||||
|
```c++
|
||||||
|
void SetupDepthStencilInfo(
|
||||||
|
EPixelFormat DepthStencilFormat,
|
||||||
|
ETextureCreateFlags DepthStencilCreateFlags,
|
||||||
|
ERenderTargetLoadAction DepthTargetLoadAction,
|
||||||
|
ERenderTargetLoadAction StencilTargetLoadAction,
|
||||||
|
FExclusiveDepthStencil DepthStencilAccess,
|
||||||
|
FGraphicsPipelineRenderTargetsInfo& RenderTargetsInfo)
|
||||||
|
{
|
||||||
|
// Setup depth stencil state
|
||||||
|
RenderTargetsInfo.DepthStencilTargetFormat = DepthStencilFormat;
|
||||||
|
RenderTargetsInfo.DepthStencilTargetFlag = DepthStencilCreateFlags;
|
||||||
|
|
||||||
|
RenderTargetsInfo.DepthTargetLoadAction = DepthTargetLoadAction;
|
||||||
|
RenderTargetsInfo.StencilTargetLoadAction = StencilTargetLoadAction;
|
||||||
|
RenderTargetsInfo.DepthStencilAccess = DepthStencilAccess;
|
||||||
|
|
||||||
|
const ERenderTargetStoreAction StoreAction = EnumHasAnyFlags(RenderTargetsInfo.DepthStencilTargetFlag, TexCreate_Memoryless) ? ERenderTargetStoreAction::ENoAction : ERenderTargetStoreAction::EStore;
|
||||||
|
RenderTargetsInfo.DepthTargetStoreAction = RenderTargetsInfo.DepthStencilAccess.IsUsingDepth() ? StoreAction : ERenderTargetStoreAction::ENoAction;
|
||||||
|
RenderTargetsInfo.StencilTargetStoreAction = RenderTargetsInfo.DepthStencilAccess.IsUsingStencil() ? StoreAction : ERenderTargetStoreAction::ENoAction;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ParallelRendering
|
||||||
|
AnisotropyPass支持并行渲染,并行渲染的判断逻辑为
|
||||||
|
```c++
|
||||||
|
const bool bEnableParallelBasePasses = GRHICommandList.UseParallelAlgorithms() && CVarParallelBasePass.GetValueOnRenderThread();
|
||||||
|
```
|
||||||
|
看得出判断条件是:
|
||||||
|
1. 显卡是否支持并行渲染。
|
||||||
|
2. CVar(r.ParallelBasePass)是否开启并行渲染。
|
||||||
|
|
||||||
|
从AnisotropyPass可以看得出并行渲染与一般渲染的差别在于:
|
||||||
|
1. FRenderTargetBinding绑定时的ERenderTargetLoadAction不同,**并行为ELoad**;**普通渲染为EClear**。
|
||||||
|
2. 调用AddPass添加了**ERDGPassFlags::SkipRenderPass**标记。
|
||||||
|
3. 并行渲染会在AddPass中构建**FRDGParallelCommandListSet ParallelCommandListSet**,并作为传入**DispatchDraw**;普通渲染传递nullptr。
|
||||||
|
4. 普通渲染会额外调用**SetStereoViewport(RHICmdList, View);**,本质是调用RHICmdList.SetViewport来设置View。
|
||||||
|
|
||||||
|
### Code
|
||||||
|
```c++
|
||||||
|
RDG_CSV_STAT_EXCLUSIVE_SCOPE(GraphBuilder, RenderAnisotropyPass);
|
||||||
|
SCOPED_NAMED_EVENT(FDeferredShadingSceneRenderer_RenderAnisotropyPass, FColor::Emerald);
|
||||||
|
SCOPE_CYCLE_COUNTER(STAT_AnisotropyPassDrawTime);
|
||||||
|
RDG_GPU_STAT_SCOPE(GraphBuilder, RenderAnisotropyPass);
|
||||||
|
|
||||||
|
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
|
||||||
|
{
|
||||||
|
FViewInfo& View = Views[ViewIndex];
|
||||||
|
|
||||||
|
if (View.ShouldRenderView())
|
||||||
|
{
|
||||||
|
FParallelMeshDrawCommandPass& ParallelMeshPass = View.ParallelMeshDrawCommandPasses[EMeshPass::AnisotropyPass];
|
||||||
|
|
||||||
|
if (!ParallelMeshPass.HasAnyDraw())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
View.BeginRenderView();
|
||||||
|
|
||||||
|
auto* PassParameters = GraphBuilder.AllocParameters<FAnisotropyPassParameters>();
|
||||||
|
PassParameters->View = View.GetShaderParameters();
|
||||||
|
PassParameters->RenderTargets.DepthStencil = FDepthStencilBinding(SceneTextures.Depth.Target, ERenderTargetLoadAction::ELoad, FExclusiveDepthStencil::DepthRead_StencilNop);
|
||||||
|
|
||||||
|
ParallelMeshPass.BuildRenderingCommands(GraphBuilder, Scene->GPUScene, PassParameters->InstanceCullingDrawParams);
|
||||||
|
if (bDoParallelPass)
|
||||||
|
{
|
||||||
|
AddClearRenderTargetPass(GraphBuilder, SceneTextures.GBufferF);
|
||||||
|
|
||||||
|
PassParameters->RenderTargets[0] = FRenderTargetBinding(SceneTextures.GBufferF, ERenderTargetLoadAction::ELoad);
|
||||||
|
|
||||||
|
GraphBuilder.AddPass(
|
||||||
|
RDG_EVENT_NAME("AnisotropyPassParallel"),
|
||||||
|
PassParameters,
|
||||||
|
ERDGPassFlags::Raster | ERDGPassFlags::SkipRenderPass,
|
||||||
|
[this, &View, &ParallelMeshPass, PassParameters](const FRDGPass* InPass, FRHICommandListImmediate& RHICmdList)
|
||||||
|
{
|
||||||
|
FRDGParallelCommandListSet ParallelCommandListSet(InPass, RHICmdList, GET_STATID(STAT_CLP_AnisotropyPass), View, FParallelCommandListBindings(PassParameters));
|
||||||
|
|
||||||
|
ParallelMeshPass.DispatchDraw(&ParallelCommandListSet, RHICmdList, &PassParameters->InstanceCullingDrawParams);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PassParameters->RenderTargets[0] = FRenderTargetBinding(SceneTextures.GBufferF, ERenderTargetLoadAction::EClear);
|
||||||
|
|
||||||
|
GraphBuilder.AddPass(
|
||||||
|
RDG_EVENT_NAME("AnisotropyPass"),
|
||||||
|
PassParameters,
|
||||||
|
ERDGPassFlags::Raster,
|
||||||
|
[this, &View, &ParallelMeshPass, PassParameters](FRHICommandList& RHICmdList)
|
||||||
|
{
|
||||||
|
SetStereoViewport(RHICmdList, View);
|
||||||
|
|
||||||
|
ParallelMeshPass.DispatchDraw(nullptr, RHICmdList, &PassParameters->InstanceCullingDrawParams);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
@ -32,6 +32,7 @@ OutGBufferD = GBuffer.CustomData
|
|||||||
OutGBufferE = GBuffer.PrecomputedShadowFactors (GBT_Unorm_8_8_8_8)
|
OutGBufferE = GBuffer.PrecomputedShadowFactors (GBT_Unorm_8_8_8_8)
|
||||||
TargetVelocity / OutGBufferF = velocity / tangent (默认不开启 带有深度<开启Lumen与距离场 或者 开启光线追踪> GBC_Raw_Float_16_16_16_16 不带深度 GBC_Raw_Float_16_16)
|
TargetVelocity / OutGBufferF = velocity / tangent (默认不开启 带有深度<开启Lumen与距离场 或者 开启光线追踪> GBC_Raw_Float_16_16_16_16 不带深度 GBC_Raw_Float_16_16)
|
||||||
TargetSeparatedMainDirLight = SingleLayerWater相关 (有SingleLayerWater才会开启 GBC_Raw_Float_11_11_10)
|
TargetSeparatedMainDirLight = SingleLayerWater相关 (有SingleLayerWater才会开启 GBC_Raw_Float_11_11_10)
|
||||||
|
OutGBufferF = Anisotropy
|
||||||
|
|
||||||
// 0..1, 2 bits, use CastContactShadow(GBuffer) or HasDynamicIndirectShadowCasterRepresentation(GBuffer) to extract
|
// 0..1, 2 bits, use CastContactShadow(GBuffer) or HasDynamicIndirectShadowCasterRepresentation(GBuffer) to extract
|
||||||
half PerObjectGBufferData;
|
half PerObjectGBufferData;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user