This commit is contained in:
2025-08-02 12:09:34 +08:00
commit e70b01cdca
2785 changed files with 575579 additions and 0 deletions

View File

@@ -0,0 +1,378 @@
---
title: Untitled
date: 2024-09-26 18:41:24
excerpt:
tags:
rating: ⭐
---
# RenderBasePass()
传入RenderBasePass()的DepthStencil逻辑如下
```c++
const FExclusiveDepthStencil::Type BasePassDepthStencilAccess =
bAllowReadOnlyDepthBasePass
? FExclusiveDepthStencil::DepthRead_StencilWrite
: FExclusiveDepthStencil::DepthWrite_StencilWrite;
```
FDeferredShadingSceneRenderer::RenderBasePass() =>
FDeferredShadingSceneRenderer::RenderBasePassInternal() =>
FBasePassMeshProcessor::TryAddMeshBatch =>
## 大致流程
1. 创建MRT并绑定、取得深度缓存。
```c++
const FExclusiveDepthStencil ExclusiveDepthStencil(BasePassDepthStencilAccess);
TStaticArray<FTextureRenderTargetBinding, MaxSimultaneousRenderTargets> BasePassTextures;
uint32 BasePassTextureCount = SceneTextures.GetGBufferRenderTargets(BasePassTextures);
Strata::AppendStrataMRTs(*this, BasePassTextureCount, BasePassTextures);
TArrayView<FTextureRenderTargetBinding> BasePassTexturesView = MakeArrayView(BasePassTextures.GetData(), BasePassTextureCount);
FRDGTextureRef BasePassDepthTexture = SceneTextures.Depth.Target;
```
2. GBuffer Clear
```c++
GraphBuilder.AddPass(RDG_EVENT_NAME("GBufferClear"), PassParameters, ERDGPassFlags::Raster,
[PassParameters, ColorLoadAction, SceneColorClearValue](FRHICommandList& RHICmdList)
{
// If no fast-clear action was used, we need to do an MRT shader clear.
if (ColorLoadAction == ERenderTargetLoadAction::ENoAction)
{
const FRenderTargetBindingSlots& RenderTargets = PassParameters->RenderTargets;
FLinearColor ClearColors[MaxSimultaneousRenderTargets];
FRHITexture* Textures[MaxSimultaneousRenderTargets];
int32 TextureIndex = 0;
RenderTargets.Enumerate([&](const FRenderTargetBinding& RenderTarget)
{
FRHITexture* TextureRHI = RenderTarget.GetTexture()->GetRHI();
ClearColors[TextureIndex] = TextureIndex == 0 ? SceneColorClearValue : TextureRHI->GetClearColor();
Textures[TextureIndex] = TextureRHI;
++TextureIndex;
});
// Clear color only; depth-stencil is fast cleared.
DrawClearQuadMRT(RHICmdList, true, TextureIndex, ClearColors, false, 0, false, 0);
}
});
```
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);
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++
void SetDepthStencilStateForBasePass(
FMeshPassProcessorRenderState& DrawRenderState,
ERHIFeatureLevel::Type FeatureLevel,
bool bDitheredLODTransition,
const FMaterial& MaterialResource,
bool bEnableReceiveDecalOutput,
bool bForceEnableStencilDitherState)
{
const bool bMaskedInEarlyPass = (MaterialResource.IsMasked() || bDitheredLODTransition) && MaskedInEarlyPass(GShaderPlatformForFeatureLevel[FeatureLevel]);
if (bEnableReceiveDecalOutput)
{
if (bMaskedInEarlyPass)
{
SetDepthStencilStateForBasePass_Internal<false, CF_Equal>(DrawRenderState, FeatureLevel);
}
else if (DrawRenderState.GetDepthStencilAccess() & FExclusiveDepthStencil::DepthWrite)
{
SetDepthStencilStateForBasePass_Internal<true, CF_GreaterEqual>(DrawRenderState, FeatureLevel);
}
else
{
SetDepthStencilStateForBasePass_Internal<false, CF_GreaterEqual>(DrawRenderState, FeatureLevel);
}
}
else if (bMaskedInEarlyPass)
{
DrawRenderState.SetDepthStencilState(TStaticDepthStencilState<false, CF_Equal>::GetRHI());
}
if (bForceEnableStencilDitherState)
{
SetDepthStencilStateForBasePass_Internal<false, CF_Equal>(DrawRenderState, FeatureLevel);
}
}
```
## AnisotropyPass
Anisotropy的RT设置
- RenderTargetSceneTextures.GBufferF。
- DepthStencilSceneTextures.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. CVarr.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);
});
}
}
}
```

View File

@@ -0,0 +1,659 @@
---
title: GBuffer&Material&BasePass
date: 2023-12-08 17:34:58
excerpt:
tags:
rating: ⭐
---
# # GBuffer
目前UE5.3会调用
- WriteGBufferInfoAutogen()
- **EncodeGBufferToMRT()**
动态生成BasePassPixelShader.usf中的**EncodeGBufferToMRT()** 的代码并且会生成一个AutogenShaderHeaders.ush文件。其路径为
`Engine\Intermediate\ShaderAutogen\PCD3D_SM5`或者`Engine\Intermediate\ShaderAutogen\PCD3D_ES3_1`
1. ***给FGBufferData添加结构体数据时需要在此添加额外代码逻辑***
2. GBuffer精度在FetchLegacyGBufferInfo()设置。
3. 是否往GBuffer中写入Velocity主要靠这个宏**WRITES_VELOCITY_TO_GBUFFER**。具体决定其数值的逻辑位于**FShaderGlobalDefines FetchShaderGlobalDefines**。主要还是靠**r.VelocityOutputPass**进行开启。
1. PS. MSAA以及VR绝对不会开启Velocity输出选项。还有就是**r.Velocity.ForceOutput**但经过测试不开启r.VelocityOutputPass依然无法输出。以及FPrimitiveSceneProxy的bAlwaysHasVelocity与bHasWorldPositionOffsetVelocity。
2. 其他相关FSR、TSR
4. 如何添加GBuffer
1. https://zhuanlan.zhihu.com/p/568775542
2. https://zhuanlan.zhihu.com/p/677772284
## UE5 GBuffer内容
[[UE GBuffer存储数据]]
```c#
OutGBufferA(MRT1) = WorldNormal/PerObjectGBufferData (GBT_Float_16_16_16_16/GBT_Unorm_11_11_10/GBT_Unorm_8_8_8_8)
OutGBufferB(MRT2) = Metallic/Specular/Roughness/EncodeShadingModelIdAndSelectiveOutputMask (GBT_Float_16_16_16_16/GBT_Unorm_8_8_8_8)
OutGBufferC(MRT3) = BaseColor/GBufferAO (GBT_Unorm_8_8_8_8)
OutGBufferD = GBuffer.CustomData (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)
TargetSeparatedMainDirLight = SingleLayerWater相关 (有SingleLayerWater才会开启 GBC_Raw_Float_11_11_10)
OutGBufferF = Anisotropy
// 0..1, 2 bits, use CastContactShadow(GBuffer) or HasDynamicIndirectShadowCasterRepresentation(GBuffer) to extract
half PerObjectGBufferData;
```
GBuffer相关信息精度、顺序可以参考FetchLegacyGBufferInfo()。
- 不存在Velocity与Tangent:
- OutGBufferD(MRT4)
- OutGBufferD(MRT5)
- TargetSeparatedMainDirLight(MRT6)
- 存在Velocity
- TargetVelocity(MRT4)
- OutGBufferD(MRT5)
- OutGBufferE(MRT6)
- TargetSeparatedMainDirLight(MRT7)
- 存在Tangent
- OutGBufferF(MRT4)
- OutGBufferD(MRT5)
- OutGBufferE(MRT6)
- TargetSeparatedMainDirLight(MRT7)
几个动态MRT的存在条件与Shader判断宏
- OutGBufferE(PrecomputedShadowFactors)r.AllowStaticLighting = 1
- GBUFFER_HAS_PRECSHADOWFACTOR
- WRITES_PRECSHADOWFACTOR_ZERO
- WRITES_PRECSHADOWFACTOR_TO_GBUFFER
- TargetVelocity(IsUsingBasePassVelocity(Platform) || Layout == GBL_ForceVelocity) ? 1 : 0;//r.VelocityOutputPass = 1
- r.VelocityOutputPass = 1时会对骨骼物体以及WPO材质物体输出速度。因为大概率会使用距离场阴影以及VSM所以会占用GBuffer Velocity所有通道。
- GBUFFER_HAS_VELOCITY
- WRITES_VELOCITY_TO_GBUFFER
- SingleLayerWater
- 默认不会写入GBuffer需要符合以下条件const bool bNeedsSeparateMainDirLightTexture = IsWaterDistanceFieldShadowEnabled(Parameters.Platform) || IsWaterVirtualShadowMapFilteringEnabled(Parameters.Platform);
- r.Water.SingleLayer.ShadersSupportDistanceFieldShadow = 1
- r.Water.SingleLayer.ShadersSupportVSMFiltering = 1
- const bool bIsSingleLayerWater = Parameters.MaterialParameters.ShadingModels.HasShadingModel(MSM_SingleLayerWater);
- Tangentfalse目前单独使用另一组MRT来存储。
- ~~GBUFFER_HAS_TANGENT~`
### ToonGBuffer修改&数据存储
```c#
OutGBufferA:PerObjectGBufferData => 可以存储额外的有关Tonn渲染功能参数。
OutGBufferB:Metallic/Specular/Roughness =>
? / SpcularPower(控制高光亮度与Mask) / ? / ?
//ToonHairMask OffsetShadowMask/SpcularMask/SpecularValue
OutGBufferC:GBufferAO =>
ToonAO
OutGBufferD:CustomData.xyzw =>
ShadowColor.rgb / NoLOffset //ShadowColor这里可以在Material里通过主光向量、ShadowStep、Shadow羽化计算多层阴影效果。
OutGBufferE:GBuffer.PrecomputedShadowFactors.xyzw =>
ToonDataID/ ToonOutlineDataID / OutlineMask(控制Outline绘制以及Outline强度) / ToonObjectID(判断是否是一个物体)
TargetVelocity / OutGBufferF = velocity / tangent //目前先不考虑输出Velocity的情况
? / ? / ? / ?
```
ToonDataID在材质编辑器中会存在SubsurfaceColor.a中ToonOutlineDataID在材质编辑器中会存在CustomData1引脚名为ToonBufferB考虑到Subsurface有一个CurvatureMap需要使用CustomData0所以这里使用了CustomData1
蓝色协议的方案
![[蓝色协议的方案#GBuffer]]
***额外添加相关宏逻辑位于ShaderCompiler.cpp***
- **GBUFFER_HAS_TOONDATA**
### 修改GBuffer格式
- [[#ShaderMaterialDerivedHelpers.cpp中的CalculateDerivedMaterialParameters()]]控制在BasePassPixelShader.usf中的MRT宏是否为true。
- [[#BasePassRendering.cpp中ModifyBasePassCSPSCompilationEnvironment()]]控制Velocity与SingleLayerWater相关的RT精度。
- [[#GBufferInfo.cpp中的FetchLegacyGBufferInfo()]]控制GBuffer精度以及数据打包情况。
#### BasePassRendering.cpp中ModifyBasePassCSPSCompilationEnvironment()
```c++
void ModifyBasePassCSPSCompilationEnvironment()
{
...
const bool bOutputVelocity = (GBufferLayout == GBL_ForceVelocity) ||
FVelocityRendering::BasePassCanOutputVelocity(Parameters.Platform);
if (bOutputVelocity)
{
// As defined in BasePassPixelShader.usf. Also account for Strata setting velocity in slot 1 as described in FetchLegacyGBufferInfo.
const int32 VelocityIndex = Strata::IsStrataEnabled() ? 1 : (IsForwardShadingEnabled(Parameters.Platform) ? 1 : 4);
OutEnvironment.SetRenderTargetOutputFormat(VelocityIndex, PF_G16R16);
}
...
const bool bNeedsSeparateMainDirLightTexture = IsWaterDistanceFieldShadowEnabled(Parameters.Platform) || IsWaterVirtualShadowMapFilteringEnabled(Parameters.Platform);
if (bIsSingleLayerWater && bNeedsSeparateMainDirLightTexture)
{
// See FShaderCompileUtilities::FetchGBufferParamsRuntime for the details
const bool bHasTangent = false;
static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.AllowStaticLighting"));
bool bHasPrecShadowFactor = (CVar ? (CVar->GetValueOnAnyThread() != 0) : 1);
uint32 TargetSeparatedMainDirLight = 5;
if (bOutputVelocity == false && bHasTangent == false)
{
TargetSeparatedMainDirLight = 5;
if (bHasPrecShadowFactor)
{
TargetSeparatedMainDirLight = 6;
}
}
else if (bOutputVelocity)
{
TargetSeparatedMainDirLight = 6;
if (bHasPrecShadowFactor)
{
TargetSeparatedMainDirLight = 7;
}
}
else if (bHasTangent)
{
TargetSeparatedMainDirLight = 6;
if (bHasPrecShadowFactor)
{
TargetSeparatedMainDirLight = 7;
}
}
OutEnvironment.SetRenderTargetOutputFormat(TargetSeparatedMainDirLight, PF_FloatR11G11B10);
...
}
```
#### GBufferInfo.cpp中的FetchLegacyGBufferInfo()
控制GBuffer精度以及数据打包情况。
#### ShaderMaterialDerivedHelpers.cpp中的CalculateDerivedMaterialParameters()
```c++
else if (Mat.IS_BASE_PASS)
{
Dst.PIXELSHADEROUTPUT_BASEPASS = 1;
if (Dst.USES_GBUFFER)
{
Dst.PIXELSHADEROUTPUT_MRT0 = (!SrcGlobal.SELECTIVE_BASEPASS_OUTPUTS || Dst.NEEDS_BASEPASS_VERTEX_FOGGING || Mat.USES_EMISSIVE_COLOR || SrcGlobal.ALLOW_STATIC_LIGHTING || Mat.MATERIAL_SHADINGMODEL_SINGLELAYERWATER);
Dst.PIXELSHADEROUTPUT_MRT1 = ((!SrcGlobal.SELECTIVE_BASEPASS_OUTPUTS || !Mat.MATERIAL_SHADINGMODEL_UNLIT));
Dst.PIXELSHADEROUTPUT_MRT2 = ((!SrcGlobal.SELECTIVE_BASEPASS_OUTPUTS || !Mat.MATERIAL_SHADINGMODEL_UNLIT));
Dst.PIXELSHADEROUTPUT_MRT3 = ((!SrcGlobal.SELECTIVE_BASEPASS_OUTPUTS || !Mat.MATERIAL_SHADINGMODEL_UNLIT));
if (SrcGlobal.GBUFFER_HAS_VELOCITY || SrcGlobal.GBUFFER_HAS_TANGENT)
{
Dst.PIXELSHADEROUTPUT_MRT4 = Dst.WRITES_VELOCITY_TO_GBUFFER || SrcGlobal.GBUFFER_HAS_TANGENT;
Dst.PIXELSHADEROUTPUT_MRT5 = (!SrcGlobal.SELECTIVE_BASEPASS_OUTPUTS || Dst.WRITES_CUSTOMDATA_TO_GBUFFER);
Dst.PIXELSHADEROUTPUT_MRT6 = (Dst.GBUFFER_HAS_PRECSHADOWFACTOR && (!SrcGlobal.SELECTIVE_BASEPASS_OUTPUTS || (Dst.WRITES_PRECSHADOWFACTOR_TO_GBUFFER && !Mat.MATERIAL_SHADINGMODEL_UNLIT)));
}
else
{
Dst.PIXELSHADEROUTPUT_MRT4 = (!SrcGlobal.SELECTIVE_BASEPASS_OUTPUTS || Dst.WRITES_CUSTOMDATA_TO_GBUFFER);
Dst.PIXELSHADEROUTPUT_MRT5 = (Dst.GBUFFER_HAS_PRECSHADOWFACTOR && (!SrcGlobal.SELECTIVE_BASEPASS_OUTPUTS || (Dst.WRITES_PRECSHADOWFACTOR_TO_GBUFFER && !Mat.MATERIAL_SHADINGMODEL_UNLIT)));
}
}
else
{
Dst.PIXELSHADEROUTPUT_MRT0 = true;
// we also need MRT for thin translucency due to dual blending if we are not on the fallback path
Dst.PIXELSHADEROUTPUT_MRT1 = (Dst.WRITES_VELOCITY_TO_GBUFFER || (Mat.DUAL_SOURCE_COLOR_BLENDING_ENABLED && Dst.MATERIAL_WORKS_WITH_DUAL_SOURCE_COLOR_BLENDING));
}
}
}
```
位于FShaderCompileUtilities::ApplyDerivedDefines()新版本逻辑遍历数据由GBufferInfo.cpp中的FetchLegacyGBufferInfo()处理。
```c++
#if 1
static bool bTestNewVersion = true;
if (bTestNewVersion)
{
//if (DerivedDefines.USES_GBUFFER)
{
for (int32 Iter = 0; Iter < FGBufferInfo::MaxTargets; Iter++)
{
if (bTargetUsage[Iter])
{
FString TargetName = FString::Printf(TEXT("PIXELSHADEROUTPUT_MRT%d"), Iter);
OutEnvironment.SetDefine(TargetName.GetCharArray().GetData(), TEXT("1"));
}
}
}
}
else
{
// This uses the legacy logic from CalculateDerivedMaterialParameters(); Just keeping it around momentarily for testing during the transition.
SET_COMPILE_BOOL_IF_TRUE(PIXELSHADEROUTPUT_MRT0)
SET_COMPILE_BOOL_IF_TRUE(PIXELSHADEROUTPUT_MRT1)
SET_COMPILE_BOOL_IF_TRUE(PIXELSHADEROUTPUT_MRT2)
SET_COMPILE_BOOL_IF_TRUE(PIXELSHADEROUTPUT_MRT3)
SET_COMPILE_BOOL_IF_TRUE(PIXELSHADEROUTPUT_MRT4)
SET_COMPILE_BOOL_IF_TRUE(PIXELSHADEROUTPUT_MRT5)
SET_COMPILE_BOOL_IF_TRUE(PIXELSHADEROUTPUT_MRT6)
}
#endif
```
### MaterialTemplate.ush
MaterialTemplate.ush中定义许多模版函数里面的具体内容会在HLSLMaterialTranslator.h中的**GetMaterialShaderCode()** 中添加。最后这些函数会在BassPassPixelShader.usf中调用。
bool bEnableExecutionFlow的作用为是否使用新的材质HLSL生成器默认为0。
```c++
static TAutoConsoleVariable<int32> CVarMaterialEnableNewHLSLGenerator(
TEXT("r.MaterialEnableNewHLSLGenerator"),
0,
TEXT("Enables the new (WIP) material HLSL generator.\n")
TEXT("0 - Don't allow\n")
TEXT("1 - Allow if enabled by material\n")
TEXT("2 - Force all materials to use new generator\n"),
ECVF_RenderThreadSafe | ECVF_ReadOnly);
```
这个和新版材质HLSL生成器有关相关生成代码为**MaterialEmitHLSL()**=>调用**GenerateMaterialTemplateHLSL()**
bCompileForComputeShader = Material->IsLightFunction();
GetPerInstanceCustomDataX分为Vertex与Pixel版本。
#### FMaterialAttributes
MaterialTemplate.ush有一处`/** Material declarations */`之后会生成对应FMaterialAttributes结构体可以在材质编辑器的HLSL中查看生成结果。这与
- MaterialAttributeDefinitionMap.cppFMaterialAttributeDefinitionMap::InitializeAttributeMap()中定义属性。
- HLSLMaterialTranslator.cppGetMaterialShaderCode()中的`for (const FGuid& AttributeID : OrderedVisibleAttributes)`:生成对应属性结构体以及属性获取函数。
#### DerivativeAutogen.GenerateUsedFunctions()
```c++
{
FString DerivativeHelpers = DerivativeAutogen.GenerateUsedFunctions(*this);
FString DerivativeHelpersAndResources = DerivativeHelpers + ResourcesString;
//LazyPrintf.PushParam(*ResourcesString);
LazyPrintf.PushParam(*DerivativeHelpersAndResources);
}
```
#### GetMaterialEmissiveForCS()以及其他函数
```c++
if (bCompileForComputeShader)
{
LazyPrintf.PushParam(*GenerateFunctionCode(CompiledMP_EmissiveColorCS, BaseDerivativeVariation));
}
else
{
LazyPrintf.PushParam(TEXT("return 0"));
}
{
FLinearColor Extinction = Material->GetTranslucentMultipleScatteringExtinction();
LazyPrintf.PushParam(*FString::Printf(TEXT("return MaterialFloat3(%.5f, %.5f, %.5f)"), Extinction.R, Extinction.G, Extinction.B));
}
LazyPrintf.PushParam(*FString::Printf(TEXT("return %.5f"), Material->GetOpacityMaskClipValue()));
{
const FDisplacementScaling DisplacementScaling = Material->GetDisplacementScaling();
LazyPrintf.PushParam(*FString::Printf(TEXT("return %.5f"), FMath::Max(0.0f, DisplacementScaling.Magnitude)));
LazyPrintf.PushParam(*FString::Printf(TEXT("return %.5f"), FMath::Clamp(DisplacementScaling.Center, 0.0f, 1.0f)));
}
LazyPrintf.PushParam(!bEnableExecutionFlow ? *GenerateFunctionCode(MP_WorldPositionOffset, BaseDerivativeVariation) : TEXT("return Parameters.MaterialAttributes.WorldPositionOffset"));
LazyPrintf.PushParam(!bEnableExecutionFlow ? *GenerateFunctionCode(CompiledMP_PrevWorldPositionOffset, BaseDerivativeVariation) : TEXT("return 0.0f"));
LazyPrintf.PushParam(!bEnableExecutionFlow ? *GenerateFunctionCode(MP_CustomData0, BaseDerivativeVariation) : TEXT("return 0.0f"));
LazyPrintf.PushParam(!bEnableExecutionFlow ? *GenerateFunctionCode(MP_CustomData1, BaseDerivativeVariation) : TEXT("return 0.0f"));
```
%.5f表示按浮点数输出小数点后面取5位其余的舍弃例如5/2 “%.5f”输出为2.50000
#### MaterialCustomizedUVs & CustomInterpolators
- `for (uint32 CustomUVIndex = 0; CustomUVIndex < NumUserTexCoords; CustomUVIndex++)`
- `for (UMaterialExpressionVertexInterpolator* Interpolator : CustomVertexInterpolators`
### 添加ToonDataAssetID 与 ToonOutlineDataAssetID笔记
1. FMaterialRenderProxy::UpdateDeferredCachedUniformExpressions()
2. FMaterialRenderProxy::EvaluateUniformExpressions()
3. FUniformExpressionSet::FillUniformBuffer()
4. EvaluatePreshader()
5. EvaluateParameter()
6. Context.MaterialRenderProxy->GetParameterValue()
可以看得出关键数据在UniformExpressionSet中这里的ParameterIndex则通过`EvaluateParameter(Stack, UniformExpressionSet, ReadPreshaderValue<uint16>(Data), Context);`进行计算。
```c++
const FMaterialNumericParameterInfo& Parameter = UniformExpressionSet->GetNumericParameter(ParameterIndex);
bool bFoundParameter = false;
// First allow proxy the chance to override parameter
if (Context.MaterialRenderProxy)
{
FMaterialParameterValue ParameterValue;
if (Context.MaterialRenderProxy->GetParameterValue(Parameter.ParameterType, Parameter.ParameterInfo, ParameterValue, Context))
{
Stack.PushValue(ParameterValue.AsShaderValue());
bFoundParameter = true;
}
}
bool FMaterialInstanceResource::GetParameterValue(EMaterialParameterType Type, const FHashedMaterialParameterInfo& ParameterInfo, FMaterialParameterValue& OutValue, const FMaterialRenderContext& Context) const
{
checkSlow(IsInParallelRenderingThread());
bool bResult = false;
// Check for hard-coded parameters
if (Type == EMaterialParameterType::Scalar && ParameterInfo.Name == GetSubsurfaceProfileParameterName())
{
check(ParameterInfo.Association == EMaterialParameterAssociation::GlobalParameter);
const USubsurfaceProfile* MySubsurfaceProfileRT = GetSubsurfaceProfileRT();
OutValue = GetSubsurfaceProfileId(MySubsurfaceProfileRT);
bResult = true;
}
else if (Type == EMaterialParameterType::Scalar && NumSpecularProfileRT() > 0)
{
for (uint32 It=0,Count=NumSpecularProfileRT();It<Count;++It)
{
if (ParameterInfo.Name == SpecularProfileAtlas::GetSpecularProfileParameterName(GetSpecularProfileRT(It)))
{
check(ParameterInfo.Association == EMaterialParameterAssociation::GlobalParameter);
OutValue = SpecularProfileAtlas::GetSpecularProfileId(GetSpecularProfileRT(It));
bResult = true;
break;
}
}
}
```
### BasePass EncodeGBufferToMRT/DecodeGBufferDataDirect逻辑笔记
主要逻辑位于FShaderCompileUtilities::WriteGBufferInfoAutogen():
```c++
void FShaderCompileUtilities::WriteGBufferInfoAutogen(EShaderPlatform TargetPlatform, ERHIFeatureLevel::Type FeatureLevel = ERHIFeatureLevel::SM5)
{
FGBufferParams DefaultParams = FetchGBufferParamsPipeline(TargetPlatform, GBL_Default);
FScopeLock MapLock(&GCriticalSection);
// For now, the logic always calculates the new GBuffer, and if it's the first time, write it, otherwise check it hasn't changed. We are doing this for
// debugging, and in the near future it will only calculate the GBuffer on the first time only.
FGBufferInfo DefaultBufferInfo = FetchFullGBufferInfo(DefaultParams);
FString AutoGenDirectory = GetAutoGenDirectory(TargetPlatform);
FString AutogenHeaderFilename = AutoGenDirectory / TEXT("AutogenShaderHeaders.ush");
FString AutogenHeaderFilenameTemp = AutoGenDirectory / TEXT("AutogenShaderHeaders_temp.ush");
if (GLastGBufferIsValid[TargetPlatform])
{
const bool bSame = IsGBufferInfoEqual(GLastGBufferInfo[TargetPlatform], DefaultBufferInfo);//判断GBufferInfo是否相同不同则触发断言
check(bSame);
}
else
{
GLastGBufferIsValid[TargetPlatform] = true;
// should cache this properly, and serialize it, but this is a temporary fix.
GLastGBufferInfo[TargetPlatform] = DefaultBufferInfo;
FString OutputFileData;
OutputFileData += TEXT("// Copyright Epic Games, Inc. All Rights Reserved.\n");
OutputFileData += TEXT("\n");
OutputFileData += TEXT("#pragma once\n");
OutputFileData += TEXT("\n");
OutputFileData += TEXT("#if FEATURE_LEVEL >= FEATURE_LEVEL_SM5\n");
OutputFileData += TEXT("float SampleDeviceZFromSceneTexturesTempCopy(float2 UV)\n");
OutputFileData += TEXT("{\n");
OutputFileData += TEXT("\treturn SceneDepthTexture.SampleLevel(SceneDepthTextureSampler, UV, 0).r;\n");
OutputFileData += TEXT("}\n");
OutputFileData += TEXT("#endif\n");
OutputFileData += TEXT("\n");
OutputFileData += TEXT("#ifndef GBUFFER_LAYOUT\n");
OutputFileData += TEXT("#define GBUFFER_LAYOUT 0\n");
OutputFileData += TEXT("#endif\n");
OutputFileData += TEXT("\n");
for (uint32 Layout = 0; Layout < GBL_Num; ++Layout)
{
FGBufferParams Params = FetchGBufferParamsPipeline(TargetPlatform, (EGBufferLayout)Layout);
FGBufferInfo BufferInfo = FetchFullGBufferInfo(Params);
OutputFileData.Appendf(TEXT("#if GBUFFER_LAYOUT == %u\n\n"), Layout);
OutputFileData += CreateGBufferEncodeFunction(BufferInfo);
OutputFileData += TEXT("\n");
OutputFileData += CreateGBufferDecodeFunctionDirect(BufferInfo);
OutputFileData += TEXT("\n");
//OutputFileData += TEXT("#if SHADING_PATH_DEFERRED\n");
OutputFileData += TEXT("#if FEATURE_LEVEL >= FEATURE_LEVEL_SM5\n");
OutputFileData += TEXT("\n");
OutputFileData += CreateGBufferDecodeFunctionVariation(BufferInfo, EGBufferDecodeType::CoordUV, FeatureLevel);
OutputFileData += TEXT("\n");
OutputFileData += CreateGBufferDecodeFunctionVariation(BufferInfo, EGBufferDecodeType::CoordUInt, FeatureLevel);
OutputFileData += TEXT("\n");
OutputFileData += CreateGBufferDecodeFunctionVariation(BufferInfo, EGBufferDecodeType::SceneTextures, FeatureLevel);
OutputFileData += TEXT("\n");
OutputFileData += CreateGBufferDecodeFunctionVariation(BufferInfo, EGBufferDecodeType::SceneTexturesLoad, FeatureLevel);
OutputFileData += TEXT("\n");
OutputFileData += TEXT("#endif\n");
OutputFileData += TEXT("\n");
OutputFileData += TEXT("#endif\n");
OutputFileData += TEXT("\n");
}
...
}
```
写入内容与这2句获取的FGbufferInfo有关`FGBufferParams Params = FetchGBufferParamsPipeline(TargetPlatform, (EGBufferLayout)Layout);`和`FGBufferInfo BufferInfo = FetchFullGBufferInfo(Params);`
![[ShaderGenerationUtil_CreateGBufferEncodeFunction.png|1200]]
## 是否需要Toon
在材质中:
```c++
FMaterialRelevance UMaterialInterface::GetRelevance_Internal(const UMaterial* Material, ERHIFeatureLevel::Type InFeatureLevel) const
{
if(Material)
{
//YivanLee's Modify 这里仅仅针对人物因为它决定了是否开启ToonGBuffer但是对于ToonLevelToonFoliageToonGrass这里并不需要开启
bool bUseToonData = MaterialResource->GetShadingModels().HasAnyShadingModel({ MSM_ToonStandard, MSM_ToonSkin, MSM_ToonHair, MSM_ToonFace, MSM_ToonEyeBrow });
}
···
MaterialRelevance.bUsesToonData = bUseToonData;
···
}
```
在渲染管线中:
```c++
//RenderUtils.cpp
bool IsUsingToonRendering(const FStaticShaderPlatform Platform)
{
    static FShaderPlatformCachedIniValue<int32> PerPlatformCVar(TEXT("r.ToonRendering.Enable"));
    if (IsMobilePlatform(Platform) || IsForwardShadingEnabled(Platform))//目前不考虑VR与移动端
    {
        return false;
    }
    else
    {
        return (PerPlatformCVar.Get(Platform) == 1);
    }
}
bool IsUsingToonOutline(const FStaticShaderPlatform Platform)
{
    static FShaderPlatformCachedIniValue<int32> PerPlatformCVar(TEXT("r.ToonRendering.ToonOutline"));
    return (PerPlatformCVar.Get(Platform) == 1) && IsUsingToonRendering(Platform);
}
bool IsUsingToonRimLighting(const FStaticShaderPlatform Platform)
{
    static FShaderPlatformCachedIniValue<int32> PerPlatformCVar(TEXT("r.ToonRendering.ToonRimLighting"));
    return (PerPlatformCVar.Get(Platform) == 1) && IsUsingToonRendering(Platform);
}
```
李兄的ToonBuffer判断逻辑
```c++
bool FDeferredShadingSceneRenderer::ShouldRenderToonDataPass() const
{
if (!SupportsToonDataMaterials(FeatureLevel, ShaderPlatform))
{
return false;
}
if (IsForwardShadingEnabled(GetFeatureLevelShaderPlatform(FeatureLevel)))
{
return false;
}
for (auto& View : Views)
{
if (View.ShouldRenderView() && View.ParallelMeshDrawCommandPasses[EMeshPass::ToonDataPass].HasAnyDraw())
{
return true;
}
}
return false;
}
```
## Toon PerObjectGBufferData具体功能表
从3开始0、1、2已被占用。
- ?
## ToonBufferData
- ToonObjectID
```c++
struct FSceneDataIntermediates
{
uint PrimitiveId;
uint InstanceId;
uint ViewIndex;
uint CullingFlags;
// Index from which we load the instance info, needed for the
uint InstanceIdLoadIndex;
FInstanceSceneData InstanceData;
FPrimitiveSceneData Primitive;
};
struct FVertexFactoryIntermediatesCommon
{
/** Cached primitive and instance data */
FSceneDataIntermediates SceneData;
#if USE_INSTANCING || USE_INSTANCE_CULLING
FVertexFactoryInstanceInput InstanceInput;
#endif
#if USE_SPLINEDEFORM
FSplineMeshShaderParams SplineMeshParams;
#endif
};
FPrimitiveSceneData GetPrimitiveData(FVertexFactoryIntermediatesCommon Intermediates)
{
return Intermediates.SceneData.Primitive;
}
```
## 高光
- PBR高光使用Roughness控制是否可行是否需要传入GBuffer一个Mask贴图
- 自定义高光:高光贴图、高光颜色、参数化高光形状、多层高光
# BasePassPixelShader
Velocity相关代码段
```c++
#if USES_GBUFFER
// -0.5 .. 0.5, could be optimzed as lower quality noise would be sufficient
float QuantizationBias = PseudoRandom( MaterialParameters.SvPosition.xy ) - 0.5f;
GBuffer.IndirectIrradiance = IndirectIrradiance;
// this is the new encode, the older encode is the #else, keeping it around briefly until the new version is confirmed stable.
#if 1
{
// change this so that we can pack everything into the gbuffer, but leave this for now
#if GBUFFER_HAS_DIFFUSE_SAMPLE_OCCLUSION
GBuffer.GenericAO = float(GBuffer.DiffuseIndirectSampleOcclusion) * (1.0f / 255.0f);
#elif ALLOW_STATIC_LIGHTING
// No space for AO. Multiply IndirectIrradiance by AO instead of storing.
GBuffer.GenericAO = EncodeIndirectIrradiance(GBuffer.IndirectIrradiance * GBuffer.GBufferAO) + QuantizationBias * (1.0 / 255.0); // Stationary sky light path
#else
GBuffer.GenericAO = GBuffer.GBufferAO; // Movable sky light path
#endif
EncodeGBufferToMRT(Out, GBuffer, QuantizationBias);
if (GBuffer.ShadingModelID == SHADINGMODELID_UNLIT && !STRATA_ENABLED) // Do not touch what strata outputs
{
Out.MRT[1] = 0;
SetGBufferForUnlit(Out.MRT[2]);
Out.MRT[3] = 0;
Out.MRT[GBUFFER_HAS_VELOCITY ? 5 : 4] = 0;
Out.MRT[GBUFFER_HAS_VELOCITY ? 6 : 5] = 0;
}
#if SINGLE_LAYER_WATER_SEPARATED_MAIN_LIGHT
// In deferred, we always output the directional light in a separated buffer.
// This is used to apply distance field shadows or light function to the main directional light.
// Strata also writes it through MRT because this is faster than through UAV.
#if STRATA_ENABLED && STRATA_INLINE_SINGLELAYERWATER
Out.MRT[(GBUFFER_HAS_VELOCITY ? 2 : 1) + (GBUFFER_HAS_PRECSHADOWFACTOR ? 1 : 0)] = float4(SeparatedWaterMainDirLightLuminance * View.PreExposure, 1.0f);
#else
if (GBuffer.ShadingModelID == SHADINGMODELID_SINGLELAYERWATER)
{
Out.MRT[(GBUFFER_HAS_VELOCITY ? 6 : 5) + (GBUFFER_HAS_PRECSHADOWFACTOR ? 1 : 0)] = float4(SeparatedWaterMainDirLightLuminance * View.PreExposure, 1.0f);
}
#endif
#endif
}
```
# 顶点色
## 蓝色协议
用于存储一些低精度数据,插值即可
- 顶点色:
- R:阴影区域控制(强度) 0~1
- G:描边宽度
- B:ToonAO
- 第二套顶点色UV Channel1
- R:深度偏移
- G:用来区分内轮廓不同部位的ID
蓝色协议的R:阴影区域标记 与 B:AO而罪恶装备使用贴图来传递信息。
## 罪恶装备
对阴影判断阈值的偏移。见前面着色部分顶点AO+手绘修正)
R:阴影偏移
G:轮廓线根据与相机的距离扩大多少的系数
B:等高线 Z 轴偏移值
# 罪恶装备
![](https://pic2.zhimg.com/80/v2-56012886fafbaf36932f03b0ad65a165_720w.jpg)8,G为阴影控AOR为高光强度参数金属和光滑材质的部分设置的更大一些。B通道用于照明控制。最大值为高光反之值越小高光越淡。![](https://pic4.zhimg.com/80/v2-748ebbdd4da3efe74054c8215be8b023_720w.jpg)
![](https://pic2.zhimg.com/80/v2-74e1a9fba264af2b18e66616d9f86831_720w.jpg)
https://zhuanlan.zhihu.com/p/360229590一文中介绍了崩坏3与原神的计算方式
崩坏3的LightMap计算方式
```c++
half4 baseColor = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv.xy);
half4 LightMapColor = SAMPLE_TEXTURE2D(_LightMap, sampler_LightMap, input.uv.xy);
half3 ShadowColor = baseColor.rgb * _ShadowMultColor.rgb;
half3 DarkShadowColor = baseColor.rgb * _DarkShadowMultColor.rgb;
//如果SFactor = 0,ShallowShadowColor为一级阴影色,否则为BaseColor。
float SWeight = (LightMapColor.g * input.color.r + input.lambert) * 0.5 + 1.125;
float SFactor = floor(SWeight - _ShadowArea);
half3 ShallowShadowColor = SFactor * baseColor.rgb + (1 - SFactor) * ShadowColor.rgb;
```
二级阴影计算:
```c++
//如果SFactor = 0,DarkShadowColor为二级阴影色,否则为一级阴影色。
SFactor = floor(SWeight - _DarkShadowArea);
DarkShadowColor = SFactor * (_FixDarkShadow * ShadowColor + (1 - _FixDarkShadow) * ShallowShadowColor) + (1 - SFactor) * DarkShadowColor;
// 平滑阴影边缘
half rampS = smoothstep(0, _ShadowSmooth, input.lambert - _ShadowArea);
half rampDS = smoothstep(0, _DarkShadowSmooth, input.lambert - _DarkShadowArea);
ShallowShadowColor.rgb = lerp(ShadowColor, baseColor.rgb, rampS);
DarkShadowColor.rgb = lerp(DarkShadowColor.rgb, ShadowColor, rampDS);
//如果SFactor = 0,FinalColor为二级阴影否则为一级阴影。
SFactor = floor(LightMapColor.g * input.color.r + 0.9f);
half4 FinalColor;
FinalColor.rgb = SFactor * ShallowShadowColor + (1 - SFactor) * DarkShadowColor;
```
**罪恶装备**
对阴影判断阈值的偏移。见前面着色部分顶点AO+手绘修正)
G : 轮廓线根据与相机的距离扩大多少的系数
B : 等高线 Z 轴偏移值
A : 轮廓厚度系数。0.5为标准1为最大厚度0为无等高线
# 蓝色协议
[[蓝色协议的方案]]
# 米哈游

View File

@@ -0,0 +1,11 @@
---
title: Untitled
date: 2025-01-15 16:33:08
excerpt:
tags:
rating: ⭐
---
在渲染线程使用UObject会导致崩溃所以使用将部分参数传递到MaterialRenderProxy的方式来规避。但这样还需要解决UToonDataAsset不会触发Material刷新MaterialRenderProxy的问题。
# 思路
1. UMaterialInstance::PreSave()

View File

@@ -0,0 +1,114 @@
---
title: ToonShaderModel
date: 2023-12-18 10:00:34
excerpt:
tags:
rating: ⭐
---
# ToonStandardCel打底
## Diffuse
```c++
//Lighting.Diffuse *= AreaLight.FalloffColor * (Falloff * HalfLambert);
//TODO:添加阴影过渡效果。
//ToonShadow添加类型1.PBR(只能调整过渡颜色) Lighting.Diffuse = DiffuseColor * (1 / PI) * AreaLight.FalloffColor * (Falloff * HalfLambert); 2.ShadowColorTexture兼容RampTexture效果也就是原神的效果还需要添加ShadowColorIntensity后需要改成曝光相关额东西。Lighting.Diffuse = DiffuseColor * (1 / PI);
//ToonLighting添加类型1.PBR多光源模式2.主光有效针对cel。
//---------------------------------------------------------
//Specular
//ToonSpecular添加类型1.PBR 2. 各项异性defaultLit3. 各项异性头发 4. 自定义高光大小与过渡效果 https://zhuanlan.zhihu.com/p/361918341 5.自定义高光形状贴图 https://zhuanlan.zhihu.com/p/640258070 https://github.com/AnCG7/URPShaderCodeSample/blob/891034b3fa6e838e2b231390755479f0f649f181/Assets/Shaders/NPR/Cartoon/Stylized%20Highlight%20(Transform).shader#L2
```
**ToonShadow**
再实现阴影偏移以及阴影羽化效果的基础上。
1. PBR兼容UE默认阴影效果。只能调节阴影过渡颜色。使用的公式为 `Lighting.Diffuse = DiffuseColor * (1 / PI) * AreaLight.FalloffColor * (Falloff * HalfLambert);`
2. ShadowColorTexture用于定义ShadowColor使用贴图指定ShadowColor
**ToonLighting**
1. PBRUE默认光照多光源模式。
2. 仅主光有效针对cel。
## Specular
**ToonSpecular**
1. PBRUE默认高光效果。
2. 各项异性defaultLitUE默认的各向异性高光效果。
3. 各项异性头发 **Kajiya-Kay**高光模型。
4. 自定义高光大小与过渡效果https://zhuanlan.zhihu.com/p/361918341
5. 自定义高光形状贴图https://zhuanlan.zhihu.com/p/640258070 https://github.com/AnCG7/URPShaderCodeSample/blob/891034b3fa6e838e2b231390755479f0f649f181/Assets/Shaders/NPR/Cartoon/Stylized%20Highlight%20(Transform).shader#L2
## 罪恶装备渲染效果还原
- 原始演讲视频:https://www.bilibili.com/video/BV1Ps411C7mw/?share_source=copy_web&vd_source=fe8142e8e12816535feaeabd6f6cdc8e
- 原始演讲PPT:https://www.ggxrd.com/Motomura_Junya_GuiltyGearXrd.pdf
- [【翻译】西川善司「实验做出的游戏图形」「GUILTY GEAR Xrd -SIGN-」中实现的「纯卡通动画的实时3D图形」的秘密前篇1](https://www.cnblogs.com/TracePlus/p/4205798.html "发布于 2015-01-06 12:56")
- [【翻译】西川善司「实验做出的游戏图形」「GUILTY GEAR Xrd -SIGN-」中实现的「纯卡通动画的实时3D图形」的秘密前篇2](https://www.cnblogs.com/TracePlus/p/4205834.html "发布于 2015-01-06 13:23")
- [【翻译】西川善司的「实验做出的游戏图形」「GUILTY GEAR Xrd -SIGN-」中实现的「纯卡通动画的实时3D图形」的秘密后篇](https://www.cnblogs.com/TracePlus/p/4205978.html "发布于 2015-01-06 14:23")
- 知乎文章
1. [x] ***https://zhuanlan.zhihu.com/p/631214546
2. [ ] https://zhuanlan.zhihu.com/p/436850004
3. https://zhuanlan.zhihu.com/p/513598386
4. https://zhuanlan.zhihu.com/p/493802718
5. [x] https://zhuanlan.zhihu.com/p/513228315
1. 其他人的Demo视频角色胜利动画。
1. [ ] [Guraaaa的技术美术作品集](https://www.bilibili.com/video/BV1xx4y1T7Er/?share_source=copy_web&vd_source=fe8142e8e12816535feaeabd6f6cdc8e&t=11)
### 贴图
- base基础色
- [x] rgb亮面颜色
- [x] **a**basecolor的A通道用于区分脸、身体区域和头发区域。BGT会兜帽内侧与脸都是黑色、头发是灰色MAY头发是灰色身体与头是黑色RAM、JKO身体与头发都是黑色SOL正脸、头发是黑色侧脸是白色。
- ShadowMap SSS
- [x] rgb暗面颜色
- **a**
- ILM
- [x] **ilmR**:高光强度 ***高光类型(待验证)***,根绝高光类型来设置不同的强度数值。
- `0` 无高光、无边缘光。为角色的眼睛或者眉毛有个例外JKO的头发。
- `(0~50]` 计算高光?、边缘光。主要的渲染区域,为角色的主要衣服与皮肤。
- `(50~100]` 贴图绘制高光ILM.b)、有边缘光。为角色的**头发**与衣服。
- `(100~150]`计算高光、无边缘光。为角色身体上的金属边缘或者刮痕SOL的武器。
- `(150~200]`贴图绘制高光、无边缘光。为角色身体上大部分的金属物件。SOL腰带上的铁环与肩膀处的铁环。
- `(200~255]`
- [x] ilmG阴影与高光Offset
- [x] ilmB高光遮罩 高光类型Mask。
- 目前猜测计算高光使用数值调整高光大小类似Roughness高光Mask直接使用这个Mask。
- `0`:无高光
- `128`:计算高光区域。
- `(128,255]`(128,255) => (0,1) 高光Mask
- [x] ilmA本衬线基于UV制作风格化内描边原理参考先前文章内描边Mask。
- [x] detailtexcoord[1]细节贴图第二套UV。
- [x] decal贴花单独一个材质。
- VertexColor
- [x] VertexColor(RAO
- [x] VertexColor(G猜测是轮廓线的PixelDepthOffset以此解决角色叠在一起可能会出现的问题。
- ~~Xrd翻译文章对应到Camera的距离轮廓线的在哪个范围膨胀的系数。~~
- [x] VertexColor(B描边粗细也就是Backface Outline的挤出数值。
- Xrd翻译文章轮廓线的Z Offset 值
- ~~VertexColor(A) ~~:不存在
- Xrd翻译文章轮廓线的粗细系数。0.5是标准1是最粗0的话就没有轮廓线。
- [x] OLM皮肤Mask部分角色RAM、JKO还会包括头发。颜色数值不一定相同RAM就不同
### 边缘光
1. 主光的方向不确定,但边缘光的方向是固定的。
2. 使用NoL计算边缘光不同区域的边缘光使用某个参数控制宽度估计是裁边
3.
![[边缘光.png]]
![[BGT边缘光.png|500]]
![[BGT边缘光2.png|500]]
![[BGT边缘光3.png|500]]
![[BGT边缘光4——大佛.png|500]]
![[BGT边缘光5.png|500]]
### TODO
- [x] 实现AmbientOcclusion叠加阴影效果。
- [x] 添加ToonDataAsset 控制是否接受阴影选项。
- [x] 使用代理Shadow模型来渲染阴影。
## UnityChan
## 蓝色协议
# 厚涂 ShaderModel通过修改预积分ShaderModel

View File

@@ -0,0 +1,18 @@
---
title: Toon多光源参考
date: 2025-03-27 19:01:13
excerpt:
tags:
rating: ⭐
---
# 前言
- [【UE5】卡通渲染着色篇3多光源](https://zhuanlan.zhihu.com/p/717533663)
- DirectionalLight
- 主要是通过判断所有灯光的Forward Shading Priority与亮度取得主光。
- 之后在FDeferredLightPS中添加一个判断是否是主光的变体并进行设置即可。
- 也可以通过FlattenNormal来减少高频信息。
- PointLight
- 可以通过FlattenNormal(ShadingModels.ush)来减少点光源计算中的高频细节
- YivanLee的多光源方案
- ShadingModels.ush中只渲染光影不渲染颜色用于合并多光源光影结果。
- 在Lighting Pass之后添加一个LightingPostProcess Pass根据合并的光影采样Ramp渲染最终光照结果。