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,10 @@
---
title: 未命名
date: 2025-03-20 18:07:58
excerpt:
tags:
rating: ⭐
---
# 前言
- [UE5 MeshShader初探](https://zhuanlan.zhihu.com/p/30320015352)
- [UE5 MeshShader完整实践](https://zhuanlan.zhihu.com/p/30893647975)

View File

@@ -0,0 +1,77 @@
---
title: Untitled
date: 2024-08-15 12:02:42
excerpt:
tags:
rating: ⭐
---
# 前言
- [UE5渲染--GPUScene与合并绘制](https://zhuanlan.zhihu.com/p/614758211)
# 相关类型
- Primitive
- C++的数据类型_FPrimitiveUniformShaderParameters_PrimitiveUniformShaderParameters.h)
- Shader的数据FPrimitiveSceneDataSceneData.ush)
- Instance
- C++的数据类型FInstanceSceneShaderDataInstanceUniformShaderParameters.h)
- Shader的数据FInstanceSceneDataSceneData.ush)
- Payload
- C++数据类型FPackedBatch、FPackedItemInstanceCullingLoadBalancer.h)
- Shader的数据类型FPackedInstanceBatch、FPackedInstanceBatchItemInstanceCullingLoadBalancer.ush)
# DeferredShadingRenderer.cpp
```c++
Scene->GPUScene.Update(GraphBuilder, GetSceneUniforms(), *Scene, ExternalAccessQueue);
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
FViewInfo& View = Views[ViewIndex];
RDG_GPU_MASK_SCOPE(GraphBuilder, View.GPUMask);
Scene->GPUScene.UploadDynamicPrimitiveShaderDataForView(GraphBuilder, *Scene, View, ExternalAccessQueue);
Scene->GPUScene.DebugRender(GraphBuilder, *Scene, GetSceneUniforms(), View);
}
```
## GPUScene数据更新
```c++
// 函数调用关系
void FDeferredShadingSceneRenderer::Render(FRDGBuilder& GraphBuilder)
{
void FGPUScene::Update(FRDGBuilder& GraphBuilder, FScene& Scene, FRDGExternalAccessQueue& ExternalAccessQueue)
{
void FGPUScene::UpdateInternal(FRDGBuilder& GraphBuilder, FScene& Scene, FRDGExternalAccessQueue& ExternalAccessQueue)
}
}
```
### 2.2、更新PrimitiveData
![](https://pic4.zhimg.com/80/v2-8bbde169a322380c8510375091517133_720w.webp)
接下来对标记为dirty的Primitive进行更新更新逻辑为找到该Primitive在PrimitiveData Buffer里对应offset位置对其进行更新。使用模板FUploadDataSourceAdapterScenePrimitives调用UploadGeneral()。
![](https://pic2.zhimg.com/80/v2-8f1c866194961597d4c3a40f24442fb1_720w.webp)
初始化5类Buffer的上传任务TaskContext
后面代码都是在对这个TaskContext进行初始化然后启动任务进行数据的上传。
![](https://pic4.zhimg.com/80/v2-57bdcff035f205a83d3ca1a76036b9df_720w.webp)
![](https://pic3.zhimg.com/80/v2-4a22a7aa675804c2598bbd5a60062922_720w.webp)
2.2.1、并行更新Primitive数据将需要更新的PrimitiveCopy到Upload Buffer里后续通过ComputeShader进行显存的上传然后更新到目标Buffer里
![](https://pic3.zhimg.com/80/v2-e0260c108d7428401228dbf7be0408da_720w.webp)
2.2.2、同理InstanceSceneData以及InstancePayloadData数据的处理。
2.2.3、同理InstanceBVHUploaderLightmapUploader。
2.2.4、最后都会调用每个Uploader的***End()方法进行GPU显存的更新***。
总结数据的上传都一样将需要更新的数据Copy到对应的UploadBuffer对应的位置里然后通过对应的ComputeShader进行更新到目标Buffer在函数FRDGAsyncScatterUploadBuffer::End()里实现,截图:
![](https://pic2.zhimg.com/80/v2-9f1c352cf6fd2900e60280c80e0e3cf1_720w.webp)

View File

@@ -0,0 +1,33 @@
---
title: Untitled
date: 2024-10-16 14:39:40
excerpt:
tags:
rating: ⭐
---
# 前言
# Stencil相关
BasePass:绘制Stencil
=>
CopyStencilToLightingChannels
=>
ClearStencil (SceneDepthZ) :清空深度缓存中的Stencil
## BasePass
BasePass中进行LIGHTING_CHANNELS、DISTANCE_FIELD_REPRESENTATION、贴花方面的Mask Bit计算设置到深度缓存的Stencil上。
```c++
template<bool bDepthTest, ECompareFunction CompareFunction>
void SetDepthStencilStateForBasePass_Internal(FMeshPassProcessorRenderState& InDrawRenderState, ERHIFeatureLevel::Type FeatureLevel)
{
const static bool bStrataDufferPassEnabled = Strata::IsStrataEnabled() && Strata::IsDBufferPassEnabled(GShaderPlatformForFeatureLevel[FeatureLevel]);
if (bStrataDufferPassEnabled)
{
SetDepthStencilStateForBasePass_Internal<bDepthTest, CompareFunction, GET_STENCIL_BIT_MASK(STRATA_RECEIVE_DBUFFER_NORMAL, 1) | GET_STENCIL_BIT_MASK(STRATA_RECEIVE_DBUFFER_DIFFUSE, 1) | GET_STENCIL_BIT_MASK(STRATA_RECEIVE_DBUFFER_ROUGHNESS, 1) | GET_STENCIL_BIT_MASK(DISTANCE_FIELD_REPRESENTATION, 1) | STENCIL_LIGHTING_CHANNELS_MASK(0x7)>(InDrawRenderState);
}
else
{
SetDepthStencilStateForBasePass_Internal<bDepthTest, CompareFunction, GET_STENCIL_BIT_MASK(RECEIVE_DECAL, 1) | GET_STENCIL_BIT_MASK(DISTANCE_FIELD_REPRESENTATION, 1) | STENCIL_LIGHTING_CHANNELS_MASK(0x7)>(InDrawRenderState);
}
}
```

View File

@@ -0,0 +1,816 @@
---
title: Untitled
date: 2025-02-11 11:30:34
excerpt:
tags:
rating: ⭐
---
# FSortedLightSetSceneInfo
有序的光源集合相关定义:
```c++
/** Data for a simple dynamic light. */
class FSimpleLightEntry
{
public:
FVector3f Color;
float Radius;
float Exponent;
float InverseExposureBlend = 0.0f;
float VolumetricScatteringIntensity;
bool bAffectTranslucency;
};
struct FSortedLightSceneInfo
{
union
{
struct
{
// Note: the order of these members controls the light sort order!
// Currently bHandledByLumen is the MSB and LightType is LSB /** The type of light. */ uint32 LightType : LightType_NumBits;
/** Whether the light has a texture profile. */
uint32 bTextureProfile : 1;
/** Whether the light uses a light function. */
uint32 bLightFunction : 1;
/** Whether the light uses lighting channels. */
uint32 bUsesLightingChannels : 1;
/** Whether the light casts shadows. */
uint32 bShadowed : 1;
/** Whether the light is NOT a simple light - they always support tiled/clustered but may want to be selected separately. */
uint32 bIsNotSimpleLight : 1;
/* We want to sort the lights that write into the packed shadow mask (when enabled) to the front of the list so we don't waste slots in the packed shadow mask. */
uint32 bDoesNotWriteIntoPackedShadowMask : 1;
/**
* True if the light doesn't support clustered deferred, logic is inverted so that lights that DO support clustered deferred will sort first in list
* Super-set of lights supporting tiled, so the tiled lights will end up in the first part of this range.
*/
uint32 bClusteredDeferredNotSupported : 1;
/** Whether the light should be handled by Lumen's Final Gather, these will be sorted to the end so they can be skipped */
uint32 bHandledByLumen : 1;
} Fields;
/** Sort key bits packed into an integer. */
int32 Packed;
} SortKey;
const FLightSceneInfo* LightSceneInfo;
int32 SimpleLightIndex;
/** Initialization constructor. */
explicit FSortedLightSceneInfo(const FLightSceneInfo* InLightSceneInfo)
: LightSceneInfo(InLightSceneInfo),
SimpleLightIndex(-1)
{
SortKey.Packed = 0;
SortKey.Fields.bIsNotSimpleLight = 1;
}
explicit FSortedLightSceneInfo(int32 InSimpleLightIndex)
: LightSceneInfo(nullptr),
SimpleLightIndex(InSimpleLightIndex)
{
SortKey.Packed = 0;
SortKey.Fields.bIsNotSimpleLight = 0;
}};
/**
* Stores info about sorted lights and ranges.
* The sort-key in FSortedLightSceneInfo gives rise to the following order:
* [SimpleLights,Clustered,UnbatchedLights,LumenLights] * Note that some shadowed lights can be included in the clustered pass when virtual shadow maps and one pass projection are used. */struct FSortedLightSetSceneInfo
{
int32 SimpleLightsEnd;
int32 ClusteredSupportedEnd;
/** First light with shadow map or */
int32 UnbatchedLightStart;
int32 LumenLightStart;
FSimpleLightArray SimpleLights;
TArray<FSortedLightSceneInfo, SceneRenderingAllocator> SortedLights;
};
```
## 开始获取有序光源集合
UE的光源分配由`FDeferredShadingSceneRenderer::Render`内的`bComputeLightGrid`变量决定的bComputeLightGrid的赋值逻辑如下
```c++
void FDeferredShadingSceneRenderer::Render(FRHICommandListImmediate& RHICmdList) {
...
bool bComputeLightGrid = false;
if (RendererOutput == ERendererOutput::FinalSceneColor)
{
if (bUseVirtualTexturing)
{
// Note, should happen after the GPU-Scene update to ensure rendering to runtime virtual textures is using the correctly updated scene
FVirtualTextureSystem::Get().EndUpdate(GraphBuilder, MoveTemp(VirtualTextureUpdater), FeatureLevel);
}
#if RHI_RAYTRACING
GatherRayTracingWorldInstancesForView(GraphBuilder, ReferenceView, RayTracingScene, InitViewTaskDatas.RayTracingRelevantPrimitives);
#endif // RHI_RAYTRACING
bool bAnyLumenEnabled = false;
{
if (bUseGBuffer)
{
bComputeLightGrid = bRenderDeferredLighting;
}
else
{
bComputeLightGrid = ViewFamily.EngineShowFlags.Lighting;
}
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
FViewInfo& View = Views[ViewIndex];
bAnyLumenEnabled = bAnyLumenEnabled
|| GetViewPipelineState(View).DiffuseIndirectMethod == EDiffuseIndirectMethod::Lumen
|| GetViewPipelineState(View).ReflectionsMethod == EReflectionsMethod::Lumen;
}
bComputeLightGrid |= (
ShouldRenderVolumetricFog() ||
VolumetricCloudWantsToSampleLocalLights(Scene, ViewFamily.EngineShowFlags) ||
ViewFamily.ViewMode != VMI_Lit ||
bAnyLumenEnabled ||
VirtualShadowMapArray.IsEnabled() ||
ShouldVisualizeLightGrid());
}
}
...
}
```
获取有序的光源集合
```c++
void FDeferredShadingSceneRenderer::Render(FRHICommandListImmediate& RHICmdList) {
...
// 有序的光源集合.
FSortedLightSetSceneInfo& SortedLightSet = *GraphBuilder.AllocObject<FSortedLightSetSceneInfo>();
{
RDG_CSV_STAT_EXCLUSIVE_SCOPE(GraphBuilder, SortLights);
RDG_GPU_STAT_SCOPE(GraphBuilder, SortLights);
ComputeLightGridOutput = GatherLightsAndComputeLightGrid(GraphBuilder, bComputeLightGrid, SortedLightSet);
}
...
}
```
PS. 简单光源都可以被分块或分簇渲染,但对于非简单光源,只有满足以下条件的光源才可被分块或分簇渲染:
- 没有使用光源的附加特性TextureProfile、LightFunction、LightingChannel
- 没有开启阴影。
- 非平行光或矩形光。
另外,是否支持分块渲染,还需要光源场景代理的`IsTiledDeferredLightingSupported`返回true长度为0的点光源才支持分块渲染。
## GatherLightsAndComputeLightGrid
```c++
FComputeLightGridOutput FDeferredShadingSceneRenderer::GatherLightsAndComputeLightGrid(FRDGBuilder& GraphBuilder, bool bNeedLightGrid, FSortedLightSetSceneInfo& SortedLightSet)
{
SCOPED_NAMED_EVENT(GatherLightsAndComputeLightGrid, FColor::Emerald);
FComputeLightGridOutput Result = {};
bool bShadowedLightsInClustered = ShouldUseClusteredDeferredShading()
&& CVarVirtualShadowOnePassProjection.GetValueOnRenderThread()
&& VirtualShadowMapArray.IsEnabled();
const bool bUseLumenDirectLighting = ShouldRenderLumenDirectLighting(Scene, Views[0]);
GatherAndSortLights(SortedLightSet, bShadowedLightsInClustered, bUseLumenDirectLighting);
if (!bNeedLightGrid)
{
SetDummyForwardLightUniformBufferOnViews(GraphBuilder, ShaderPlatform, Views);
return Result;
}
bool bAnyViewUsesForwardLighting = false;
bool bAnyViewUsesLumen = false;
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
const FViewInfo& View = Views[ViewIndex];
bAnyViewUsesForwardLighting |= View.bTranslucentSurfaceLighting || ShouldRenderVolumetricFog() || View.bHasSingleLayerWaterMaterial || VolumetricCloudWantsToSampleLocalLights(Scene, ViewFamily.EngineShowFlags) || ShouldVisualizeLightGrid();
bAnyViewUsesLumen |= GetViewPipelineState(View).DiffuseIndirectMethod == EDiffuseIndirectMethod::Lumen || GetViewPipelineState(View).ReflectionsMethod == EReflectionsMethod::Lumen;
}
const bool bCullLightsToGrid = GLightCullingQuality
&& (IsForwardShadingEnabled(ShaderPlatform) || bAnyViewUsesForwardLighting || IsRayTracingEnabled() || ShouldUseClusteredDeferredShading() ||
bAnyViewUsesLumen || ViewFamily.EngineShowFlags.VisualizeMeshDistanceFields || VirtualShadowMapArray.IsEnabled());
// Store this flag if lights are injected in the grids, check with 'AreLightsInLightGrid()'
bAreLightsInLightGrid = bCullLightsToGrid;
Result = ComputeLightGrid(GraphBuilder, bCullLightsToGrid, SortedLightSet);
return Result;
}
```
- GatherAndSortLights收集与排序当前场景中所有的可见光源当前View
- ComputeLightGrid是在锥体空间frustum space裁剪局部光源和反射探针到3D格子中构建每个视图相关的光源列表和格子。
# RenderLights() -> RenderLight()
## InternalRenderLight()
## DeferredLightVertexShaders
```c++
// 输入参数.
struct FInputParams
{
float2 PixelPos;
float4 ScreenPosition;
float2 ScreenUV;
float3 ScreenVector;
};
// 派生参数.
struct FDerivedParams
{
float3 CameraVector;
float3 WorldPosition;
};
// 获取派生参数.
FDerivedParams GetDerivedParams(in FInputParams Input, in float SceneDepth)
{
FDerivedParams Out;
#if LIGHT_SOURCE_SHAPE > 0
// With a perspective projection, the clip space position is NDC * Clip.w
// With an orthographic projection, clip space is the same as NDC
float2 ClipPosition = Input.ScreenPosition.xy / Input.ScreenPosition.w * (View.ViewToClip[3][3] < 1.0f ? SceneDepth : 1.0f);
Out.WorldPosition = mul(float4(ClipPosition, SceneDepth, 1), View.ScreenToWorld).xyz;
Out.CameraVector = normalize(Out.WorldPosition - View.WorldCameraOrigin);
#else
Out.WorldPosition = Input.ScreenVector * SceneDepth + View.WorldCameraOrigin;
Out.CameraVector = normalize(Input.ScreenVector);
#endif
return Out;
}
Texture2D<uint> LightingChannelsTexture;
uint GetLightingChannelMask(float2 UV)
{
uint2 IntegerUV = UV * View.BufferSizeAndInvSize.xy;
return LightingChannelsTexture.Load(uint3(IntegerUV, 0)).x;
}
float GetExposure()
{
return View.PreExposure;
}
```
向往文章中的SetupLightDataForStandardDeferred()变为InitDeferredLightFromUniforms()。位于LightDataUniform.ush。
```c++
FDeferredLightData InitDeferredLightFromUniforms(uint InLightType)
{
const bool bIsRadial = InLightType != LIGHT_TYPE_DIRECTIONAL;
FDeferredLightData Out;
Out.TranslatedWorldPosition = GetDeferredLightTranslatedWorldPosition();
Out.InvRadius = DeferredLightUniforms.InvRadius;
Out.Color = DeferredLightUniforms.Color;
Out.FalloffExponent = DeferredLightUniforms.FalloffExponent;
Out.Direction = DeferredLightUniforms.Direction;
Out.Tangent = DeferredLightUniforms.Tangent;
Out.SpotAngles = DeferredLightUniforms.SpotAngles;
Out.SourceRadius = DeferredLightUniforms.SourceRadius;
Out.SourceLength = bIsRadial ? DeferredLightUniforms.SourceLength : 0;
Out.SoftSourceRadius = DeferredLightUniforms.SoftSourceRadius;
Out.SpecularScale = DeferredLightUniforms.SpecularScale;
Out.ContactShadowLength = abs(DeferredLightUniforms.ContactShadowLength);
Out.ContactShadowLengthInWS = DeferredLightUniforms.ContactShadowLength < 0.0f;
Out.ContactShadowCastingIntensity = DeferredLightUniforms.ContactShadowCastingIntensity;
Out.ContactShadowNonCastingIntensity = DeferredLightUniforms.ContactShadowNonCastingIntensity;
Out.DistanceFadeMAD = DeferredLightUniforms.DistanceFadeMAD;
Out.ShadowMapChannelMask = DeferredLightUniforms.ShadowMapChannelMask;
Out.ShadowedBits = DeferredLightUniforms.ShadowedBits;
Out.bInverseSquared = bIsRadial && DeferredLightUniforms.FalloffExponent == 0; // Directional lights don't use 'inverse squared attenuation'
Out.bRadialLight = bIsRadial;
Out.bSpotLight = InLightType == LIGHT_TYPE_SPOT;
Out.bRectLight = InLightType == LIGHT_TYPE_RECT;
Out.RectLightData.BarnCosAngle = DeferredLightUniforms.RectLightBarnCosAngle;
Out.RectLightData.BarnLength = DeferredLightUniforms.RectLightBarnLength;
Out.RectLightData.AtlasData.AtlasMaxLevel = DeferredLightUniforms.RectLightAtlasMaxLevel;
Out.RectLightData.AtlasData.AtlasUVOffset = DeferredLightUniforms.RectLightAtlasUVOffset;
Out.RectLightData.AtlasData.AtlasUVScale = DeferredLightUniforms.RectLightAtlasUVScale;
Out.HairTransmittance = InitHairTransmittanceData();
return Out;
}
```
### DeferredLightPixelMain
```c++
void DeferredLightPixelMain(
#if LIGHT_SOURCE_SHAPE > 0
float4 InScreenPosition : TEXCOORD0,
#else
float2 ScreenUV : TEXCOORD0,
float3 ScreenVector : TEXCOORD1,
#endif
float4 SVPos : SV_POSITION,
out float4 OutColor : SV_Target0
#if STRATA_OPAQUE_ROUGH_REFRACTION_ENABLED
, out float3 OutOpaqueRoughRefractionSceneColor : SV_Target1
, out float3 OutSubSurfaceSceneColor : SV_Target2
#endif
)
{
const float2 PixelPos = SVPos.xy;
OutColor = 0;
#if STRATA_OPAQUE_ROUGH_REFRACTION_ENABLED
OutOpaqueRoughRefractionSceneColor = 0;
OutSubSurfaceSceneColor = 0;
#endif
// Convert input data (directional/local light)
// 计算屏幕UV
FInputParams InputParams = (FInputParams)0;
InputParams.PixelPos = SVPos.xy;
#if LIGHT_SOURCE_SHAPE > 0
InputParams.ScreenPosition = InScreenPosition;
InputParams.ScreenUV = InScreenPosition.xy / InScreenPosition.w * View.ScreenPositionScaleBias.xy + View.ScreenPositionScaleBias.wz;
InputParams.ScreenVector = 0;
#else
InputParams.ScreenPosition = 0;
InputParams.ScreenUV = ScreenUV;
InputParams.ScreenVector = ScreenVector;
#endif
#if STRATA_ENABLED
FStrataAddressing StrataAddressing = GetStrataPixelDataByteOffset(PixelPos, uint2(View.BufferSizeAndInvSize.xy), Strata.MaxBytesPerPixel);
FStrataPixelHeader StrataPixelHeader = UnpackStrataHeaderIn(Strata.MaterialTextureArray, StrataAddressing, Strata.TopLayerTexture);
BRANCH
if (StrataPixelHeader.BSDFCount > 0 // This test is also enough to exclude sky pixels
#if USE_LIGHTING_CHANNELS
//灯光通道逻辑
&& (GetLightingChannelMask(InputParams.ScreenUV) & DeferredLightUniforms.LightingChannelMask)
#endif
)
{
//通过SceneDepth获取的CameraVector以及当前像素的世界坐标
const float SceneDepth = CalcSceneDepth(InputParams.ScreenUV);
const FDerivedParams DerivedParams = GetDerivedParams(InputParams, SceneDepth);
//设置获取光源各种信息
FDeferredLightData LightData = InitDeferredLightFromUniforms(CURRENT_LIGHT_TYPE);
UpdateLightDataColor(LightData, InputParams, DerivedParams);//根据当前世界坐标计算LightData.Color *= 大气&云&阴影的衰减值 * IES灯亮度非IES灯数值为1
float3 V =-DerivedParams.CameraVector;
float3 L = LightData.Direction; // Already normalized
float3 ToLight = L;
float LightMask = 1;
if (LightData.bRadialLight)
{
LightMask = GetLocalLightAttenuation(DerivedParams.TranslatedWorldPosition, LightData, ToLight, L);
}
if (LightMask > 0)
{
FShadowTerms ShadowTerms = { StrataGetAO(StrataPixelHeader), 1.0, 1.0, InitHairTransmittanceData() };
float4 LightAttenuation = GetLightAttenuationFromShadow(InputParams, SceneDepth);
float Dither = InterleavedGradientNoise(InputParams.PixelPos, View.StateFrameIndexMod8);
const uint FakeShadingModelID = 0;
const float FakeContactShadowOpacity = 1.0f;
float4 PrecomputedShadowFactors = StrataReadPrecomputedShadowFactors(StrataPixelHeader, PixelPos, SceneTexturesStruct.GBufferETexture);
GetShadowTerms(SceneDepth, PrecomputedShadowFactors, FakeShadingModelID, FakeContactShadowOpacity,
LightData, DerivedParams.TranslatedWorldPosition, L, LightAttenuation, Dither, ShadowTerms);
FStrataDeferredLighting StrataLighting = StrataDeferredLighting(
LightData,
V,
L,
ToLight,
LightMask,
ShadowTerms,
Strata.MaterialTextureArray,
StrataAddressing,
StrataPixelHeader);
OutColor += StrataLighting.SceneColor;
#if STRATA_OPAQUE_ROUGH_REFRACTION_ENABLED
OutOpaqueRoughRefractionSceneColor += StrataLighting.OpaqueRoughRefractionSceneColor;
OutSubSurfaceSceneColor += StrataLighting.SubSurfaceSceneColor;
#endif
}
}
#else // STRATA_ENABLED
//取得屏幕空间数据FGbufferData、AO
FScreenSpaceData ScreenSpaceData = GetScreenSpaceData(InputParams.ScreenUV);
// Only light pixels marked as using deferred shading
BRANCH if (ScreenSpaceData.GBuffer.ShadingModelID > 0
#if USE_LIGHTING_CHANNELS
&& (GetLightingChannelMask(InputParams.ScreenUV) & DeferredLightUniforms.LightingChannelMask)
#endif
)
{
//通过SceneDepth获取的CameraVector以及当前像素的世界坐标
const float SceneDepth = CalcSceneDepth(InputParams.ScreenUV);
const FDerivedParams DerivedParams = GetDerivedParams(InputParams, SceneDepth);
//设置获取光源各种信息
FDeferredLightData LightData = InitDeferredLightFromUniforms(CURRENT_LIGHT_TYPE);
UpdateLightDataColor(LightData, InputParams, DerivedParams);//根据当前世界坐标计算LightData.Color *= 大气&云&阴影的衰减值 * IES灯亮度非IES灯数值为1
#if USE_HAIR_COMPLEX_TRANSMITTANCE
//针对ShadingModel Hair同时需要CustomData.a > 0)计算头发散射结果
if (ScreenSpaceData.GBuffer.ShadingModelID == SHADINGMODELID_HAIR && ShouldUseHairComplexTransmittance(ScreenSpaceData.GBuffer))
{
LightData.HairTransmittance = EvaluateDualScattering(ScreenSpaceData.GBuffer, DerivedParams.CameraVector, -DeferredLightUniforms.Direction);
}
#endif
//计算当前像素的抖动值
float Dither = InterleavedGradientNoise(InputParams.PixelPos, View.StateFrameIndexMod8);
float SurfaceShadow = 1.0f;
float4 LightAttenuation = GetLightAttenuationFromShadow(InputParams, SceneDepth);//根绝是否开启VSM 分别从VirtualShadowMap 或者 LightAttenuationTexture上一阶段渲染的ShadowProjction 获取灯光衰减值。
float4 Radiance = GetDynamicLighting(DerivedParams.TranslatedWorldPosition, DerivedParams.CameraVector, ScreenSpaceData.GBuffer, ScreenSpaceData.AmbientOcclusion, ScreenSpaceData.GBuffer.ShadingModelID, LightData, LightAttenuation, Dither, uint2(InputParams.PixelPos), SurfaceShadow);
OutColor += Radiance;
}
#endif // STRATA_ENABLED
// RGB:SceneColor Specular and Diffuse
// A:Non Specular SceneColor Luminance
// So we need PreExposure for both color and alpha
OutColor.rgba *= GetExposure();
#if STRATA_OPAQUE_ROUGH_REFRACTION_ENABLED
// Idem
OutOpaqueRoughRefractionSceneColor *= GetExposure();
OutSubSurfaceSceneColor *= GetExposure();
#endif
}
#endif
```
#### GetLightAttenuationFromShadow() => GetPerPixelLightAttenuation()
原文https://zhuanlan.zhihu.com/p/23216110797
有提到阴影模糊问题。
FDeferredLightPS::FParameters GetDeferredLightPSParameters()可以看到该Sampler的模式是Point模式。
```c++
float4 GetPerPixelLightAttenuation(float2 UV)
{
return DecodeLightAttenuation(Texture2DSampleLevel(LightAttenuationTexture, LightAttenuationTextureSampler, UV, 0));
}
```
之后可以仿照GetPerPixelLightAttenuation写一个针对ToonShadow的函数
```c++
//对卡通阴影进行降采样抗锯齿
float4 GetPerPixelLightAttenuationToonAA(float2 UV)
{
int texture_x, texture_y;
LightAttenuationTexture.GetDimensions(texture_x, texture_y);
float2 texelSize = float2(1.0 / texture_x, 1.0 / texture_y);
float2 sampleOffsets[4] = {
float2(-1.5, 0.5),
float2( 0.5, 0.5),
float2(-1.5, -1.5),
float2( 0.5, -1.5)
};
float4 shadowSum = float4(0,0,0,0);
for (int i = 0; i < 4; i++)
{
float2 sampleUV = UV + sampleOffsets[i] * texelSize;
shadowSum += DecodeLightAttenuation(Texture2DSampleLevel(LightAttenuationTexture, LightAttenuationTextureSampler_Toon, sampleUV, 0));
}
return shadowSum * 0.25;
}
//获取卡通灯光衰减
float4 GetLightAttenuationFromShadowToonAA(in FInputParams InputParams, float SceneDepth, float3 TranslatedWorldPosition)
{
float4 LightAttenuation = float4(1, 1, 1, 1);
#if USE_VIRTUAL_SHADOW_MAP_MASK
if (VirtualShadowMapId != INDEX_NONE)
{
float ShadowMask = GetVirtualShadowMapMaskForLight(ShadowMaskBits[InputParams.PixelPos], uint2(InputParams.PixelPos), SceneDepth, VirtualShadowMapId, TranslatedWorldPosition);
return ShadowMask.xxxx;
}else
#endif
{
return GetPerPixelLightAttenuationToonAA(InputParams.ScreenUV);
}
}
```
### GetDynamicLighting() => GetDynamicLightingSplit()
```c++
FDeferredLightingSplit GetDynamicLightingSplit(
float3 TranslatedWorldPosition, float3 CameraVector, FGBufferData GBuffer, float AmbientOcclusion, uint ShadingModelID,
FDeferredLightData LightData, float4 LightAttenuation, float Dither, uint2 SVPos,
inout float SurfaceShadow)
{
FLightAccumulator LightAccumulator = AccumulateDynamicLighting(TranslatedWorldPosition, CameraVector, GBuffer, AmbientOcclusion, ShadingModelID, LightData, LightAttenuation, Dither, SVPos, SurfaceShadow);
return LightAccumulator_GetResultSplit(LightAccumulator);
}
```
LightAccumulator_GetResultSplit()针对Subsurface`RetDiffuse.a = In.ScatterableLightLuma;` 或者 `RetDiffuse.a = Luminance(In.ScatterableLight);`
```c++
FDeferredLightingSplit LightAccumulator_GetResultSplit(FLightAccumulator In)
{
float4 RetDiffuse;
float4 RetSpecular;
if (VISUALIZE_LIGHT_CULLING == 1)
{
// a soft gradient from dark red to bright white, can be changed to be different
RetDiffuse = 0.1f * float4(1.0f, 0.25f, 0.075f, 0) * In.EstimatedCost;
RetSpecular = 0.1f * float4(1.0f, 0.25f, 0.075f, 0) * In.EstimatedCost;
}
else
{
RetDiffuse = float4(In.TotalLightDiffuse, 0);
RetSpecular = float4(In.TotalLightSpecular, 0);
//针对Subsurface会额外对RetDiffuse的Alpha设置数值 ScatterableLight的亮度数值
if (SUBSURFACE_CHANNEL_MODE == 1 )
{
if (View.bCheckerboardSubsurfaceProfileRendering == 0)
{
// RGB accumulated RGB HDR color, A: specular luminance for screenspace subsurface scattering
RetDiffuse.a = In.ScatterableLightLuma;
}
}
else if (SUBSURFACE_CHANNEL_MODE == 2)
{
// RGB accumulated RGB HDR color, A: view independent (diffuse) luminance for screenspace subsurface scattering
// 3 add, 1 mul, 2 mad, can be optimized to use 2 less temporary during accumulation and remove the 3 add
RetDiffuse.a = Luminance(In.ScatterableLight);
// todo, need second MRT for SUBSURFACE_CHANNEL_MODE==2
}
}
FDeferredLightingSplit Ret;
Ret.DiffuseLighting = RetDiffuse;
Ret.SpecularLighting = RetSpecular;
return Ret;
}
```
#### AccumulateDynamicLighting
```c++
FLightAccumulator AccumulateDynamicLighting(
float3 TranslatedWorldPosition, half3 CameraVector, FGBufferData GBuffer, half AmbientOcclusion, uint ShadingModelID,
FDeferredLightData LightData, half4 LightAttenuation, float Dither, uint2 SVPos,
inout float SurfaceShadow)
{
FLightAccumulator LightAccumulator = (FLightAccumulator)0;
half3 V = -CameraVector;
half3 N = GBuffer.WorldNormal;
//针对开启CLEAR_COAT_BOTTOM_NORMAL的清漆ShadingModel进行Normal处理
BRANCH if( GBuffer.ShadingModelID == SHADINGMODELID_CLEAR_COAT && CLEAR_COAT_BOTTOM_NORMAL)
{
const float2 oct1 = ((float2(GBuffer.CustomData.a, GBuffer.CustomData.z) * 4) - (512.0/255.0)) + UnitVectorToOctahedron(GBuffer.WorldNormal);
N = OctahedronToUnitVector(oct1);
}
float3 L = LightData.Direction; // Already normalized
float3 ToLight = L;
float3 MaskedLightColor = LightData.Color;//灯光颜色
float LightMask = 1;
// 获取辐射光源的衰减值衰减方法根据LightData.bInverseSquared会分别使用新版衰减方法InverseSquared 或者 旧方法。如果是SpotLight与RectLight就乘以SpotLight、RectLight对应的形状衰减数值。
if (LightData.bRadialLight)
{
LightMask = GetLocalLightAttenuation( TranslatedWorldPosition, LightData, ToLight, L );
MaskedLightColor *= LightMask;
}
LightAccumulator.EstimatedCost += 0.3f; // running the PixelShader at all has a cost
BRANCH
if( LightMask > 0 )//如果不是完全死黑就计算阴影部分逻辑
{
FShadowTerms Shadow;
Shadow.SurfaceShadow = AmbientOcclusion;//GBuffer中的AO
Shadow.TransmissionShadow = 1;
Shadow.TransmissionThickness = 1;
Shadow.HairTransmittance.OpaqueVisibility = 1;
const float ContactShadowOpacity = GBuffer.CustomData.a;//TODO:修正ToonStandard对应的逻辑
//
GetShadowTerms(GBuffer.Depth, GBuffer.PrecomputedShadowFactors, GBuffer.ShadingModelID, ContactShadowOpacity,
LightData, TranslatedWorldPosition, L, LightAttenuation, Dither, Shadow);
SurfaceShadow = Shadow.SurfaceShadow;
LightAccumulator.EstimatedCost += 0.3f; // add the cost of getting the shadow terms
#if SHADING_PATH_MOBILE
const bool bNeedsSeparateSubsurfaceLightAccumulation = UseSubsurfaceProfile(GBuffer.ShadingModelID);
FDirectLighting Lighting = (FDirectLighting)0;
half NoL = max(0, dot(GBuffer.WorldNormal, L));
#if TRANSLUCENCY_NON_DIRECTIONAL
NoL = 1.0f;
#endif
Lighting = EvaluateBxDF(GBuffer, N, V, L, NoL, Shadow);
Lighting.Specular *= LightData.SpecularScale;
LightAccumulator_AddSplit( LightAccumulator, Lighting.Diffuse, Lighting.Specular, Lighting.Diffuse, MaskedLightColor * Shadow.SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation );
LightAccumulator_AddSplit( LightAccumulator, Lighting.Transmission, 0.0f, Lighting.Transmission, MaskedLightColor * Shadow.TransmissionShadow, bNeedsSeparateSubsurfaceLightAccumulation );
#else // SHADING_PATH_MOBILE
BRANCH
if( Shadow.SurfaceShadow + Shadow.TransmissionShadow > 0 )
{
const bool bNeedsSeparateSubsurfaceLightAccumulation = UseSubsurfaceProfile(GBuffer.ShadingModelID);//判断是否需要SubsurfaceProfile计算
#if NON_DIRECTIONAL_DIRECT_LIGHTING // 非平行直接光
float Lighting;
if( LightData.bRectLight )//面光源
{
FRect Rect = GetRect( ToLight, LightData );
Lighting = IntegrateLight( Rect );
}
else //点光源以及胶囊光源
{
FCapsuleLight Capsule = GetCapsule( ToLight, LightData );
Lighting = IntegrateLight( Capsule, LightData.bInverseSquared );
}
float3 LightingDiffuse = Diffuse_Lambert( GBuffer.DiffuseColor ) * Lighting;//Lambert照明 * 积分结果
LightAccumulator_AddSplit(LightAccumulator, LightingDiffuse, 0.0f, 0, MaskedLightColor * Shadow.SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation);
#else
FDirectLighting Lighting;
if (LightData.bRectLight)//面光源
{
FRect Rect = GetRect( ToLight, LightData );
const FRectTexture SourceTexture = ConvertToRectTexture(LightData);
#if REFERENCE_QUALITY
Lighting = IntegrateBxDF( GBuffer, N, V, Rect, Shadow, SourceTexture, SVPos );
#else
Lighting = IntegrateBxDF( GBuffer, N, V, Rect, Shadow, SourceTexture);
#endif
}
else //点光源以及胶囊光源
{
FCapsuleLight Capsule = GetCapsule( ToLight, LightData );
#if REFERENCE_QUALITY
Lighting = IntegrateBxDF( GBuffer, N, V, Capsule, Shadow, SVPos );
#else
Lighting = IntegrateBxDF( GBuffer, N, V, Capsule, Shadow, LightData.bInverseSquared );
#endif
}
Lighting.Specular *= LightData.SpecularScale;
//累加Diffuse + Specular光照结果Diffuse项还会作为散射进行计算根绝散射模式不同赋予 FLightAccumulator.ScatterableLightLuma 或者 FLightAccumulator.ScatterableLight
LightAccumulator_AddSplit( LightAccumulator, Lighting.Diffuse, Lighting.Specular, Lighting.Diffuse, MaskedLightColor * Shadow.SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation );
//散射项计算
LightAccumulator_AddSplit( LightAccumulator, Lighting.Transmission, 0.0f, Lighting.Transmission, MaskedLightColor * Shadow.TransmissionShadow, bNeedsSeparateSubsurfaceLightAccumulation );
LightAccumulator.EstimatedCost += 0.4f; // add the cost of the lighting computations (should sum up to 1 form one light)
#endif
}
#endif // SHADING_PATH_MOBILE
}
return LightAccumulator;
}
```
光源新衰减公式,相关计算位于`GetLocalLightAttenuation()`
$$Falloff = \frac{saturate(1-(distance/lightRadius)^4)^2}{distance^2 + 1}$$
光源旧衰减公式相关函数位于DynamicLightingCommon.ush中的`RadialAttenuation()`
$$Falloff = (1 - saturate(length(WorldLightVector)))^ {FalloffExponent}$$
##### GetShadowTerms()
```c++
void GetShadowTerms(float SceneDepth, half4 PrecomputedShadowFactors, uint ShadingModelID, float ContactShadowOpacity, FDeferredLightData LightData, float3 TranslatedWorldPosition, half3 L, half4 LightAttenuation, float Dither, inout FShadowTerms Shadow)
{
float ContactShadowLength = 0.0f;
const float ContactShadowLengthScreenScale = GetTanHalfFieldOfView().y * SceneDepth;
BRANCH
if (LightData.ShadowedBits)
{
// 重新映射ShadowProjection结果
// Remapping the light attenuation buffer (see ShadowRendering.cpp)
// LightAttenuation: Light function + per-object shadows in z, per-object SSS shadowing in w,
// Whole scene directional light shadows in x, whole scene directional light SSS shadows in y
// Get static shadowing from the appropriate GBuffer channel
#if ALLOW_STATIC_LIGHTING
half UsesStaticShadowMap = dot(LightData.ShadowMapChannelMask, half4(1, 1, 1, 1));
half StaticShadowing = lerp(1, dot(PrecomputedShadowFactors, LightData.ShadowMapChannelMask), UsesStaticShadowMap);
#else
half StaticShadowing = 1.0f;
#endif
if (LightData.bRadialLight || SHADING_PATH_MOBILE)//RadialLight或者是移动端使用以下逻辑。bRadialLight一般是 PointLight or SpotLight。径向衰减radial attenuation指光照强度随距离光源的远近而衰减的特性通常遵循平方反比定律
{
// Remapping the light attenuation buffer (see ShadowRendering.cpp)
Shadow.SurfaceShadow = LightAttenuation.z * StaticShadowing;//RadialLight灯光的阴影项计算不受AO影响赋值Light function + per-object的ShadowProjection
// SSS uses a separate shadowing term that allows light to penetrate the surface
//@todo - how to do static shadowing of SSS correctly?
Shadow.TransmissionShadow = LightAttenuation.w * StaticShadowing;//per-object SSS shadowing
Shadow.TransmissionThickness = LightAttenuation.w;//per-object SSS shadowing
}
else
{
// Remapping the light attenuation buffer (see ShadowRendering.cpp)
// Also fix up the fade between dynamic and static shadows
// to work with plane splits rather than spheres.
float DynamicShadowFraction = DistanceFromCameraFade(SceneDepth, LightData);
// For a directional light, fade between static shadowing and the whole scene dynamic shadowing based on distance + per object shadows
Shadow.SurfaceShadow = lerp(LightAttenuation.x, StaticShadowing, DynamicShadowFraction);//根据计算出动态阴影的衰减值来插值ShadowProject与静态阴影。x:方向光阴影
// Fade between SSS dynamic shadowing and static shadowing based on distance
Shadow.TransmissionShadow = min(lerp(LightAttenuation.y, StaticShadowing, DynamicShadowFraction), LightAttenuation.w);// w:per-object SSS shadowing
Shadow.SurfaceShadow *= LightAttenuation.z;//Light function + per-object shadows in z
Shadow.TransmissionShadow *= LightAttenuation.z;
// Need this min or backscattering will leak when in shadow which cast by non perobject shadow(Only for directional light)
Shadow.TransmissionThickness = min(LightAttenuation.y, LightAttenuation.w);
}
FLATTEN
if (LightData.ShadowedBits > 1 && LightData.ContactShadowLength > 0)
{
ContactShadowLength = LightData.ContactShadowLength * (LightData.ContactShadowLengthInWS ? 1.0f : ContactShadowLengthScreenScale);
}
}
#if SUPPORT_CONTACT_SHADOWS //接触阴影相关逻辑
#if STRATA_ENABLED == 0
if (LightData.ShadowedBits < 2 && (ShadingModelID == SHADINGMODELID_HAIR))
{
ContactShadowLength = 0.2 * ContactShadowLengthScreenScale;
}
// World space distance to cover eyelids and eyelashes but not beyond
if (ShadingModelID == SHADINGMODELID_EYE)
{
ContactShadowLength = 0.5;
}
#endif
#if MATERIAL_CONTACT_SHADOWS
ContactShadowLength = 0.2 * ContactShadowLengthScreenScale;
#endif
BRANCH
if (ContactShadowLength > 0.0)
{
float StepOffset = Dither - 0.5;
bool bHitCastContactShadow = false;
bool bHairNoShadowLight = ShadingModelID == SHADINGMODELID_HAIR && !LightData.ShadowedBits;
float HitDistance = ShadowRayCast( TranslatedWorldPosition, L, ContactShadowLength, 8, StepOffset, bHairNoShadowLight, bHitCastContactShadow );//通过RayMarching来计算是否HitContactShadow以及HitDistance。
if ( HitDistance > 0.0 )
{
float ContactShadowOcclusion = bHitCastContactShadow ? LightData.ContactShadowCastingIntensity : LightData.ContactShadowNonCastingIntensity;
#if STRATA_ENABLED == 0
// Exponential attenuation is not applied on hair/eye/SSS-profile here, as the hit distance (shading-point to blocker) is different from the estimated
// thickness (closest-point-from-light to shading-point), and this creates light leaks. Instead we consider first hit as a blocker (old behavior)
BRANCH
if (ContactShadowOcclusion > 0.0 &&
IsSubsurfaceModel(ShadingModelID) &&
ShadingModelID != SHADINGMODELID_HAIR &&
ShadingModelID != SHADINGMODELID_EYE &&
ShadingModelID != SHADINGMODELID_SUBSURFACE_PROFILE)
{
// Reduce the intensity of the shadow similar to the subsurface approximation used by the shadow maps path
// Note that this is imperfect as we don't really have the "nearest occluder to the light", but this should at least
// ensure that we don't darken-out the subsurface term with the contact shadows
float Density = SubsurfaceDensityFromOpacity(ContactShadowOpacity);
ContactShadowOcclusion *= 1.0 - saturate( exp( -Density * HitDistance ) );
}
#endif
float ContactShadow = 1.0 - ContactShadowOcclusion;
//根据是否命中赋予对应的ContactShadow亮度数值之后乘以Shadow.SurfaceShadow与Shadow.TransmissionShadow。
Shadow.SurfaceShadow *= ContactShadow;
Shadow.TransmissionShadow *= ContactShadow;
}
}
#endif
Shadow.HairTransmittance = LightData.HairTransmittance;
Shadow.HairTransmittance.OpaqueVisibility = Shadow.SurfaceShadow;
}
```

View File

@@ -0,0 +1,44 @@
---
title: RenderLights
date: 2023-04-09 10:23:21
excerpt:
tags:
rating: ⭐
---
# 关键函数
取得所有ShadowMap的投影信息
```c++
const FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[LightSceneInfo->Id];
const TArray<FProjectedShadowInfo*, SceneRenderingAllocator>& ShadowMaps = VisibleLightInfo.ShadowsToProject;
for (int32 ShadowIndex = 0; ShadowIndex < ShadowMaps.Num(); ShadowIndex++)
{
const FProjectedShadowInfo* ProjectedShadowInfo = ShadowMaps[ShadowIndex];
}
```
# 透明体积图元渲染
## InjectSimpleTranslucencyLightingVolumeArray
插入简单透明体积物体渲染。应该是根据3D贴图渲染体积效果。默认状态下不运行。
- InjectSimpleLightsTranslucentLighting
- InjectSimpleTranslucentLightArray
## InjectTranslucencyLightingVolume
在收集用于渲染透明体积的灯光代理信息后进行渲染,主要用于云的渲染。
- InjectTranslucencyLightingVolume
# 直接光照
## RenderVirtualShadowMapProjectionMaskBits
- VirtualShadowMapProjectionMaskBits
- VirtualShadowMapProjection(RayCount:%u(%s),SamplesPerRay:%u,Input:%s%s)
输出到名为`Shadow.Virtual.MaskBits`与`Shadow.Virtual.MaskBits(HairStrands)`的UAV。
## AddClusteredDeferredShadingPass
## RenderSimpleLightsStandardDeferred
## RenderLight
针对每个灯在ShadowProjectionOnOpaque渲染ShadowMask
- VirualShadowMapProjection
- CompositeVirtualShadowMapMask

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,413 @@
---
title: Untitled
date: 2024-09-25 14:59:32
excerpt:
tags:
rating: ⭐
---
# 前言
可以使用DrawDynamicMeshPass()实现在插件中使用MeshDraw绘制Pass。
参考文章:
- ***UE5为HairStrands添加自定义深度与模板***:https://zhuanlan.zhihu.com/p/689578355
# MeshDraw
推荐学习:
- CustomDepth
- RenderBasePassInternal()
- RenderAnisotropyPass()
Shader推荐
- DepthOnlyVertexShader.usf
- DepthOnlyPixelShader.usf
## BasePass
### DrawBasePass()
该函数在FDeferredShadingSceneRenderer::RenderBasePassInternal()中调用。
DrawNaniteMaterialPass() => SubmitNaniteIndirectMaterial()
## PSO
- RDG 04 Graphics Pipeline State Initializer https://zhuanlan.zhihu.com/p/582020846
- FGraphicsPipelineStateInitializer
- FRHIDepthStencilState* DepthStencilState
- FRHIBlendState* BlendState
- FRHIRasterizerState* RasterizerState
- EPrimitiveType PrimitiveType
- FBoundShaderStateInput BoundShaderState.VertexDeclarationRHI
- FBoundShaderStateInput BoundShaderState.VertexShaderRHI
- FBoundShaderStateInput BoundShaderState.PixelShaderRHI
- ……
// 应用
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit,0);
## FMeshPassProcessorRenderState
- FMeshPassProcessorRenderState
- FRHIBlendState* BlendState
- FRHIDepthStencilState* DepthStencilState
- FExclusiveDepthStencil::Type DepthStencilAccess
- FRHIUniformBuffer* ViewUniformBuffer
- FRHIUniformBuffer* InstancedViewUniformBuffer
- FRHIUniformBuffer* PassUniformBuffer
- FRHIUniformBuffer* NaniteUniformBuffer
- uint32 StencilRef = 0;
### FRHIBlendState
使用***FBlendStateInitializerRHI()*** 进行初始化。
它定义了8个渲染对象一般我们只用第一组它的七个参数分别是
- Color
- Color Write Mask
- Color Blend 混合类型
- Color Src 混合因子
- Color Dest 混合因子
- Alpha
- Alpha Blend 混合类型
- Alpha Src 混合因子
- Alpha Dest 混合因子
```c++
FRHIBlendState* CopyBlendState = TStaticBlendState<CW_RGB, BO_Add, BF_SourceAlpha, BF_InverseSourceAlpha, BO_Add, BF_Zero, BF_One>::GetRHI();
```
颜色写入蒙版:
```c++
enum EColorWriteMask
{
CW_RED = 0x01,
CW_GREEN = 0x02,
CW_BLUE = 0x04,
CW_ALPHA = 0x08,
CW_NONE = 0,
CW_RGB = CW_RED | CW_GREEN | CW_BLUE,
CW_RGBA = CW_RED | CW_GREEN | CW_BLUE | CW_ALPHA,
CW_RG = CW_RED | CW_GREEN,
CW_BA = CW_BLUE | CW_ALPHA,
EColorWriteMask_NumBits = 4,
};
```
#### 混合运算
混合运算符对于颜色混合方程和Alpha混合方程效果是一样的这里就只用颜色混合方程来做讲解。
| BlendOperation | 颜色混合方程 |
| -------------------- | ---------------------------------------------------------------------------------- |
| BO_Add | $$C=C_{src} \otimes F_{src} + C_{dst} \otimes F_{dst}Csrc⊗Fsrc+Cdst⊗Fdst$$ |
| BO_Subtract | $$C = C_{src} \otimes F_{src} - C_{dst} \otimes F_{dst}C=Csrc⊗FsrcCdst⊗Fdst$$ |
| BO_ReverseSubtract | $$C = C_{dst} \otimes F_{dst} - C_{src} \otimes F_{src}C=Cdst⊗FdstCsrc⊗Fsrc$$ |
| BO_Min | $$C = Min(C_{src} , C_{dst} )C=Min(Csrc,Cdst)$$ |
| BO_Max | $$C = Max(C_{src} , C_{dst} )C=Max(Csrc,Cdst)$$ |
| BO_Min和BO_Max忽略了混合因子 | |
#### 混合因子
| BlendFactor | 颜色混合因子 | Alpha混合因子 |
| ----------------------------- | ---------------------------------------------------------------- | ------------------------ |
| BF_Zero | $$F = (0,0,0)F=(0,0,0)$$ | $$F=0F=0$$ |
| BF_One | $$F=(1,1,1)F=(1,1,1)$$ | $$F=1F=1$$ |
| BF_SourceColor | $$F=(r_{src},g_{src},b_{src})F=(rsrc,gsrc,bsrc)$$ | |
| BF_InverseSourceColor | $$F=(1-r_{src},1-g_{src},1-b_{src})F=(1rsrc,1gsrc,1bsrc)$$ | |
| BF_SourceAlpha | $$F=(a_{src},a_{src},a_{src})F=(asrc,asrc,asrc)$$ | $$F=a_{src}F=asrc$$ |
| BF_InverseSourceAlpha | $$F=(1-a_{src},1-a_{src},1-a_{src})F=(1asrc,1asrc,1asrc)$$ | $$F=1-a_{src}F=1asrc$$ |
| BF_DestAlpha | $$F=(a_{dst},a_{dst},a_{dst})F=(adst,adst,adst)$$ | $$F=a_{dst}F=adst$$ |
| BF_InverseDestAlpha | $$F=(1-a_{dst},1-a_{dst},1-a_{dst})F=(1adst,1adst,1adst)$$ | $$F=1-a_{dst}F=1adst$$ |
| BF_DestColor | $$F=(r_{dst},g_{dst},b_{dst})F=(rdst,gdst,bdst)$$ | |
| BF_InverseDestColor | $$F=(1-r_{dst},1-g_{dst},1-b_{dst})F=(1rdst,1gdst,1bdst)$$ | |
| BF_ConstantBlendFactor | F=(r,g,b)F=(r,g,b) | F=aF=a |
| BF_InverseConstantBlendFactor | F=(1-r,1-g,1-b)F=(1r,1g,1b) | F=1-aF=1a |
| BF_Source1Color | 未知 | 未知 |
| BF_InverseSource1Color | 未知 | 未知 |
| BF_Source1Alpha | 未知 | 未知 |
| BF_InverseSource1Alpha | 未知 | 未知 |
最后四个选项没有在DirectX中找到对应的选项没有继续探究前面的应该足够一般使用了。
### FRHIDepthStencilState
```c++
TStaticDepthStencilState<
bEnableDepthWrite, // 是否启用深度写入
DepthTest, // 深度测试比较函数
bEnableFrontFaceStencil, // (正面)启用模板
FrontFaceStencilTest, // (正面)模板测试操作
FrontFaceStencilFailStencilOp, //(正面)模板测试失败时如何更新模板缓冲区
FrontFaceDepthFailStencilOp, //(正面)深度测试失败时如何更新模板缓冲区
FrontFacePassStencilOp, //(正面)通过模板测试时如何更新模板缓冲区
bEnableBackFaceStencil, // (背面)启用模板
BackFaceStencilTest, // (背面)模板失败操作
BackFaceStencilFailStencilOp, //(背面)模板测试失败时如何更新模板缓冲区
BackFaceDepthFailStencilOp, //(背面)深度测试失败时如何更新模板缓冲区
BackFacePassStencilOp, //(背面)通过模板测试时如何更新模板红冲去
StencilReadMask, // 模板读取Mask
StencilWriteMask // 模板写入Mask
>
```
```c++
//一般使用这个
TStaticDepthStencilState<true, CF_DepthNearOrEqual>::GetRHI();
//CustomStencil中使用的
TStaticDepthStencilState<true, CF_DepthNearOrEqual, true, CF_Always, SO_Keep, SO_Keep, SO_Replace, false, CF_Always, SO_Keep, SO_Keep, SO_Keep, 255, 255>::GetRHI()
```
#### DepthTest
深度测试比较函数。
```c++
enum ECompareFunction
{
CF_Less,
CF_LessEqual,
CF_Greater,
CF_GreaterEqual,
CF_Equal,
CF_NotEqual,
CF_Never, // 总是返回false
CF_Always, // 总是返回true
ECompareFunction_Num,
ECompareFunction_NumBits = 3,
// Utility enumerations
CF_DepthNearOrEqual = (((int32)ERHIZBuffer::IsInverted != 0) ? CF_GreaterEqual : CF_LessEqual),
CF_DepthNear = (((int32)ERHIZBuffer::IsInverted != 0) ? CF_Greater : CF_Less),
CF_DepthFartherOrEqual = (((int32)ERHIZBuffer::IsInverted != 0) ? CF_LessEqual : CF_GreaterEqual),
CF_DepthFarther = (((int32)ERHIZBuffer::IsInverted != 0) ? CF_Less : CF_Greater),
};
```
```c++
enum EStencilOp
{
SO_Keep,
SO_Zero,
SO_Replace,
SO_SaturatedIncrement,
SO_SaturatedDecrement,
SO_Invert,
SO_Increment,
SO_Decrement,
EStencilOp_Num,
EStencilOp_NumBits = 3,
};
```
### CustomStencil
#### InitCustomDepthStencilContext()
根据当前平台是否支持使用ComputeShader直接输出结果bComputeExport、以及是否写入Stencil缓存以此来创建不同的资源。最终输出FCustomDepthContext。
```c++
struct FCustomDepthContext
{
FRDGTextureRef InputDepth = nullptr;
FRDGTextureSRVRef InputStencilSRV = nullptr;
FRDGTextureRef DepthTarget = nullptr;
FRDGTextureRef StencilTarget = nullptr;
bool bComputeExport = true;
};
```
#### EmitCustomDepthStencilTargets()
根据bComputeExport分别使用RDG的ComputeShader与PixelShader输出DepthStencil。
- CS使用FComputeShaderUtils::AddPass()
- PS使用NaniteExportGBuffer.usf的**EmitCustomDepthStencilPS()**FPixelShaderUtils::AddFullscreenPass()
以**FEmitCustomDepthStencilPS**(NaniteExportGBuffer.usf)为例额外输入的Nanite相关变量
- FSceneUniformParameters Scene
- StructuredBuffer`<`FPackedView`>` InViews
- ByteAddressBuffer VisibleClustersSWHW
- FIntVector4, PageConstants
- Texture2D`<`UlongType`>`, VisBuffer64
- ByteAddressBuffer MaterialSlotTable
#### FinalizeCustomDepthStencil()
替换输出的Depth&Stencil。
# FViewInfo
FViewInfo& ViewInfo
- WriteView.bSceneHasSkyMaterial |= bSceneHasSkyMaterial;
- WriteView.bHasSingleLayerWaterMaterial |= bHasSingleLayerWaterMaterial;
- WriteView.bHasCustomDepthPrimitives |= bHasCustomDepthPrimitives;
- WriteView.bHasDistortionPrimitives |= bHasDistortionPrimitives;
- WriteView.bUsesCustomDepth |= bUsesCustomDepth;
- WriteView.bUsesCustomStencil |= bUsesCustomStencil;
- FRelevancePacket::Finalize()
相关性:
- 相关性定义
- FStaticMeshBatchRelevance
- FMaterialRelevance
- View相关计算
- FViewInfo::Init()
- FRelevancePacket
- FRelevancePacket::Finalize()
# 相关宏定义
- SCOPE_CYCLE_COUNTER(STAT_BasePassDrawTime);
- DECLARE_CYCLE_STAT_EXTERN(TEXT("Base pass drawing"),STAT_BasePassDrawTime,STATGROUP_SceneRendering, RENDERCORE_API);
- DEFINE_STAT(STAT_BasePassDrawTime);
- DEFINE_GPU_STAT(NaniteBasePass);
- DECLARE_GPU_STAT_NAMED_EXTERN(NaniteBasePass, TEXT("Nanite BasePass"));
- GET_STATID(STAT_CLP_BasePass)
- FRDGParallelCommandListSet ParallelCommandListSet(InPass, RHICmdList, GET_STATID(STAT_CLP_BasePass), View, FParallelCommandListBindings(PassParameters));
- DECLARE_CYCLE_STAT(TEXT("BasePass"), STAT_CLP_BasePass, STATGROUP_ParallelCommandListMarkers);
# NaniteMeshDraw
`Engine\Source\Runtime\Renderer\Private\Nanite\`NaniteMaterials.h & NaniteMaterials.cpp
PS.使用的Shader必须是`FNaniteGlobalShader`的子类。
以下是Nanite物体的CustomDepth绘制过程
```c++
bool FSceneRenderer::RenderCustomDepthPass(
FRDGBuilder& GraphBuilder,
FCustomDepthTextures& CustomDepthTextures,
const FSceneTextureShaderParameters& SceneTextures,
TConstArrayView<Nanite::FRasterResults> PrimaryNaniteRasterResults,
TConstArrayView<Nanite::FPackedView> PrimaryNaniteViews)
{
if (!CustomDepthTextures.IsValid())
{
return false;
}
// Determine if any of the views have custom depth and if any of them have Nanite that is rendering custom depth
// 构建NaniteDrawLists用于后面的绘制
bool bAnyCustomDepth = false;
TArray<FNaniteCustomDepthDrawList, SceneRenderingAllocator> NaniteDrawLists;
NaniteDrawLists.AddDefaulted(Views.Num());
uint32 TotalNaniteInstances = 0;
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex)
{
FViewInfo& View = Views[ViewIndex];
if (View.ShouldRenderView() && View.bHasCustomDepthPrimitives)
{
if (PrimaryNaniteRasterResults.IsValidIndex(ViewIndex))
{
const FNaniteVisibilityResults& VisibilityResults = PrimaryNaniteRasterResults[ViewIndex].VisibilityResults;
// Get the Nanite instance draw list for this view. (NOTE: Always use view index 0 for now because we're not doing
// multi-view yet).
NaniteDrawLists[ViewIndex] = BuildNaniteCustomDepthDrawList(View, 0u, VisibilityResults);
TotalNaniteInstances += NaniteDrawLists[ViewIndex].Num();
}
bAnyCustomDepth = true;
}
}
SET_DWORD_STAT(STAT_NaniteCustomDepthInstances, TotalNaniteInstances);
..
if (TotalNaniteInstances > 0)
{
RDG_EVENT_SCOPE(GraphBuilder, "Nanite CustomDepth");
const FIntPoint RasterTextureSize = CustomDepthTextures.Depth->Desc.Extent;
FIntRect RasterTextureRect(0, 0, RasterTextureSize.X, RasterTextureSize.Y);
if (Views.Num() == 1)
{
const FViewInfo& View = Views[0];
if (View.ViewRect.Min.X == 0 && View.ViewRect.Min.Y == 0)
{
RasterTextureRect = View.ViewRect;
}
}
const bool bWriteCustomStencil = IsCustomDepthPassWritingStencil();
Nanite::FSharedContext SharedContext{};
SharedContext.FeatureLevel = Scene->GetFeatureLevel();
SharedContext.ShaderMap = GetGlobalShaderMap(SharedContext.FeatureLevel);
SharedContext.Pipeline = Nanite::EPipeline::Primary;
// TODO: If !bWriteCustomStencil, we could copy off the depth and rasterize depth-only (probable optimization)
//初始化Nanite::FRasterContext RasterContext。
Nanite::FRasterContext RasterContext = Nanite::InitRasterContext(
GraphBuilder,
SharedContext,
ViewFamily,
RasterTextureSize,
RasterTextureRect,
false, // bVisualize
Nanite::EOutputBufferMode::VisBuffer,
true, // bClearTarget
nullptr, // RectMinMaxBufferSRV
0, // NumRects
nullptr, // ExternalDepthBuffer
true // bCustomPass
);
//用于构建FCustomDepthContext结构体创建对应的贴图资源。主要包含了InputDepth、InputStencilSRV、DepthTarget、StencilTarget以及bComputeExport是否使用ComputeShader输出
Nanite::FCustomDepthContext CustomDepthContext = Nanite::InitCustomDepthStencilContext(
GraphBuilder,
CustomDepthTextures,
bWriteCustomStencil);
Nanite::FConfiguration CullingConfig = { 0 };//Nanite剔除设置用的较多的是bUpdateStreaming、bPrimaryContext、bTwoPassOcclusion设置为true。
CullingConfig.bUpdateStreaming = true;
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex)
{
RDG_EVENT_SCOPE_CONDITIONAL(GraphBuilder, Views.Num() > 1, "View%d", ViewIndex);
FViewInfo& View = Views[ViewIndex];
if (!View.ShouldRenderView() || NaniteDrawLists[ViewIndex].Num() == 0)
{
continue;
}
//创建Nanite渲染器
auto NaniteRenderer = Nanite::IRenderer::Create(
GraphBuilder,
*Scene,
View,
GetSceneUniforms(),
SharedContext,
RasterContext,
CullingConfig,
View.ViewRect,
/* PrevHZB = */ nullptr
);
NaniteRenderer->DrawGeometry(
Scene->NaniteRasterPipelines[ENaniteMeshPass::BasePass],
PrimaryNaniteRasterResults[ViewIndex].VisibilityResults,
*Nanite::FPackedViewArray::Create(GraphBuilder, PrimaryNaniteViews[ViewIndex]),
NaniteDrawLists[ViewIndex]
);
Nanite::FRasterResults RasterResults;
NaniteRenderer->ExtractResults( RasterResults );
// Emit depth
Nanite::EmitCustomDepthStencilTargets(
GraphBuilder,
*Scene,
View,
RasterResults.PageConstants,
RasterResults.VisibleClustersSWHW,
RasterResults.ViewsBuffer,
RasterContext.VisBuffer64,
CustomDepthContext
);
}
Nanite::FinalizeCustomDepthStencil(GraphBuilder, CustomDepthContext, CustomDepthTextures);
}
...
}
```
- Nanite::InitRasterContext()初始化Nanite::FRasterContext RasterContext。
- Nanite::InitCustomDepthStencilContext()位于NaniteMaterials.cpp用于构建FCustomDepthContext结构体创建对应的贴图资源。主要包含了InputDepth、InputStencilSRV、DepthTarget、StencilTarget以及bComputeExport是否使用ComputeShader输出
- auto NaniteRenderer = Nanite::IRenderer::Create()创建Nanite渲染器
- NaniteRenderer->DrawGeometry()Nanite物体绘制
- NaniteRenderer->ExtractResults( RasterResults ):提取渲染结果。
- ***Nanite::EmitCustomDepthStencilTargets()***使用提取到的Nanite::FRasterResults RasterResults输出CustomDepth。结果存储在 FCustomDepthContext& CustomDepthContext中。
- FDepthExportCS / FEmitCustomDepthStencilPS
- Nanite::FinalizeCustomDepthStencil()将CustomDepthContext中的结果输出到CustomDepthTextures上。
## BasePass Nanite
1. Render()中向RenderBasePass()传入const TArrayView<Nanite::FRasterResults>& NaniteRasterResults。
2. 调用Lambda RenderNaniteBasePass()本质上是调用Nanite::DrawBasePass()进行渲染。

View File

@@ -0,0 +1,20 @@
---
title: Untitled
date: 2024-11-14 12:19:36
excerpt:
tags:
rating: ⭐
---
# 前言
探索思路:
# 问题解决方法
添加相关性overrider将bDynamicRelevance设置成true。
```c++
FPrimitiveViewRelevance FOutlineSkeletalMeshSceneProxy::GetViewRelevance(const FSceneView* View) const
{
FPrimitiveViewRelevance Result = FSkeletalMeshSceneProxy::GetViewRelevance(View);
Result.bDynamicRelevance = true;
return Result;
}
```

View File

@@ -0,0 +1,154 @@
---
title: Untitled
date: 2024-09-23 19:47:03
excerpt:
tags:
rating: ⭐
---
# ShadowPassSwitch
## Cpp
MaterialExpressionShadowReplace.h
MaterialExpressions.cpp
```c++
int32 UMaterialExpressionShadowReplace::Compile(class FMaterialCompiler* Compiler, int32 OutputIndex)
{
if (!Default.GetTracedInput().Expression)
{
return Compiler->Errorf(TEXT("Missing input Default"));
}
else if (!Shadow.GetTracedInput().Expression)
{
return Compiler->Errorf(TEXT("Missing input Shadow"));
}
else
{
const int32 Arg1 = Default.Compile(Compiler);
const int32 Arg2 = Shadow.Compile(Compiler);
return Compiler->ShadowReplace(Arg1, Arg2);
}
}
```
MaterialCompiler.h => **HLSLMaterialTranslator.cpp**
```c++
int32 FHLSLMaterialTranslator::ShadowReplace(int32 Default, int32 Shadow)
{
return GenericSwitch(TEXT("GetShadowReplaceState()"), Shadow, Default);
}
```
```c++
int32 FHLSLMaterialTranslator::GenericSwitch(const TCHAR* SwitchExpressionText, int32 IfTrue, int32 IfFalse)
{
if (IfTrue == INDEX_NONE || IfFalse == INDEX_NONE)
{
return INDEX_NONE;
}
// exactly the same inputs on both sides - no need to generate anything extra
if (IfTrue == IfFalse)
{
return IfTrue;
}
FMaterialUniformExpression* IfTrueExpression = GetParameterUniformExpression(IfTrue);
FMaterialUniformExpression* IfFalseExpression = GetParameterUniformExpression(IfFalse);
if (IfTrueExpression &&
IfFalseExpression &&
IfTrueExpression->IsConstant() &&
IfFalseExpression->IsConstant())
{
FMaterialRenderContext DummyContext(nullptr, *Material, nullptr);
FLinearColor IfTrueValue;
FLinearColor IfFalseValue;
IfTrueExpression->GetNumberValue(DummyContext, IfTrueValue);
IfFalseExpression->GetNumberValue(DummyContext, IfFalseValue);
if (IfTrueValue == IfFalseValue)
{
// If both inputs are wired to == constant values, avoid adding the runtime switch
// This will avoid breaking various offline checks for constant values
return IfTrue;
}
}
// Both branches of '?:' need to be the same type
const EMaterialValueType ResultType = GetArithmeticResultType(IfTrue, IfFalse);
const FString IfTrueCode = CoerceParameter(IfTrue, ResultType);
const FString IfFalseCode = CoerceParameter(IfFalse, ResultType);
if (IsLWCType(ResultType))
{
AddLWCFuncUsage(ELWCFunctionKind::Other);
return AddCodeChunk(ResultType, TEXT("LWCSelect(%s, %s, %s)"), SwitchExpressionText, *IfTrueCode, *IfFalseCode);
}
else
{
return AddCodeChunk(ResultType, TEXT("(%s ? (%s) : (%s))"), SwitchExpressionText, *IfTrueCode, *IfFalseCode);
}
}
```
>可以看得出最终会编译成(%s ? (%s) : (%s)),也就是 GetShadowReplaceState() ? True的Shader False时的Shader。
## Shader
### Common.ush
```c++
// NOTE: The raytraced implementation of the ShadowPassSwitch node is kept in RayTracingShaderUtils.ush as it needs to access per ray information.
#if RAYHITGROUPSHADER == 0
// Experimental way to allow adjusting the OpacityMask for shadow map rendering of masked materials.
// This is exposed via the ShadowPassSwitch material node. This can also be accessed with a Custom
// material node. If this turns out to be very useful we can expose as MaterialFunction
// and potentially expose other queries as well (e.g. SkeletalMesh, HitProxy, ).
// @return 0:no, 1:yes
bool GetShadowReplaceState()
{
#if SHADOW_DEPTH_SHADER
return true;
#else
return false;
#endif
}
float IsShadowDepthShader()
{
return GetShadowReplaceState() ? 1.0f : 0.0f;
}
#endif // RAYHITGROUPSHADER == 0
```
可以看得出主要通过**SHADOW_DEPTH_SHADER**宏进行判断而渲染阴影的Shader ShadowDepthVertexShader.usf&ShadowDepthPixelShader.usf都定义该宏为1。
### NaniteRasterizer.usf
```c++
// This must be defined before including Common.ush (see GetShadowReplaceState)
#define SHADOW_DEPTH_SHADER DEPTH_ONLY
```
在NaniteCullRaster.cpp中DEPTH_ONLY设置为1。
### RayTracingShaderUtils.ush
光追相关
```c++
#ifndef RAYHITGROUPSHADER
#error "This header should only be included in raytracing contexts"
#endif
#ifndef PATH_TRACING // Path Tracing has a similar implemental with a slightly different set of flags
#include "RayTracingCommon.ush"
static int CurrentPayloadInputFlags = 0;
bool GetShadowReplaceState()
{
return (CurrentPayloadInputFlags & RAY_TRACING_PAYLOAD_INPUT_FLAG_SHADOW_RAY) != 0;
}
float IsShadowDepthShader()
{
return GetShadowReplaceState() ? 1.0f : 0.0f;
}
#endif
```

View File

@@ -0,0 +1,24 @@
---
title: PSO Precache机制笔记
date: 2025-02-08 20:42:16
excerpt:
tags:
rating: ⭐
---
# 前言
- [UE5.3] PSO Cache&PreCache 源码阅读:https://zhuanlan.zhihu.com/p/679832250
- [UE5.3] PSO Cache&PreCache 源码阅读(二):https://zhuanlan.zhihu.com/p/681803986
- Unreal Engine 5.2 MeshPass拓展:https://zhuanlan.zhihu.com/p/671423486
- 优化UE5的PSO卡顿FileCachePreCache和异步PSO https://zhuanlan.zhihu.com/p/1898646962561094034
# 执行链
- FGraphEventArray UPrimitiveComponent::PrecachePSOs()
- void UMaterialInterface::InitDefaultMaterials()
- UMaterial::PrecachePSOs
UMaterial::PrecachePSOs => **MaterialResource->CollectPSOs()** => PrecacheMaterialPSOs() => PrecachePSOs() => CollectPSOs() => CollectPSOInitializers()
# CollectPSOInitializers()
接口IPSOCollector::CollectPSOInitializers()
## 其他MeshProcessor实现

View File

@@ -0,0 +1,324 @@
## 前言
>RDG = Rendering Dependency Graph
RDG主要包含两个重要组件一个是FRDGBuilder负责构建Render graph的资源及添加pass等构建RenderGraph。另一个是FRDGResourceRenderGraph的资源类所有资源都由它派生。
官方入门ppt:https://epicgames.ent.box.com/s/ul1h44ozs0t2850ug0hrohlzm53kxwrz
因为加载速度较慢所以我搬运到了有道云笔记http://note.youdao.com/noteshare?id=a7e2856ad141f44f6b48db6e95419920&sub=E5276AAD6DAA40409586C0552B8E163A
另外我还推荐看:
https://papalqi.cn/index.php/2020/01/21/rendering-dependency-graph/
https://zhuanlan.zhihu.com/p/101149903
**本文也将引用上文中的若干内容。**
**注意**
1. 本文为了便于理解把诸如typedef FShaderDrawSymbols SHADER;这种类型别名都改成原本的类型名了。
2. 本文属于本人边学边写的学习笔记,不可避免地会有错误。如有发现还请指出,尽请见谅。
## 推荐用于学习的代码
PostProcessTestImage.cpp
GpuDebugRendering.cpp
- ShaderPrint.cpp 具体实现
- ShaderPrint.h 绘制函数声明
- ShaderPrintParameters.h 渲染变量与资源声明
本人推荐这个ShaderPrint简单的同时又可以进行扩展以此实现更多debug方式。本文中没有说明出处的代码默认来自于ShaderPrint中。
## 相关头文件
你可以查看RenderGraph.h查看官方对于RDG系统的介绍。
- #include "RenderGraphDefinitions.h"
- #include "RenderGraphResources.h"
- #include "RenderGraphPass.h"
- #include "RenderGraphBuilder.h"
- #include "RenderGraphUtils.h"
- #include "ShaderParameterStruct.h"
- #include "ShaderParameterMacros.h"
## 资源声明与绑定
### Struct的字节对齐问题
在声明Struct时需要注意一下字节对齐的问题。这里我引用一段papalqi博客中的文章
>当然在设置中我们可能还需要注意一些简单的问题。由于unreal 采用16字节自动对齐的原则所以在编写代码时我们实际上对任何成员的顺序是需要注意的。例如下图中的顺序调整。宏系统的另一个特性是着色器数据的自动对齐。Unreal引擎使用与平台无关的数据对齐规则来实现着色器的可移植性。
主要规则是每个成员都是按照其大小的下一个幂进行对齐的但前提是大于4个字节。例如
- 指针是8字节对齐的即使在32位平台上也是如此
- 浮点、uint32、int32是四字节对齐的
- FVector2DFIntPoint是8字节对齐的
- FVector和FVector 4是16字节对齐的。
>作者(Author)papalqi 链接(URL)https://papalqi.cn/index.php/2020/01/21/rendering-dependency-graph/
**所以我们需要根据位宽对变量与资源进行排序:**
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/imagesRDG-%E5%8F%98%E9%87%8F%E4%BD%8D%E5%AE%BD%E8%87%AA%E5%8A%A8%E5%AF%B9%E9%BD%90.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/imagesRDG-%E5%8F%98%E9%87%8F%E4%BD%8D%E5%AE%BD%E6%89%8B%E5%8A%A8%E5%AF%B9%E9%BD%90.png)
进行手动位宽对齐,以减少额外的内存与带宽占用。
### 资源的初始化与绑定
其中资源分为使用RDG托管与非托管的。下面是ShaderPrint的部分代码
```
// Initialize graph managed resources
// Symbols buffer contains Count + 1 elements. The first element is only used as a counter.
FRDGBufferRef SymbolBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(ShaderPrintItem), GetMaxSymbolCount() + 1), TEXT("ShaderPrintSymbolBuffer"));
FRDGBufferRef IndirectDispatchArgsBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateIndirectDesc(4), TEXT("ShaderPrintIndirectDispatchArgs"));
FRDGBufferRef IndirectDrawArgsBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateIndirectDesc(5), TEXT("ShaderPrintIndirectDrawArgs"));
// Non graph managed resources
FUniformBufferRef UniformBuffer = CreateUniformBuffer(View);
FShaderResourceViewRHIRef ValuesBuffer = View.ShaderPrintValueBuffer.SRV;
FTextureRHIRef FontTexture = GEngine->MiniFontTexture != nullptr ? GEngine->MiniFontTexture->Resource->TextureRHI : GSystemTextures.BlackDummy->GetRenderTargetItem().ShaderResourceTexture;;
```
**使用RDG托管的资源**会使用GraphBuilder.CreateBuffer()创建一个FRDGBufferDesc在之后会使用GraphBuilder.CreateUAV()、CreateSRV()、CreateTexture()创建具体资源时,作为第一个形参。
**不使用RDG托管的资源**都只需要定义、计算后直接绑定即可Uniform的创建请见下文。
```
FShaderBuildIndirectDispatchArgsCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FShaderBuildIndirectDispatchArgsCS::FParameters>();
PassParameters->UniformBufferParameters = UniformBuffer;
PassParameters->ValuesBuffer = ValuesBuffer;
PassParameters->RWSymbolsBuffer = GraphBuilder.CreateUAV(SymbolBuffer, EPixelFormat::PF_R32_UINT);
PassParameters->RWIndirectDispatchArgsBuffer = GraphBuilder.CreateUAV(IndirectDispatchArgsBuffer, EPixelFormat::PF_R32_UINT);
```
GpuDebugRendering.cpp中的代码可以看得出是直接绑定的。
```
//bIsBehindDepth是一个之前设置的bool变量
ShaderDrawVSPSParameters* PassParameters = GraphBuilder.AllocParameters<ShaderDrawVSPSParameters>();
PassParameters->ShaderDrawPSParameters.ColorScale = bIsBehindDepth ? 0.4f : 1.0f;
```
### 声明资源宏
这里介绍几种常用的。这些宏位于Runtime\RenderCore\Public\ShaderParameterMacros.h中。另外还有一组RDG版本的宏这些宏声明的资源需要先使用 GraphBuilder.CreateBuffer()初始化资源后再调用对应GraphBuilder.CreateXXXX()完成创建,。这个头文件中还包含了若干案例代码,为了文章的简洁性这里就不贴了。
#### 常规变量
```c++
SHADER_PARAMETER(float, MyScalar)
SHADER_PARAMETER(FMatrix, MyMatrix)
SHADER_PARAMETER_RDG_BUFFER(Buffer<float4>, MyBuffer)
```
#### 结构体
在ShaderPrint中全局的结构体的声明都写在头文件中。在自己定义的GlobalShader调用`SHADER_PARAMETER_STRUCT_REF(FMyNestedStruct, MyStruct)`来设置全局结构体的指针进行引用。使用结构体的Shader需要使用`SHADER_USE_PARAMETER_STRUCT(FMyShaderClassCS, FGlobalShader);`进行标记。
```c++
//声明一个全局的结构体变量
EGIN_GLOBAL_SHADER_PARAMETER_STRUCT(FMyParameterStruct, RENDERER_API)
END_GLOBAL_SHADER_PARAMETER_STRUCT()
IMPLEMENT_GLOBAL_SHADER_PARAMETER_STRUCT(FMyParameterStruct, "MyShaderBindingName");
```
```c++
//声明一个全局结构体的引用
BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT(FGlobalViewParameters,)
SHADER_PARAMETER(FVector4, ViewSizeAndInvSize)
// ...
END_GLOBAL_SHADER_PARAMETER_STRUCT()
BEGIN_SHADER_PARAMETER_STRUCT(FOtherStruct)
SHADER_PARAMETER_STRUCT_REF(FMyNestedStruct, MyStruct)
END_SHADER_PARAMETER_STRUCT()
```
```c++
//为使用结构化着色器参数API的着色器类打上标签。
class FMyShaderClassCS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FMyShaderClassCS);
SHADER_USE_PARAMETER_STRUCT(FMyShaderClassCS, FGlobalShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters)
SHADER_PARAMETER(FMatrix, ViewToClip)
//...
END_SHADER_PARAMETER_STRUCT()
};
```
#### 数组
```c++
SHADER_PARAMETER_ARRAY(float, MyScalarArray, [8])
SHADER_PARAMETER_ARRAY(FMatrix, MyMatrixArray, [2])
SHADER_PARAMETER_RDG_BUFFER_ARRAY(Buffer<float4>, MyArrayOfBuffers, [4])
```
#### Texture
```c++
SHADER_PARAMETER_TEXTURE(Texture2D, MyTexture)
SHADER_PARAMETER_TEXTURE_ARRAY(Texture2D, MyArrayOfTextures, [8])
```
#### SRV
```c++
SHADER_PARAMETER_SRV(Texture2D, MySRV)
SHADER_PARAMETER_SRV_ARRAY(Texture2D, MyArrayOfSRVs, [8])
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer<float4>, MySRV)
SHADER_PARAMETER_RDG_BUFFER_SRV_ARRAY(Buffer<float4>, MyArrayOfSRVs, [4])
```
#### UAV
```c++
SHADER_PARAMETER_UAV(Texture2D, MyUAV)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer<float4>, MyUAV)
SHADER_PARAMETER_RDG_BUFFER_UAV_ARRAY(RWBuffer<float4>, MyArrayOfUAVs, [4])
```
#### Sampler
```c++
SHADER_PARAMETER_SAMPLER(SamplerState, MySampler)
SHADER_PARAMETER_SAMPLER_ARRAY(SamplerState, MyArrayOfSamplers, [8])
```
#### 不太懂是干什么的
```c++
//Adds a render graph tracked buffer upload.
//Example:
SHADER_PARAMETER_RDG_BUFFER_UPLOAD(Buffer<float4>, MyBuffer)
```
```c++
BEGIN_SHADER_PARAMETER_STRUCT(ShaderDrawVSPSParameters, )
SHADER_PARAMETER_STRUCT_INCLUDE(FShaderDrawDebugVS::FParameters, ShaderDrawVSParameters)
SHADER_PARAMETER_STRUCT_INCLUDE(FShaderDrawDebugPS::FParameters, ShaderDrawPSParameters)
END_SHADER_PARAMETER_STRUCT()
```
## 设置变量
首先我们要了解清楚给一个Pass设置变量的步骤
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/imagesPassParameterSetup.png)
>当增加一个RGPass它必须带有Shader参数可以是任何Shader参数比如UnifromBufferTexture等。且参数使用" GraphBuilder.AllocaParameter "来分配保留所有参数的结构体因为Lambda执行被延迟确保了正确的生命周期。参数采用宏的形式来声明。且参数结构体的声明最好的方法是内联直接在每个Pass的ShaderClass内声明好结构。
>首先得在Shader里使用宏SHADER_USE_PARAMETERSTRUCT(FYouShader, ShaderType)设置Shader需要使用Prameter。然后需要实现一个FParameter的宏包裹的结构体里面声明该Pass需要用到的所有参数参数基本上都是靠新的RDG系列宏来声明。需要注意一点的是对于UnifromBuffer需要使用StructRef来引用一层可以理解为Parameter结构体里面还有一个结构体。
```c++
FShaderDrawSymbols::FParameters* PassParameters = GraphBuilder.AllocParameters<FShaderDrawSymbols::FParameters>();
PassParameters->RenderTargets[0] = FRenderTargetBinding(OutputTexture, ERenderTargetLoadAction::ENoAction);
PassParameters->UniformBufferParameters = UniformBuffer;
PassParameters->MiniFontTexture = FontTexture;
PassParameters->SymbolsBuffer = GraphBuilder.CreateSRV(SymbolBuffer);
PassParameters->IndirectDrawArgsBuffer = IndirectDrawArgsBuffer;
```
对于代码中UniformBuffer变量ShaderPrint是这么设置的
```
FUniformBufferRef UniformBuffer = CreateUniformBuffer(View);
```
```
typedef TUniformBufferRef<FUniformBufferParameters> FUniformBufferRef;
// Fill the uniform buffer parameters
void SetUniformBufferParameters(FViewInfo const& View, FUniformBufferParameters& OutParameters)
{
const float FontWidth = (float)FMath::Max(CVarFontSize.GetValueOnRenderThread(), 1) / (float)FMath::Max(View.UnconstrainedViewRect.Size().X, 1);
const float FontHeight = (float)FMath::Max(CVarFontSize.GetValueOnRenderThread(), 1) / (float)FMath::Max(View.UnconstrainedViewRect.Size().Y, 1);
const float SpaceWidth = (float)FMath::Max(CVarFontSpacingX.GetValueOnRenderThread(), 1) / (float)FMath::Max(View.UnconstrainedViewRect.Size().X, 1);
const float SpaceHeight = (float)FMath::Max(CVarFontSpacingY.GetValueOnRenderThread(), 1) / (float)FMath::Max(View.UnconstrainedViewRect.Size().Y, 1);
OutParameters.FontSize = FVector4(FontWidth, FontHeight, SpaceWidth + FontWidth, SpaceHeight + FontHeight);
OutParameters.MaxValueCount = GetMaxValueCount();
OutParameters.MaxSymbolCount = GetMaxSymbolCount();
}
// Return a uniform buffer with values filled and with single frame lifetime
FUniformBufferRef CreateUniformBuffer(FViewInfo const& View)
{
FUniformBufferParameters Parameters;
SetUniformBufferParameters(View, Parameters);
return FUniformBufferRef::CreateUniformBufferImmediate(Parameters, UniformBuffer_SingleFrame);
}
```
看得出创建步骤为:
1. 创建之前使用宏声明的结构体的对象。
2. 对结构体中变量进行赋值。
3. 包一层TUniformBufferRef并使用CreateUniformBufferImmediate返回FUniformBufferRef。
4. 绘制函数中对对应命名空间FParameters结构体进行资源绑定。
可以看得出对于Uniform中的普通变量是直接设置的。
### 创建Buffer
调用GraphBuilder.CreateBuffer()即可创建缓存返回一个FRDGBufferRef对象。但CreateBuffer()的第一个形参中FRDGBufferDesc可以使用Desc有好几种CreateIndirectDesc、CreateStructuredDesc、CreateBufferDesc。其中CreateIndirectDesc应该是与RDG的IndirectDraw/Dispatch机制有关。对于结构体可以使用CreateStructuredDesc声明资源时用StructuredBuffer与RWStructuredBuffer宏。使用CreateBufferDesc需要手动计算占用空间与元素个数。代码大致如下
```
GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateIndirectDesc(4), TEXT("BurleyIndirectDispatchArgs"));
GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(ShaderDrawDebugElement), GetMaxShaderDrawElementCount()), TEXT("ShaderDrawDataBuffer"));
GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateBufferDesc(sizeof(uint32), 1), TEXT("HairDebugSampleCounter"));
```
### 绑定SRV与UAV
使用SRV/UAV宏后其对应的Buffer需要使用GraphBuilder.CreateSRV/GraphBuilder.CreateUAV进行绑定。注意这里分为Buffer_UAV与Texture_UAV
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/imagesReadingFromABufferUsingAnSRV.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/imagesBindingUAVsForPixelShaders.png)
**Buffer_UAV**:在创建Buffer后再调用CreateUAV
```
//HairStrandsClusters.cpp中的代码
FRDGBufferRef GlobalRadiusScaleBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateBufferDesc(sizeof(float), ClusterData.ClusterCount), TEXT("HairGlobalRadiusScaleBuffer"));
Parameters->GlobalRadiusScaleBuffer = GraphBuilder.CreateUAV(GlobalRadiusScaleBuffer, PF_R32_FLOAT);
```
**使用Texture2D来绑定SRV与UAV。**
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/imagesCreateAUACForTexture.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/imagesCreateASRVForTexture.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/imagesPassParameter.png)
### 绑定RenderTarget
如需使用RenderTarget只要在FParameter结构体声明中加一行RENDER_TARGET_BINDING_SLOTS()即可。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/RenderTargetBindingsSlots.png)
之后就可以进行RT的绑定。
**绑定储存颜色数据的RT**
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/imagesBindingAColorRenderTarget.png)
颜色数据RT绑定时需要注意数组下标号。
**绑定深度模板缓存**
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/BindingDepthStencilTarget.png)
FParameter中的RenderTarget对象为FShadowMapRenderTargets类其地址与类内的容器变量ColorTargets一样所以可以使用这个写法。
```
class FShadowMapRenderTargets
{
public:
TArray<IPooledRenderTarget*, SceneRenderingAllocator> ColorTargets;
IPooledRenderTarget* DepthTarget;
}
```
### 绑定非当前GraphPass的资源
>需要注意的是一个GraphPass内的资源有可能不是由Graph创建的这个时候就需要使用GraphBuilder.RegisterExternalBuffer/Texture来把某个PoolRT或者RHIBuffer转成RDGResource才能使用。同样的吧一个RDGResource转成PoolRT或者RHIBuffer的方法则是GraphBuilder.QueueExternalBuffer/Texture感觉这两对更适合叫ImportResource和ExportResource。如下图。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/imagesRegistration.png)
>Check out GRenderTargetPool.CreateUntrackedElement()to get a TRefCountPtr<IPooledRenderTarget>if need to register a different from RHI resource (for instance the very old FRenderTarget)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/imagesExtractionQueries.png)
## AddPass
>GraphBuilder.AddPass()主要用来配置管线状态用于延迟执行。比如使用 FGraphicsPipelineStateInitializer对象来配置PSO并调用RHI的API来进行绘制。或者使用SetComputeSahder()来DispatchCompute。注意此时还不会实际执行绘制而是在所有AddPass完成后调用GraphBuilder.Execute()才实际执行。而且更主要的是SetShaderParameters()也是在这儿做这个函数是UE封装的因为我们的一个Pass只能有一个AllocParameter所以这个东西里面是塞了UnifromBuffer SRV UAV等等各种东西。在以前的流程里面是自己再Shader里封装Shader.SetSRV/UAV/Unifrom等等的函数而现在则只需要吧所有参数塞一起并在GraphPass内设置即可。RDG会自动检测参数的每一个成员和类型自动SetUAV/SRV/Unifrom。
```
GraphBuilder.AddPass(
RDG_EVENT_NAME("DrawSymbols"),
PassParameters,
ERDGPassFlags::Raster,
[VertexShader, PixelShader, PassParameters](FRHICommandListImmediate& RHICmdListImmediate)
{
FGraphicsPipelineStateInitializer GraphicsPSOInit;
RHICmdListImmediate.ApplyCachedRenderTargets(GraphicsPSOInit);
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
GraphicsPSOInit.BlendState = TStaticBlendState<CW_RGBA, BO_Add, BF_One, BF_InverseSourceAlpha, BO_Add, BF_Zero, BF_One>::GetRHI();
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GetVertexDeclarationFVector4();
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
SetGraphicsPipelineState(RHICmdListImmediate, GraphicsPSOInit);
SetShaderParameters(RHICmdListImmediate, VertexShader, VertexShader.GetVertexShader(), *PassParameters);
SetShaderParameters(RHICmdListImmediate, PixelShader, PixelShader.GetPixelShader(), *PassParameters);
RHICmdListImmediate.DrawIndexedPrimitiveIndirect(GTwoTrianglesIndexBuffer.IndexBufferRHI, PassParameters->IndirectDrawArgsBuffer->GetIndirectRHICallBuffer(), 0);
});
```

View File

@@ -0,0 +1,295 @@
## 前言
在插件中使用RDG调用ComputeShader的方法我花了没几天就搞定了。但PixelShader相对来说就麻烦了怎么搞都没有绘制到RT上。最后还是通过改写DrawFullscreenPixelShader的代码搞定了。
另外说一下PixelShader的调用和传统的GlobalShader调用很相似。
## 设置Shader虚拟目录
这个之前忘记说了,所以这里补充一下,具体操作为在插件的模块启动函数中添加以下代码::
```
void FBRPluginsModule::StartupModule()
{
FString PluginShaderDir = FPaths::Combine(IPluginManager::Get().FindPlugin(TEXT("BRPlugins"))->GetBaseDir(), TEXT("Shaders"));
AddShaderSourceDirectoryMapping(TEXT("/BRPlugins"), PluginShaderDir);
}
```
之后就可以使用这个虚拟目录来定义Shader
```
IMPLEMENT_GLOBAL_SHADER(FSimpleRDGComputeShader, "/BRPlugins/Private/SimpleComputeShader.usf", "MainCS", SF_Compute);
```
## 参考案例
这里我没什么管理可以推荐的感觉都不是很好。但你可以搜索ERDGPassFlags::Raster就可以找到RDG调用PixelShader的代码。
## 使用DrawFullscreenPixelShader绘制
在RDG中已经封装了一个绘制函数DrawFullscreenPixelShader()可以拿来做测试。使用的方法也比较简单直接在GraphBuilder.AddPass的Lambda中调用DrawFullscreenPixelShader即可。但
其使用的顶点格式是公共资源(CommonRenderResources.h)中的GFilterVertexDeclaration.VertexDeclarationRHI、GScreenRectangleVertexBuffer.VertexBufferRHI、GScreenRectangleIndexBuffer.IndexBufferRHI。
```
void FScreenRectangleVertexBuffer::InitRHI()
{
TResourceArray<FFilterVertex, VERTEXBUFFER_ALIGNMENT> Vertices;
Vertices.SetNumUninitialized(6);
Vertices[0].Position = FVector4(1, 1, 0, 1);
Vertices[0].UV = FVector2D(1, 1);
Vertices[1].Position = FVector4(0, 1, 0, 1);
Vertices[1].UV = FVector2D(0, 1);
Vertices[2].Position = FVector4(1, 0, 0, 1);
Vertices[2].UV = FVector2D(1, 0);
Vertices[3].Position = FVector4(0, 0, 0, 1);
Vertices[3].UV = FVector2D(0, 0);
//The final two vertices are used for the triangle optimization (a single triangle spans the entire viewport )
Vertices[4].Position = FVector4(-1, 1, 0, 1);
Vertices[4].UV = FVector2D(-1, 1);
Vertices[5].Position = FVector4(1, -1, 0, 1);
Vertices[5].UV = FVector2D(1, -1);
// Create vertex buffer. Fill buffer with initial data upon creation
FRHIResourceCreateInfo CreateInfo(&Vertices);
VertexBufferRHI = RHICreateVertexBuffer(Vertices.GetResourceDataSize(), BUF_Static, CreateInfo);
}
```
DrawFullscreenPixelShader()使用的VertexShader是FScreenVertexShaderVSusf为FullscreenVertexShader.usf。代码如下
```
#include "../Common.ush"
void MainVS(
float2 InPosition : ATTRIBUTE0,
float2 InUV : ATTRIBUTE1, // TODO: kill
out float4 Position : SV_POSITION)
{
Position = float4(InPosition.x * 2.0 - 1.0, 1.0 - 2.0 * InPosition.y, 0, 1);
}
```
这里可以看得出一个问题那就是PixelShader无法获得UV坐标。所以DrawFullscreenPixelShader()能做事情很有限。因此本人写的例子是自定义了顶点格式。
## CommonRenderResources
Ue4的已经帮我们设置好了几个基础的VertexDeclaration位于RenderCore\Public\CommonRenderResources.h。
GEmptyVertexDeclaration.VertexDeclarationRHI在Shader中的Input为
```
in uint InstanceId : SV_InstanceID,
in uint VertexId : SV_VertexID,
```
GFilterVertexDeclaration.VertexDeclarationRHI在Shader中的Input为
```
in float4 InPosition : ATTRIBUTE0,
in float2 InUV : ATTRIBUTE1,
```
对应的顶点缓存与索引缓存为TGlobalResource<FScreenRectangleVertexBuffer> GScreenRectangleVertexBuffer与TGlobalResource<FScreenRectangleIndexBuffer> GScreenRectangleIndexBuffer;
如果你想在调用PixelShader时使用FScreenRectangleVertexBuffer就需要转换UV坐标了(-1,1)=>(0,1)因为FScreenRectangleVertexBuffer的UV定义范围为(-1,1)。
## RenderTarget的传入与绑定
传入的RenderTarget皆可以用Texture2D类型声明。例如
```
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_STRUCT_REF(FSimpleUniformStructParameters, SimpleUniformStruct)
SHADER_PARAMETER_TEXTURE(Texture2D, TextureVal)
SHADER_PARAMETER_SAMPLER(SamplerState, TextureSampler)
SHADER_PARAMETER(FVector4, SimpleColor)
RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT()
```
绑定需要在上面的声明宏中加入RENDER_TARGET_BINDING_SLOTS(),之后设置变量时绑定:
```
FSimpleRDGPixelShader::FParameters *Parameters = GraphBuilder.AllocParameters<FSimpleRDGPixelShader::FParameters>();
Parameters->RenderTargets[0] = FRenderTargetBinding(RDGRenderTarget, ERenderTargetLoadAction::ENoAction);
```
还可以绑定DepthStencil
```
Parameters->RenderTargets.DepthStencil = FDepthStencilBinding(OutDepthTexture,ERenderTargetLoadAction::ELoad,ERenderTargetLoadAction::ELoad,FExclusiveDepthStencil::DepthNop_StencilWrite);
```
在USF中对应Out为
```
out float4 OutColor : SV_Target0,
out float OutDepth : SV_Depth
```
如果有多个RenderTarget绑定会如SV_Target0、SV_Target1、SV_Target2一般递增。
## 资源清理
本人案例中因为只有一个Pass所以就没有用这两个函数。
```
ValidateShaderParameters(PixelShader, Parameters);
ClearUnusedGraphResources(PixelShader, Parameters);
```
## RDGPixelDraw
直接上代码了。
```
void RDGDraw(FRHICommandListImmediate &RHIImmCmdList, FTexture2DRHIRef RenderTargetRHI, FSimpleShaderParameter InParameter, const FLinearColor InColor, FTexture2DRHIRef InTexture)
{
check(IsInRenderingThread());
//Create PooledRenderTarget
FPooledRenderTargetDesc RenderTargetDesc = FPooledRenderTargetDesc::Create2DDesc(RenderTargetRHI->GetSizeXY(),RenderTargetRHI->GetFormat(), FClearValueBinding::Black, TexCreate_None, TexCreate_RenderTargetable | TexCreate_ShaderResource | TexCreate_UAV, false);
TRefCountPtr<IPooledRenderTarget> PooledRenderTarget;
//RDG Begin
FRDGBuilder GraphBuilder(RHIImmCmdList);
FRDGTextureRef RDGRenderTarget = GraphBuilder.CreateTexture(RenderTargetDesc, TEXT("RDGRenderTarget"));
//Setup Parameters
FSimpleUniformStructParameters StructParameters;
StructParameters.Color1 = InParameter.Color1;
StructParameters.Color2 = InParameter.Color2;
StructParameters.Color3 = InParameter.Color3;
StructParameters.Color4 = InParameter.Color4;
StructParameters.ColorIndex = InParameter.ColorIndex;
FSimpleRDGPixelShader::FParameters *Parameters = GraphBuilder.AllocParameters<FSimpleRDGPixelShader::FParameters>();
Parameters->TextureVal = InTexture;
Parameters->TextureSampler = TStaticSamplerState<SF_Trilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
Parameters->SimpleColor = InColor;
Parameters->SimpleUniformStruct = TUniformBufferRef<FSimpleUniformStructParameters>::CreateUniformBufferImmediate(StructParameters, UniformBuffer_SingleFrame);
Parameters->RenderTargets[0] = FRenderTargetBinding(RDGRenderTarget, ERenderTargetLoadAction::ENoAction);
const ERHIFeatureLevel::Type FeatureLevel = GMaxRHIFeatureLevel; //ERHIFeatureLevel::SM5
FGlobalShaderMap *GlobalShaderMap = GetGlobalShaderMap(FeatureLevel);
TShaderMapRef<FSimpleRDGVertexShader> VertexShader(GlobalShaderMap);
TShaderMapRef<FSimpleRDGPixelShader> PixelShader(GlobalShaderMap);
//ValidateShaderParameters(PixelShader, Parameters);
//ClearUnusedGraphResources(PixelShader, Parameters);
GraphBuilder.AddPass(
RDG_EVENT_NAME("RDGDraw"),
Parameters,
ERDGPassFlags::Raster,
[Parameters, VertexShader, PixelShader, GlobalShaderMap](FRHICommandList &RHICmdList) {
FRHITexture2D *RT = Parameters->RenderTargets[0].GetTexture()->GetRHI()->GetTexture2D();
RHICmdList.SetViewport(0, 0, 0.0f, RT->GetSizeX(), RT->GetSizeY(), 1.0f);
FGraphicsPipelineStateInitializer GraphicsPSOInit;
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GTextureVertexDeclaration.VertexDeclarationRHI;
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
RHICmdList.SetStencilRef(0);
SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), *Parameters);
RHICmdList.SetStreamSource(0, GRectangleVertexBuffer.VertexBufferRHI, 0);
RHICmdList.DrawIndexedPrimitive(
GRectangleIndexBuffer.IndexBufferRHI,
/*BaseVertexIndex=*/0,
/*MinIndex=*/0,
/*NumVertices=*/4,
/*StartIndex=*/0,
/*NumPrimitives=*/2,
/*NumInstances=*/1);
});
GraphBuilder.QueueTextureExtraction(RDGRenderTarget, &PooledRenderTarget);
GraphBuilder.Execute();
//Copy Result To RenderTarget Asset
RHIImmCmdList.CopyTexture(PooledRenderTarget->GetRenderTargetItem().ShaderResourceTexture, RenderTargetRHI->GetTexture2D(), FRHICopyTextureInfo());
}
```
调用方法和之前的ComputeShader部分相同这里就不赘述了。具体的可以参考我的插件。
### 自定义顶点格式部分
```
struct FTextureVertex
{
FVector4 Position;
FVector2D UV;
};
class FRectangleVertexBuffer : public FVertexBuffer
{
public:
/** Initialize the RHI for this rendering resource */
void InitRHI() override
{
TResourceArray<FTextureVertex, VERTEXBUFFER_ALIGNMENT> Vertices;
Vertices.SetNumUninitialized(6);
Vertices[0].Position = FVector4(1, 1, 0, 1);
Vertices[0].UV = FVector2D(1, 1);
Vertices[1].Position = FVector4(-1, 1, 0, 1);
Vertices[1].UV = FVector2D(0, 1);
Vertices[2].Position = FVector4(1, -1, 0, 1);
Vertices[2].UV = FVector2D(1, 0);
Vertices[3].Position = FVector4(-1, -1, 0, 1);
Vertices[3].UV = FVector2D(0, 0);
//The final two vertices are used for the triangle optimization (a single triangle spans the entire viewport )
Vertices[4].Position = FVector4(-1, 1, 0, 1);
Vertices[4].UV = FVector2D(-1, 1);
Vertices[5].Position = FVector4(1, -1, 0, 1);
Vertices[5].UV = FVector2D(1, -1);
// Create vertex buffer. Fill buffer with initial data upon creation
FRHIResourceCreateInfo CreateInfo(&Vertices);
VertexBufferRHI = RHICreateVertexBuffer(Vertices.GetResourceDataSize(), BUF_Static, CreateInfo);
}
};
class FRectangleIndexBuffer : public FIndexBuffer
{
public:
/** Initialize the RHI for this rendering resource */
void InitRHI() override
{
// Indices 0 - 5 are used for rendering a quad. Indices 6 - 8 are used for triangle optimization.
const uint16 Indices[] = {0, 1, 2, 2, 1, 3, 0, 4, 5};
TResourceArray<uint16, INDEXBUFFER_ALIGNMENT> IndexBuffer;
uint32 NumIndices = UE_ARRAY_COUNT(Indices);
IndexBuffer.AddUninitialized(NumIndices);
FMemory::Memcpy(IndexBuffer.GetData(), Indices, NumIndices * sizeof(uint16));
// Create index buffer. Fill buffer with initial data upon creation
FRHIResourceCreateInfo CreateInfo(&IndexBuffer);
IndexBufferRHI = RHICreateIndexBuffer(sizeof(uint16), IndexBuffer.GetResourceDataSize(), BUF_Static, CreateInfo);
}
};
class FTextureVertexDeclaration : public FRenderResource
{
public:
FVertexDeclarationRHIRef VertexDeclarationRHI;
virtual void InitRHI() override
{
FVertexDeclarationElementList Elements;
uint32 Stride = sizeof(FTextureVertex);
Elements.Add(FVertexElement(0, STRUCT_OFFSET(FTextureVertex, Position), VET_Float2, 0, Stride));
Elements.Add(FVertexElement(0, STRUCT_OFFSET(FTextureVertex, UV), VET_Float2, 1, Stride));
VertexDeclarationRHI = RHICreateVertexDeclaration(Elements);
}
virtual void ReleaseRHI() override
{
VertexDeclarationRHI.SafeRelease();
}
};
/*
* Vertex Resource Declaration
*/
extern TGlobalResource<FTextureVertexDeclaration> GTextureVertexDeclaration;
extern TGlobalResource<FRectangleVertexBuffer> GRectangleVertexBuffer;
extern TGlobalResource<FRectangleIndexBuffer> GRectangleIndexBuffer;
```
```
TGlobalResource<FTextureVertexDeclaration> GTextureVertexDeclaration;
TGlobalResource<FRectangleVertexBuffer> GRectangleVertexBuffer;
TGlobalResource<FRectangleIndexBuffer> GRectangleIndexBuffer;
```

View File

@@ -0,0 +1,249 @@
## 前言
UE4 RDG(RenderDependencyGraph)渲染框架本质上是在原有渲染框架上基础上进行再次封装它主要的设计目的就为了更好的管理每个资源的生命周期。同时Ue4的渲染管线已经都替换成了RDG框架了但依然有很多非重要模块以及第三方插件没有替换所以掌握以下RDG框架还是十分有必要的。
上一篇文章已经大致介绍了RDG框架的使用方法。看了前文的资料与官方的ppt再看一下渲染管线的的代码基本就可以上手了写Shader。但作为一个工作与Ue4一点关系的业余爱好者用的2014年的电脑通过修改渲染管线的方式来写Shader不太现实编译一次3小时真心伤不起。同时google与Epic论坛也没有在插件中使用RDG的资料所以我就花了些时间探索了一下用法最后写了本文。**因为非全职开发UE4时间精力有限不可避免得会有些错误还请见谅。** 代码写在我的插件里如果感觉有用麻烦Star一下。位于在Rendering下的SimpleRDG.cpp与SimpleRenderingExample.h中。
[https://github.com/blueroseslol/BRPlugins](https://github.com/blueroseslol/BRPlugins)
首先还是从ComputeShader开始因为比较简单。
## 参考文件
下面推荐几个参考文件强烈推荐看GenerateMips包含RDG Compute与GlobalShader两个案例。
- GenerateMips.cpp
- ShaderPrint.cpp
- PostProcessCombineLUTs.cpp
搜索ERDGPassFlags::Compute就可以找到RDG调用ComputeShader的代码。
## RDG的数据导入与导出
RDG需要使用RegisterExternalBuffer/Texture导入数据GraphBuilder.QueueExternalBuffer/Texture取出渲染结果这需要使用一个TRefCountPtr<IPooledRenderTarget>对象。直接使用RHIImmCmdList.CopyTexture尝试将FRDGTextureRef的数据拷贝出来时会触发禁止访问的断言。
## UAV
UAV用于保存ComputeShader的计算结果它的创建步骤如下
### 实现使用宏声明Shader变量
```
SHADER_PARAMETER_UAV(Texture2D, MyUAV)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer<float4>, MyUAV)
```
### 使用对应的函数创建并且绑定到对应Shader变量上
SHADER_PARAMETER_UAV对应CreateTexture()SHADER_PARAMETER_RDG_BUFFER_UAV对应CreateUAV()。(前者没试过)
可以使用FRDGTextureUAVDesc与Buff两种方式进行创建。
## SRV创建与使用
UAV是不能直接在Shader里读取所以需要通过创建SRV的方式来读取。因为我并没有测试SRV所以这里贴一下FGenerateMips中的部分代码
```c++
TSharedPtr<FGenerateMipsStruct> FGenerateMips::SetupTexture(FRHITexture* InTexture, const FGenerateMipsParams& InParams)
{
check(InTexture->GetTexture2D());
TSharedPtr<FGenerateMipsStruct> GenMipsStruct = MakeShareable(new FGenerateMipsStruct());
FPooledRenderTargetDesc Desc;
Desc.Extent.X = InTexture->GetSizeXYZ().X;
Desc.Extent.Y = InTexture->GetSizeXYZ().Y;
Desc.TargetableFlags = TexCreate_ShaderResource | TexCreate_RenderTargetable | TexCreate_UAV;
Desc.Format = InTexture->GetFormat();
Desc.NumMips = InTexture->GetNumMips();;
Desc.DebugName = TEXT("GenerateMipPooledRTTexture");
//Create the Pooled Render Target Resource from the input texture
FRHIResourceCreateInfo CreateInfo(Desc.DebugName);
//Initialise a new render target texture for creating an RDG Texture
FSceneRenderTargetItem RenderTexture;
//Update all the RenderTexture info
RenderTexture.TargetableTexture = InTexture;
RenderTexture.ShaderResourceTexture = InTexture;
RenderTexture.SRVs.Empty(Desc.NumMips);
RenderTexture.MipUAVs.Empty(Desc.NumMips);
for (uint8 MipLevel = 0; MipLevel < Desc.NumMips; MipLevel++)
{
FRHITextureSRVCreateInfo SRVDesc;
SRVDesc.MipLevel = MipLevel;
RenderTexture.SRVs.Emplace(SRVDesc, RHICreateShaderResourceView((FTexture2DRHIRef&)InTexture, SRVDesc));
RenderTexture.MipUAVs.Add(RHICreateUnorderedAccessView(InTexture, MipLevel));
}
RHIBindDebugLabelName(RenderTexture.TargetableTexture, Desc.DebugName);
RenderTexture.UAV = RenderTexture.MipUAVs[0];
//Create the RenderTarget from the PooledRenderTarget Desc and the new RenderTexture object.
GRenderTargetPool.CreateUntrackedElement(Desc, GenMipsStruct->RenderTarget, RenderTexture);
//Specify the Sampler details based on the input.
GenMipsStruct->Sampler.Filter = InParams.Filter;
GenMipsStruct->Sampler.AddressU = InParams.AddressU;
GenMipsStruct->Sampler.AddressV = InParams.AddressV;
GenMipsStruct->Sampler.AddressW = InParams.AddressW;
return GenMipsStruct;
}
```
```c++
void FGenerateMips::Compute(FRHICommandListImmediate& RHIImmCmdList, FRHITexture* InTexture, TSharedPtr<FGenerateMipsStruct> GenMipsStruct)
{
check(IsInRenderingThread());
//Currently only 2D textures supported
check(InTexture->GetTexture2D());
//Ensure the generate mips structure has been initialised correctly.
check(GenMipsStruct);
//Begin rendergraph for executing the compute shader
FRDGBuilder GraphBuilder(RHIImmCmdList);
FRDGTextureRef GraphTexture = GraphBuilder.RegisterExternalTexture(GenMipsStruct->RenderTarget, TEXT("GenerateMipsGraphTexture"));
···
FRDGTextureSRVDesc SRVDesc = FRDGTextureSRVDesc::CreateForMipLevel(GraphTexture, MipLevel - 1);
FGenerateMipsCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FGenerateMipsCS::FParameters>();
PassParameters->MipInSRV = GraphBuilder.CreateSRV(SRVDesc);
}
```
可以看出是先通过CreateUntrackedElement()创建IPooledRenderTarget之后再调用RegisterExternalTexture进行注册最后再调用CreateSRV创建SRV。
另外IPooledRenderTarget除了有CreateUntrackedElement()还有FindFreeElement()。这个函数就适合在多Pass RDG中使用了。
```
FRDGTextureRef GraphTexture = GraphBuilder.RegisterExternalTexture(GenMipsStruct->RenderTarget, TEXT("GenerateMipsGraphTexture"));
FRDGTextureSRVDesc SRVDesc = FRDGTextureSRVDesc::CreateForMipLevel(GraphTexture, MipLevel - 1);
FGenerateMipsCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FGenerateMipsCS::FParameters>();
PassParameters->MipInSRV = GraphBuilder.CreateSRV(SRVDesc);
```
### 在Shader中读取SRV
读取SRV与读取Texture相同首先需要创建采样器
```
SHADER_PARAMETER_SAMPLER(SamplerState, MipSampler)
PassParameters->MipSampler = RHIImmCmdList.CreateSamplerState(GenMipsStruct->Sampler);
```
之后就可以想Texture2D那样进行取值了
```
#pragma once
#include "Common.ush"
#include "GammaCorrectionCommon.ush"
float2 TexelSize;
Texture2D MipInSRV;
#if GENMIPS_SRGB
RWTexture2D<half4> MipOutUAV;
#else
RWTexture2D<float4> MipOutUAV;
#endif
SamplerState MipSampler;
[numthreads(8, 8, 1)]
void MainCS(uint3 DT_ID : SV_DispatchThreadID)
{
float2 UV = TexelSize * (DT_ID.xy + 0.5f);
#if GENMIPS_SRGB
half4 outColor = MipInSRV.SampleLevel(MipSampler, UV, 0);
outColor = half4(LinearToSrgb(outColor.xyz), outColor.w);
#else
float4 outColor = MipInSRV.SampleLevel(MipSampler, UV, 0);
#endif
#if GENMIPS_SWIZZLE
MipOutUAV[DT_ID.xy] = outColor.zyxw;
#else
MipOutUAV[DT_ID.xy] = outColor;
#endif
}
```
## RDGCompute完整代码
以下就是我写的例子,因为比较简单而且有注释,所以就不详细解释了。
```c++
void RDGCompute(FRHICommandListImmediate &RHIImmCmdList, FTexture2DRHIRef RenderTargetRHI, FSimpleShaderParameter InParameter)
{
check(IsInRenderingThread());
//Create PooledRenderTarget
FPooledRenderTargetDesc RenderTargetDesc = FPooledRenderTargetDesc::Create2DDesc(RenderTargetRHI->GetSizeXY(),RenderTargetRHI->GetFormat(), FClearValueBinding::Black, TexCreate_None, TexCreate_RenderTargetable | TexCreate_ShaderResource | TexCreate_UAV, false);
TRefCountPtr<IPooledRenderTarget> PooledRenderTarget;
//RDG Begin
FRDGBuilder GraphBuilder(RHIImmCmdList);
FRDGTextureRef RDGRenderTarget = GraphBuilder.CreateTexture(RenderTargetDesc, TEXT("RDGRenderTarget"));
//Setup Parameters
FSimpleUniformStructParameters StructParameters;
StructParameters.Color1 = InParameter.Color1;
StructParameters.Color2 = InParameter.Color2;
StructParameters.Color3 = InParameter.Color3;
StructParameters.Color4 = InParameter.Color4;
StructParameters.ColorIndex = InParameter.ColorIndex;
FSimpleRDGComputeShader::FParameters *Parameters = GraphBuilder.AllocParameters<FSimpleRDGComputeShader::FParameters>();
FRDGTextureUAVDesc UAVDesc(RDGRenderTarget);
Parameters->SimpleUniformStruct = TUniformBufferRef<FSimpleUniformStructParameters>::CreateUniformBufferImmediate(StructParameters, UniformBuffer_SingleFrame);
Parameters->OutTexture = GraphBuilder.CreateUAV(UAVDesc);
//Get ComputeShader From GlobalShaderMap
const ERHIFeatureLevel::Type FeatureLevel = GMaxRHIFeatureLevel; //ERHIFeatureLevel::SM5
FGlobalShaderMap *GlobalShaderMap = GetGlobalShaderMap(FeatureLevel);
TShaderMapRef<FSimpleRDGComputeShader> ComputeShader(GlobalShaderMap);
//Compute Thread Group Count
FIntVector ThreadGroupCount(
RenderTargetRHI->GetSizeX() / 32,
RenderTargetRHI->GetSizeY() / 32,
1);
//ValidateShaderParameters(PixelShader, Parameters);
//ClearUnusedGraphResources(PixelShader, Parameters);
GraphBuilder.AddPass(
RDG_EVENT_NAME("RDGCompute"),
Parameters,
ERDGPassFlags::Compute,
[Parameters, ComputeShader, ThreadGroupCount](FRHICommandList &RHICmdList) {
FComputeShaderUtils::Dispatch(RHICmdList, ComputeShader, *Parameters, ThreadGroupCount);
});
GraphBuilder.QueueTextureExtraction(RDGRenderTarget, &PooledRenderTarget);
GraphBuilder.Execute();
//Copy Result To RenderTarget Asset
RHIImmCmdList.CopyTexture(PooledRenderTarget->GetRenderTargetItem().ShaderResourceTexture, RenderTargetRHI->GetTexture2D(), FRHICopyTextureInfo());
//RHIImmCmdList.CopyToResolveTarget(PooledRenderTarget->GetRenderTargetItem().ShaderResourceTexture, RenderTargetRHI->GetTexture2D(), FResolveParams());
}
```
## 调用绘制函数
与传统方法类似调用上述渲染函数时需要使用ENQUEUE_RENDER_COMMAND(CaptureCommand)[]。下面是我写在蓝图函数库的代码。
```
void USimpleRenderingExampleBlueprintLibrary::UseRDGComput(const UObject *WorldContextObject, UTextureRenderTarget2D *OutputRenderTarget, FSimpleShaderParameter Parameter)
{
check(IsInGameThread());
FTexture2DRHIRef RenderTargetRHI = OutputRenderTarget->GameThread_GetRenderTargetResource()->GetRenderTargetTexture();
ENQUEUE_RENDER_COMMAND(CaptureCommand)
(
[RenderTargetRHI, Parameter](FRHICommandListImmediate &RHICmdList) {
SimpleRenderingExample::RDGCompute(RHICmdList, RenderTargetRHI, Parameter);
});
}
```
## 如何使用
直接在蓝图中调用即可
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/UseRDGCompute.png)
注意RenderTarget的格式需要与UAV的格式一一对应。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/RTFormat.png)
结果:
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/Images/RDGComputeShaderResult.png)

View File

@@ -0,0 +1,208 @@
# 参考文章
- UE4https://zhuanlan.zhihu.com/p/446587397
- UE5.2 https://zhuanlan.zhihu.com/p/658700282
- UE5.1 https://zhuanlan.zhihu.com/p/565837897
# 备注
添加的定义:
- MATERIAL_SHADINGMODEL_TOONSTANDARD
- SHADINGMODELID_TOONSTANDARD
- PIXEL_INSPECTOR_SHADINGMODELID_TOONSTANDARD
添加的功能:
- Material
- 材质编辑器引脚
- 相关引脚控制
- ShaderModel
- ViewMode - BufferVisualization
- ShaderModel可视化
- ToonBuffer可视化
- PixelInspector
# c++部分
## 添加ShaderModel
- EngineTypes.h 在EMaterialShadingModel中添加ShaderModel枚举
## 材质宏添加数值
ShaderMaterial.h给材质宏`MATERIAL_SHADINGMODEL_TOONSTANDARD`添加数值。
```c++
uint8 MATERIAL_SHADINGMODEL_TOONSTANDARD : 1;
```
之后再ShaderGenerationUtil.cpp的ApplyFetchEnvironment()与DetermineUsedMaterialSlots()添加对应代码。
## 添加材质编辑器引脚
- SceneTypes.h 在EMaterialProperty中添加2个引脚属性名称枚举。
- Material.h 在UMaterial类中添加FScalarMaterialInput类型变量CustomData2与CustomData3。
### MaterialExpressions.cpp
- 给MakeMaterialAttributes节点增加对应引脚
- 添加CustomData2与CustomData3声明。位于MaterialExpressionMakeMaterialAttributes.h
- 修改UMaterialExpressionMakeMaterialAttributes::Compile()增加CustomData2与CustomData3的对应列。
- 给BreakMaterialAttributes节点增加对应引脚
- 修改UMaterialExpressionBreakMaterialAttributes::UMaterialExpressionBreakMaterialAttributes()增加CustomData2与CustomData3的对应列。
- 修改UMaterialExpressionBreakMaterialAttributes::Serialize(),增加两列`Outputs[OutputIndex].SetMask(1, 1, 0, 0, 0); ++OutputIndex;`
- 修改BuildPropertyToIOIndexMap()增加CustomData2与CustomData3的对应列并且将最后一行的index改成合适的数值。
- 修改断言条件`static_assert(MP_MAX == 32`=>`static_assert(MP_MAX == 34`
### MaterialShared.cpp
- 修改FMaterialAttributeDefinitionMap::InitializeAttributeMap(),给CustomData2与CustomData3添加对应码只需与之前的不重复即可。
- 修改FMaterialAttributeDefinitionMap::GetAttributeOverrideForMaterial(),修改新添加的ShaderModel的引脚在材质编辑器中的显示名称。
### MaterialShader.cpp
- 修改GetShadingModelString()给新增加的ShaderModel添加返回字符串。
- 修改UpdateMaterialShaderCompilingStats(),给性能统计添加新增加的ShaderModel条件判断,`else if (ShadingModels.HasAnyShadingModel({ MSM_DefaultLit, MSM_Subsurface, MSM_PreintegratedSkin, MSM_ClearCoat, MSM_Cloth, MSM_SubsurfaceProfile, MSM_TwoSidedFoliage, MSM_SingleLayerWater, MSM_ThinTranslucent ,MSM_NPRShading}))`
### Material.cpp
- 修改UMaterial::PostLoad(),给新增加的引脚添加对应的两行代码,来对材质属性进行重新排序,`DoMaterialAttributeReorder(&CustomData2, UE4Ver, RenderObjVer);`
- 修改UMaterial::GetExpressionInputForProperty(),给新增加的引脚添加对应的两行代码。
- 修改UMaterial::CompilePropertyEx(),给新增加的引脚添加对应的两行代码。编译材质属性。
- 修改static bool IsPropertyActive_Internal()控制材质编辑器中引脚是否开启。给CustomData添加对应的代码。
### HLSLMaterialTranslator.cpp
控制材质节点编译拼接成完整的ShaderCode。
- 修改FHLSLMaterialTranslator::FHLSLMaterialTranslator()给SharedPixelProperties数组中引脚对应index赋值为true。
- 修改FHLSLMaterialTranslator::GetMaterialEnvironment()给新增加的ShaderModel添加宏。
- 修改FHLSLMaterialTranslator::GetMaterialShaderCode()。给新增加的引脚添加对应的两行`LazyPrintf.PushParam(*GenerateFunctionCode(MP_CustomData1));`。该函数会读取`/Engine/Private/MaterialTemplate.ush`并对替换字符格式化。
- 修改FHLSLMaterialTranslator::Translate(),为新增加的两个引脚增加两行:
```c#
Chunk[MP_CustomData2] = Material->CompilePropertyAndSetMaterialProperty(MP_CustomData2 ,this);//NPR Shading
Chunk[MP_CustomData3] = Material->CompilePropertyAndSetMaterialProperty(MP_CustomData3 ,this);
```
### MaterialGraph.cpp
材质界面代码。修改UMaterialGraph::RebuildGraph(),用来显示新添加的两个引脚:
```c#
MaterialInputs.Add( FMaterialInputInfo(FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_CustomData2, Material), MP_CustomData2, FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_CustomData2, Material)));
MaterialInputs.Add( FMaterialInputInfo(FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_CustomData3, Material), MP_CustomData3, FMaterialAttributeDefinitionMap::GetDisplayNameForMaterial(MP_CustomData3, Material)));
```
### 解决添加引脚后
添加引脚后会出现`PropertyConnectedBitmask cannot contain entire EMaterialProperty enumeration.`的编译错误。需要将
```c#
uint32 PropertyConnectedBitmask;
```
=》
```c#
uint64 PropertyConnectedBitmask;
```
再将函数中的转换类型改成uint64即可。
```c#
ENGINE_API bool IsConnected(EMaterialProperty Property) { return ((PropertyConnectedBitmask >> (uint64)Property) & 0x1) != 0; }
ENGINE_API void SetConnectedProperty(EMaterialProperty Property, bool bIsConnected)
{
PropertyConnectedBitmask = bIsConnected ? PropertyConnectedBitmask | (1i64 << (uint64)Property) : PropertyConnectedBitmask & ~(1i64 << (uint64)Property);
}
```
## 控制引脚
### Material.cpp控制引脚开启
在IsPropertyActive_Internal()中修改:
```c++
case MP_CustomData0:
Active = ShadingModels.HasAnyShadingModel({ MSM_ClearCoat, MSM_Hair, MSM_Cloth, MSM_Eye, MSM_SubsurfaceProfile, MSM_ToonStandard, MSM_PreintegratedSkin });
break;
case MP_CustomData1:
Active = ShadingModels.HasAnyShadingModel({ MSM_ClearCoat, MSM_Eye, MSM_ToonStandard, MSM_PreintegratedSkin });
break;
```
### MaterialShared.cpp(修改引脚显示名称)
在MaterialShared.cpp的GetAttributeOverrideForMaterial()中修改:
```c++
case MP_CustomData0:
CustomPinNames.Add({ MSM_ClearCoat, "Clear Coat" });
CustomPinNames.Add({ MSM_Hair, "Backlit" });
CustomPinNames.Add({ MSM_Cloth, "Cloth" });
CustomPinNames.Add({ MSM_Eye, "Iris Mask" });
CustomPinNames.Add({ MSM_SubsurfaceProfile, "Curvature" });
CustomPinNames.Add({ MSM_ToonStandard, "ToonDataA" });
CustomPinNames.Add({ MSM_PreintegratedSkin, "ToonDataA" });
return FText::FromString(GetPinNameFromShadingModelField(Material->GetShadingModels(), CustomPinNames, "Custom Data 0"));
case MP_CustomData1:
CustomPinNames.Add({ MSM_ClearCoat, "Clear Coat Roughness" });
CustomPinNames.Add({ MSM_Eye, "Iris Distance" });
CustomPinNames.Add({ MSM_ToonStandard, "ToonDataB" });
CustomPinNames.Add({ MSM_PreintegratedSkin, "ToonDataB" });
return FText::FromString(GetPinNameFromShadingModelField(Material->GetShadingModels(), CustomPinNames, "Custom Data 1"));
```
### ShaderMaterialDerivedHelpers.cpp(CustomData数据传递)
在CalculateDerivedMaterialParameters()中修改代码让CustomData渲染到GBuffer上
```c++
// Only some shader models actually need custom data.
Dst.WRITES_CUSTOMDATA_TO_GBUFFER = (Dst.USES_GBUFFER && (Mat.MATERIAL_SHADINGMODEL_SUBSURFACE || Mat.MATERIAL_SHADINGMODEL_PREINTEGRATED_SKIN || Mat.MATERIAL_SHADINGMODEL_SUBSURFACE_PROFILE || Mat.MATERIAL_SHADINGMODEL_CLEAR_COAT || Mat.MATERIAL_SHADINGMODEL_TWOSIDED_FOLIAGE || Mat.MATERIAL_SHADINGMODEL_HAIR || Mat.MATERIAL_SHADINGMODEL_CLOTH || Mat.MATERIAL_SHADINGMODEL_EYE));
```
## PixelInspectorResult
在PixelInspectorResult.h中添加`PIXEL_INSPECTOR_SHADINGMODELID_TOONSTANDARD`宏定义。
之后在PixelInspectorResult.cpp的DecodeShadingModel()函数中添加定义。
### PixelInspectorDetailsCustomization修改菜单显示屏蔽属性用
# Shader部分
## MaterialTemplate.ush
给新增加的引脚增加对应的格式化代码:
```c#
half GetMaterialCustomData2(FMaterialPixelParameters Parameters)
{
%s;
}
half GetMaterialCustomData3(FMaterialPixelParameters Parameters)
{
%s;
}
```
## ShadingCommon.ush
- 新增加的ShaderModel添加ID宏`#define SHADINGMODELID_NPRSHADING 12`
- 修改float3 GetShadingModelColor(uint ShadingModelID)给添加的ShaderModel设置一个显示颜色。
## Definitions.ush定义材质宏
位于`Engine\Shaders\Private\Definitions.ush`。
## BasePassCommon.ush
- ~~修改#define WRITES_CUSTOMDATA_TO_GBUFFER宏在最后的条件判断中添加新增加的ShaderModel~~。**UE5.1将该逻辑转移到C++中**
## DeferredShadingCommon.ush
- 修改bool HasCustomGBufferData(int ShadingModelID)在条件判断中添加新增加的ShaderModel。
## ShadingModelsMaterial.ush
- 修改void SetGBufferForShadingModel()该函数用于设置输出的GBuffer给新增加的ShaderModel添加对应的代码段
```c#
#ifdef MATERIAL_SHADINGMODEL_NPRSHADING
else if(ShadingModel == SHADINGMODELID_NPRSHADING)
{
GBuffer.CustomData.x=saturate(GetMaterialCustomData0(MaterialParameters));
GBuffer.CustomData.y=saturate(GetMaterialCustomData1(MaterialParameters));
GBuffer.CustomData.z=saturate(GetMaterialCustomData2(MaterialParameters));
GBuffer.CustomData.w=saturate(GetMaterialCustomData3(MaterialParameters));
}
#endif
```
## 光照实现
- ShadingModel.ushBxDF实现。
- DeferredLightingCommon.ush延迟光照实现。
# 相关文件
- UMaterialInterface、UMaterial、UMaterialInstance、FMaterial、FMaterialResource、FMateialRenderProxy、FMaterialInstanceResource、FDefaultMaterialInstance
- MaterialShader.cpp
- HLSLMaterialTranslator.cpp
- MaterialHLSLEmitter:塞入ShaderModel宏
- Material.cpp:开启引脚
- MaterialAttributeDefinitionMap.cpp
- ShaderMaterialDerivedHelpers.cpp
- ShaderGenerationUtil.cpp
- ShadingCommon.ush
# Material编辑器相关代码
- FMaterialInstanceBasePropertyOverrides
- FMaterialInstanceParameterDetails
https://zhuanlan.zhihu.com/p/565776677
https://www.cnblogs.com/timlly/p/15109132.html

View File

@@ -0,0 +1,171 @@
BasePassPixelShader.usf中的FPixelShaderInOut_MainPS()为BasePass阶段Shader的主要逻辑。会被PixelShaderOutputCommon.usf的MainPS(),即PixelShader入口函数中被调用。
该阶段会取得材质编辑器各个引脚的计算结果在一些计算下最终输出GBuffer以备后续光照计算。可以认为是“紧接”材质编辑器的下一步工作。相关的c++逻辑位于FDeferredShadingSceneRenderer::Render()的RenderBasePass()中。
但因为个人能力与时间所限,只能写一篇杂乱笔记作为记录,以供后续使用。
<!--more-->
### 780~889计算变量并且填充FMaterialPixelParameters MaterialParameters。BaseColor、Metallic、Specular就位于867~877。
### 915~1072计算GBuffer或者DBuffer
- 915~942:计算贴花相关的DBuffer
- 954~1028按照ShaderModel来填充GBuffer。983~1008 Velocity、1013~1022 使用法线来调整粗糙度在皮肤以及车漆ShaderModel中有用到
- 1041GBuffer.DiffuseColor = BaseColor - BaseColor * Metallic;
- 1059~1072使用法线清漆ShaderModel还会计算的底层法线计算BentNormal以及GBufferAO。使用SphericalGaussian
### 1081~1146
#### 1086~1116计算DiffuseColorForIndirect
DiffuseColorForIndirectDiffuseDir只在Hair中计算
- 次表面与皮肤DiffuseColorForIndirect += SubsurfaceColor;
- 布料DiffuseColorForIndirect += SubsurfaceColor * saturate(GetMaterialCustomData0(MaterialParameters));
- 头发DiffuseColorForIndirect = 2*PI * HairShading( GBuffer, L, V, N, 1, TransmittanceData, 0, 0.2, uint2(0,0);
#### 1118~1120计算预间接光照结果
GetPrecomputedIndirectLightingAndSkyLight
采样对应的预结算缓存:
1. PRECOMPUTED_IRRADIANCE_VOLUME_LIGHTING根据TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_NONDIRECTIONAL来判断是进行读取顶点AO值还是采样体积关照贴图来作为IrradianceSH的值。最后累加到OutDiffuseLighting上。
2. CACHED_VOLUME_INDIRECT_LIGHTING采样IndirectLightingCache最后累加到最后累加到OutDiffuseLighting上。
3. 采样HQ_TEXTURE_LIGHTMAP或者LQ_TEXTURE_LIGHTMAP最后累加到OutDiffuseLighting上。
调用GetSkyLighting()取得天光值并累加到OutDiffuseLighting上。最后计算OutDiffuseLighting的亮度值最后作为OutIndirectIrradiance输出。
#### 1138计算DiffuseColor
DiffuseColor=Diffuse间接照明 * Diffse颜色 + 次表面间接光照 * 次表面颜色+AO
```c#
DiffuseColor += (DiffuseIndirectLighting * DiffuseColorForIndirect + SubsurfaceIndirectLighting * SubsurfaceColor) * AOMultiBounce( GBuffer.BaseColor, DiffOcclusion );
```
#### 1140~1146SingleLayerWater 覆盖颜色操作
```c#
GBuffer.DiffuseColor *= BaseMaterialCoverageOverWater;
DiffuseColor *= BaseMaterialCoverageOverWater;
```
### 1148~1211
1. 使用ForwardDirectLighting的DiffuseLighting与SpecularLighting累加ColorTHIN_TRANSLUCENT Model则为 DiffuseColor与ColorSeparateSpecular。
2. SIMPLE_FORWARD_DIRECTIONAL_LIGHT调用GetSimpleForwardLightingDirectionalLight()计算方向光结果。
根据光照模式累加最后累加到Color上
```c#
#if STATICLIGHTING_SIGNEDDISTANCEFIELD
DirectionalLighting *= GBuffer.PrecomputedShadowFactors.x;
#elif PRECOMPUTED_IRRADIANCE_VOLUME_LIGHTING
DirectionalLighting *= GetVolumetricLightmapDirectionalLightShadowing(VolumetricLightmapBrickTextureUVs);
#elif CACHED_POINT_INDIRECT_LIGHTING
DirectionalLighting *= IndirectLightingCache.DirectionalLightShadowing;
#endif
Color += DirectionalLighting;
```
```c#
float3 GetSimpleForwardLightingDirectionalLight(FGBufferData GBuffer, float3 DiffuseColor, float3 SpecularColor, float Roughness, float3 WorldNormal, float3 CameraVector)
{
float3 V = -CameraVector;
float3 N = WorldNormal;
float3 L = ResolvedView.DirectionalLightDirection;
float NoL = saturate( dot( N, L ) );
float3 LightColor = ResolvedView.DirectionalLightColor.rgb * PI;
FShadowTerms Shadow = { 1, 1, 1, InitHairTransmittanceData() };
FDirectLighting Lighting = EvaluateBxDF( GBuffer, N, V, L, NoL, Shadow );
// Not computing specular, material was forced fully rough
return LightColor * (Lighting.Diffuse + Lighting.Transmission);
}
```
### 1213~1273渲染雾效果
包括VertexFog、PixelFog、体积雾以及体积光效果lit translucency)
体积雾只要使用View.VolumetricFogGridZParams中的值计算UV调用Texture3DSampleLevel采样FogStruct.IntegratedLightScattering最后的值为float4(VolumetricFogLookup.rgb + GlobalFog.rgb * VolumetricFogLookup.a, VolumetricFogLookup.a * GlobalFog.a);。
### 1283~1310取得材质中自发光值得计算结果并且累加到Color上
```c#
half3 Emissive = GetMaterialEmissive(PixelMaterialInputs);
#if !POST_PROCESS_SUBSURFACE && !MATERIAL_SHADINGMODEL_THIN_TRANSLUCENT
// For skin we need to keep them separate. We also keep them separate for thin translucent.
// Otherwise just add them together.
Color += DiffuseColor;
#endif
#if !MATERIAL_SHADINGMODEL_THIN_TRANSLUCENT
Color += Emissive;
```
### 1312~1349SingleLayerWater光照计算
计算SunIlluminance、WaterDiffuseIndirectIlluminance、Normal、ViewVector、EnvBrdf预积分G F * 高光颜色位于BRDF.ush后根据设置采用对应的方式(前向渲染与延迟渲染方式)
```c#
const float3 SunIlluminance = ResolvedView.DirectionalLightColor.rgb * PI; // times PI because it is divided by PI on CPU (=luminance) and we want illuminance here.
const float3 WaterDiffuseIndirectIlluminance = DiffuseIndirectLighting * PI;// DiffuseIndirectLighting is luminance. So we need to multiply by PI to get illuminance.
```
### 1352~1372超薄透明物体光照计算
### 1375~1529GBuffer相关
1. BlendMode处理
2. GBuffer.IndirectIrradiance = IndirectIrradiance;
3. 调用LightAccumulator_Add()累加关照对BaseColor的影响。Out.MRT[0]=FLightAccumulator.TotalLight
4. 调用EncodeGBuffer(),填充GBuffer12345数据。
5. Out.MRT[4] = OutVelocity;
6. Out.MRT[GBUFFER_HAS_VELOCITY ? 5 : 4] = OutGBufferD;
7. Out.MRT[GBUFFER_HAS_VELOCITY ? 6 : 5] = OutGBufferE;
8. Out.MRT[0].rgb *= ViewPreExposure;
### 1553FinalizeVirtualTextureFeedback
# UE5
## Lumen相关
- GetSkyLighting()
- Lumen
- GetTranslucencyGIVolumeLighting()
- SkyLighting
- GetEffectiveSkySHDiffuse()
- GetVolumetricLightmapSkyBentNormal()
- GetSkyBentNormalAndOcclusion()
**GetSkyLighting()** 演示了采样SkyLight与Lumen的方法。
### SkyLighting
GetEffectiveSkySHDiffuse()是一个宏会根据平台指向下面2个函数
```c++
/**
* Computes sky diffuse lighting from the SH irradiance map.
* This has the SH basis evaluation and diffuse convolution weights combined for minimal ALU's - see "Stupid Spherical Harmonics (SH) Tricks"
*/
float3 GetSkySHDiffuse(float3 Normal)
{
float4 NormalVector = float4(Normal, 1.0f);
float3 Intermediate0, Intermediate1, Intermediate2;
Intermediate0.x = dot(SkyIrradianceEnvironmentMap[0], NormalVector);
Intermediate0.y = dot(SkyIrradianceEnvironmentMap[1], NormalVector);
Intermediate0.z = dot(SkyIrradianceEnvironmentMap[2], NormalVector);
float4 vB = NormalVector.xyzz * NormalVector.yzzx;
Intermediate1.x = dot(SkyIrradianceEnvironmentMap[3], vB);
Intermediate1.y = dot(SkyIrradianceEnvironmentMap[4], vB);
Intermediate1.z = dot(SkyIrradianceEnvironmentMap[5], vB);
float vC = NormalVector.x * NormalVector.x - NormalVector.y * NormalVector.y;
Intermediate2 = SkyIrradianceEnvironmentMap[6].xyz * vC;
// max to not get negative colors
return max(0, Intermediate0 + Intermediate1 + Intermediate2);
}
/**
* Computes sky diffuse lighting from the SH irradiance map.
* This has the SH basis evaluation and diffuse convolution weights combined for minimal ALU's - see "Stupid Spherical Harmonics (SH) Tricks"
* Only does the first 3 components for speed.
*/
float3 GetSkySHDiffuseSimple(float3 Normal)
{
float4 NormalVector = float4(Normal, 1);
float3 Intermediate0;
Intermediate0.x = dot(SkyIrradianceEnvironmentMap[0], NormalVector);
Intermediate0.y = dot(SkyIrradianceEnvironmentMap[1], NormalVector);
Intermediate0.z = dot(SkyIrradianceEnvironmentMap[2], NormalVector);
// max to not get negative colors
return max(0, Intermediate0);
}
```

View File

@@ -0,0 +1,304 @@
# 参考
Tone mapping进化论 https://zhuanlan.zhihu.com/p/21983679
## Filmic tone mapping
2010年Uncharted 2的ToneMapping方法。这个方法的本质是把原图和让艺术家用专业照相软件模拟胶片的感觉人肉tone mapping后的结果去做曲线拟合得到一个高次曲线的表达式。这样的表达式应用到渲染结果后就能在很大程度上自动接近人工调整的结果。
```c#
float3 F(float3 x)
{
const float A = 0.22f;
const float B = 0.30f;
const float C = 0.10f;
const float D = 0.20f;
const float E = 0.01f;
const float F = 0.30f;
return ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F;
}
float3 Uncharted2ToneMapping(float3 color, float adapted_lum)
{
const float WHITE = 11.2f;
return F(1.6f * adapted_lum * color) / F(WHITE);
}
```
## Academy Color Encoding SystemACES
>是一套颜色编码系统或者说是一个新的颜色空间。它是一个通用的数据交换格式一方面可以不同的输入设备转成ACES另一方面可以把ACES在不同的显示设备上正确显示。不管你是LDR还是HDR都可以在ACES里表达出来。这就直接解决了VDR的问题不同设备间都可以互通数据。
>更好的地方是按照前面说的ACES为的是解决所有设备之间的颜色空间转换问题。所以这个tone mapper不但可以用于HDR到LDR的转换还可以用于从一个HDR转到另一个HDR。也就是从根本上解决了VDR的问题。这个函数的输出是线性空间的所以要接到LDR的设备只要做一次sRGB校正。要接到HDR10的设备只要做一次Rec 2020颜色矩阵乘法。Tone mapping部分是通用的这也是比之前几个算法都好的地方。
```c#
float3 ACESToneMapping(float3 color, float adapted_lum)
{
const float A = 2.51f;
const float B = 0.03f;
const float C = 2.43f;
const float D = 0.59f;
const float E = 0.14f;
color *= adapted_lum;
return (color * (A * color + B)) / (color * (C * color + D) + E);
}
```
## ToneMapPass
位于PostProcessing.cpp中
```c#
FTonemapInputs PassInputs;
PassSequence.AcceptOverrideIfLastPass(EPass::Tonemap, PassInputs.OverrideOutput);
PassInputs.SceneColor = SceneColor;
PassInputs.Bloom = Bloom;
PassInputs.EyeAdaptationTexture = EyeAdaptationTexture;
PassInputs.ColorGradingTexture = ColorGradingTexture;
PassInputs.bWriteAlphaChannel = AntiAliasingMethod == AAM_FXAA || IsPostProcessingWithAlphaChannelSupported();
PassInputs.bOutputInHDR = bTonemapOutputInHDR;
SceneColor = AddTonemapPass(GraphBuilder, View, PassInputs);
```
如代码所示需要给Shader提供渲染结果、Bloom结果、曝光结果、合并的LUT。
1. 获取输出RT对象如果输出RT无效则根据当前设备来设置RT格式默认为PF_B8G8R8A8。(LinearEXR=>PF_A32B32G32R32FLinearNoToneCurve、LinearWithToneCurve=>PF_FloatRGBA)
2. 从后处理设置中获取BloomDirtMaskTexture。
3. 从控制台变量获取SharpenDiv6。
4. 计算色差参数ChromaticAberrationParams
5. 创建共有的Shader变量 FTonemapParameters并将所有参数都进行赋值。
6. 为桌面端的ToneMapping生成排列向量。
7. 根据RT类型使用PixelShader或者ComputeShader进行渲染。
8. 返回右值。
BuildCommonPermutationDomain()构建的FCommonDomain应该是为了给引擎传递宏。其中Settings为FPostProcessSettings。
using FCommonDomain = TShaderPermutationDomain<
- FTonemapperBloomDim(USE_BLOOM):Settings.BloomIntensity > 0.0
- FTonemapperGammaOnlyDim(USE_GAMMA_ONLY):true
- FTonemapperGrainIntensityDim(USE_GRAIN_INTENSITY):Settings.GrainIntensity > 0.0f
- FTonemapperVignetteDim(USE_VIGNETTE):Settings.VignetteIntensity > 0.0f
- FTonemapperSharpenDim(USE_SHARPEN):CVarTonemapperSharpen.GetValueOnRenderThread() > 0.0f
- FTonemapperGrainJitterDim(USE_GRAIN_JITTER):Settings.GrainJitter > 0.0f
- FTonemapperSwitchAxis(NEEDTOSWITCHVERTICLEAXIS):函数形参bSwitchVerticalAxis
- FTonemapperMsaaDim(METAL_MSAA_HDR_DECODE):函数形参bMetalMSAAHDRDecode
- FTonemapperUseFXAA(USE_FXAA):View.AntiAliasingMethod == AAM_FXAA
>;
using FDesktopDomain = TShaderPermutationDomain<
- FCommonDomain,
- FTonemapperColorFringeDim(USE_COLOR_FRINGE):
- FTonemapperGrainQuantizationDim(USE_GRAIN_QUANTIZATION) FTonemapperOutputDeviceDim为LinearNoToneCurve与LinearWithToneCurve时为false否则为true。
- FTonemapperOutputDeviceDim(DIM_OUTPUT_DEVICE):ETonemapperOutputDevice(CommonParameters.OutputDevice.OutputDevice)
>;
```
enum class ETonemapperOutputDevice
{
sRGB,
Rec709,
ExplicitGammaMapping,
ACES1000nitST2084,
ACES2000nitST2084,
ACES1000nitScRGB,
ACES2000nitScRGB,
LinearEXR,
LinearNoToneCurve,
LinearWithToneCurve,
MAX
};
```
### Shader
>在当前实现下,渲染场景的完整处理通过 ACES Viewing Transform 进行处理。此流程的工作原理是使用"参考场景的"和"参考显示的"图像。
- 参考场景的 图像保有源材质的原始 线性光照 数值,不限制曝光范围。
- 参考显示的 图像是最终的图像,将变为所用显示的色彩空间。
使用此流程后,初始源文件用于不同显示时便无需每次进行较色编辑。相反,输出的显示将映射到 正确的色彩空间。
>ACES Viewing Transform 在查看流程中将按以下顺序进行
- Look Modification Transform (LMT) - 这部分抓取应用了创意"外观"(颜色分级和矫正)的 ACES 颜色编码图像, 输出由 ACES 和 Reference Rendering TransformRRT及 Output Device TransformODT渲染的图像。
- Reference Rendering Transform (RRT) - 之后,这部分抓取参考场景的颜色值,将它们转换为参考显示。 在此流程中,它使渲染图像不再依赖于特定显示器,反而能保证它输出到特定显示器时拥有正确而宽泛的色域和动态范围(尚未创建的图像同样如此)。
- Output Device Transform (ODT) - 最后,这部分抓取 RRT 的 HDR 数据输出,将其与它们能够显示的不同设备和色彩空间进行比对。 因此,每个目标需要将其自身的 ODT 与 Rec709、Rec2020、DCI-P3 等进行比对。
默认参数:
r.HDR.EnableHDROutput:设为 1 时,它将重建交换链并启用 HDR 输出。
r.HDR.Display.OutputDevice
- 0:sRGB (LDR) (默认)
- 1:Rec709 (LDR)
- 2:显式伽马映射 (LDR)
- 3:ACES 1000-nit ST-2084 (Dolby PQ) (HDR)
- 4:ACES 2000-nit ST-2084 (Dolby PQ) (HDR)
- 5:ACES 1000-nit ScRGB (HDR)
- 6:ACES 2000-nit ScRGB (HDR)
r.HDR.Display.ColorGamut
- 0:Rec709 / sRGB, D65 (默认)
- 1:DCI-P3, D65
- 2:Rec2020 / BT2020, D65
- 3:ACES, D60
- 4:ACEScg, D60
我的测试设备是:
- 宏碁(Acer) 暗影骑士24.5英寸FastIPS 280Hz小金刚HDR400
- ROG 枪神5 笔记本 HDIM连接
- UE4.27.2 源码版
经过实际还是无法打开HDR输出着实有些可惜。所以一般显示器的Shader代码为使用RenderDoc抓帧
```c#
float4 TonemapCommonPS(
float2 UV,
float3 ExposureScaleVignette,
float4 GrainUV,
float2 ScreenPos,
float2 FullViewUV,
float4 SvPosition
)
{
float4 OutColor = 0;
const float OneOverPreExposure = View_OneOverPreExposure;
float Grain = GrainFromUV(GrainUV.zw);
float2 SceneUV = UV.xy;
float4 SceneColor = SampleSceneColor(SceneUV);
SceneColor.rgb *= OneOverPreExposure;
float ExposureScale = ExposureScaleVignette.x;
float SharpenMultiplierDiv6 = TonemapperParams.y;
float3 LinearColor = SceneColor.rgb * ColorScale0.rgb;
float2 BloomUV = ColorToBloom_Scale * UV + ColorToBloom_Bias;
BloomUV = clamp(BloomUV, Bloom_UVViewportBilinearMin, Bloom_UVViewportBilinearMax);
float4 CombinedBloom = Texture2DSample(BloomTexture, BloomSampler, BloomUV);
CombinedBloom.rgb *= OneOverPreExposure;
float2 DirtLensUV = ConvertScreenViewportSpaceToLensViewportSpace(ScreenPos) * float2(1.0f, -1.0f);
float3 BloomDirtMaskColor = Texture2DSample(BloomDirtMaskTexture, BloomDirtMaskSampler, DirtLensUV * .5f + .5f).rgb * BloomDirtMaskTint.rgb;
LinearColor += CombinedBloom.rgb * (ColorScale1.rgb + BloomDirtMaskColor);
LinearColor *= ExposureScale;
LinearColor.rgb *= ComputeVignetteMask( ExposureScaleVignette.yz, TonemapperParams.x );
float3 OutDeviceColor = ColorLookupTable(LinearColor);
float LuminanceForPostProcessAA = dot(OutDeviceColor, float3 (0.299f, 0.587f, 0.114f));
float GrainQuantization = 1.0/256.0;
float GrainAdd = (Grain * GrainQuantization) + (-0.5 * GrainQuantization);
OutDeviceColor.rgb += GrainAdd;
OutColor = float4(OutDeviceColor, saturate(LuminanceForPostProcessAA));
[branch]
if(bOutputInHDR)
{
OutColor.rgb = ST2084ToLinear(OutColor.rgb);
OutColor.rgb = OutColor.rgb / EditorNITLevel;
OutColor.rgb = LinearToPostTonemapSpace(OutColor.rgb);
}
return OutColor;
}
```
关键函数是这个对非HDR设备进行Log编码。
```c#
half3 ColorLookupTable( half3 LinearColor )
{
float3 LUTEncodedColor;
// Encode as ST-2084 (Dolby PQ) values
#if (DIM_OUTPUT_DEVICE == TONEMAPPER_OUTPUT_ACES1000nitST2084 || DIM_OUTPUT_DEVICE == TONEMAPPER_OUTPUT_ACES2000nitST2084 || DIM_OUTPUT_DEVICE == TONEMAPPER_OUTPUT_ACES1000nitScRGB || DIM_OUTPUT_DEVICE == TONEMAPPER_OUTPUT_ACES2000nitScRGB || DIM_OUTPUT_DEVICE == TONEMAPPER_OUTPUT_LinearEXR || DIM_OUTPUT_DEVICE == TONEMAPPER_OUTPUT_NoToneCurve || DIM_OUTPUT_DEVICE == TONEMAPPER_OUTPUT_WithToneCurve)
// ST2084 expects to receive linear values 0-10000 in nits.
// So the linear value must be multiplied by a scale factor to convert to nits.
LUTEncodedColor = LinearToST2084(LinearColor * LinearToNitsScale);
#else
LUTEncodedColor = LinToLog( LinearColor + LogToLin( 0 ) );
#endif
float3 UVW = LUTEncodedColor * ((LUTSize - 1) / LUTSize) + (0.5f / LUTSize);
#if USE_VOLUME_LUT == 1
half3 OutDeviceColor = Texture3DSample( ColorGradingLUT, ColorGradingLUTSampler, UVW ).rgb;
#else
half3 OutDeviceColor = UnwrappedTexture3DSample( ColorGradingLUT, ColorGradingLUTSampler, UVW, LUTSize ).rgb;
#endif
return OutDeviceColor * 1.05;
}
float3 LogToLin( float3 LogColor )
{
const float LinearRange = 14;
const float LinearGrey = 0.18;
const float ExposureGrey = 444;
// Using stripped down, 'pure log', formula. Parameterized by grey points and dynamic range covered.
float3 LinearColor = exp2( ( LogColor - ExposureGrey / 1023.0 ) * LinearRange ) * LinearGrey;
//float3 LinearColor = 2 * ( pow(10.0, ((LogColor - 0.616596 - 0.03) / 0.432699)) - 0.037584 ); // SLog
//float3 LinearColor = ( pow( 10, ( 1023 * LogColor - 685 ) / 300) - .0108 ) / (1 - .0108); // Cineon
//LinearColor = max( 0, LinearColor );
return LinearColor;
}
float3 LinToLog( float3 LinearColor )
{
const float LinearRange = 14;
const float LinearGrey = 0.18;
const float ExposureGrey = 444;
// Using stripped down, 'pure log', formula. Parameterized by grey points and dynamic range covered.
float3 LogColor = log2(LinearColor) / LinearRange - log2(LinearGrey) / LinearRange + ExposureGrey / 1023.0; // scalar: 3log2 3mad
//float3 LogColor = (log2(LinearColor) - log2(LinearGrey)) / LinearRange + ExposureGrey / 1023.0;
//float3 LogColor = log2( LinearColor / LinearGrey ) / LinearRange + ExposureGrey / 1023.0;
//float3 LogColor = (0.432699 * log10(0.5 * LinearColor + 0.037584) + 0.616596) + 0.03; // SLog
//float3 LogColor = ( 300 * log10( LinearColor * (1 - .0108) + .0108 ) + 685 ) / 1023; // Cineon
LogColor = saturate( LogColor );
return LogColor;
}
```
## CombineLUTS Pass
实际会在GetCombineLUTParameters()中调用也就是CombineLUTS (PS) Pass。实际的作用是绘制一个3D LUT图毕竟ToneMapping实际也就是一个曲线所以可以合并到一起。核心函数位于PostProcessCombineLUTs.usf的**float4 CombineLUTsCommon(float2 InUV, uint InLayerIndex)**
- 计算原始LUT Neutral 。
- 对HDR设备使用ST2084解码对LDR设备使用Log解码。**LinearColor = LogToLin(LUTEncodedColor) - LogToLin(0);**
- 白平衡。
- 在sRGB色域之外扩展明亮的饱和色彩以伪造广色域渲染(Expand bright saturated colors outside the sRGB gamut to fake wide gamut rendering.)
- 颜色矫正:对颜色ColorSaturation、ColorContrast、ColorGamma、ColorGain、ColorOffset矫正操作。
- 蓝色矫正。
- ToneMapping与之前计算结果插值。
- 反蓝色矫正。
- 从AP1到sRGB的转换并Clip掉gamut的值。
- 颜色矫正。
- Gamma矫正。
- 线性颜色=》设备颜色OutDeviceColor = LinearToSrgb( OutputGamutColor );部分HDR设备则会调用对应的矩阵调整并用LinearToST2084().
- 简单处理OutColor.rgb = OutDeviceColor / 1.05;
所以核心的Tonemapping函数为位于TonemaperCommon.ush的**half3 FilmToneMap( half3 LinearColor)**与**half3 FilmToneMapInverse( half3 ToneColor)**
```
// Blue correction
ColorAP1 = lerp( ColorAP1, mul( BlueCorrectAP1, ColorAP1 ), BlueCorrection );
// Tonemapped color in the AP1 gamut
float3 ToneMappedColorAP1 = FilmToneMap( ColorAP1 );
ColorAP1 = lerp(ColorAP1, ToneMappedColorAP1, ToneCurveAmount);
// Uncorrect blue to maintain white point
ColorAP1 = lerp( ColorAP1, mul( BlueCorrectInvAP1, ColorAP1 ), BlueCorrection );
```
## ue4
后处理中的ToneMapping曲线参数为
Slope: 0.98
Toe: 0.3
Shoulder: 0.22
Black Clip: 0
White Clip: 0.025
![](https://docs.unrealengine.com/5.0/Images/designing-visuals-rendering-and-graphics/post-process-effects/color-grading/DefaultSettings_FilmicToneMapper.webp)

View File

@@ -0,0 +1,86 @@
之前有看过MeshDraw的流程发现MeshDraw部分还是和材质编辑器帮得死死的。不过应该可以通过自定义MeshDrawPass、图元类、定点工厂来直接使用VS与PS Shader。不过这样就无法使用材质编辑器进行简单的Shader编写与使用Material Instance来调节参数只能使用c++进行参数传递非常得不方便在使用材质编辑器的情况使用CustomNode可以更多的自由度比如使用一些UE在ush定义的函数以及循环等;更好的可读性以及方便项目升级与后续项目使用等。
这里我总结了一些CustomNode使用方法。使用CustomNode时最好将ConsoleVariables.ini中的 r.ShaderDevelopmentMode设置为1。这样可以看到更多的Shader错误信息但老实说UE4的Shader错误提示真心不能与U3D比……
>采用CustomNode+Include usf文件的方式使用Ctrl+Shift+.不会真正的更新代码,必须手动断开节点连接再连接上,才会触发重新编译。
<!--more-->
### IncludeFilePaths
给Material添加ush与usf文件包含只支持以上2个后缀名。CustomNode会在生成的CustomExpressionX()之前加上
```c#
#include "你填入的文件路径"
```
这样你就可以在插件中的模块c++的StartupModule()中定义Shader映射目录之后将Shader函数代码写在插件里。映射操作大致如下
```c#
void FXXXModule::StartupModule()
{
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
FString PluginShaderDir=FPaths::Combine(IPluginManager::Get().FindPlugin(TEXT("XXX"))->GetBaseDir(),TEXT("Shaders"));
AddShaderSourceDirectoryMapping(TEXT("/Plugin/YYY"),PluginShaderDir);
}
```
### Additional Defines
用来定义宏假设DefineName为ValueDefineValue为1。那么Custom将会在生成的CustomExpressionX()之前加上:
```c#
#ifndef Value
#define Value 1
#endif//Value
```
### Additional Outputs
设置完OutputName与OutputType后就会在生成的函数的形参利添加对应类型的引用
```c#
CustomExpression0(FMaterialPixelParameters Parameters, inout MaterialFloat Ret1)
```
之后就可以CustomNode节点中给这个形参赋值最后从CustomNode节点生成的输出节点中去到数值。
### 在CustomNode中使用节点代码
前几年刚接触CustomNode的时候一直都在思考如果使用一些带有`Parameters`参数的函数,比如`AbsoluteWorldPosition:GetWorldPosition(Parameters)`。这几天在回过头看了一下,只需要在函数中添加一个`FMaterialPixelParameters Parameters`或者`FMaterialVertexParameters Parameters`形参,之后就可以在函数利使用这些函数了。
#### 常用节点HlSL代码
- AbsoluteWorldPosition:GetWorldPosition(Parameters)
- AbsoluteWorldPosition(ExcludingMaterialOffsets):GetPrevWorldPosition(Parameters)
- VertexNormalPosition:Parameters.TangentToWorld[2];
- PixelNormalWS:Parameters.WorldNormal
- ObjectPosition:GetObjectWorldPosition(Parameters)
- CameraPosition:ResolvedView.WorldCameraOrigin
- LightVector:Parameters.LightVector
- ResolvedView.DirectionalLightDirection
- ResolvedView.DirectionalLightColor.rgb
- ResolvedView.SkyLightColor.rgb;
- ResolvedView.PreExposure
- EyeAdaptation:EyeAdaptationLookup() 位于EyeAdaptationCommon.ush
需要开启大气雾:
- SkyAtmosphereLightColor:·.AtmosphereLightColor[LightIndex].rgb
- SkyAtmosphereLightDirection:ResolvedView.AtmosphereLightDirection[LightIndex].xyz
节点代码中的`Parameters`为`FMaterialPixelParameters Parameters`或者`FMaterialVertexParameters Parameters`结构体两者都可以在MaterialTemplate.ush中找到成员定义。
其他一些节点中会使用`View.XXX`来获取变量,这里的`View`等于`ResolvedView`。具体的变量可以通过查看`FViewUniformShaderParameters`c++)。
剩下的一些节点代码可以通过材质编辑器的Window-ShaderCode-HLSL来找到。具体的方式是将所需节点连到对应引脚上之后将生成过得代码复制出来再进行寻找。当然也可以直接在FHLSLMaterialTranslator中来寻找对应节点的代码。
一些常用shader函数都可以在Common.ush找到。
## Texture
在CustomNode里使用Texture只需要给CustomNode连上TextureObject节点之后材质编辑器会自动生成对应的Sampler。例如Pin名称为XXX那就会生成XXXSampler之后向函数传递这2个参数即可。
函数中的形参类型为Texture2D与SamplerState。
TextureCoord虽然可以通过代码调用但会出现错误可能是因为材质编辑器没有检测到TextureCoord节点以至于没有添加对应代码所致。所以TextureCoord节点还是需要连接到CustomNode的pin上无法通过代码省略。
#### 默认贴图
所以一些控制类的贴图可以使用引擎里的资源这些资源在Engine Content中需要勾选显示Engine Content选项后才会显示
Texture2D'/Engine/EngineResources/WhiteSquareTexture.WhiteSquareTexture'
Texture2D'/Engine/EngineResources/Black.Black'
## HLSL分支控制关键字
一些if与for语句会产生变体所以在CustomNode里可以通过添加以下关键字来进行控制变体产生。
### if语句
- branch添加了branch标签的if语句shader会根据判断语句只执行当前情况的代码这样会产生跳转指令。
- flatten添加了flatten标签的if语句shader会执行全部情况的分支代码然后根据判断语句来决定使用哪个结果。
### for语句
- unroll添加了unroll标签的for循环是可以展开的直到循环条件终止代价是产生更多机器码
- loop添加了loop标签的for循环不能展开流式控制每次的循环迭代for默认是loop

View File

@@ -0,0 +1,122 @@
# UE4渲染用贴图资源实时更换
最近做卡通渲染美术同学反应每次更换渲染贴图都需要手动替换资源并重启引擎相当麻烦。所以花时间了解了一下UE4的渲染资源逻辑。并且找到了解决方法。
## 可参考的资源
在FViewUniformShaderParameter中有
SHADER_PARAMETER_TEXTURE(Texture2D, PreIntegratedBRDF)
SHADER_PARAMETER_TEXTURE(Texture2D, PerlinNoiseGradientTexture) 在渲染管线中调用FSystemTextures::InitializeCommonTextures生成之后通过GSystemTextures加载
SHADER_PARAMETER_TEXTURE(Texture3D, PerlinNoise3DTexture) 在渲染管线中调用FSystemTextures::InitializeCommonTextures生成之后通过GSystemTextures加载
SHADER_PARAMETER_TEXTURE(Texture2D, AtmosphereTransmittanceTexture)
在Material界面中有SubsufaceProfileSubsufaceProfile类SubsurfaceProfile.h
### USubsurfaceProfile
资源的类型为USubsurfaceProfile实现了2个接口函数
```c++
void USubsurfaceProfile::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent)
{
const FSubsurfaceProfileStruct SettingsLocal = this->Settings;
USubsurfaceProfile* Profile = this;
ENQUEUE_RENDER_COMMAND(UpdateSubsurfaceProfile)(
[SettingsLocal, Profile](FRHICommandListImmediate& RHICmdList)
{
// any changes to the setting require an update of the texture
GSubsurfaceProfileTextureObject.UpdateProfile(SettingsLocal, Profile);
});
}
```
```c++
void USubsurfaceProfile::BeginDestroy()
{
USubsurfaceProfile* Ref = this;
ENQUEUE_RENDER_COMMAND(RemoveSubsurfaceProfile)(
[Ref](FRHICommandList& RHICmdList)
{
GSubsurfaceProfileTextureObject.RemoveProfile(Ref);
});
Super::BeginDestroy();
}
```
在UMaterialInterface::UpdateMaterialRenderProxy()中开始触发下列渲染资源操作:
- 升级渲染资源**GSubsurfaceProfileTextureObject.UpdateProfile(SettingsLocal, Profile);**
- 移除渲染资源**GSubsurfaceProfileTextureObject.RemoveProfile(Ref);**
本质上更新SubsurfaceProfileEntries数组后将渲染进程里的GSSProfiles释放掉。在渲染管线中主要通过**GetSubsufaceProfileTexture_RT(RHICmdList);**来获取这个资源,其顺序为
>GetSubsufaceProfileTexture_RT()=>GSubsurfaceProfileTextureObject.GetTexture(RHICmdList);=>return GSSProfiles;
如果GSSProfiles无效则调用CreateTexture()对SubsurfaceProfile进行编码并生成新的GSSProfiles。
### PreIntegratedBRDF
1. 在FViewUniformShaderParameters中添加**PreIntegratedBRDF**贴图变量
2. 在FViewUniformShaderParameters::FViewUniformShaderParameters(),进行初始化**PreIntegratedBRDF = GWhiteTexture->TextureRHI;**
3. 在FViewInfo::SetupUniformBufferParameters()中进行资源指定:**ViewUniformShaderParameters.PreIntegratedBRDF = GEngine->PreIntegratedSkinBRDFTexture->Resource->TextureRHI;**
1. 在UEngine中定义资源指针**class UTexture2D* PreIntegratedSkinBRDFTexture;**与**FSoftObjectPath PreIntegratedSkinBRDFTextureName;**
2. 在UEngine::InitializeObjectReferences()中调用**LoadEngineTexture(PreIntegratedSkinBRDFTexture, *PreIntegratedSkinBRDFTextureName.ToString());**载入贴图。
```c++
template <typename TextureType>
static void LoadEngineTexture(TextureType*& InOutTexture, const TCHAR* InName)
{
if (!InOutTexture)
{
InOutTexture = LoadObject<TextureType>(nullptr, InName, nullptr, LOAD_None, nullptr);
}
if (FPlatformProperties::RequiresCookedData() && InOutTexture)
{
InOutTexture->AddToRoot();
}
}
```
### AtmosphereTransmittanceTexture
1. 在FViewUniformShaderParameters中添加**AtmosphereTransmittanceTexture**贴图变量
2. 在FViewUniformShaderParameters::FViewUniformShaderParameters(),进行初始化**AtmosphereTransmittanceTexture = GWhiteTexture->TextureRHI;**
3. 在FViewUniformShaderParameters::SetupUniformBufferParameters()中:**ViewUniformShaderParameters.AtmosphereTransmittanceTexture = OrBlack2DIfNull(AtmosphereTransmittanceTexture);**
4. 在FogRendering阶段InitAtmosphereConstantsInView()中进行资源指定:**View.AtmosphereTransmittanceTexture = (FogInfo.TransmittanceResource && FogInfo.TransmittanceResource->TextureRHI.GetReference()) ? (FTextureRHIRef)FogInfo.TransmittanceResource->TextureRHI : GBlackTexture->TextureRHI;**
5. 在UAtmosphericFogComponent::UpdatePrecomputedData()中对FogInfo进行预计算在Tick事件中调用每帧调用
在UpdatePrecomputedData()中的最后还会调用以下函数来对资源进行更新:
```c++
PrecomputeCounter = EValid;
FPlatformMisc::MemoryBarrier();
Scene->AtmosphericFog->bPrecomputationAcceptedByGameThread = true;
// Resolve to data...
ReleaseResource();
// Wait for release...
FlushRenderingCommands();
InitResource();
FComponentReregisterContext ReregisterContext(this);
```
生成贴图逻辑(部分)如下:
```c++
FScene* Scene = GetScene() ? GetScene()->GetRenderScene() : NULL;
{
int32 SizeX = PrecomputeParams.TransmittanceTexWidth;
int32 SizeY = PrecomputeParams.TransmittanceTexHeight;
int32 TotalByte = sizeof(FColor) * SizeX * SizeY;
check(TotalByte == Scene->AtmosphericFog->PrecomputeTransmittance.GetBulkDataSize());
const FColor* PrecomputeData = (const FColor*)Scene->AtmosphericFog->PrecomputeTransmittance.Lock(LOCK_READ_ONLY);
TransmittanceData.Lock(LOCK_READ_WRITE);
FColor* TextureData = (FColor*)TransmittanceData.Realloc(TotalByte);
FMemory::Memcpy(TextureData, PrecomputeData, TotalByte);
TransmittanceData.Unlock();
Scene->AtmosphericFog->PrecomputeTransmittance.Unlock();
}
```
### 本人尝试方法
个人觉得SubsurfaceProfile的方法是最好的但也因为相对麻烦所以放弃。我最后选择了修改UEngine(GEngine)中对应的的UTexture指针来实现渲染资源替换因为每帧都还会调用SetupUniformBufferParameters()来指定渲染用的资源。
为了保证载入的资源的生命周期我实现了一个UEngineSubSystem子类声明了若干UTexture指针并且移植了LoadEngineTexture()。这样就可以实现Runtime更换渲染资源了。大致代码如下
```c++
UToonEngineSubsystem* EngineSubsystem = GEngine->GetEngineSubsystem<UToonEngineSubsystem>();
EngineSubsystem->LoadEngineTexture(EngineSubsystem->ToonRampTexture, *ToonRampTexture->GetPathName());
GEngine->ToonRampTexture=EngineSubsystem->ToonRampTexture;
```

View File

@@ -0,0 +1,58 @@
---
title: Untitled
date: 2024-08-11 14:30:00
excerpt:
tags:
rating: ⭐
---
# 前言
- 官方文档:https://dev.epicgames.com/documentation/zh-cn/unreal-engine/large-world-coordinates-rendering-in-unreal-engine-5
- 相关代码文件:
- LargeWorldCoordinates.ush
- Math/DoubleFloat.ushUE5.3不存在)
- DoubleFloat.hUE5.3不存在)
## 相关HLSL类型
| HLSL类型 | 说明 |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `FDFScalar` `FDFVector2/3/4` | 这些类型是float1/2/3/4的更高精度对应项。在内部由两个floatN矢量构成High和Low。 |
| `FDFMatrix` | 类似于float4x4但包含一个额外的float3 PreTranslation坐标。将矢量乘以此类型时该矢量会首先使用PreTranslation进行转译然后再应用float4x4矩阵。此类型适合变换为世界空间LocalToWorld。 |
| `FDFInverseMatrix` | 类似于float4x4但包含一个额外的float3 PostTranslation坐标。将矢量乘以此类型时该矢量会在应用float4x4矩阵之后使用PostTranslation进行转译。此类型适合变换为世界空间LocalToWorld。 |
运算符带有前缀"DF",并有多种变体,可平衡精度和性能。变体在函数名称中用前缀或后缀来标记。例如:
- `DFFast*` 是一个后缀。例如, `DFFastAdd` 是一个后缀变体。此函数更快,但精度更低。使用此变体可获得最高速度,但会牺牲精度。对于基础运算符,每个函数的文档中都提供了论文参考资料,给出了关于其精度极限的详细说明。
- `DF*Demote` 是一个前缀。例如DFFastAddDemote是一个前缀变体。此变体会返回32位结果而不是双精度值。`DF*Demote` 的函数可用,且类似于 `LWCToFloat` 函数。使用 `DF*Demote` 函数可免除不必要的计算,效率更高。这是首选的截断方法。
# FLWCVector及相关类型
在虚幻引擎中,有多个复合类型使用专用于大型世界坐标的不同底层数学类型结构。 这些类型带有"FLWC"前缀,可在 `LargeWorldCoordinates.ush` 源文件中找到。大部分着色器现在使用DoubleFloat类型。但是一些系统仍使用FLWC类型。
可用的HLSL类型有
|HLSL类型|说明|
|---|---|
|`FLWCScalar` `FLWCVector2/3/4`|这些类型类似于float1/2/3/4但是它们包含额外的图块坐标。因此FLWCVector2由float2图块和float2偏移组成。表示的值由以下公式计算图块 * TileSize + 偏移其中TileSize表示一个被定义为256k的常量值。|
|`FLWCMatrix` `FLWCInverseMatrix`|这些类型类似于float4x4但是它们都包含额外的float3图块坐标。|
|`FLWCMatrix`|乘以矩阵后将图块坐标添加到结果。|
|`FLWCInverseMatrix`|在乘以矩阵之前添加图块坐标。|
|`FLWCMatrix`|此类型适合变换为世界空间LocalToWorld。|
|`FLWCInverseMatrix`|此类型适合从世界空间进行变换WorldToLocal。|
`LWCAdd` 或 `LWCRsqrt` 等运算可以在这些类型上执行。接受LWC输入值的运算将返回LWC输出 `LWCAdd` ),而其他运算会返回常规浮点输出( `LWCRsqrt` )。将坐标变小的运算提供了返回常规浮点的途径。
>FLWC类型提供的精度相较于DoubleFloat库更低。对于靠近图块边缘的矢量固定图块大小意味着 **图块Tile** 组件中的位不被使用,而 **偏移Offset** 只能存储截断的坐标。在虚幻引擎5.4之前这会导致物体在世界中移动时精度时高时低主要表现为物体抖动和振动。DoubleFloat数学类型通过删除固定大小解决了该问题因为无论值的量级如何这两个组件始终会存储尽可能高的精度。这在概念上类似于定点与浮点算术之间的差异。
# 材质
**基于坐标的世界空间位置**节点例如WorldPosition、Object、Camera、Actor和Particle以及TransformPosition节点会使用LWC类型的输出因此这些Input节点输出类型改变后续计算节点类型。
可以考虑将这些节点的 **世界位置原点类型World Position Origin Type** 属性设置为使用 **摄像机相对世界位置Camera Relative World Position** 实现最优精度和性能。LWC => float)
# 从UE4到UE5的转换指南
View & ResolvedView =>`PrimaryView`
着色器代码中的许多变量已被切换为LWC变体。以下似乎一些重要的示例
- `SceneData.ush` 、 `FPrimitiveSceneData` 和 `FInstanceSceneData` 有各种更新的矩阵和位置矢量。
- 光源位置和反射捕获位置。
- 全局摄像机统一数据更改了各种矩阵和偏移例如PreViewTranslation。例如在SceneView和FNaniteView中。
TranslatedWorldSpace是虚幻引擎着色器中使用的现有引擎概念。它在之前旨在通过相对于摄像机原点工作来提高精度。但是此行为对LWC很有用因为在TranslatedWorldSpace中运行时可以使用浮点数。我们建议不要将WorldPosition转换为双精度值而是重构函数以改用转译的世界空间这会带来出色的性能同时保留高精度。例如
```cpp
float3 TranslatedWorldPosition = mul(Input.LocalPosition, PrimaryView.LocalToTranslatedWorld);
```

View File

@@ -0,0 +1,141 @@
## Ue4后处理逻辑简析
### APostProcessVolume
通常我们在后处理体积也就是APostProcessVolume设置后处理效果。它存储了struct FPostProcessSettings Settings;
加入关卡后会存储在UWorld的PostProcessVolumes中之后依次调用DoPostProcessVolume=》OverridePostProcessSettings之后修改FSceneView中的FFinalPostProcessSettings FinalPostProcessSettings。对所有属性进行插值计算
最后就可以通过View.FinalPostProcessSettings来读取后处理参数了。
### AddPostProcessingPasses
控制渲染的变量主要用下方式获取
- 从FViewInfo里直接获取
- 从FFinalPostProcessSettings获取View.FinalPostProcessSettings
- 从FEngineShowFlags获取View.Family->EngineShowFlags
- 从ConsoleVariable中获取
获取各种Buffer与变量之后
```c#
const FIntRect PrimaryViewRect = View.ViewRect;
const FSceneTextureParameters SceneTextureParameters = GetSceneTextureParameters(GraphBuilder, Inputs.SceneTextures);
const FScreenPassRenderTarget ViewFamilyOutput = FScreenPassRenderTarget::CreateViewFamilyOutput(Inputs.ViewFamilyTexture, View);
const FScreenPassTexture SceneDepth(SceneTextureParameters.SceneDepthTexture, PrimaryViewRect);
const FScreenPassTexture SeparateTranslucency(Inputs.SeparateTranslucencyTextures->GetColorForRead(GraphBuilder), PrimaryViewRect);
const FScreenPassTexture CustomDepth((*Inputs.SceneTextures)->CustomDepthTexture, PrimaryViewRect);
const FScreenPassTexture Velocity(SceneTextureParameters.GBufferVelocityTexture, PrimaryViewRect);
const FScreenPassTexture BlackDummy(GSystemTextures.GetBlackDummy(GraphBuilder));
// Scene color is updated incrementally through the post process pipeline.
FScreenPassTexture SceneColor((*Inputs.SceneTextures)->SceneColorTexture, PrimaryViewRect);
// Assigned before and after the tonemapper.
FScreenPassTexture SceneColorBeforeTonemap;
FScreenPassTexture SceneColorAfterTonemap;
// Unprocessed scene color stores the original input.
const FScreenPassTexture OriginalSceneColor = SceneColor;
// Default the new eye adaptation to the last one in case it's not generated this frame.
const FEyeAdaptationParameters EyeAdaptationParameters = GetEyeAdaptationParameters(View, ERHIFeatureLevel::SM5);
FRDGTextureRef LastEyeAdaptationTexture = GetEyeAdaptationTexture(GraphBuilder, View);
FRDGTextureRef EyeAdaptationTexture = LastEyeAdaptationTexture;
// Histogram defaults to black because the histogram eye adaptation pass is used for the manual metering mode.
FRDGTextureRef HistogramTexture = BlackDummy.Texture;
const FEngineShowFlags& EngineShowFlags = View.Family->EngineShowFlags;
const bool bVisualizeHDR = EngineShowFlags.VisualizeHDR;
const bool bViewFamilyOutputInHDR = GRHISupportsHDROutput && IsHDREnabled();
const bool bVisualizeGBufferOverview = IsVisualizeGBufferOverviewEnabled(View);
const bool bVisualizeGBufferDumpToFile = IsVisualizeGBufferDumpToFileEnabled(View);
const bool bVisualizeGBufferDumpToPIpe = IsVisualizeGBufferDumpToPipeEnabled(View);
const bool bOutputInHDR = IsPostProcessingOutputInHDR();
```
读取参数并设置
```c#
TOverridePassSequence<EPass> PassSequence(ViewFamilyOutput);
PassSequence.SetNames(PassNames, UE_ARRAY_COUNT(PassNames));
PassSequence.SetEnabled(EPass::VisualizeStationaryLightOverlap, EngineShowFlags.StationaryLightOverlap);
PassSequence.SetEnabled(EPass::VisualizeLightCulling, EngineShowFlags.VisualizeLightCulling);
#if WITH_EDITOR
PassSequence.SetEnabled(EPass::SelectionOutline, GIsEditor && EngineShowFlags.Selection && EngineShowFlags.SelectionOutline && !EngineShowFlags.Wireframe && !bVisualizeHDR && !IStereoRendering::IsStereoEyeView(View));
PassSequence.SetEnabled(EPass::EditorPrimitive, FSceneRenderer::ShouldCompositeEditorPrimitives(View));
#else
PassSequence.SetEnabled(EPass::SelectionOutline, false);
PassSequence.SetEnabled(EPass::EditorPrimitive, false);
#endif
PassSequence.SetEnabled(EPass::VisualizeShadingModels, EngineShowFlags.VisualizeShadingModels);
PassSequence.SetEnabled(EPass::VisualizeGBufferHints, EngineShowFlags.GBufferHints);
PassSequence.SetEnabled(EPass::VisualizeSubsurface, EngineShowFlags.VisualizeSSS);
PassSequence.SetEnabled(EPass::VisualizeGBufferOverview, bVisualizeGBufferOverview || bVisualizeGBufferDumpToFile || bVisualizeGBufferDumpToPIpe);
PassSequence.SetEnabled(EPass::VisualizeHDR, EngineShowFlags.VisualizeHDR);
#if WITH_EDITOR
PassSequence.SetEnabled(EPass::PixelInspector, View.bUsePixelInspector);
#else
PassSequence.SetEnabled(EPass::PixelInspector, false);
#endif
PassSequence.SetEnabled(EPass::HMDDistortion, EngineShowFlags.StereoRendering && EngineShowFlags.HMDDistortion);
PassSequence.SetEnabled(EPass::HighResolutionScreenshotMask, IsHighResolutionScreenshotMaskEnabled(View));
PassSequence.SetEnabled(EPass::PrimaryUpscale, PaniniConfig.IsEnabled() || (View.PrimaryScreenPercentageMethod == EPrimaryScreenPercentageMethod::SpatialUpscale && PrimaryViewRect.Size() != View.GetSecondaryViewRectSize()));
PassSequence.SetEnabled(EPass::SecondaryUpscale, View.RequiresSecondaryUpscale() || View.Family->GetSecondarySpatialUpscalerInterface() != nullptr);
```
这些操作一直到`PassSequence.Finalize();`。
### 后处理Pass处理
主要的Pass有这么一些
```c#
TEXT("MotionBlur"),
TEXT("Tonemap"),
TEXT("FXAA"),
TEXT("PostProcessMaterial (AfterTonemapping)"),
TEXT("VisualizeDepthOfField"),
TEXT("VisualizeStationaryLightOverlap"),
TEXT("VisualizeLightCulling"),
TEXT("SelectionOutline"),
TEXT("EditorPrimitive"),
TEXT("VisualizeShadingModels"),
TEXT("VisualizeGBufferHints"),
TEXT("VisualizeSubsurface"),
TEXT("VisualizeGBufferOverview"),
TEXT("VisualizeHDR"),
TEXT("PixelInspector"),
TEXT("HMDDistortion"),
TEXT("HighResolutionScreenshotMask"),
TEXT("PrimaryUpscale"),
TEXT("SecondaryUpscale")
```
之前读取了参数对这些Pass是否开启进行了设置。之后以这种格式使用Shader对传入的图形进行后处理。
```c#
if (PassSequence.IsEnabled(EPass::MotionBlur))
{
FMotionBlurInputs PassInputs;
PassSequence.AcceptOverrideIfLastPass(EPass::MotionBlur, PassInputs.OverrideOutput);
PassInputs.SceneColor = SceneColor;
PassInputs.SceneDepth = SceneDepth;
PassInputs.SceneVelocity = Velocity;
PassInputs.Quality = GetMotionBlurQuality();
PassInputs.Filter = GetMotionBlurFilter();
// Motion blur visualization replaces motion blur when enabled.
if (bVisualizeMotionBlur)
{
SceneColor = AddVisualizeMotionBlurPass(GraphBuilder, View, PassInputs);
}
else
{
SceneColor = AddMotionBlurPass(GraphBuilder, View, PassInputs);
}
}
SceneColor = AddAfterPass(EPass::MotionBlur, SceneColor);
```
这些效果的代码都在UnrealEngine\Engine\Source\Runtime\Renderer\Private\PostProcess中。
### 后处理材质调用
AddPostProcessMaterialChain
=》
AddPostProcessMaterialPass()为实际的绘制函数。最后在AddDrawScreenPass()中进行绘制。DrawScreenPass()=>DrawPostProcessPass=>DrawPostProcessPass()
### 推荐参考的后处理代码
PostProcessBloomSetup.h
VisualizeShadingModels.cpp

View File

@@ -0,0 +1,16 @@
#### 渲染循环发起以及渲染函数
渲染更新由UGameEngine::Tick()发起。
```
UGameEngine::Tick
|
-RedrawViewports()
|
-GameViewport->Viewport->Draw
|
-EnqueueBeginRenderFrame()
SetRequiresVsync()
EnqueueEndRenderFrame()
```
#### FDeferredShadingSceneRenderer
FDeferredShadingSceneRenderer继承自FSceneRenderer从Render函数中可以了解到延迟渲染的整个过程。每个Pass的渲染流程。

View File

@@ -0,0 +1,579 @@
---
title: VirtualTexture学习笔记
date: 2024-02-20 18:26:49
excerpt:
tags:
rating: ⭐
---
# 前言
- UE4 Runtime Virtual Texture 实现机制及源码解析:https://zhuanlan.zhihu.com/p/143709152
- **UE Virtual Texture图文浅析**:https://zhuanlan.zhihu.com/p/642580472
## 相关概念
- Virtual Texture虚拟纹理以下简称 VT
- Runtime Virtual TextureUE4 运行时虚拟纹理系统,以下简称 RVT
- VT feedback存储当前屏幕像素对应的 VT Page 信息,用于加载 VT 数据。
- VT Physical Texture虚拟纹理对应的物理纹理资源
- PageTable虚拟纹理页表用来寻址 VT Physical Texture Page 数据。
- PageTable Texture包含虚拟纹理页表数据的纹理资源通过此纹理资源可查询 Physical Texture Page 信息。有些 VT 系统也叫 Indirection Texture由于本文分析 UE4 VT 的内容,这里采用 UE4 术语。
- PageTable Buffer包含虚拟纹理页表数据内容的 GPU Buffer 资源。
### 地址映射
地址映射在Virtual Texture是一个很重要的环节就是如何将一个Virtual Texture的texel映射到Physical Texture的texel上这里还需要适配当高分辨率的page没有加载的情况需要得到已经加载的对应低分辨率的page地址。
#### 四叉树映射
![](https://pic1.zhimg.com/80/v2-754fb67195882775cb95dcb1d2366ad8_720w.webp)
这里每个四叉树的节点的内容存的就是bias和scale,这样就可以将虚拟纹理的地址转换成物理纹理的地址了假如没有找到也可以用父节点的地址来得到低分辨率的。但是这里要找到对应的节点需要搜索这个四叉树搜索的速度取决于树的高度也就是mipmap的层级在差的低mip的地址上效率会比较差。
###  Feedback Rendering
在Virtual Texture中一个很重要的事情是要有一个可以决定加载哪些page的策略这个策略就是要有一个叫Feedback Rendering的过程。这个可以设计为一个单独的pass或者跟Pre-Z或者GBuffer同时。渲染生成的这张texture里面存的就是虚纹理的page坐标mip level和可能的虚纹理id用来应对多虚纹理的情况
![](https://pic1.zhimg.com/80/v2-d74b3fb551b162d4fadb0fb82c299560_720w.webp)
可以看到上图由于page的变化在屏幕空间是比较低的所以Feedback的RT是不需要全分辨率的低分辨率渲染就可以了。对于半透明物体或者alpha test的物体在Feedback Rendering的过程中只能当作是不透明物体来渲染那样就会在屏幕像素上随机产生当前像素的可能结果。与之相类似的如果一个屏幕像素用到了两个page也会是随机出现一种在最后的结果RT上。这样虽然可以让所有需要的page都加载但是可能会遇到另外一个问题那就是可能会发生这一帧加载的page下一帧的时候被卸载掉了然后再下一帧又要加载这样会导致物理纹理一直在置换即便屏幕像素并未改变物理纹理的page也无法稳定下来。为了解决这个问题需要设计一个调色板对于半透明物体间隔出现全透明或者不透明对于多page的情况则需要设计为间隔出现不同page的结果这样就能同时加载所有page并且保持稳定。但是如果出现了多层半透明物体的叠加或者多个page的情况如何设计一个合理的调色板变成了一个问题。这里可以考虑为每个像素匹配一个linked list这个需要额外的硬件支持叫structured append and consume buffers。
接着就是对这个Feedback的结果进行分析了分析需要将Feedback的结果读回CPU这里可以使用compute shader解析这个结果然后输出到一个更简单的buffer上去
![](https://pic3.zhimg.com/80/v2-6a057ea34ed1ee004892220798ea01ae_720w.webp)
这样可以使回读操作更快处理page更新也能更快。对于如何更新page也需要策略我们需要尽量不阻塞执行异步的加载page但是对于完全不存在任何一个mip的page我们还是需要同步加载防止显示出错。在异步的过程中我们需要对需要加载page设置优先级比如需要加载的mip level和已经存在的mip level相差越大的优先级越高被越多像素要求加载的page的优先级越高这里需要设计一个完整的加载策略。
### Texture Poping
由于page是异步加载的这是有延时的当加载的mip比当前显示的差很远的时候我们渲染会使用新加载的更清晰的mip,这样我们会看到非常明显的跳变。假如我们用了软实现的Tri-linear Filtering那么当加载的mip level跟当前显示的mip level相差很大的时候需要做一个delay等待中间的mip page的加载然后再去更新。对于没有Tri-linear Filtering的实现就得逐渐更新page使得过度平滑。一个可能的方法是upsample低分辨率的mip直到高分辨率的mip加载。但是这样仍然会出现跳变由于采样的位置其实发生了突变。
![](https://pic1.zhimg.com/80/v2-7019ba61ac6c2607e994096ede9a6aa0_720w.webp)
上图可以看到当分辨率增加2倍之后结果会发生很大的不同。解决的方案是先把upsample的低分辨率page加载到一个物理纹理的page当高分辨率的加载好了插值过度那个物理纹理的page这样采样的位置没有发生改变只是每个像素的颜色在渐变就不会有跳变出现了。
# UE5VirtualTexture相关实现
为读向往大佬文章的学习笔记。
## VT 系统概述
从原理上来说VT 系统主要由 2 大阶段构成VT 数据准备和 VT 运行时采样阶段。
1. VT 数据准备阶段:
1. 生成 VT feedback 数据
2. 生成 VT 纹理数据,并更新到指定 VT Physical Texture 对应的 Page
3. 根据 feedback 数据生成并更新 PageTable 数据
2. VT 运行时采样阶段:
1. 根据屏幕空间坐标以及相关信息生成 VT Physical Texture UV
2. 对 VT Physical Texture 执行采样
UE4 的 RVT 基本上也是按照这个原理和流程来实现的,本文就按照这个顺序来详细讲解。在讲解之前,为了便于后续理解,先来了解下 UE5 RVT 的实现机制。
## UE5 RVT 实现机制概述
IVirtualTexture 是 UE5 VT 最重要的接口,它是如何产生 VT 数据的接口,主要有两个抽象函数
- RequestPageData请求页面数据
- ProducePageData产生页面数据
在UE5中其子类有
- FLightmapPreviewVirtualTexture
- FNullVirtualTextureProducer
- FRuntimeVirtualTextureProducer
- FUploadingVirtualTexture
- FVirtualTextureAddressRedirect
- FVirtualTextureLevelRedirector
对于 RVT 来说,实现此接口的是 FRuntimeVirtualTextureProducer也就是作为运行时产生 Page 纹理数据的类,对于 SVT 来说,实现此接口的是 FUploadingVirtualTexture用于从磁盘中流送上传 Page 纹理数据。
FVirtualTextureSystem 是全局单件类,包含了 UE5 VT 系统中大部分核心逻辑和流程,驱动这个系统工作的是 Update 函数,分别在 PC/Console Pipeline 的 FDeferredShadingSceneRenderer::Render 和 Mobile Pipeline 的 FMobileSceneRenderer::Render 中调用.
在 VT 中只有 Diffuse 是远远不够的,在实际的着色过程中经常需要其它的纹理数据来进行光照计算,比如 Normal、Roughness、Specular 等等UE4 的 RVT 使用了 Layer 的概念,每个 Layer 代表不同的 Physical Texture在 UE4 中可以支持底色Diffuse、法线Normal、Roughness粗糙度、高光度Specular、遮罩Mask等不同内容的 VT这些数据以 Pack 的方式保存在多张 Physical Texture 的不同通道中,在使用时通过 Unpack 以及组合的形式解算出来进行光照计算。这些 Physical Texture 的寻址信息保存在同一个 VT 上的 PageTable Texture 的不同颜色通道中,下文会详细描述。
UE4 RVT 中所使用的 GPU 资源有以下 3 种:
- PageTable Buffer 用于在 CPU 端只写的 PageTable 数据。
- PageTable Texture 用于在采样 VT 时获取指定 VT Physical Texture Page 数据,此资源数据不是在 CPU 端填充,而是由 PageTable Buffer 通过 RHICommandList 在 GPU 上填充。
- VT Physical Texture 实际存储纹理数据的资源,通过 VT feedback 从磁盘或运行时动态生成纹理数据,并在 VT 数据准备阶段中更新到 VT Physical Texture 中。
其中 VT Physical Texture 资源包含在 FVirtualTexturePhysicalSpace 类中PageTable Buffer/Texture 包含在 FVirtualTextureSpace 类中。
FVirtualTextureSystem的会提交请求最终会调用**FRuntimeVirtualTextureProducer::ProducePageData()** ,最后会在**FRuntimeVirtualTextureFinalizer::Finalize()** 中 调用 **RuntimeVirtualTexture::RenderPages()** 函数渲染到 VT Physical Texture 上。
## UE5中相关类
- **FVirtualTextureSystem**单例类用于全局控制VT流程。
- URuntimeVirtualTextureUObject
- FRuntimeVirtualTextureRenderResource
- UVirtualTextureUObject
- UVirtualTexture2DUTexture2D
# UE5 VirtualHeightfieldMesh简述
https://zhuanlan.zhihu.com/p/575398476
## 可能的相关类
- VirtualHeightfieldMesh
- UVirtualHeightfieldMeshComponent
- **UHeightfieldMinMaxTexture**
- BuildTexture()
- FVirtualHeightfieldMeshSceneProxy
- FVirtualHeightfieldMeshRendererExtension
- AddWork()
- **SubmitWork()**
- FVirtualTextureFeedbackBuffer 参考[[#Pass1的补充VirtualTextureFeedback]]
- UNiagaraDataInterfaceLandscape
- UNiagaraDataInterfaceVirtualTexture(**NiagaraDataInterfaceVirtualTextureTemplate.ush**)
- GetAttributesValid()
- SampleRVTLayer()
- SampleRVT()
- URuntimeVirtualTextureComponent
## VirtualHeightfieldMesh
首先是MinMaxTexture。全称**UHeightfieldMinMaxTexture**下简称MinMaxTexture可以说是整个VHM中最重要的部分之一。它是离线生成的目的主要是以下几个
1. 用作Instance的剔除遮挡剔除查询Frustum剔除
2. 用作决定VHM的LOD
3. 用作平滑VHM的顶点位置
其中比较关键的几个成员变量为:
- TObjectPtr<class UTexture2D> TextureBGRA8格式、贴图大小与RVT的Tile数量一致、有全部mipmap。每个像素存储RVT一个Tile中的最小值以及最大值各为16bit、encode在RGBA的4个通道上。
- TObjectPtr<class UTexture2D> LodBiasTextureG8格式、贴图大小与RTV的Tile数量一致、无mipmap。每个像素存储了Texture对应像素周围3x3blur之后的结果。
- TObjectPtr<class UTexture2D> LodBiasMinMaxTextureBGRA8格式、贴图大小与RTV的Tile数量一致、有全部mipmap。类似于HZB、每个像素存储LodBiasTexture的最小值以及最大值各为8bit、存在RG两个通道上。
- int32 MaxCPULevels表示共需要在CPU端存储多少层level的数据。
- TArray`<FVector2D>` TextureDataCPU端存储Texture贴图的数据共MaxCPULevels层mipmap。
### TextureData的获取
因此要生成MinMaxTexture、最关键的就是要得到TextureData其入口函数为位于**HeightfieldMinMaxTextureBuilder.cpp**的**VirtualHeightfieldmesh::BuildMinMaxTexture**中。由于Texture存储的是RVT中每个Tile中最小最大值因此不难想象到其大致流程可以分为以下几步
1. 遍历RVT的每个Tile并绘制到一张中间贴图上然后计算这张中间纹理的最小最大值、存储至目标贴图对应的位置上
2. 为目标贴图生成mipmap
3. 将目标贴图回读至CPU、得到TextureData。
将Tile绘制到一张中间贴图使用的是自带的***RuntimeVirtualTexture::RenderPagesStandAlone***函数计算最小最大值是通过Downsample的方式计算而成。如下图所示为2x2Tiles、4TileSize的RVT计算Tile0的最小最大值的示意过程图
![](https://pic1.zhimg.com/80/v2-77747e03d3ed0c82ac4e5fde77481ef8_720w.webp)
Downsample的ComputeShader为**TMinMaxTextureCS**。遍历计算完每个Tile的最小最大值后同样通过Downsample为目标贴图生成全mipmap。
最后为了将贴图回读到CPU先是通过CopyTexture的方式将目标贴图的各个mipmap复制到各自带有CPUReadback Flag的贴图后再通过MapStagingSurface/UnmapStagingSurface的方式复制到CPU内存上。由于是比较常规的操作就不过多介绍了。
至此也就得到了带有所有mipmap的CPU端的TextureData接着将此作为参数调用UHeightfieldMinMaxTexture::BuildTexture以生成剩下的内容即Texture、LodBiasTexture、LodBiasMinMaxTexture
### FVirtualHeightfieldMeshSceneProxy
至此离线生成的MinMaxTexture介绍完毕后面都是实时渲染内容的介绍部分。所有内容都围绕着VHM的SceneProxy也就是**FVirtualHeightfieldMeshSceneProxy**展开。
#### 遮挡剔除
> 关于硬件的遮挡剔除用法可以参考DX12的官方sample[[8]](https://zhuanlan.zhihu.com/p/575398476#ref_8)
首先是遮挡剔除部分VHM做了Tile级别且带有LOD的遮挡剔除。VHM的SceneProxy重写了函数GetOcclusionQueries函数实现只是简单地返回OcclusionVolumes
![](https://pic3.zhimg.com/80/v2-cbd74e7df53d61b693ea76a6e9fcdd52_720w.webp)
OcclusionVolumes的构建在函数BuildOcclusionVolumes中其基本思路为取MinMaxTexture中**CPU端的TextureData**的数据、获得每个Tile的高度最小最大值来创建该Tile的Bounds信息。
可以看到OcclusionVolumes是带有Lod的。当然实际上这里的代码的LodIndex不一定从0开始因为Component中有一项成员变量**NumOcclusionLod**、表示创建多少层mipmap的OcclusionVolumes。另外有一点需要注意的是NumOcclusionLod默认值为0、也就是说VHM的遮挡剔除默认没有开启。
![](https://pic4.zhimg.com/80/v2-07fe400ddc004833a8816ba59c124217_720w.webp)
由于VHM需要在ComputePass中动态地构建Instance绘制的IndirectArgs、因此SceneProxy还重写了函数AcceptOcclusionResults用以获取遮挡剔除的结果。具体是将UE返回的遮挡剔除的结果存在贴图OcclusionTexture上、以便能够作为SRV在后续的Pass中访问
```cpp
void FVirtualHeightfieldMeshSceneProxy::AcceptOcclusionResults(FSceneView const* View, TArray<bool>* Results, int32 ResultsStart, int32 NumResults)
{
// 由于构建IndirectArgs跟SceneProxy不在同一个地方因此用了一个全局变量来保存遮挡剔除的结果
FOcclusionResults& OcclusionResults = GOcclusionResults.Emplace(FOcclusionResultsKey(this, View));
OcclusionResults.TextureSize = OcclusionGridSize;
OcclusionResults.NumTextureMips = NumOcclusionLods;
// 创建贴图并将遮挡剔除结果Copy至贴图上
FRHIResourceCreateInfo CreateInfo(TEXT("VirtualHeightfieldMesh.OcclusionTexture"));
OcclusionResults.OcclusionTexture = RHICreateTexture2D(OcclusionGridSize.X, OcclusionGridSize.Y, PF_G8, NumOcclusionLods, 1, TexCreate_ShaderResource, CreateInfo);
bool const* Src = Results->GetData() + ResultsStart;
FIntPoint Size = OcclusionGridSize;
for (int32 MipIndex = 0; MipIndex < NumOcclusionLods; ++MipIndex)
{
uint32 Stride;
uint8* Dst = (uint8*)RHILockTexture2D(OcclusionResults.OcclusionTexture, MipIndex, RLM_WriteOnly, Stride, false);
for (int Y = 0; Y < Size.Y; ++Y)
{
for (int X = 0; X < Size.X; ++X)
{
Dst[Y * Stride + X] = *(Src++) ? 255 : 0;
}
}
RHIUnlockTexture2D(OcclusionResults.OcclusionTexture, MipIndex, false);
Size.X = FMath::Max(Size.X / 2, 1);
Size.Y = FMath::Max(Size.Y / 2, 1);
}
}
```
### 整体思路
至此就开始真正的VHM的Mesh的数据构建了。为了后续的代码细节能够更加易懂这里再说明一下VHM构建mesh的整体思路假设我们有一个工作队列为QueueBuffer每一项工作就是从QueueBuffer中取出一项工作更准确地说取出一个Quad、对这个Quad判断是否需要进行细化、如果需要细分则将这个Quad细分为4个Quad并塞入QueueBuffer中。
重复这个取出→处理→放回的过程直到QueueBuffer中没有工作为止。示意图如下
![](https://pic3.zhimg.com/80/v2-cc719d48e269b018c4acec257c6f09ea_720w.webp)
### RVT相关代码Pass1CollectQuad
如果不能细分那么就会增加一个Instance、将其Instance的数据写入RWQuadBuffer中。RWQuadBuffer将会用在后续的CullInstance Pass中以真正地构建IndirectArgs
```c++
// 无法继续细分的情况
// 用以后续对RVT进行采样
uint PhysicalAddress = PageTableTexture.Load(int3(Pos, Level));
InterlockAdd(RWQuadInfo.Write, 1, Write);
RWQuadBuffer[Write] = Pack(InitQuadRenderItem(Pos, Level, PhysicalAddress, bCull | bOcclude));
```
> 其中的RWQuadInfo是我编的变量名、实际的代码中并不存在。或者说实际上这里的变量名是RWIndirectArgsBuffer但是并不是前面所说的用以绘制的IndirectArgs。为了不让大家混淆这里改了下变量名
> 另外也能由此看出的是VHM也许曾经想过利用IndirectArgs数组来绘制即DXSample中将符合条件的生成IndirectArgs放进数组中。但是最后改成的是一个IndirectArgs但是Instance的绘制方式
PS. PageTableTexture的类型为**RHITextuire**。相关Shader代码位于**VirtualHeightfieldMesh.usf**
#### Pass1的补充VirtualTextureFeedback
不再继续进行细分后、说明后续就要对该Level的RVT进行采样因此需要处理对应的Feedback信息、让虚幻可以加载对应的Page。shader代码如下图所示
![](https://pic2.zhimg.com/80/v2-49057653549e0465a8ae0ec0049cb731_720w.webp)
c++中则要将这个RWFeedbackBuffer喂给虚幻的函数**SubmitVirtualTextureFeedbackBuffer**
![](https://pic4.zhimg.com/80/v2-d08c53985729b0d03f5ecf1d317cfc87_720w.webp)
### 相关代码段
```c++
FVertexFactoryIntermediates GetVertexFactoryIntermediates(FVertexFactoryInput Input)
{
...
// Sample height from virtual texture.
VTUniform Uniform = VTUniform_Unpack(VHM.VTPackedUniform);
Uniform.vPageBorderSize -= .5f * VHM.PhysicalTextureSize.y; // Half texel offset is used in VT write and in sampling because we want texel locations to match landscape vertices.
VTPageTableUniform PageTableUniform = VTPageTableUniform_Unpack(VHM.VTPackedPageTableUniform0, VHM.VTPackedPageTableUniform1);
VTPageTableResult VTResult0 = TextureLoadVirtualPageTableLevel(VHM.PageTableTexture, PageTableUniform, NormalizedPos, VTADDRESSMODE_CLAMP, VTADDRESSMODE_CLAMP, floor(SampleLevel));
float2 UV0 = VTComputePhysicalUVs(VTResult0, 0, Uniform);
float Height0 = VHM.HeightTexture.SampleLevel(VHM.HeightSampler, UV0, 0);
VTPageTableResult VTResult1 = TextureLoadVirtualPageTableLevel(VHM.PageTableTexture, PageTableUniform, NormalizedPos, VTADDRESSMODE_CLAMP, VTADDRESSMODE_CLAMP, ceil(SampleLevel));
float2 UV1 = VTComputePhysicalUVs(VTResult1, 0, Uniform);
float Height1 = VHM.HeightTexture.SampleLevel(VHM.HeightSampler, UV1, 0);
float Height = lerp(Height0.x, Height1.x, frac(SampleLevel));
...
}
```
渲染线程创建VT的相关逻辑
```c++
void FVirtualHeightfieldMeshSceneProxy::CreateRenderThreadResources()
{
if (RuntimeVirtualTexture != nullptr)
{
if (!bCallbackRegistered)
{
GetRendererModule().AddVirtualTextureProducerDestroyedCallback(RuntimeVirtualTexture->GetProducerHandle(), &OnVirtualTextureDestroyedCB, this);
bCallbackRegistered = true;
}
//URuntimeVirtualTexture* RuntimeVirtualTexture;
if (RuntimeVirtualTexture->GetMaterialType() == ERuntimeVirtualTextureMaterialType::WorldHeight)
{
AllocatedVirtualTexture = RuntimeVirtualTexture->GetAllocatedVirtualTexture();
NumQuadsPerTileSide = RuntimeVirtualTexture->GetTileSize();
if (AllocatedVirtualTexture != nullptr)
{
// Gather vertex factory uniform parameters.
FVirtualHeightfieldMeshVertexFactoryParameters UniformParams;
UniformParams.PageTableTexture = AllocatedVirtualTexture->GetPageTableTexture(0);
UniformParams.HeightTexture = AllocatedVirtualTexture->GetPhysicalTextureSRV(0, false);
UniformParams.HeightSampler = TStaticSamplerState<SF_Bilinear>::GetRHI();
UniformParams.LodBiasTexture = LodBiasTexture ? LodBiasTexture->GetResource()->TextureRHI : GBlackTexture->TextureRHI;
UniformParams.LodBiasSampler = TStaticSamplerState<SF_Point>::GetRHI();
UniformParams.NumQuadsPerTileSide = NumQuadsPerTileSide;
FUintVector4 PackedUniform;
AllocatedVirtualTexture->GetPackedUniform(&PackedUniform, 0);
UniformParams.VTPackedUniform = PackedUniform;
FUintVector4 PackedPageTableUniform[2];
AllocatedVirtualTexture->GetPackedPageTableUniform(PackedPageTableUniform);
UniformParams.VTPackedPageTableUniform0 = PackedPageTableUniform[0];
UniformParams.VTPackedPageTableUniform1 = PackedPageTableUniform[1];
const float PageTableSizeX = AllocatedVirtualTexture->GetWidthInTiles();
const float PageTableSizeY = AllocatedVirtualTexture->GetHeightInTiles();
UniformParams.PageTableSize = FVector4f(PageTableSizeX, PageTableSizeY, 1.f / PageTableSizeX, 1.f / PageTableSizeY);
const float PhysicalTextureSize = AllocatedVirtualTexture->GetPhysicalTextureSize(0);
UniformParams.PhysicalTextureSize = FVector2f(PhysicalTextureSize, 1.f / PhysicalTextureSize);
UniformParams.VirtualHeightfieldToLocal = FMatrix44f(UVToLocal);
UniformParams.VirtualHeightfieldToWorld = FMatrix44f(UVToWorld); // LWC_TODO: Precision loss
UniformParams.MaxLod = AllocatedVirtualTexture->GetMaxLevel();
UniformParams.LodBiasScale = LodBiasScale;
// Create vertex factory.
VertexFactory = new FVirtualHeightfieldMeshVertexFactory(GetScene().GetFeatureLevel(), UniformParams);
VertexFactory->InitResource(FRHICommandListImmediate::Get());
}
}
}
}
```
# RVT生成相关
# RVT相关操作总结
CPU端创建
```c++
```
作为UniformParameter传递到GPU端
```c++
AllocatedVirtualTexture = RuntimeVirtualTexture->GetAllocatedVirtualTexture();
//PageTableTexture、Texture&Sampler
FVirtualHeightfieldMeshVertexFactoryParameters UniformParams;
UniformParams.PageTableTexture = AllocatedVirtualTexture->GetPageTableTexture(0);
UniformParams.HeightTexture = AllocatedVirtualTexture->GetPhysicalTextureSRV(0, false);
UniformParams.HeightSampler = TStaticSamplerState<SF_Bilinear>::GetRHI();
//VTPackedUniform&VTPackedPageTableUniform
FUintVector4 PackedUniform;
AllocatedVirtualTexture->GetPackedUniform(&PackedUniform, 0);
UniformParams.VTPackedUniform = PackedUniform;
FUintVector4 PackedPageTableUniform[2];
AllocatedVirtualTexture->GetPackedPageTableUniform(PackedPageTableUniform);
UniformParams.VTPackedPageTableUniform0 = PackedPageTableUniform[0];
UniformParams.VTPackedPageTableUniform1 = PackedPageTableUniform[1];
//PageTableSize
const float PageTableSizeX = AllocatedVirtualTexture->GetWidthInTiles();
const float PageTableSizeY = AllocatedVirtualTexture->GetHeightInTiles();
UniformParams.PageTableSize = FVector4f(PageTableSizeX, PageTableSizeY, 1.f / PageTableSizeX, 1.f / PageTableSizeY);
//PhysicalTextureSize
const float PhysicalTextureSize = AllocatedVirtualTexture->GetPhysicalTextureSize(0);
UniformParams.PhysicalTextureSize = FVector2f(PhysicalTextureSize, 1.f / PhysicalTextureSize);
//Local <=> World Matrix
UniformParams.VirtualHeightfieldToLocal = FMatrix44f(UVToLocal);
UniformParams.VirtualHeightfieldToWorld = FMatrix44f(UVToWorld); // LWC_TODO: Precision loss
//MaxLod
UniformParams.MaxLod = AllocatedVirtualTexture->GetMaxLevel();
```
GPU端采样
```c++
VTUniform Uniform = VTUniform_Unpack(VHM.VTPackedUniform);
Uniform.vPageBorderSize -= .5f * VHM.PhysicalTextureSize.y; // Half texel offset is used in VT write and in sampling because we want texel locations to match landscape vertices.
VTPageTableUniform PageTableUniform = VTPageTableUniform_Unpack(VHM.VTPackedPageTableUniform0, VHM.VTPackedPageTableUniform1);
VTPageTableResult VTResult0 = TextureLoadVirtualPageTableLevel(VHM.PageTableTexture, PageTableUniform, NormalizedPos, VTADDRESSMODE_CLAMP, VTADDRESSMODE_CLAMP, floor(SampleLevel));
float2 UV0 = VTComputePhysicalUVs(VTResult0, 0, Uniform);
float Height0 = VHM.HeightTexture.SampleLevel(VHM.HeightSampler, UV0, 0);
VTPageTableResult VTResult1 = TextureLoadVirtualPageTableLevel(VHM.PageTableTexture, PageTableUniform, NormalizedPos, VTADDRESSMODE_CLAMP, VTADDRESSMODE_CLAMP, ceil(SampleLevel));
float2 UV1 = VTComputePhysicalUVs(VTResult1, 0, Uniform);
float Height1 = VHM.HeightTexture.SampleLevel(VHM.HeightSampler, UV1, 0);
float Height = lerp(Height0.x, Height1.x, frac(SampleLevel));
```
**NiagaraDataInterfaceVirtualTextureTemplate.ush**中的代码:
```c++
//其他相关VT操作函数位于VirtualTextureCommon.ush
float4 SampleRVTLayer_{ParameterName}(float2 SampleUV, Texture2D InTexture, Texture2D<uint4> InPageTable, uint4 InTextureUniforms)
{
VTPageTableResult PageTable = TextureLoadVirtualPageTableLevel(InPageTable, VTPageTableUniform_Unpack({ParameterName}_PageTableUniforms[0], {ParameterName}_PageTableUniforms[1]), SampleUV, VTADDRESSMODE_CLAMP, VTADDRESSMODE_CLAMP, 0.0f);
return TextureVirtualSample(InTexture, {ParameterName}_SharedSampler, PageTable, 0, VTUniform_Unpack(InTextureUniforms));
}
void SampleRVT_{ParameterName}(in float3 WorldPosition, out bool bInsideVolume, out float3 BaseColor, out float Specular, out float Roughness, out float3 Normal, out float WorldHeight, out float Mask)
{
bInsideVolume = false;
BaseColor = float3(0.0f, 0.0f, 0.0f);
Specular = 0.5f;
Roughness = 0.5f;
Normal = float3(0.0f, 0.0f, 1.0f);
WorldHeight = 0.0f;
Mask = 1.0f;
// Get Sample Location
FLWCVector3 LWCWorldPosition = MakeLWCVector3({ParameterName}_SystemLWCTile, WorldPosition);
FLWCVector3 LWCUVOrigin = MakeLWCVector3({ParameterName}_SystemLWCTile, {ParameterName}_UVUniforms[0].xyz);
float2 SampleUV = VirtualTextureWorldToUV(LWCWorldPosition, LWCUVOrigin, {ParameterName}_UVUniforms[1].xyz, {ParameterName}_UVUniforms[2].xyz);
// Test to see if we are inside the volume, but still take the samples as it will clamp to the edge
bInsideVolume = all(SampleUV >- 0.0f) && all(SampleUV < 1.0f);
// Sample Textures
float4 LayerSample[3];
LayerSample[0] = ({ParameterName}_ValidLayersMask & 0x1) != 0 ? SampleRVTLayer_{ParameterName}(SampleUV, {ParameterName}_VirtualTexture0, {ParameterName}_VirtualTexture0PageTable, {ParameterName}_VirtualTexture0TextureUniforms) : 0;
LayerSample[1] = ({ParameterName}_ValidLayersMask & 0x2) != 0 ? SampleRVTLayer_{ParameterName}(SampleUV, {ParameterName}_VirtualTexture1, {ParameterName}_VirtualTexture1PageTable, {ParameterName}_VirtualTexture1TextureUniforms) : 0;
LayerSample[2] = ({ParameterName}_ValidLayersMask & 0x4) != 0 ? SampleRVTLayer_{ParameterName}(SampleUV, {ParameterName}_VirtualTexture2, {ParameterName}_VirtualTexture2PageTable, {ParameterName}_VirtualTexture2TextureUniforms) : 0;
// Sample Available Attributes
switch ( {ParameterName}_MaterialType )
{
case ERuntimeVirtualTextureMaterialType_BaseColor:
{
BaseColor = LayerSample[0].xyz;
break;
}
case ERuntimeVirtualTextureMaterialType_BaseColor_Normal_Roughness:
{
BaseColor = VirtualTextureUnpackBaseColorSRGB(LayerSample[0]);
Roughness = LayerSample[1].y;
Normal = VirtualTextureUnpackNormalBGR565(LayerSample[1]);
break;
}
case ERuntimeVirtualTextureMaterialType_BaseColor_Normal_DEPRECATED:
case ERuntimeVirtualTextureMaterialType_BaseColor_Normal_Specular:
{
BaseColor = LayerSample[0].xyz;
Specular = LayerSample[1].x;
Roughness = LayerSample[1].y;
Normal = VirtualTextureUnpackNormalBC3BC3(LayerSample[0], LayerSample[1]);
break;
}
case ERuntimeVirtualTextureMaterialType_BaseColor_Normal_Specular_YCoCg:
{
BaseColor = VirtualTextureUnpackBaseColorYCoCg(LayerSample[0]);
Specular = LayerSample[2].x;
Roughness = LayerSample[2].y;
Normal = VirtualTextureUnpackNormalBC5BC1(LayerSample[1], LayerSample[2]);
break;
}
case ERuntimeVirtualTextureMaterialType_BaseColor_Normal_Specular_Mask_YCoCg:
{
BaseColor = VirtualTextureUnpackBaseColorYCoCg(LayerSample[0]);
Specular = LayerSample[2].x;
Roughness = LayerSample[2].y;
Normal = VirtualTextureUnpackNormalBC5BC1(LayerSample[1], LayerSample[2]);
Mask = LayerSample[2].w;
break;
}
case ERuntimeVirtualTextureMaterialType_WorldHeight:
{
WorldHeight = VirtualTextureUnpackHeight(LayerSample[0], {ParameterName}_WorldHeightUnpack);
break;
}
}
}
```
RVT的UV计算逻辑在VirtualTextureWorldToUV()中:
```glsl
float2 VirtualTextureWorldToUV(in float3 WorldPos, in float3 Origin, in float3 U, in float3 V)
{
float3 P = WorldPos - Origin;
return saturate(float2(dot(P, U), dot(P, V)));
}
```
从代码可以看出,根据当前像素的世界空间位置以及 RVT Volume 原点Volume 左下角、Volume 边界大小的 UV 范围(经过世界旋转变换的 XY 轴乘以 Volume 缩放-即 Volume 大小-的倒数,这些计算在 **URuntimeVirtualTexture::Initialize()** 中完成),求出当前像素在 RVT 中的 UV 坐标。
TextureComputeVirtualMipLevel() 函数计算 RVT 的 mipLevel为了实现较好的混合效果这里根据当前帧 Id 生成交错的随机 noise 扰动 level。
```cpp
int TextureComputeVirtualMipLevel(
in out VTPageTableResult OutResult,
float2 dUVdx, float2 dUVdy, float MipBias,
float2 SvPositionXY,
VTPageTableUniform PageTableUniform)
{
OutResult.dUVdx = dUVdx * PageTableUniform.SizeInPages;
OutResult.dUVdy = dUVdy * PageTableUniform.SizeInPages;
// Always compute mip level using MipLevelAniso2D, even if VIRTUAL_TEXTURE_ANISOTROPIC_FILTERING is disabled
// This way the VT mip level selection will come much closer to HW mip selection, even if we're not sampling the texture using anisotropic filtering const float ComputedLevel = MipLevelAniso2D(OutResult.dUVdx, OutResult.dUVdy, PageTableUniform.MaxAnisoLog2);
const float GlobalMipBias = GetGlobalVirtualTextureMipBias();
#if VIRTUAL_TEXTURE_MANUAL_TRILINEAR_FILTERING
const float Noise = 0.f;
#else
const float Noise = GetStochasticMipNoise(SvPositionXY);
#endif
const float MipLevel = ComputedLevel + MipBias + GlobalMipBias + Noise;
const float MipLevelFloor = floor(MipLevel);
OutResult.MipLevelFrac = MipLevel - MipLevelFloor;
return (int)MipLevelFloor + int(PageTableUniform.vPageTableMipBias);
}
```
TextureLoadVirtualPageTableInternal 函数代码如下:
```cpp
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));
uint vPageX = (uint(OutResult.UV.x) + PageTableUniform.XOffsetInPages) >> vLevelClamped;
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));
#if VIRTUAL_TEXTURE_MANUAL_TRILINEAR_FILTERING
// Second page table for trilinear.
const uint vLevelClamped2 = clamp(vLevel + 1, 0, int(PageTableUniform.MaxLevel));
const uint vPageX2 = (uint(OutResult.UV.x) + PageTableUniform.XOffsetInPages) >> vLevelClamped2;
const uint vPageY2 = (uint(OutResult.UV.y) + PageTableUniform.YOffsetInPages) >> vLevelClamped2;
OutResult.PageTableValue[2] = PageTable0.Load(int3(vPageX2, vPageY2, vLevelClamped2)); OutResult.PageTableValue[3] = PageTable1.Load(int3(vPageX2, vPageY2, vLevelClamped2));
// Alternate requests to both mip levels
if ((View.FrameNumber & 1u) == 0u)
{ vLevel += 1; vPageX = vPageX2; vPageY = vPageY2; }#endif
// 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;
}```
这个函数主要 2个作用一是生成用于寻址 VT Physical Texture 的 PageTableValue另一个是生成 feedback Request 数据,具体有以下几个步骤:
1. 根据 UV 寻址模式修正虚拟纹理坐标
2. 根据当前 VT 的 Page 数量和上一步修正过的虚拟纹理坐标计算出 VT 坐标对应的 Page 坐标。
3. 通过 Page 坐标加上 Page 的 XY 偏移,再根据 mipLevel计算出 PageTable Texture 的 UV 坐标,然后使用这个 UV 坐标和 mipLevel 采样 PageTable Texture 得到在 Physical Texture 上的信息,保存在 PageTableValue 中,在接下来的流程中使用。
4. 将第 3 步计算好的 PageTable Texture 的 Page 坐标和 mipLevel 保存在 VTPageTableResult 中,最后通过 StoreVirtualTextureFeedback 函数写入到 VT feedback Buffer 中。
***TextureVirtualSample***
采样所需的 VTPageTableResult 数据准备完毕,在 TextureVirtualSample 函数中就是执行真正的 Physical Texture 采样逻辑,代码如下:
```c++
MaterialFloat4 TextureVirtualSample(
Texture2D Physical, SamplerState PhysicalSampler,
VTPageTableResult PageTableResult, uint LayerIndex,
VTUniform Uniform)
{
const float2 pUV = VTComputePhysicalUVs(PageTableResult, LayerIndex, Uniform);
return Physical.SampleGrad(PhysicalSampler, pUV, PageTableResult.dUVdx, PageTableResult.dUVdy);
}
```
这个函数很简单,只有 2 个函数调用,第一行 VTComputePhysicalUVs 用于生成 Physical Texture UV 坐标,第二行用于执行渐变采样,所以这里重点是如何生成 Physical Texture UV 坐标VTComputePhysicalUVs 函数代码如下:
```cpp
float2 VTComputePhysicalUVs(in out VTPageTableResult PageTableResult, uint LayerIndex, VTUniform Uniform)
{
const uint PackedPageTableValue = PageTableResult.PageTableValue[LayerIndex / 4u][LayerIndex & 3u];
// See packing in PageTableUpdate.usf
const uint vLevel = PackedPageTableValue & 0xf;
const float UVScale = 1.0f / (float)(1 << vLevel);
const float pPageX = (float)((PackedPageTableValue >> 4) & ((1 << Uniform.PageCoordinateBitCount) - 1));
const float pPageY = (float)(PackedPageTableValue >> (4 + Uniform.PageCoordinateBitCount));
const float2 vPageFrac = frac(PageTableResult.UV * UVScale);
const float2 pUV = float2(pPageX, pPageY) * Uniform.pPageSize + (vPageFrac * Uniform.vPageSize + Uniform.vPageBorderSize);
const float ddxyScale = UVScale * Uniform.vPageSize;
PageTableResult.dUVdx *= ddxyScale;
PageTableResult.dUVdy *= ddxyScale;
return pUV;
}
```
VT还存在一个反馈机制具体可以参考[[#Pass1的补充VirtualTextureFeedback]]
```c++
/** GPU fence pool. Contains a fence array that is kept in sync with the FeedbackItems ring buffer. Fences are used to know when a transfer is ready to Map() without stalling. */
/** GPU 栅栏池。其中包含一个与 FeedbackItems 环形缓冲区保持同步的栅栏数组。栅栏用于了解传输何时准备就绪,可在不停滞的情况下进行 Map()。 */
class FFeedbackGPUFencePool* Fences;
```
# 使用RVT实现3D高斯 LOD思路
AI数据侧
1. 确定点云数据是否可以划分成四叉树的数据结构,也就是将一堆点云按照一个**距离阈值** 进行分割,最终形成一个四叉树。
1. 确定是否可以生成金字塔结构贴图直接写入到Mipmap结构里或者生成多张基于2的幕长度贴图。
UE侧
目前已经测试过SVT可以放入到Niagara Texture Sampler中。同时也可以将SVT放到Texture2DArray中。
1. 将3D高斯各种贴图制作成SVT之后塞入Texture2DArray在Niagara中采样。
2. 在Niagara中根据Niagara 粒子ID对SVT进行采样。

View File

@@ -0,0 +1,314 @@
# Yivanlee 添加Pass与GBuffer笔记
### 给BaseScalability.ini 添加渲染质量命令行
```ini
[EffectsQuality@0]
[EffectsQuality@1]
r.ToonDataMaterials=0
[EffectsQuality@2]
[EffectsQuality@3]
[EffectsQuality@Cine]
r.ToonDataMaterials=1
```
### 增加bUsesToonData选项
1. MaterialRelevance.h的FMaterialRelevance
2. HLSLMaterialTranslator.h与HLSLMaterialTranslator.cpp的FHLSLMaterialTranslator类
3. MaterialInterface.cpp的UMaterialInterface::GetRelevance_Internal
4. PrimitiveSceneInfo.cpp的FBatchingSPDI.DrawMesh()
5. SceneCore.h的FStaticMeshBatchRelevance类
### 定义Stat宏
RenderCore.cpp与RenderCore.h里定义ToonDataPass渲染Stat。
```c#
//h
DECLARE_CYCLE_STAT_EXTERN(TEXT("ToonData pass drawing"), STAT_ToonDataPassDrawTime, STATGROUP_SceneRendering, RENDERCORE_API);
//cpp
DEFINE_STAT(STAT_ToonDataPassDrawTime);
```
BasePassRendering.cpp里定义渲染状态宏。
```c#
DECLARE_CYCLE_STAT(TEXT("ToonDataPass"), STAT_CLM_ToonDataPass, STATGROUP_CommandListMarkers);
DECLARE_CYCLE_STAT(TEXT("AfterToonDataPass"), STAT_CLM_AfterToonDataPass, STATGROUP_CommandListMarkers);
```
### 添加渲染用的RT
SceneRenderTargets.h与SceneRenderTargets.cpp
```c++
//h
TRefCountPtr<IPooledRenderTarget> ToonBufferA;
//cpp
FSceneRenderTargets::FSceneRenderTargets(const FViewInfo& View, const FSceneRenderTargets& SnapshotSource)
: LightAccumulation(GRenderTargetPool.MakeSnapshot(SnapshotSource.LightAccumulation))
···
, ToonBufferA(GRenderTargetPool.MakeSnapshot(SnapshotSource.ToonBufferA))
```
修改SetupSceneTextureUniformParameters(),在GBuffer代码段中增加`SceneTextureParameters.ToonBufferATexture = bCanReadGBufferUniforms && EnumHasAnyFlags(SetupMode, ESceneTextureSetupMode::GBufferF) && SceneContext.ToonBufferA ? GetRDG(SceneContext.ToonBufferA) : BlackDefault2D;`
在SceneTextureParameters.h与SceneTextureParameters.cpp中将新增加的RT添加到FSceneTextureParameters中并且在GetSceneTextureParameters中注册RT,并在另一个同名函数中添加`Parameters.ToonBufferATexture = (*SceneTextureUniformBuffer)->ToonBufferATexture;`。
在FSceneTextureUniformParameters中添加`SHADER_PARAMETER_RDG_TEXTURE(Texture2D, ToonBufferATexture)`
### 添加SceneVisibility中的ToonDataPass定义
在SceneVisibility.h中的MarkRelevant()添加
```c#
if (StaticMeshRelevance.bUseToonData)
{
DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::ToonDataPass);
}
```
在ComputeDynamicMeshRelevance()中添加
```c#
if (ViewRelevance.bUsesToonData)
{
PassMask.Set(EMeshPass::ToonDataPass);
View.NumVisibleDynamicMeshElements[EMeshPass::ToonDataPass] += NumElements;
}
```
#### 修改DecodeGBufferData()以及相关函数
- 修改RayTracingDeferredShadingCommon.ush的DecodeGBufferData()
- 修改DeferredShadingCommon.ush中的FGBufferData添加ToonDataA变量并修改DecodeGBufferData()、GetGBufferDataUint()、GetGBufferData()、
- 修改SceneTextureParameters.ush中的ToonData变量声明`Texture2D ToonBufferATexture;`、`#define ToonBufferATextureSampler GlobalPointClampedSampler`以及`GetGBufferDataFromSceneTextures()`;SceneTexturesCommon.ush中的`#define SceneTexturesStruct_ToonBufferATextureSampler SceneTexturesStruct.PointClampSampler`
### 增加ToonDataPass MeshDrawPass已实现增加GBuffer
- 在MeshPassProcessor.h增加ToonDataPass MeshDrawPass定义。
- 在DeferredShadingRenderer.h添加渲染函数声明。
- 在新添加的ToonDataRendering.h与ToonDataRendering.cpp中添加MeshDrawPass声明与定义。
- 在ToonDataPassShader.usf中实现
```
// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
AnisotropyPassShader.usf: Outputs Anisotropy and World Tangent to GBufferF
=============================================================================*/
#include "Common.ush"
#include "/Engine/Generated/Material.ush"
#include "/Engine/Generated/VertexFactory.ush"
#include "DeferredShadingCommon.ush"
struct FToonDataPassVSToPS
{
float4 Position : SV_POSITION;
FVertexFactoryInterpolantsVSToPS Interps;
#if USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS
float3 PixelPositionExcludingWPO : TEXCOORD7;
#endif
};
#if USING_TESSELLATION
struct FAnisotropyPassVSToDS
{
FVertexFactoryInterpolantsVSToDS FactoryInterpolants;
float4 Position : VS_To_DS_Position;
#if USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS
float3 PixelPositionExcludingWPO : TEXCOORD7;
#endif
OPTIONAL_VertexID_VS_To_DS
};
#define FVertexOutput FAnisotropyPassVSToDS
#define VertexFactoryGetInterpolants VertexFactoryGetInterpolantsVSToDS
#else
#define FVertexOutput FToonDataPassVSToPS
#define VertexFactoryGetInterpolants VertexFactoryGetInterpolantsVSToPS
#endif
#if USING_TESSELLATION
#define FPassSpecificVSToDS FAnisotropyPassVSToDS
#define FPassSpecificVSToPS FToonDataPassVSToPS
FAnisotropyPassVSToDS PassInterpolate(FAnisotropyPassVSToDS a, float aInterp, FAnisotropyPassVSToDS b, float bInterp)
{
FAnisotropyPassVSToDS O;
O.FactoryInterpolants = VertexFactoryInterpolate(a.FactoryInterpolants, aInterp, b.FactoryInterpolants, bInterp);
#if USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS
TESSELLATION_INTERPOLATE_MEMBER(PixelPositionExcludingWPO);
#endif
return O;
}
FToonDataPassVSToPS PassFinalizeTessellationOutput(FAnisotropyPassVSToDS Interpolants, float4 WorldPosition, FMaterialTessellationParameters MaterialParameters)
{
FToonDataPassVSToPS O;
O.Interps = VertexFactoryAssignInterpolants(Interpolants.FactoryInterpolants);
O.Position = mul(WorldPosition, ResolvedView.TranslatedWorldToClip);
#if USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS
O.PixelPositionExcludingWPO = Interpolants.PixelPositionExcludingWPO;
#endif
return O;
}
#include "Tessellation.ush"
#endif
/*=============================================================================
* Vertex Shader
*============================================================================*/
void MainVertexShader(
FVertexFactoryInput Input,
OPTIONAL_VertexID
out FVertexOutput Output
#if USE_GLOBAL_CLIP_PLANE && !USING_TESSELLATION
, out float OutGlobalClipPlaneDistance : SV_ClipDistance
#endif
#if INSTANCED_STEREO
, uint InstanceId : SV_InstanceID
#if !MULTI_VIEW
, out float OutClipDistance : SV_ClipDistance1
#else
, out uint ViewportIndex : SV_ViewPortArrayIndex
#endif
#endif
)
{
#if INSTANCED_STEREO
const uint EyeIndex = GetEyeIndex(InstanceId);
ResolvedView = ResolveView(EyeIndex);
#if !MULTI_VIEW
OutClipDistance = 0.0;
#else
ViewportIndex = EyeIndex;
#endif
#else
uint EyeIndex = 0;
ResolvedView = ResolveView();
#endif
FVertexFactoryIntermediates VFIntermediates = GetVertexFactoryIntermediates(Input);
float4 WorldPos = VertexFactoryGetWorldPosition(Input, VFIntermediates);
float4 WorldPositionExcludingWPO = WorldPos;
float3x3 TangentToLocal = VertexFactoryGetTangentToLocal(Input, VFIntermediates);
FMaterialVertexParameters VertexParameters = GetMaterialVertexParameters(Input, VFIntermediates, WorldPos.xyz, TangentToLocal);
// Isolate instructions used for world position offset
// As these cause the optimizer to generate different position calculating instructions in each pass, resulting in self-z-fighting.
// This is only necessary for shaders used in passes that have depth testing enabled.
{
WorldPos.xyz += GetMaterialWorldPositionOffset(VertexParameters);
}
#if USING_TESSELLATION
// Transformation is done in Domain shader when tessellating
Output.Position = WorldPos;
#else
{
float4 RasterizedWorldPosition = VertexFactoryGetRasterizedWorldPosition(Input, VFIntermediates, WorldPos);
#if ODS_CAPTURE
float3 ODS = OffsetODS(RasterizedWorldPosition.xyz, ResolvedView.TranslatedWorldCameraOrigin.xyz, ResolvedView.StereoIPD);
Output.Position = INVARIANT(mul(float4(RasterizedWorldPosition.xyz + ODS, 1.0), ResolvedView.TranslatedWorldToClip));
#else
Output.Position = INVARIANT(mul(RasterizedWorldPosition, ResolvedView.TranslatedWorldToClip));
#endif
}
#if INSTANCED_STEREO && !MULTI_VIEW
BRANCH
if (IsInstancedStereo())
{
// Clip at the center of the screen
OutClipDistance = dot(Output.Position, EyeClipEdge[EyeIndex]);
// Scale to the width of a single eye viewport
Output.Position.x *= 0.5 * ResolvedView.HMDEyePaddingOffset;
// Shift to the eye viewport
Output.Position.x += (EyeOffsetScale[EyeIndex] * Output.Position.w) * (1.0f - 0.5 * ResolvedView.HMDEyePaddingOffset);
}
#elif XBOXONE_BIAS_HACK
// XB1 needs a bias in the opposite direction to fix FORT-40853
// XBOXONE_BIAS_HACK is defined only in a custom node in a particular material
// This should be removed with a future shader compiler update
Output.Position.z -= 0.0001 * Output.Position.w;
#endif
#if USE_GLOBAL_CLIP_PLANE
OutGlobalClipPlaneDistance = dot(ResolvedView.GlobalClippingPlane, float4(WorldPos.xyz - ResolvedView.PreViewTranslation.xyz, 1));
#endif
#endif
#if USING_TESSELLATION
Output.FactoryInterpolants = VertexFactoryGetInterpolants( Input, VFIntermediates, VertexParameters );
#else
Output.Interps = VertexFactoryGetInterpolants(Input, VFIntermediates, VertexParameters);
#endif // #if USING_TESSELLATION
#if INSTANCED_STEREO
#if USING_TESSELLATION
Output.Interps.InterpolantsVSToPS.EyeIndex = EyeIndex;
#else
Output.Interps.EyeIndex = EyeIndex;
#endif
#endif
#if USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS
Output.PixelPositionExcludingWPO = WorldPositionExcludingWPO.xyz;
#endif
OutputVertexID( Output );
}
/*=============================================================================
* Pixel Shader
*============================================================================*/
void MainPixelShader(
in INPUT_POSITION_QUALIFIERS float4 SvPosition : SV_Position,
FVertexFactoryInterpolantsVSToPS Input
#if USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS
, float3 PixelPositionExcludingWPO : TEXCOORD7
#endif
OPTIONAL_IsFrontFace
OPTIONAL_OutDepthConservative
, out float4 ToonBufferA : SV_Target0
#if MATERIALBLENDING_MASKED_USING_COVERAGE
, out uint OutCoverage : SV_Coverage
#endif
)
{
#if INSTANCED_STEREO
ResolvedView = ResolveView(Input.EyeIndex);
#else
ResolvedView = ResolveView();
#endif
// Manual clipping here (alpha-test, etc)
FMaterialPixelParameters MaterialParameters = GetMaterialPixelParameters(Input, SvPosition);
FPixelMaterialInputs PixelMaterialInputs;
#if USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS
float4 ScreenPosition = SvPositionToResolvedScreenPosition(SvPosition);
float3 TranslatedWorldPosition = SvPositionToResolvedTranslatedWorld(SvPosition);
CalcMaterialParametersEx(MaterialParameters, PixelMaterialInputs, SvPosition, ScreenPosition, bIsFrontFace, TranslatedWorldPosition, PixelPositionExcludingWPO);
#else
CalcMaterialParameters(MaterialParameters, PixelMaterialInputs, SvPosition, bIsFrontFace);
#endif
#if OUTPUT_PIXEL_DEPTH_OFFSET
ApplyPixelDepthOffsetToMaterialParameters(MaterialParameters, PixelMaterialInputs, OutDepth);
#endif
#if MATERIALBLENDING_MASKED_USING_COVERAGE
OutCoverage = DiscardMaterialWithPixelCoverage(MaterialParameters, PixelMaterialInputs);
#endif
//float Anisotropy = GetMaterialAnisotropy(PixelMaterialInputs);
//float3 WorldTangent = MaterialParameters.WorldTangent;
ToonBufferA = float4(0.2, 0.1, 0.8, 1.0);
}
```

View File

@@ -0,0 +1,170 @@
## 描边
- RenderToonOutlineToBaseColor
- RenderToonOutlineToSceneColor
相关Shader
- ToonDataPassShader.usf
- ToonOutline.usf
- ToonShadingModel.ush
相关RT
```
//Begin YivanLee's Modify
TRefCountPtr<IPooledRenderTarget> SceneColorCopy;
TRefCountPtr<IPooledRenderTarget> BaseColorCopy;
TRefCountPtr<IPooledRenderTarget> ToonBufferDepth;
TRefCountPtr<IPooledRenderTarget> ToonOutlineTexture;
TRefCountPtr<IPooledRenderTarget> ToonOutlineMaskBlurTexture;
TRefCountPtr<IPooledRenderTarget> ToonIDOutlineTexture;
//ToonDataTexture01 is ToonNormal
TRefCountPtr<IPooledRenderTarget> ToonDataTexture01;
//ToonDataTexture02 is R: ShadowController G: B: A:
TRefCountPtr<IPooledRenderTarget> ToonDataTexture02;
//ToonDataTexture03 is OutlineColorMask and OutlineMask
TRefCountPtr<IPooledRenderTarget> ToonDataTexture03;
//ToonDataTexture04 is IDTexture
TRefCountPtr<IPooledRenderTarget> ToonDataTexture04;
//End YivanLee's Modify
```
### GBuffer
ToonData0 = float4(N * 0.5 + 0.5, 1.0f);//WorldNormal
ToonData1 = GetMaterialToonDataA(MaterialParameters);//Shadow controller
ToonData2 = GetMaterialToonDataB(MaterialParameters);//OutlinleColor,OutlineMask
ToonData3 = GetMaterialToonDataC(MaterialParameters);//IDTexture,OutlineWidth
### BasePass部分
位于FDeferredShadingSceneRenderer::RenderBasePass()最后,
```
if (ShouldRenderToonDataPass())
{
//Begin Recreate ToonData Render targets
SceneContext.ReleaseToonDataTarget();
SceneContext.AllocateToonDataTarget(GraphBuilder.RHICmdList);
SceneContext.ReleaseToonDataGBuffer();
SceneContext.AllocateToonDataGBuffer(GraphBuilder.RHICmdList);
//End Recreate ToonData Render targets
TStaticArray<FRDGTextureRef, MaxSimultaneousRenderTargets> ToonDataPassTextures;
uint32 ToonDataTextureCount = SceneContext.GetToonDataGBufferRenderTargets(GraphBuilder, ToonDataPassTextures);
TArrayView<FRDGTextureRef> ToonDataPassTexturesView = MakeArrayView(ToonDataPassTextures.GetData(), ToonDataTextureCount);
ERenderTargetLoadAction ToonTargetsAction;
if (bEnableParallelBasePasses)//Windows DirectX12
{
ToonTargetsAction = ERenderTargetLoadAction::ELoad;
}
else//Windows DirectX11
{
ToonTargetsAction = ERenderTargetLoadAction::EClear;
}
FRenderTargetBindingSlots ToonDataPassRenderTargets = GetRenderTargetBindings(ToonTargetsAction, ToonDataPassTexturesView);
ToonDataPassRenderTargets.DepthStencil = FDepthStencilBinding(SceneDepthTexture, ERenderTargetLoadAction::ELoad, ERenderTargetLoadAction::ELoad, ExclusiveDepthStencil);
ToonDataPassRenderTargets.ShadingRateTexture = GVRSImageManager.GetVariableRateShadingImage(GraphBuilder, ViewFamily, nullptr, EVRSType::None);
AddSetCurrentStatPass(GraphBuilder, GET_STATID(STAT_CLM_ToonDataPass));
RenderToonDataPass(GraphBuilder, ToonDataPassTextures, ToonDataTextureCount, ToonDataPassRenderTargets, bEnableParallelBasePasses);
AddSetCurrentStatPass(GraphBuilder, GET_STATID(STAT_CLM_AfterToonDataPass));
RenderToonOutlineToBaseColor(GraphBuilder, SceneDepthTexture, bEnableParallelBasePasses);
}
```
#### RenderNormalDepthOutline
- ToonOutlineMain:使用拉普拉斯算子与Sobel算子计算并混合结果。计算Depth与Normal最后Length(float4(Normal,Depth));
- ToonIDOutlinePSMain:使用Sobel计算描边。
#### RenderToonIDOutline
#### CombineOutlineToBaseColor
### 渲染管线Render()
RenderToonOutlineToSceneColor()位于RenderLights()之后与RenderDeferredReflectionsAndSkyLighting()之前的位置。
## ShaderModel
### DefaultLitBxDF
```c#
Lighting.Diffuse = AreaLight.FalloffColor * (Falloff * NoL) * Diffuse_Lambert( GBuffer.DiffuseColor );
Lighting.Specular = AreaLight.FalloffColor * (Falloff * NoL) * SpecularGGX(GBuffer.Roughness, GBuffer.SpecularColor, Context, NoL, AreaLight);
```
### Toon
- ToonDataAR ShadowOffset GBA 未使用
- ToonDataBRGB OutlineColor A OutlineMask
- ToonDataCRGB IDMap A OutlineWidth
- PreIntegratedToonBRDF: R 为NoL 预积分Ramp G 为GGX高光预积分值。
- PreIntegratedToonSkinBRDFRGB为皮肤预积分颜色。
- SubsurfaceColor该数据存放在CustomData.rgb位置,在天光计算中其作用
PS.ToonShadingStandard没有使用SubsurfaceColor。
#### ToonShadingStandard
在原始的PBR公式做了以下修改
固有色部分
1. 使用ShadowOffset(ToonDataA.r)来控制阴影区域的偏移也就是类似UTS的Step。但使用lerp(Context.NoL, 1.0, ShadowOffset),这导致偏移并不易于控制。
2. 计算FallOffMask预积分衰减调整系数。使用ShadowOffset过的NoL与Metalic作为UV对PreIntegratedToonBRDF图进行查表返回r值。
3. Lighting.Diffuse = AreaLight.FalloffColor * FallOffMask * GBuffer.BaseColor / 3.1415927f;
高光部分
1. D预积分GGX使用NoH与Roughness作为UV对PreIntegratedToonBRDF进行查表返回g值。
2. F边缘光效果系数return smoothstep(0.67, 1.0, 1 - NoV);
3. Lighting.Specular = (F + D) * (AreaLight.FalloffColor * GBuffer.SpecularColor * FallOffMask * 8);
```c++
float ShadowOffset = GBuffer.ToonDataA.r;
float FallOffMask = Falloff * GetPreintegratedToonBRDF(lerp(Context.NoL, 1.0, ShadowOffset), GBuffer.Metallic);
Lighting.Diffuse = AreaLight.FalloffColor * FallOffMask * GBuffer.BaseColor / 3.1415927f;
float R2 = GBuffer.Roughness * GBuffer.Roughness;
float ToonGGX = GetPreintegratedToonSpecBRDF(Context.NoH, GBuffer.Roughness);
float D = lerp(ToonGGX, 0.0, R2);
float3 F = GetToonF(Context.NoV);
Lighting.Specular = (F + D) * (AreaLight.FalloffColor * GBuffer.SpecularColor * FallOffMask * 8);
```
#### ToonShadingSkin
在ToonShadingStandard的基础上做了以下修改
固有色部分:
1. 使用ShadowOffset来偏移Context.NoL * Shadow.SurfaceShadow来获得ShadowMask。
2. 使用ShadowMask与Opacity作为UV来查询PreIntegratedToonSkinBRDF返回rgb值。
3. Lighting.Diffuse = AreaLight.FalloffColor * FallOffMask * GBuffer.BaseColor / 3.1415927f * PreintegratedBRDF;
高光部分相同。
#### ToonShadingHair
在ToonShadingStandard的基础上做了以下修改
固有色部分相同。
高光部分增加各向异性计算:
```c++
float3 H = normalize(L + V);
float HoL = dot(H, geotangent);
float sinTH = saturate(sqrt(1 - HoL * HoL));
float spec = pow(sinTH, lerp(256, 4, GBuffer.Roughness));
float R2 = GBuffer.Roughness * GBuffer.Roughness;
float3 F = GetToonF(Context.NoV);
spec += F;
Lighting.Specular = AreaLight.FalloffColor * FallOffMask * spec * GBuffer.BaseColor;
```
#### 天光(环境光)
阴影部分的光照主要为环境光逻辑为于ReflectionEnvironmentSkyLighting()
```
/*BeginYivanLee's Modify*/
float3 SkyLighting = float3(0.0, 0.0, 0.0);
BRANCH
if(ShadingModelID == SHADINGMODELID_TOONSTANDARD || ShadingModelID == SHADINGMODELID_TOONHAIR || ShadingModelID == SHADINGMODELID_TOONSKIN)
{
float3 SubsurfaceColor = ExtractSubsurfaceColor(GBuffer);
float3 SkyToonLighting = GBuffer.BaseColor * SubsurfaceColor.rgb;
float3 SkyDiffuseLighting = SkyLightDiffuse(GBuffer, AmbientOcclusion, BufferUV, ScreenPosition, BentNormal, DiffuseColor) * CloudAmbientOcclusion;
SkyLighting = lerp(SkyDiffuseLighting, SkyToonLighting, 0.8f);
}
else
{
SkyLighting = SkyLightDiffuse(GBuffer, AmbientOcclusion, BufferUV, ScreenPosition, BentNormal, DiffuseColor) * CloudAmbientOcclusion;
}
/*EndYivanLee's Modify*/
```

View File

@@ -0,0 +1,9 @@
---
title: 剖析虚幻渲染体系09- 材质体系
date: 2024-02-04 21:44:37
excerpt:
tags:
rating: ⭐
---
# 前言
https://www.cnblogs.com/timlly/p/15109132.html

View File

@@ -0,0 +1,215 @@
---
title: 参考SubsurfaceProfile 模改ToonDataAsset
date: 2023-02-04 20:38:39
excerpt:
tags:
rating: ⭐⭐
---
# 原理
## PreShader
在材质编译期,将名为 **__SubsurfaceProfile** 的Uniform表达式塞入材质中。
```c++
int32 FHLSLMaterialTranslator::NumericParameter(EMaterialParameterType ParameterType, FName ParameterName, const UE::Shader::FValue& InDefaultValue)
{
const UE::Shader::EValueType ValueType = GetShaderValueType(ParameterType);
check(InDefaultValue.GetType() == ValueType);
UE::Shader::FValue DefaultValue(InDefaultValue);
// If we're compiling a function, give the function a chance to override the default parameter value
FMaterialParameterMetadata Meta;
if (GetParameterOverrideValueForCurrentFunction(ParameterType, ParameterName, Meta))
{
DefaultValue = Meta.Value.AsShaderValue();
check(DefaultValue.GetType() == ValueType);
}
const uint32* PrevDefaultOffset = DefaultUniformValues.Find(DefaultValue);
uint32 DefaultOffset;
if (PrevDefaultOffset)
{
DefaultOffset = *PrevDefaultOffset;
}
else
{
DefaultOffset = MaterialCompilationOutput.UniformExpressionSet.AddDefaultParameterValue(DefaultValue);
DefaultUniformValues.Add(DefaultValue, DefaultOffset);
}
FMaterialParameterInfo ParameterInfo = GetParameterAssociationInfo();
ParameterInfo.Name = ParameterName;
const int32 ParameterIndex = MaterialCompilationOutput.UniformExpressionSet.FindOrAddNumericParameter(ParameterType, ParameterInfo, DefaultOffset);
return AddUniformExpression(new FMaterialUniformExpressionNumericParameter(ParameterInfo, ParameterIndex), GetMaterialValueType(ParameterType), TEXT(""));
}
```
`const int32 ParameterIndex = MaterialCompilationOutput.UniformExpressionSet.FindOrAddNumericParameter(ParameterType, ParameterInfo, DefaultOffset);`
`return AddUniformExpression(new FMaterialUniformExpressionNumericParameter(ParameterInfo, ParameterIndex), GetMaterialValueType(ParameterType), TEXT(""));`
之后在`Chunk[MP_SubsurfaceColor] = AppendVector(SubsurfaceColor, CodeSubsurfaceProfile);`将结果编译成`MaterialFloat4(MaterialFloat3(1.00000000,1.00000000,1.00000000),Material.PreshaderBuffer[2].x)`
## 填充PreShader结构体
1. 从MeshDraw框架的FMeshElementCollector::AddMesh()开始,执行`MeshBatch.MaterialRenderProxy->UpdateUniformExpressionCacheIfNeeded(Views[ViewIndex]->GetFeatureLevel());`开始更新材质的UniformExpression。
2. `FMaterialRenderProxy::UpdateUniformExpressionCacheIfNeeded()`:取得材质指针之后评估材质表达式。
3. `FMaterialRenderProxy::EvaluateUniformExpressions()`从渲染线程取得材质的ShaderMap再从ShaderMap取得UniformExpressionSet。
4. `FUniformExpressionSet::FillUniformBuffer`Dump preshader results into buffer.
1. FEmitContext::EmitPreshaderOrConstantPreshaderHeader = &UniformExpressionSet.UniformPreshaders.AddDefaulted_GetRef();
# 将ToonData的ID塞入材质
存在问题如何将SubsurfaceProfile Asset的ID塞入材质中
```c++
int32 FMaterialCompiler::ScalarParameter(FName ParameterName, float DefaultValue)
{
return NumericParameter(EMaterialParameterType::Scalar, ParameterName, DefaultValue);
}
int32 FHLSLMaterialTranslator::NumericParameter(EMaterialParameterType ParameterType, FName ParameterName, const UE::Shader::FValue& InDefaultValue)
{
const UE::Shader::EValueType ValueType = GetShaderValueType(ParameterType);
check(InDefaultValue.GetType() == ValueType);
UE::Shader::FValue DefaultValue(InDefaultValue);
// If we're compiling a function, give the function a chance to override the default parameter value
FMaterialParameterMetadata Meta;
if (GetParameterOverrideValueForCurrentFunction(ParameterType, ParameterName, Meta))
{ DefaultValue = Meta.Value.AsShaderValue();
check(DefaultValue.GetType() == ValueType);
}
const uint32* PrevDefaultOffset = DefaultUniformValues.Find(DefaultValue);
uint32 DefaultOffset;
if (PrevDefaultOffset)
{
DefaultOffset = *PrevDefaultOffset;
}else
{
DefaultOffset = MaterialCompilationOutput.UniformExpressionSet.AddDefaultParameterValue(DefaultValue);
DefaultUniformValues.Add(DefaultValue, DefaultOffset);
}
FMaterialParameterInfo ParameterInfo = GetParameterAssociationInfo();
ParameterInfo.Name = ParameterName;
const int32 ParameterIndex = MaterialCompilationOutput.UniformExpressionSet.FindOrAddNumericParameter(ParameterType, ParameterInfo, DefaultOffset);
return AddUniformExpression(new FMaterialUniformExpressionNumericParameter(ParameterInfo, ParameterIndex), GetMaterialValueType(ParameterType), TEXT(""));
}
bool FMaterialHLSLGenerator::GetParameterOverrideValueForCurrentFunction(EMaterialParameterType ParameterType, FName ParameterName, FMaterialParameterMetadata& OutResult) const
{
bool bResult = false;
if (!ParameterName.IsNone())
{ // Give every function in the callstack on opportunity to override the parameter value
// Parameters in outer functions take priority // For example, if a layer instance calls a function instance that includes an overriden parameter, we want to use the value from the layer instance rather than the function instance for (const FFunctionCallEntry* FunctionEntry : FunctionCallStack)
{ const UMaterialFunctionInterface* CurrentFunction = FunctionEntry->MaterialFunction;
if (CurrentFunction)
{
if (CurrentFunction->GetParameterOverrideValue(ParameterType, ParameterName, OutResult))
{ bResult = true;
break;
}
}
}
}
return bResult;
}
// Finds a parameter by name from the game thread, traversing the chain up to the BaseMaterial.
FScalarParameterValue* GameThread_GetScalarParameterValue(UMaterialInstance* MaterialInstance, FName Name)
{
UMaterialInterface* It = 0;
FMaterialParameterInfo ParameterInfo(Name); // @TODO: This will only work for non-layered parameters
while(MaterialInstance)
{
if(FScalarParameterValue* Ret = GameThread_FindParameterByName(MaterialInstance->ScalarParameterValues, ParameterInfo))
{
return Ret;
}
It = MaterialInstance->Parent;
MaterialInstance = Cast<UMaterialInstance>(It);
}
return 0;
}
template <typename ParameterType>
ParameterType* GameThread_FindParameterByName(TArray<ParameterType>& Parameters, const FHashedMaterialParameterInfo& ParameterInfo)
{
for (int32 ParameterIndex = 0; ParameterIndex < Parameters.Num(); ParameterIndex++)
{
ParameterType* Parameter = &Parameters[ParameterIndex];
if (Parameter->ParameterInfo == ParameterInfo)
{
return Parameter;
}
}
return NULL;
}
void UMaterialFunctionInstance::OverrideMaterialInstanceParameterValues(UMaterialInstance* Instance)
{
// Dynamic parameters
Instance->ScalarParameterValues = ScalarParameterValues;
Instance->VectorParameterValues = VectorParameterValues;
Instance->DoubleVectorParameterValues = DoubleVectorParameterValues;
Instance->TextureParameterValues = TextureParameterValues;
Instance->RuntimeVirtualTextureParameterValues = RuntimeVirtualTextureParameterValues;
Instance->FontParameterValues = FontParameterValues;
// Static parameters
FStaticParameterSet StaticParametersOverride = Instance->GetStaticParameters();
StaticParametersOverride.EditorOnly.StaticSwitchParameters = StaticSwitchParameterValues;
StaticParametersOverride.EditorOnly.StaticComponentMaskParameters = StaticComponentMaskParameterValues;
Instance->UpdateStaticPermutation(StaticParametersOverride);
}
```
和UMaterialExpressionStrataLegacyConversion::Compile()无关。
GetSubsurfaceProfileParameterName()
__SubsurfaceProfile
1. 在FHLSLMaterialTranslator::Translate() => NumericParameter()会往MaterialCompilationOutput.UniformExpressionSet以及DefaultUniformValues添加默认值以及Offset。最终会调用AddUniformExpression()
## MaterialRenderProxy
```c++
void SetSubsurfaceProfileRT(const USubsurfaceProfile* Ptr) { SubsurfaceProfileRT = Ptr; }
const USubsurfaceProfile* GetSubsurfaceProfileRT() const { return SubsurfaceProfileRT; }
/** 0 if not set, game thread pointer, do not dereference, only for comparison */
const USubsurfaceProfile* SubsurfaceProfileRT;
```
## UMaterialInterface
```c++
uint8 bOverrideSubsurfaceProfile:1;//UE5转移至UMaterialInstance中
TObjectPtr<class USubsurfaceProfile> SubsurfaceProfile;
void UMaterialInterface::UpdateMaterialRenderProxy(FMaterialRenderProxy& Proxy)
//还有所有子类实现 UMaterialInstance、UMaterial
USubsurfaceProfile* UMaterialInterface::GetSubsurfaceProfile_Internal() const
```
## MaterialShared.h
```c++
inline bool UseSubsurfaceProfile(FMaterialShadingModelField ShadingModel)
{
return ShadingModel.HasShadingModel(MSM_SubsurfaceProfile) || ShadingModel.HasShadingModel(MSM_Eye);
}
```
## UMaterial
```c++
USubsurfaceProfile* UMaterial::GetSubsurfaceProfile_Internal() const
{
checkSlow(IsInGameThread());
return SubsurfaceProfile;
}
```
## UMaterialInstance
```c++
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = MaterialInstance)
uint8 bOverrideSubsurfaceProfile:1;
```

View File

@@ -0,0 +1,737 @@
---
title: Untitled
date: 2024-02-04 12:57:56
excerpt:
tags:
rating: ⭐
---
# 前言
原文地址:https://www.cnblogs.com/timlly/p/15156626.html
# 概念
- FRenderResource是渲染线程的渲染资源基础父类实际的数据和逻辑由子类实现。可以认为是渲染线程中承载**CPU相关相关渲染的载体**。
- 比如输入的顶点数据、顶点Index数据、贴图数据等。
- FRHIResource抽象了GPU侧的资源也是众多RHI资源类型的父类。可以认为是承载**显卡API相关资源的载体**。
- 比如TextureSampler、TextureObject等。
- FRHICommand其父类为**FRHICommandBase**结构体。其含有**FRHICommandBase* Next**用来保存下一个Command的指针所以存储他的结构为**链表**。
- 含有接口void void ExecuteAndDestruct(FRHICommandListBase& CmdList, FRHICommandListDebugContext& DebugContext)。执行完就销毁。
- UE使用**FRHICOMMAND_MACRO**宏来快速定义各种RHICommand。主要功能包含
- 数据和资源的设置、更新、清理、转换、拷贝、回读。
- 图元绘制。
- Pass、SubPass、场景、ViewPort等的开始和结束事件。
- 栅栏、等待、广播接口。
- 光线追踪。
- Slate、调试相关的命令。
- FRHICommandList是**RHI的指令队列**用来管理、执行一组FRHICommand的对象。
- 其子类有**FRHICommandListImmediate**立即执行队列、FRHIComputeCommandList_RecursiveHazardous与TRHIComputeCommandList_RecursiveHazardous命令列表的递归使用
- IRHICommandContext是RHI的命令上下文接口类定义了一组图形API相关的操作。在可以并行处理命令列表的平台上它是一个单独的对象类。
- 主要的接口函数有:
- 派发ComputeShader
- 渲染查询(可见性?)
- 相关开始/结束函数。
- 设置数据Viewport、GraphicsPipelineState等
- 设置ShadserParameter
- 绘制图元
- 纹理拷贝/更新
- Raytracing
- IRHICommandContext的接口和FRHICommandList的接口高度相似且重叠。IRHICommandContext还有许多子类
- IRHICommandContextPSOFallback不支持真正的图形管道的RHI命令上下文。
- FNullDynamicRHI空实现的动态绑定RHI。
- FOpenGLDynamicRHIOpenGL的动态RHI。
- FD3D11DynamicRHID3D11的动态RHI。
- FMetalRHICommandContextMetal平台的命令上下文。
- FD3D12CommandContextBaseD3D12的命令上下文。
- FVulkanCommandListContextVulkan平台的命令队列上下文。
- FEmptyDynamicRHI动态绑定的RHI实现的接口。
- FValidationContext校验上下文。
- IRHICommandContextContainerIRHICommandContextContainer就是包含了IRHICommandContext对象的类型。相当于存储了一个或一组命令上下文的容器以支持并行化地提交命令队列只在D3D12、Metal、Vulkan等现代图形API中有实现。
- D3D12存储了FD3D12Adapter* Adapter、FD3D12CommandContext* CmdContext、 FD3D12CommandContextRedirector* CmdContextRedirector。
- FDynamicRHIFDynamicRHI是由动态绑定的RHI实现的接口它定义的接口和CommandList、CommandContext比较相似。
- 代码详见[[#FDynamicRHI]]
- FRHICommandListExecutor负责将**Renderer层的RHI中间指令转译或直接调用到目标平台的图形API**它在RHI体系中起着举足轻重的作用。
- FParallelCommandListSet用于实现并行渲染。使用案例详见[[#FParallelCommandListSet]]。目前5.3只有下面2个子类
- FRDGParallelCommandListSet
- FShadowParallelCommandListSet
## FDynamicRHI
```c++
class RHI_API FDynamicRHI
{
public:
virtual ~FDynamicRHI() {}
virtual void Init() = 0;
virtual void PostInit() {}
virtual void Shutdown() = 0;
void InitPixelFormatInfo(const TArray<uint32>& PixelFormatBlockBytesIn);
// ---- RHI接口 ----
// 下列接口要求FlushType: Thread safe
virtual FSamplerStateRHIRef RHICreateSamplerState(const FSamplerStateInitializerRHI& Initializer) = 0;
virtual FRasterizerStateRHIRef RHICreateRasterizerState(const FRasterizerStateInitializerRHI& Initializer) = 0;
virtual FDepthStencilStateRHIRef RHICreateDepthStencilState(const FDepthStencilStateInitializerRHI& Initializer) = 0;
virtual FBlendStateRHIRef RHICreateBlendState(const FBlendStateInitializerRHI& Initializer) = 0;
// 下列接口要求FlushType: Wait RHI Thread
virtual FVertexDeclarationRHIRef RHICreateVertexDeclaration(const FVertexDeclarationElementList& Elements) = 0;
virtual FPixelShaderRHIRef RHICreatePixelShader(TArrayView<const uint8> Code, const FSHAHash& Hash) = 0;
virtual FVertexShaderRHIRef RHICreateVertexShader(TArrayView<const uint8> Code, const FSHAHash& Hash) = 0;
virtual FHullShaderRHIRef RHICreateHullShader(TArrayView<const uint8> Code, const FSHAHash& Hash) = 0;
virtual FDomainShaderRHIRef RHICreateDomainShader(TArrayView<const uint8> Code, const FSHAHash& Hash) = 0;
virtual FGeometryShaderRHIRef RHICreateGeometryShader(TArrayView<const uint8> Code, const FSHAHash& Hash) = 0;
virtual FComputeShaderRHIRef RHICreateComputeShader(TArrayView<const uint8> Code, const FSHAHash& Hash) = 0;
// FlushType: Must be Thread-Safe.
virtual FRenderQueryPoolRHIRef RHICreateRenderQueryPool(ERenderQueryType QueryType, uint32 NumQueries = UINT32_MAX);
inline FComputeFenceRHIRef RHICreateComputeFence(const FName& Name);
virtual FGPUFenceRHIRef RHICreateGPUFence(const FName &Name);
virtual void RHICreateTransition(FRHITransition* Transition, ERHIPipeline SrcPipelines, ERHIPipeline DstPipelines, ERHICreateTransitionFlags CreateFlags, TArrayView<const FRHITransitionInfo> Infos);
virtual void RHIReleaseTransition(FRHITransition* Transition);
// FlushType: Thread safe.
virtual FStagingBufferRHIRef RHICreateStagingBuffer();
virtual void* RHILockStagingBuffer(FRHIStagingBuffer* StagingBuffer, FRHIGPUFence* Fence, uint32 Offset, uint32 SizeRHI);
virtual void RHIUnlockStagingBuffer(FRHIStagingBuffer* StagingBuffer);
// FlushType: Thread safe, but varies depending on the RHI
virtual FBoundShaderStateRHIRef RHICreateBoundShaderState(FRHIVertexDeclaration* VertexDeclaration, FRHIVertexShader* VertexShader, FRHIHullShader* HullShader, FRHIDomainShader* DomainShader, FRHIPixelShader* PixelShader, FRHIGeometryShader* GeometryShader) = 0;
// FlushType: Thread safe
virtual FGraphicsPipelineStateRHIRef RHICreateGraphicsPipelineState(const FGraphicsPipelineStateInitializer& Initializer);
// FlushType: Thread safe, but varies depending on the RHI
virtual FUniformBufferRHIRef RHICreateUniformBuffer(const void* Contents, const FRHIUniformBufferLayout& Layout, EUniformBufferUsage Usage, EUniformBufferValidation Validation) = 0;
virtual void RHIUpdateUniformBuffer(FRHIUniformBuffer* UniformBufferRHI, const void* Contents) = 0;
// FlushType: Wait RHI Thread
virtual FIndexBufferRHIRef RHICreateIndexBuffer(uint32 Stride, uint32 Size, uint32 InUsage, ERHIAccess InResourceState, FRHIResourceCreateInfo& CreateInfo) = 0;
virtual void* RHILockIndexBuffer(FRHICommandListImmediate& RHICmdList, FRHIIndexBuffer* IndexBuffer, uint32 Offset, uint32 Size, EResourceLockMode LockMode);
virtual void RHIUnlockIndexBuffer(FRHICommandListImmediate& RHICmdList, FRHIIndexBuffer* IndexBuffer);
virtual void RHITransferIndexBufferUnderlyingResource(FRHIIndexBuffer* DestIndexBuffer, FRHIIndexBuffer* SrcIndexBuffer);
// FlushType: Wait RHI Thread
virtual FVertexBufferRHIRef RHICreateVertexBuffer(uint32 Size, uint32 InUsage, ERHIAccess InResourceState, FRHIResourceCreateInfo& CreateInfo) = 0;
// FlushType: Flush RHI Thread
virtual void* RHILockVertexBuffer(FRHICommandListImmediate& RHICmdList, FRHIVertexBuffer* VertexBuffer, uint32 Offset, uint32 SizeRHI, EResourceLockMode LockMode);
virtual void RHIUnlockVertexBuffer(FRHICommandListImmediate& RHICmdList, FRHIVertexBuffer* VertexBuffer);
// FlushType: Flush Immediate (seems dangerous)
virtual void RHICopyVertexBuffer(FRHIVertexBuffer* SourceBuffer, FRHIVertexBuffer* DestBuffer) = 0;
virtual void RHITransferVertexBufferUnderlyingResource(FRHIVertexBuffer* DestVertexBuffer, FRHIVertexBuffer* SrcVertexBuffer);
// FlushType: Wait RHI Thread
virtual FStructuredBufferRHIRef RHICreateStructuredBuffer(uint32 Stride, uint32 Size, uint32 InUsage, ERHIAccess InResourceState, FRHIResourceCreateInfo& CreateInfo) = 0;
// FlushType: Flush RHI Thread
virtual void* RHILockStructuredBuffer(FRHICommandListImmediate& RHICmdList, FRHIStructuredBuffer* StructuredBuffer, uint32 Offset, uint32 SizeRHI, EResourceLockMode LockMode);
virtual void RHIUnlockStructuredBuffer(FRHICommandListImmediate& RHICmdList, FRHIStructuredBuffer* StructuredBuffer);
// FlushType: Wait RHI Thread
virtual FUnorderedAccessViewRHIRef RHICreateUnorderedAccessView(FRHIStructuredBuffer* StructuredBuffer, bool bUseUAVCounter, bool bAppendBuffer) = 0;
// FlushType: Wait RHI Thread
virtual FUnorderedAccessViewRHIRef RHICreateUnorderedAccessView(FRHITexture* Texture, uint32 MipLevel) = 0;
// FlushType: Wait RHI Thread
virtual FUnorderedAccessViewRHIRef RHICreateUnorderedAccessView(FRHITexture* Texture, uint32 MipLevel, uint8 Format);
(......)
// RHI帧更新须从主线程调用FlushType: Thread safe
virtual void RHITick(float DeltaTime) = 0;
// 阻塞CPU直到GPU执行完成变成空闲. FlushType: Flush Immediate (seems wrong)
virtual void RHIBlockUntilGPUIdle() = 0;
// 开始当前帧并确保GPU正在积极地工作 FlushType: Flush Immediate (copied from RHIBlockUntilGPUIdle)
virtual void RHISubmitCommandsAndFlushGPU() {};
// 通知RHI准备暂停它.
virtual void RHIBeginSuspendRendering() {};
// 暂停RHI渲染并将控制权交给系统的操作, FlushType: Thread safe
virtual void RHISuspendRendering() {};
// 继续RHI渲染, FlushType: Thread safe
virtual void RHIResumeRendering() {};
// FlushType: Flush Immediate
virtual bool RHIIsRenderingSuspended() { return false; };
// FlushType: called from render thread when RHI thread is flushed
// 仅在FRHIResource::FlushPendingDeletes内的延迟删除之前每帧调用.
virtual void RHIPerFrameRHIFlushComplete();
// 执行命令队列, FlushType: Wait RHI Thread
virtual void RHIExecuteCommandList(FRHICommandList* CmdList) = 0;
// FlushType: Flush RHI Thread
virtual void* RHIGetNativeDevice() = 0;
// FlushType: Flush RHI Thread
virtual void* RHIGetNativeInstance() = 0;
// 获取命令上下文. FlushType: Thread safe
virtual IRHICommandContext* RHIGetDefaultContext() = 0;
// 获取计算上下文. FlushType: Thread safe
virtual IRHIComputeContext* RHIGetDefaultAsyncComputeContext();
// FlushType: Thread safe
virtual class IRHICommandContextContainer* RHIGetCommandContextContainer(int32 Index, int32 Num) = 0;
// 直接由渲染线程调用的接口, 以优化RHI调用.
virtual FVertexBufferRHIRef CreateAndLockVertexBuffer_RenderThread(class FRHICommandListImmediate& RHICmdList, uint32 Size, uint32 InUsage, ERHIAccess InResourceState, FRHIResourceCreateInfo& CreateInfo, void*& OutDataBuffer);
virtual FIndexBufferRHIRef CreateAndLockIndexBuffer_RenderThread(class FRHICommandListImmediate& RHICmdList, uint32 Stride, uint32 Size, uint32 InUsage, ERHIAccess InResourceState, FRHIResourceCreateInfo& CreateInfo, void*& OutDataBuffer);
(......)
// Buffer Lock/Unlock
virtual void* LockVertexBuffer_BottomOfPipe(class FRHICommandListImmediate& RHICmdList, ...);
virtual void* LockIndexBuffer_BottomOfPipe(class FRHICommandListImmediate& RHICmdList, ...);
(......)
};
```
以上只显示了部分接口,其中部分接口要求从渲染线程调用,部分须从游戏线程调用。大多数接口在被调用前需刷新指定类型的命令,比如:
```c++
class RHI_API FDynamicRHI
{
// FlushType: Wait RHI Thread
void RHIExecuteCommandList(FRHICommandList* CmdList);
// FlushType: Flush Immediate
void RHIBlockUntilGPUIdle();
// FlushType: Thread safe
void RHITick(float DeltaTime);
};
```
可以在**FRHICommandListImmediate**的**ExecuteCommandList()**、**BlockUntilGPUIdle()**、**Tick()** 看到调用。
>需要注意的是传统图形APID3D11、OpenGL除了继承FDynamicRHI还需要继承**IRHICommandContextPSOFallback**因为需要借助后者的接口处理PSO的数据和行为以保证传统和现代API对PSO的一致处理行为。也正因为此现代图形APID3D12、Vulkan、Metal不需要继承**IRHICommandContext**的任何继承体系的类型,单单直接继承**FDynamicRHI**就可以处理RHI层的所有数据和操作。
既然现代图形APID3D12、Vulkan、Metal的**DynamicRHI**没有继承**IRHICommandContext**的任何继承体系的类型那么它们是如何实现FDynamicRHI::RHIGetDefaultContext的接口下面以FD3D12DynamicRHI为例
## FParallelCommandListSet
```c++
//Engine\Source\Runtime\Renderer\Private\DepthRendering.cpp
void FDeferredShadingSceneRenderer::RenderPrePass(FRDGBuilder& GraphBuilder, FRDGTextureRef SceneDepthTexture, FInstanceCullingManager& InstanceCullingManager, FRDGTextureRef* FirstStageDepthBuffer)
{
RDG_EVENT_SCOPE(GraphBuilder, "PrePass %s %s", GetDepthDrawingModeString(DepthPass.EarlyZPassMode), GetDepthPassReason(DepthPass.bDitheredLODTransitionsUseStencil, ShaderPlatform));
RDG_CSV_STAT_EXCLUSIVE_SCOPE(GraphBuilder, RenderPrePass);
RDG_GPU_STAT_SCOPE(GraphBuilder, Prepass);
SCOPED_NAMED_EVENT(FDeferredShadingSceneRenderer_RenderPrePass, FColor::Emerald);
SCOPE_CYCLE_COUNTER(STAT_DepthDrawTime);
const bool bParallelDepthPass = GRHICommandList.UseParallelAlgorithms() && CVarParallelPrePass.GetValueOnRenderThread();
RenderPrePassHMD(GraphBuilder, SceneDepthTexture);
if (DepthPass.IsRasterStencilDitherEnabled())
{
AddDitheredStencilFillPass(GraphBuilder, Views, SceneDepthTexture, DepthPass);
}
auto RenderDepthPass = [&](uint8 DepthMeshPass)
{
check(DepthMeshPass == EMeshPass::DepthPass || DepthMeshPass == EMeshPass::SecondStageDepthPass);
const bool bSecondStageDepthPass = DepthMeshPass == EMeshPass::SecondStageDepthPass;
if (bParallelDepthPass)
{
RDG_WAIT_FOR_TASKS_CONDITIONAL(GraphBuilder, IsDepthPassWaitForTasksEnabled());
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);
FMeshPassProcessorRenderState DrawRenderState;
SetupDepthPassState(DrawRenderState);
const bool bShouldRenderView = View.ShouldRenderView() && (bSecondStageDepthPass ? View.bUsesSecondStageDepthPass : true);
if (bShouldRenderView)
{
View.BeginRenderView();
FDepthPassParameters* PassParameters = GetDepthPassParameters(GraphBuilder, View, SceneDepthTexture);
View.ParallelMeshDrawCommandPasses[DepthMeshPass].BuildRenderingCommands(GraphBuilder, Scene->GPUScene, PassParameters->InstanceCullingDrawParams);
GraphBuilder.AddPass(
bSecondStageDepthPass ? RDG_EVENT_NAME("SecondStageDepthPassParallel") : RDG_EVENT_NAME("DepthPassParallel"),
PassParameters,
ERDGPassFlags::Raster | ERDGPassFlags::SkipRenderPass,
[this, &View, PassParameters, DepthMeshPass](const FRDGPass* InPass, FRHICommandListImmediate& RHICmdList)
{
//并行渲染逻辑主要在这里
FRDGParallelCommandListSet ParallelCommandListSet(InPass, RHICmdList, GET_STATID(STAT_CLP_Prepass), View, FParallelCommandListBindings(PassParameters));
ParallelCommandListSet.SetHighPriority();
View.ParallelMeshDrawCommandPasses[DepthMeshPass].DispatchDraw(&ParallelCommandListSet, RHICmdList, &PassParameters->InstanceCullingDrawParams);
});
RenderPrePassEditorPrimitives(GraphBuilder, View, PassParameters, DrawRenderState, DepthPass.EarlyZPassMode, InstanceCullingManager);
}
}
}
···
}
//Engine\Source\Runtime\Renderer\Private\MeshDrawCommands.cpp
void FParallelMeshDrawCommandPass::DispatchDraw(FParallelCommandListSet* ParallelCommandListSet, FRHICommandList& RHICmdList, const FInstanceCullingDrawParams* InstanceCullingDrawParams) const
{
TRACE_CPUPROFILER_EVENT_SCOPE(ParallelMdcDispatchDraw);
if (MaxNumDraws <= 0)
{
return;
}
FMeshDrawCommandOverrideArgs OverrideArgs;
if (InstanceCullingDrawParams)
{
OverrideArgs = GetMeshDrawCommandOverrideArgs(*InstanceCullingDrawParams);
}
if (ParallelCommandListSet)
{
const ENamedThreads::Type RenderThread = ENamedThreads::GetRenderThread();
FGraphEventArray Prereqs;
if (ParallelCommandListSet->GetPrereqs())
{
Prereqs.Append(*ParallelCommandListSet->GetPrereqs());
}
if (TaskEventRef.IsValid())
{
Prereqs.Add(TaskEventRef);
}
// Distribute work evenly to the available task graph workers based on NumEstimatedDraws.
// Every task will then adjust it's working range based on FVisibleMeshDrawCommandProcessTask results.
const int32 NumThreads = FMath::Min<int32>(FTaskGraphInterface::Get().GetNumWorkerThreads(), ParallelCommandListSet->Width);
const int32 NumTasks = FMath::Min<int32>(NumThreads, FMath::DivideAndRoundUp(MaxNumDraws, ParallelCommandListSet->MinDrawsPerCommandList));
const int32 NumDrawsPerTask = FMath::DivideAndRoundUp(MaxNumDraws, NumTasks);
for (int32 TaskIndex = 0; TaskIndex < NumTasks; TaskIndex++)
{
const int32 StartIndex = TaskIndex * NumDrawsPerTask;
const int32 NumDraws = FMath::Min(NumDrawsPerTask, MaxNumDraws - StartIndex);
checkSlow(NumDraws > 0);
FRHICommandList* CmdList = ParallelCommandListSet->NewParallelCommandList();
FGraphEventRef AnyThreadCompletionEvent = TGraphTask<FDrawVisibleMeshCommandsAnyThreadTask>::CreateTask(&Prereqs, RenderThread)
.ConstructAndDispatchWhenReady(*CmdList, TaskContext.InstanceCullingContext, TaskContext.MeshDrawCommands, TaskContext.MinimalPipelineStatePassSet,
OverrideArgs,
TaskContext.InstanceFactor,
TaskIndex, NumTasks);
ParallelCommandListSet->AddParallelCommandList(CmdList, AnyThreadCompletionEvent, NumDraws);
}
}
else
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_MeshPassDrawImmediate);
WaitForMeshPassSetupTask(IsInActualRenderingThread() ? EWaitThread::Render : EWaitThread::Task);
if (TaskContext.bUseGPUScene)
{
if (TaskContext.MeshDrawCommands.Num() > 0)
{
TaskContext.InstanceCullingContext.SubmitDrawCommands(
TaskContext.MeshDrawCommands,
TaskContext.MinimalPipelineStatePassSet,
OverrideArgs,
0,
TaskContext.MeshDrawCommands.Num(),
TaskContext.InstanceFactor,
RHICmdList);
}
}
else
{
SubmitMeshDrawCommandsRange(TaskContext.MeshDrawCommands, TaskContext.MinimalPipelineStatePassSet, nullptr, 0, 0, TaskContext.bDynamicInstancing, 0, TaskContext.MeshDrawCommands.Num(), TaskContext.InstanceFactor, RHICmdList);
}
}
}
```
## 普通Pass渲染
```c++
// 代码为UE5旧版本代码
// Engine\Source\Runtime\RHI\Public\RHIResources.h
// 渲染通道信息.
struct FRHIRenderPassInfo
{
// 渲染纹理信息.
struct FColorEntry
{
FRHITexture* RenderTarget;
FRHITexture* ResolveTarget;
int32 ArraySlice;
uint8 MipIndex;
ERenderTargetActions Action;
};
FColorEntry ColorRenderTargets[MaxSimultaneousRenderTargets];
// 深度模板信息.
struct FDepthStencilEntry
{
FRHITexture* DepthStencilTarget;
FRHITexture* ResolveTarget;
EDepthStencilTargetActions Action;
FExclusiveDepthStencil ExclusiveDepthStencil;
};
FDepthStencilEntry DepthStencilRenderTarget;
// 解析参数.
FResolveParams ResolveParameters;
// 部分RHI可以使用纹理来控制不同区域的采样和/或阴影分辨率
FTextureRHIRef FoveationTexture = nullptr;
// 部分RHI需要一个提示遮挡查询将在这个渲染通道中使用
uint32 NumOcclusionQueries = 0;
bool bOcclusionQueries = false;
// 部分RHI需要知道在为部分资源转换生成mip映射的情况下这个渲染通道是否将读取和写入相同的纹理.
bool bGeneratingMips = false;
// 如果这个renderpass应该是多视图则需要多少视图.
uint8 MultiViewCount = 0;
// 部分RHI的提示渲染通道将有特定的子通道.
ESubpassHint SubpassHint = ESubpassHint::None;
// 是否太多UAV.
bool bTooManyUAVs = false;
bool bIsMSAA = false;
// 不同的构造函数.
// Color, no depth, optional resolve, optional mip, optional array slice
explicit FRHIRenderPassInfo(FRHITexture* ColorRT, ERenderTargetActions ColorAction, FRHITexture* ResolveRT = nullptr, uint32 InMipIndex = 0, int32 InArraySlice = -1);
// Color MRTs, no depth
explicit FRHIRenderPassInfo(int32 NumColorRTs, FRHITexture* ColorRTs[], ERenderTargetActions ColorAction);
// Color MRTs, no depth
explicit FRHIRenderPassInfo(int32 NumColorRTs, FRHITexture* ColorRTs[], ERenderTargetActions ColorAction, FRHITexture* ResolveTargets[]);
// Color MRTs and depth
explicit FRHIRenderPassInfo(int32 NumColorRTs, FRHITexture* ColorRTs[], ERenderTargetActions ColorAction, FRHITexture* DepthRT, EDepthStencilTargetActions DepthActions, FExclusiveDepthStencil InEDS = FExclusiveDepthStencil::DepthWrite_StencilWrite);
// Color MRTs and depth
explicit FRHIRenderPassInfo(int32 NumColorRTs, FRHITexture* ColorRTs[], ERenderTargetActions ColorAction, FRHITexture* ResolveRTs[], FRHITexture* DepthRT, EDepthStencilTargetActions DepthActions, FRHITexture* ResolveDepthRT, FExclusiveDepthStencil InEDS = FExclusiveDepthStencil::DepthWrite_StencilWrite);
// Depth, no color
explicit FRHIRenderPassInfo(FRHITexture* DepthRT, EDepthStencilTargetActions DepthActions, FRHITexture* ResolveDepthRT = nullptr, FExclusiveDepthStencil InEDS = FExclusiveDepthStencil::DepthWrite_StencilWrite);
// Depth, no color, occlusion queries
explicit FRHIRenderPassInfo(FRHITexture* DepthRT, uint32 InNumOcclusionQueries, EDepthStencilTargetActions DepthActions, FRHITexture* ResolveDepthRT = nullptr, FExclusiveDepthStencil InEDS = FExclusiveDepthStencil::DepthWrite_StencilWrite);
// Color and depth
explicit FRHIRenderPassInfo(FRHITexture* ColorRT, ERenderTargetActions ColorAction, FRHITexture* DepthRT, EDepthStencilTargetActions DepthActions, FExclusiveDepthStencil InEDS = FExclusiveDepthStencil::DepthWrite_StencilWrite);
// Color and depth with resolve
explicit FRHIRenderPassInfo(FRHITexture* ColorRT, ERenderTargetActions ColorAction, FRHITexture* ResolveColorRT,
FRHITexture* DepthRT, EDepthStencilTargetActions DepthActions, FRHITexture* ResolveDepthRT, FExclusiveDepthStencil InEDS = FExclusiveDepthStencil::DepthWrite_StencilWrite);
// Color and depth with resolve and optional sample density
explicit FRHIRenderPassInfo(FRHITexture* ColorRT, ERenderTargetActions ColorAction, FRHITexture* ResolveColorRT,
FRHITexture* DepthRT, EDepthStencilTargetActions DepthActions, FRHITexture* ResolveDepthRT, FRHITexture* InFoveationTexture, FExclusiveDepthStencil InEDS = FExclusiveDepthStencil::DepthWrite_StencilWrite);
enum ENoRenderTargets
{
NoRenderTargets,
};
explicit FRHIRenderPassInfo(ENoRenderTargets Dummy);
explicit FRHIRenderPassInfo();
inline int32 GetNumColorRenderTargets() const;
RHI_API void Validate() const;
RHI_API void ConvertToRenderTargetsInfo(FRHISetRenderTargetsInfo& OutRTInfo) const;
(......)
};
// Engine\Source\Runtime\RHI\Public\RHICommandList.h
class RHI_API FRHICommandList : public FRHIComputeCommandList
{
public:
void BeginRenderPass(const FRHIRenderPassInfo& InInfo, const TCHAR* Name)
{
if (InInfo.bTooManyUAVs)
{
UE_LOG(LogRHI, Warning, TEXT("RenderPass %s has too many UAVs"));
}
InInfo.Validate();
// 直接调用RHI的接口.
if (Bypass())
{
GetContext().RHIBeginRenderPass(InInfo, Name);
}
// 分配RHI命令.
else
{
TCHAR* NameCopy = AllocString(Name);
ALLOC_COMMAND(FRHICommandBeginRenderPass)(InInfo, NameCopy);
}
// 设置在RenderPass内标记.
Data.bInsideRenderPass = true;
// 缓存活动的RT.
CacheActiveRenderTargets(InInfo);
// 重置子Pass.
ResetSubpass(InInfo.SubpassHint);
Data.bInsideRenderPass = true;
}
void EndRenderPass()
{
// 调用或分配RHI接口.
if (Bypass())
{
GetContext().RHIEndRenderPass();
}
else
{
ALLOC_COMMAND(FRHICommandEndRenderPass)();
}
// 重置在RenderPass内标记.
Data.bInsideRenderPass = false;
// 重置子Pass标记为None.
ResetSubpass(ESubpassHint::None);
}
};
```
它们的使用案例如下:
主要是`FRHIRenderPassInfo RenderPassInfo(1, ColorRTs, ERenderTargetActions::DontLoad_DontStore)`与`RHICmdList.BeginRenderPass(RenderPassInfo, TEXT("Test_MultiDrawIndirect"))`
```c++
bool FRHIDrawTests::Test_MultiDrawIndirect(FRHICommandListImmediate& RHICmdList)
{
if (!GRHIGlobals.SupportsMultiDrawIndirect)
{
return true;
}
// Probably could/should automatically enable in the outer scope when running RHI Unit Tests
// RenderCaptureInterface::FScopedCapture RenderCapture(true /*bEnable*/, &RHICmdList, TEXT("Test_MultiDrawIndirect"));
static constexpr uint32 MaxInstances = 8;
// D3D12 does not have a way to get the base instance ID (SV_InstanceID always starts from 0), so we must emulate it...
const uint32 InstanceIDs[MaxInstances] = { 0, 1, 2, 3, 4, 5, 6, 7 };
FBufferRHIRef InstanceIDBuffer = CreateBufferWithData(EBufferUsageFlags::VertexBuffer, ERHIAccess::VertexOrIndexBuffer, TEXT("Test_MultiDrawIndirect_InstanceID"), MakeArrayView(InstanceIDs));
FVertexDeclarationElementList VertexDeclarationElements;
VertexDeclarationElements.Add(FVertexElement(0, 0, VET_UInt, 0, 4, true /*per instance frequency*/));
FVertexDeclarationRHIRef VertexDeclarationRHI = PipelineStateCache::GetOrCreateVertexDeclaration(VertexDeclarationElements);
const uint16 Indices[3] = { 0, 1, 2 };
FBufferRHIRef IndexBuffer = CreateBufferWithData(EBufferUsageFlags::IndexBuffer, ERHIAccess::VertexOrIndexBuffer, TEXT("Test_MultiDrawIndirect_IndexBuffer"), MakeArrayView(Indices));
static constexpr uint32 OutputBufferStride = sizeof(uint32);
static constexpr uint32 OutputBufferSize = OutputBufferStride * MaxInstances;
FRHIResourceCreateInfo OutputBufferCreateInfo(TEXT("Test_MultiDrawIndirect_OutputBuffer"));
FBufferRHIRef OutputBuffer = RHICmdList.CreateBuffer(OutputBufferSize, EBufferUsageFlags::UnorderedAccess | EBufferUsageFlags::SourceCopy, OutputBufferStride, ERHIAccess::UAVCompute, OutputBufferCreateInfo);
const uint32 CountValues[4] = { 1, 1, 16, 0 };
FBufferRHIRef CountBuffer = CreateBufferWithData(EBufferUsageFlags::DrawIndirect | EBufferUsageFlags::UnorderedAccess, ERHIAccess::IndirectArgs, TEXT("Test_MultiDrawIndirect_Count"), MakeArrayView(CountValues));
const FRHIDrawIndexedIndirectParameters DrawArgs[] =
{
// IndexCountPerInstance, InstanceCount, StartIndexLocation, BaseVertexLocation, StartInstanceLocation
{3, 1, 0, 0, 0}, // fill slot 0
// gap in slot 1
{3, 2, 0, 0, 2}, // fill slots 2, 3 using 1 sub-draw
// gap in slot 4
{3, 1, 0, 0, 5}, // fill slots 5, 6 using 2 sub-draws
{3, 1, 0, 0, 6},
{3, 1, 0, 0, 7}, // this draw is expected to never execute
};
const uint32 ExpectedDrawnInstances[MaxInstances] = { 1, 0, 1, 1, 0, 1, 1, 0 };
FBufferRHIRef DrawArgBuffer = CreateBufferWithData(EBufferUsageFlags::DrawIndirect | EBufferUsageFlags::UnorderedAccess | EBufferUsageFlags::VertexBuffer, ERHIAccess::IndirectArgs,
TEXT("Test_MultiDrawIndirect_DrawArgs"), MakeArrayView(DrawArgs));
FUnorderedAccessViewRHIRef OutputBufferUAV = RHICmdList.CreateUnorderedAccessView(OutputBuffer,
FRHIViewDesc::CreateBufferUAV()
.SetType(FRHIViewDesc::EBufferType::Typed)
.SetFormat(PF_R32_UINT));
RHICmdList.ClearUAVUint(OutputBufferUAV, FUintVector4(0));
const FIntPoint RenderTargetSize(4, 4);
FRHITextureDesc RenderTargetTextureDesc(ETextureDimension::Texture2D, ETextureCreateFlags::RenderTargetable, PF_B8G8R8A8, FClearValueBinding(), RenderTargetSize, 1, 1, 1, 1, 0);
FRHITextureCreateDesc RenderTargetCreateDesc(RenderTargetTextureDesc, ERHIAccess::RTV, TEXT("Test_MultiDrawIndirect_RenderTarget"));
FTextureRHIRef RenderTarget = RHICreateTexture(RenderTargetCreateDesc);
TShaderMapRef<FTestDrawInstancedVS> VertexShader(GetGlobalShaderMap(GMaxRHIFeatureLevel));
TShaderMapRef<FTestDrawInstancedPS> PixelShader(GetGlobalShaderMap(GMaxRHIFeatureLevel));
FGraphicsPipelineStateInitializer GraphicsPSOInit;
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = VertexDeclarationRHI;
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
GraphicsPSOInit.PrimitiveType = EPrimitiveType::PT_TriangleList;
FRHITexture* ColorRTs[1] = { RenderTarget.GetReference() };
FRHIRenderPassInfo RenderPassInfo(1, ColorRTs, ERenderTargetActions::DontLoad_DontStore);
RHICmdList.Transition(FRHITransitionInfo(OutputBufferUAV, ERHIAccess::UAVCompute, ERHIAccess::UAVGraphics, EResourceTransitionFlags::None));
RHICmdList.BeginUAVOverlap(); // Output UAV can be written without syncs between draws (each draw is expected to write into different slots)
RHICmdList.BeginRenderPass(RenderPassInfo, TEXT("Test_MultiDrawIndirect"));
RHICmdList.SetViewport(0, 0, 0, float(RenderTargetSize.X), float(RenderTargetSize.Y), 1);
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0);
check(InstanceIDBuffer->GetStride() == 4);
RHICmdList.SetStreamSource(0, InstanceIDBuffer, 0);
FRHIBatchedShaderParameters ShaderParameters;
ShaderParameters.SetUAVParameter(PixelShader->OutDrawnInstances.GetBaseIndex(), OutputBufferUAV);
RHICmdList.SetBatchedShaderParameters(PixelShader.GetPixelShader(), ShaderParameters);
const uint32 DrawArgsStride = sizeof(DrawArgs[0]);
const uint32 CountStride = sizeof(CountValues[0]);
RHICmdList.MultiDrawIndexedPrimitiveIndirect(IndexBuffer,
DrawArgBuffer, DrawArgsStride*0, // 1 sub-draw with instance index 0
CountBuffer, CountStride*0, // count buffer contains 1 in this slot
5 // expect to draw only 1 instance due to GPU-side upper bound
);
RHICmdList.MultiDrawIndexedPrimitiveIndirect(IndexBuffer,
DrawArgBuffer, DrawArgsStride*1, // 1 sub-draw with 2 instances at base index 2
CountBuffer, CountStride*1, // count buffer contains 1 in this slot
4 // expect to draw only 1 instance due to GPU-side upper bound
);
RHICmdList.MultiDrawIndexedPrimitiveIndirect(IndexBuffer,
DrawArgBuffer, DrawArgsStride*2, // 2 sub-draws with 1 instance each starting at base index 5
CountBuffer, CountStride*2, // count buffer contains 16 in this slot
2 // expect to draw only 2 instances due to CPU-side upper bound
);
RHICmdList.MultiDrawIndexedPrimitiveIndirect(IndexBuffer,
DrawArgBuffer, DrawArgsStride*4, // 1 sub-draw with 1 instance each starting at base index 7
CountBuffer, CountStride*3, // count buffer contains 0 in this slot
1 // expect to skip the draw due to GPU-side count of 0
);
RHICmdList.MultiDrawIndexedPrimitiveIndirect(IndexBuffer,
DrawArgBuffer, DrawArgsStride*4, // 1 sub-draw with 1 instance each starting at base index 7
CountBuffer, CountStride*0, // count buffer contains 1 in this slot
0 // expect to skip the draw due to CPU-side count of 0
);
RHICmdList.EndRenderPass();
RHICmdList.EndUAVOverlap();
RHICmdList.Transition(FRHITransitionInfo(OutputBufferUAV, ERHIAccess::UAVGraphics, ERHIAccess::CopySrc, EResourceTransitionFlags::None));
TConstArrayView<uint8> ExpectedOutputView = MakeArrayView(reinterpret_cast<const uint8*>(ExpectedDrawnInstances), sizeof(ExpectedDrawnInstances));
bool bSucceeded = FRHIBufferTests::VerifyBufferContents(TEXT("Test_MultiDrawIndirect"), RHICmdList, OutputBuffer, ExpectedOutputView);
return bSucceeded;
}
```
## Subpass
先说一下Subpass的由来、作用和特点。
在传统的多Pass渲染中每个Pass结束时通常会渲染出一组渲染纹理部分成为着色器参数提供给下一个Pass采样读取。这种纹理采样方式不受任何限制可以读取任意的领域像素使用任意的纹理过滤方式。这种方式虽然使用灵活但在TBRTile-Based Renderer硬件架构的设备中会有较大的消耗渲染纹理的Pass通常会将渲染结果存储在On-chip的Tile Memory中待Pass结束后会写回GPU显存VRAM写回GPU显存是个耗时耗耗电的操作。
![](https://img2020.cnblogs.com/blog/1617944/202108/1617944-20210818142400565-369905116.jpg)
_传统多Pass之间的内存存取模型多次发生于On-Chip和全局存储器之间。_
如果出现一种特殊的纹理使用情况上一个Pass渲染处理的纹理立即被下一个Pass使用并且下一个Pass只采样像素位置自身的数据而不需要采样邻域像素的位置。这种情况就符合了Subpass的使用情景。使用Subpass渲染的纹理结果只会存储在Tile Memory中在Subpass结束后不会写回VRAM而直接提供Tile Memory的数据给下一个Subpass采样读取。这样就避免了传统Pass结束写回GPU显存以及下一个Pass又从GPU显存读数据的耗时耗电操作从而提升了性能。
![](https://img2020.cnblogs.com/blog/1617944/202108/1617944-20210818142406863-486058547.jpg)
_Subpass之间的内存存取模型都发生在On-Chip内。_
Subpass的相关代码主要集中在移动端中。UE涉及Subpass的接口和类型如下
```c++
// 提供给RHI的Subpass标记.
enum class ESubpassHint : uint8
{
None, // 传统渲染(非Subpass)
DepthReadSubpass, // 深度读取Subpass.
DeferredShadingSubpass, // 移动端延迟着色Subpass.
};
// Engine\Source\Runtime\RHI\Public\RHICommandList.h
class RHI_API FRHICommandListBase : public FNoncopyable
{
(......)
protected:
// PSO上下文.
struct FPSOContext
{
uint32 CachedNumSimultanousRenderTargets = 0;
TStaticArray<FRHIRenderTargetView, MaxSimultaneousRenderTargets> CachedRenderTargets;
FRHIDepthRenderTargetView CachedDepthStencilTarget;
// Subpass提示标记.
ESubpassHint SubpassHint = ESubpassHint::None;
uint8 SubpassIndex = 0;
uint8 MultiViewCount = 0;
bool HasFragmentDensityAttachment = false;
} PSOContext;
};
class RHI_API FRHICommandList : public FRHIComputeCommandList
{
public:
void BeginRenderPass(const FRHIRenderPassInfo& InInfo, const TCHAR* Name)
{
(......)
CacheActiveRenderTargets(InInfo);
// 设置Subpass数据.
ResetSubpass(InInfo.SubpassHint);
Data.bInsideRenderPass = true;
}
void EndRenderPass()
{
(......)
// 重置Subpass标记为None.
ResetSubpass(ESubpassHint::None);
}
// 下一个Subpass.
void NextSubpass()
{
// 分配或调用RHI接口.
if (Bypass())
{
GetContext().RHINextSubpass();
}
else
{
ALLOC_COMMAND(FRHICommandNextSubpass)();
}
// 增加Subpass计数.
IncrementSubpass();
}
// 增加subpass计数.
void IncrementSubpass()
{
PSOContext.SubpassIndex++;
}
// 重置Subpass数据.
void ResetSubpass(ESubpassHint SubpassHint)
{
PSOContext.SubpassHint = SubpassHint;
PSOContext.SubpassIndex = 0;
}
};
```

View File

@@ -0,0 +1,365 @@
---
title: 剖析虚幻渲染体系11- RDG
date: 2024-02-04 21:42:54
excerpt:
tags:
rating: ⭐
---
# 前言
原文地址:https://www.cnblogs.com/timlly/p/15217090.html
# 概念
- FRDGAllocator简单的C++对象分配器, 用MemStack分配器追踪和销毁物体。
- FComputeShaderUtils
- Dispatch()派发ComputeShader到RHI命令列表携带其参数。
- DispatchIndirect()派发非直接的ComputeShader到RHI命令列表,,携带其参数。
- AddPass():派发计算着色器到**RenderGraphBuilder** 携带其参数。
- ClearUAV()清理UAV。
- AddCopyTexturePass()增加拷贝纹理Pass。
- AddCopyToResolveTargetPass()增加拷贝到解析目标的Pass。
- AddEnqueueCopyPass()增加回读纹理的Pass。
- AddPass()无参数的Pass增加。
- AddBeginUAVOverlapPass()/AddEndUAVOverlapPass() 其它特殊Pass。
- FRDGResourceRDG资源并不是直接用RHI资源而是包裹了RHI资源引用然后针对不同类型的资源各自封装且增加了额外的信息。
- FRDGUniformBuffer、TRDGUniformBuffer
- FRDGParentResource一种由图跟踪分配生命周期的渲染图资源。可能有引用它的子资源(例如视图)
- FRDGView
- FRDGBuffer、FRDGBufferSRV、FRDGBufferUAV
- FRDGViewableResource一种由图跟踪分配生命周期的RPGResource。可能有引用它的子资源
- FRDGTexture
- FRDGView
- FRDGUnorderedAccessView、FRDGTextureUAV
- FRDGShaderResourceView、FRDGTextureSRV
- FRDGTextureDesc创建渲染纹理的描述信息。
- FRDGPooledTexture贴图池里的贴图资源。
- FRDGPooledBuffer池化的缓冲区。
- FRHITransition用于表示RHI中挂起的资源转换的**不透明表面材质**数据结构。
- FRDGBarrierBatchRDG屏障批。
- FRDGBarrierBatchBegin
- FRDGBarrierBatchEnd
- FRDGPass
- TRDGLambdaPass
- FRDGSentinelPass哨兵Pass用于开始/结束。
- [[#FRDGBuilder]]
## RDG基础类型
```c++
enum class ERDGBuilderFlags
{
None = 0,
/** Allows the builder to parallelize execution of passes. Without this flag, all passes execute on the render thread. */
AllowParallelExecute = 1 << 0
};
/** Flags to annotate a pass with when calling AddPass. */
enum class ERDGPassFlags : uint16
{
/** Pass doesn't have any inputs or outputs tracked by the graph. This may only be used by the parameterless AddPass function. */
None = 0,
/** Pass uses rasterization on the graphics pipe. */
Raster = 1 << 0,
/** Pass uses compute on the graphics pipe. */
Compute = 1 << 1,
/** Pass uses compute on the async compute pipe. */
AsyncCompute = 1 << 2,
/** Pass uses copy commands on the graphics pipe. */
Copy = 1 << 3,
/** Pass (and its producers) will never be culled. Necessary if outputs cannot be tracked by the graph. */
NeverCull = 1 << 4,
/** Render pass begin / end is skipped and left to the user. Only valid when combined with 'Raster'. Disables render pass merging for the pass. */
SkipRenderPass = 1 << 5,
/** Pass will never have its render pass merged with other passes. */
NeverMerge = 1 << 6,
/** Pass will never run off the render thread. */
NeverParallel = 1 << 7,
ParallelTranslate = 1 << 8,
/** Pass uses copy commands but writes to a staging resource. */
Readback = Copy | NeverCull
};
/** Flags to annotate a render graph buffer. */
enum class ERDGBufferFlags : uint8
{
None = 0,
/** Tag the buffer to survive through frame, that is important for multi GPU alternate frame rendering. */
MultiFrame = 1 << 0,
/** The buffer is ignored by RDG tracking and will never be transitioned. Use the flag when registering a buffer with no writable GPU flags.
* Write access is not allowed for the duration of the graph. This flag is intended as an optimization to cull out tracking of read-only
* buffers that are used frequently throughout the graph. Note that it's the user's responsibility to ensure the resource is in the correct
* readable state for use with RDG passes, as RDG does not know the exact state of the resource.
*/
SkipTracking = 1 << 1,
/** When set, RDG will perform its first barrier without splitting. Practically, this means the resource is left in its initial state
* until the first pass it's used within the graph. Without this flag, the resource is split-transitioned at the start of the graph.
*/
ForceImmediateFirstBarrier = 1 << 2,
};
/** Flags to annotate a render graph texture. */
enum class ERDGTextureFlags : uint8
{
None = 0,
/** Tag the texture to survive through frame, that is important for multi GPU alternate frame rendering. */
MultiFrame = 1 << 0,
/** The buffer is ignored by RDG tracking and will never be transitioned. Use the flag when registering a buffer with no writable GPU flags.
* Write access is not allowed for the duration of the graph. This flag is intended as an optimization to cull out tracking of read-only
* buffers that are used frequently throughout the graph. Note that it's the user's responsibility to ensure the resource is in the correct
* readable state for use with RDG passes, as RDG does not know the exact state of the resource.
*/
SkipTracking = 1 << 1,
/** When set, RDG will perform its first barrier without splitting. Practically, this means the resource is left in its initial state
* until the first pass it's used within the graph. Without this flag, the resource is split-transitioned at the start of the graph.
*/
ForceImmediateFirstBarrier = 1 << 2,
/** Prevents metadata decompression on this texture. */
MaintainCompression = 1 << 3,
};
ENUM_CLASS_FLAGS(ERDGTextureFlags);
/** Flags to annotate a view with when calling CreateUAV. */
enum class ERDGUnorderedAccessViewFlags : uint8
{
None = 0,
// The view will not perform UAV barriers between consecutive usage.
SkipBarrier = 1 << 0
};
ENUM_CLASS_FLAGS(ERDGUnorderedAccessViewFlags);
/** The set of concrete parent resource types. */
enum class ERDGViewableResourceType : uint8
{
Texture,
Buffer,
MAX
};
/** The set of concrete view types. */
enum class ERDGViewType : uint8
{
TextureUAV,
TextureSRV,
BufferUAV,
BufferSRV,
MAX
};
inline ERDGViewableResourceType GetParentType(ERDGViewType ViewType)
{
switch (ViewType)
{
case ERDGViewType::TextureUAV:
case ERDGViewType::TextureSRV:
return ERDGViewableResourceType::Texture;
case ERDGViewType::BufferUAV:
case ERDGViewType::BufferSRV:
return ERDGViewableResourceType::Buffer;
}
checkNoEntry();
return ERDGViewableResourceType::MAX;
}
enum class ERDGResourceExtractionFlags : uint8
{
None = 0,
// Allows the resource to remain transient. Only use this flag if you intend to register the resource back
// into the graph and release the reference. This should not be used if the resource is cached for a long
// period of time.
AllowTransient = 1,
};
enum class ERDGInitialDataFlags : uint8
{
/** Specifies the default behavior, which is to make a copy of the initial data for replay when
* the graph is executed. The user does not need to preserve lifetime of the data pointer.
*/
None = 0,
/** Specifies that the user will maintain ownership of the data until the graph is executed. The
* upload pass will only use a reference to store the data. Use caution with this flag since graph
* execution is deferred! Useful to avoid the copy if the initial data lifetime is guaranteed to
* outlive the graph.
*/
NoCopy = 1 << 0
};
enum class ERDGPooledBufferAlignment : uint8
{
// The buffer size is not aligned.
None,
// The buffer size is aligned up to the next page size.
Page,
// The buffer size is aligned up to the next power of two.
PowerOfTwo
};
/** Returns the equivalent parent resource type for a view type. */
inline ERDGViewableResourceType GetViewableResourceType(ERDGViewType ViewType)
{
switch (ViewType)
{
case ERDGViewType::TextureUAV:
case ERDGViewType::TextureSRV:
return ERDGViewableResourceType::Texture;
case ERDGViewType::BufferUAV:
case ERDGViewType::BufferSRV:
return ERDGViewableResourceType::Buffer;
default:
checkNoEntry();
return ERDGViewableResourceType::MAX;
}
}
using ERDGTextureMetaDataAccess = ERHITextureMetaDataAccess;
/** Returns the associated FRHITransitionInfo plane index. */
inline int32 GetResourceTransitionPlaneForMetadataAccess(ERDGTextureMetaDataAccess Metadata)
{
switch (Metadata)
{
case ERDGTextureMetaDataAccess::CompressedSurface:
case ERDGTextureMetaDataAccess::HTile:
case ERDGTextureMetaDataAccess::Depth:
return FRHITransitionInfo::kDepthPlaneSlice;
case ERDGTextureMetaDataAccess::Stencil:
return FRHITransitionInfo::kStencilPlaneSlice;
default:
return 0;
}
}
```
## FRDGBuilder
FRDGBuilder是RDG体系的心脏和发动机也是个大管家负责收集渲染Pass和参数编译Pass、数据处理资源依赖裁剪和优化各类数据还有提供执行接口。
重要函数:
- FindExternalTexture():查找外部纹理, 若找不到返回null.
- RegisterExternalTexture()注册外部池内RT到RDG, 以便RDG追踪之. 池内RT可能包含两种RHI纹理: MSAA和非MSAA。
- RegisterExternalBuffer()注册外部缓冲区到RDG, 以便RDG追踪之.
- 资源创建接口:
- CreateTexture()
- CreateBuffer()
- CreateUAV()
- CreateSRV()
- CreateUniformBuffer()
- 分配内存, 内存由RDG管理生命周期。
- Alloc()
- AllocPOD()
- AllocObject()
- AllocParameters()
- AddPass()
- FRDGPassRef AddPass(FRDGEventName&& Name, const ParameterStructType* ParameterStruct, ERDGPassFlags Flags, ExecuteLambdaType&& ExecuteLambda); 增加有参数的LambdaPass。
- FRDGPassRef AddPass(FRDGEventName&& Name, const FShaderParametersMetadata* ParametersMetadata, const void* ParameterStruct, ERDGPassFlags Flags, ExecuteLambdaType&& ExecuteLambda); 增加带有实时生成结构体的LambdaPass
- FRDGPassRef AddPass(FRDGEventName&& Name, ERDGPassFlags Flags, ExecuteLambdaType&& ExecuteLambda);增加没有参数的LambdaPass
- QueueTextureExtraction()在Builder执行末期, 提取池内纹理到指定的指针. 对于RDG创建的资源, 这将延长GPU资源的生命周期直到执行指针被填充. 如果指定纹理将转换为AccessFinal状态, 否则将转换为kDefaultAccessFinal状态。
- QueueBufferExtraction()在Builder执行末期, 提取缓冲区到指定的指针。
- PreallocateTexture()/PreallocateBuffer():预分配资源. 只对RDG创建的资源, 会强制立即分配底层池内资源, 有效地将其推广到外部资源. 这将增加内存压力但允许使用GetPooled{Texture, Buffer}查询池中的资源. 主要用于增量地将代码移植到RDG.
- GetPooledTexture()/GetPooledBuffer():立即获取底层资源, 只允许用于注册或预分配的资源。
- SetTextureAccessFinal()/SetBufferAccessFinal():设置执行之后的状态。
- 变量
- RDG对象注册表
- FRDGPassRegistry Passes;
- FRDGTextureRegistry Textures;
- FRDGBufferRegistry Buffers;
- FRDGViewRegistry Views;
- FRDGUniformBufferRegistry UniformBuffers;
### FRDGBuilder::Compile
RDG编译期间的逻辑非常复杂步骤繁多先后经历构建生产者和消费者的依赖关系确定Pass的裁剪等各类标记调整资源的生命周期裁剪Pass处理Pass的资源转换和屏障处理异步计算Pass的依赖和引用关系查找并建立分叉和合并Pass节点合并所有具体相同渲染目标的光栅化Pass等步骤。
# RDG开发
### 注册外部资源
如果我们已有非RDG创建的资源可以在RDG使用么答案是可以通过FRDGBuilder::RegisterExternalXXX接口可以完成将外部资源注册到RDG系统中。下面以注册纹理为例
```c++
// 在RDG外创建RHI资源.
FRHIResourceCreateInfo CreateInfo;
FTexture2DRHIRef MyRHITexture = RHICreateTexture2D(1024, 768, PF_B8G8R8A8, 1, 1, TexCreate_CPUReadback, CreateInfo);
// 将外部创建的RHI资源注册成RDG资源.
FRDGTextureRef MyExternalRDGTexture = GraphBuilder.RegisterExternalTexture(MyRHITexture);
```
需要注意的是外部注册的资源RDG无法控制和管理其生命周期需要保证RDG使用期间外部资源的生命周期处于正常状态否则将引发异常甚至程序崩溃。
如果想从RDG资源获取RHI资源的实例以下代码可达成
```c++
FRHITexture* MyRHITexture = MyRDGTexture.GetRHI();
```
用图例展示RHI资源和RDG资源之间的转换关系
- FRHIResource =>FRDGBuilder::RegisterExternalXXX =>FRDGResource
- FRDGResource => FRDGResource::GetRHI => FRHIResource
### 提取资源
RDG收集Pass之后并非立即执行而是延迟执行包括资源被延迟创建或分配这就导致了一个问题如果想将渲染后的资源赋值给某个变量无法使用立即模式需要适配延迟执行模式。这种适配延迟执行的资源提取是通过以下接口来实现的
- FRDGBuilder::QueueTextureExtraction
- FRDGBuilder::QueueBufferExtraction
```c++
// 创建RDG纹理.
FRDGTextureRef MyRDGTexture;
FRDGTextureDesc MyTextureDesc = FRDGTextureDesc::Create2D(OutputExtent, HistoryPixelFormat, FClearValueBinding::Black, TexCreate_ShaderResource | TexCreate_UAV);
MyRDGTexture = GraphBuilder.CreateTexture(MyTextureDesc, "MyRDGTexture", ERDGTextureFlags::MultiFrame);
// 创建UAV并作为Pass的shader参数.
(......)
PassParameters->MyRDGTextureUAV = GraphBuilder.CreateUAV(MyRDGTexture);
(......)
// 增加Pass, 以便渲染图像到MyRDGTextureUAV.
FComputeShaderUtils::AddPass(GraphBuilder, RDG_EVENT_NAME("MyCustomPass", ...), ComputeShader, PassParameters, FComputeShaderUtils::GetGroupCount(8, 8));
// 入队提取资源.
TRefCountPtr<IPooledRenderTarget>* OutputRT;
GraphBuilder.QueueTextureExtraction(MyRDGTexture, &OutputRT);
// 对提取的OutputRT进行后续操作.
(......)
```
# RDG调试
RDG系统存在一些控制台命令其名称和描述如下
|控制台变量|描述|
|---|---|
|**r.RDG.AsyncCompute**|控制异步计算策略0-禁用1-为异步计算Pass启用标记默认2-开启所有使用compute命令列表的计算通道。|
|**r.RDG.Breakpoint**|当满足某些条件时断点到调试器的断点位置。0-禁用1~4-不同的特殊调试模式。|
|**r.RDG.ClobberResources**|在分配时间用指定的清理颜色清除所有渲染目标和纹理/缓冲UAV。用于调试。|
|**r.RDG.CullPasses**|RDG是否开启裁剪无用的Pass。0-禁用1-开启(默认)。|
|**r.RDG.Debug**|允许输出在连接和执行过程中发现的效率低下的警告。|
|**r.RDG.Debug.FlushGPU**|开启每次Pass执行后刷新指令到GPU。当设置(r.RDG.AsyncCompute=0)时禁用异步计算。|
|**r.RDG.Debug.GraphFilter**|将某些调试事件过滤到特定的图中。|
|**r.RDG.Debug.PassFilter**|将某些调试事件过滤到特定的Pass。|
|**r.RDG.Debug.ResourceFilter**|将某些调试事件过滤到特定的资源。|
|**r.RDG.DumpGraph**|将多个可视化日志转储到磁盘。0-禁用1-显示生产者、消费者Pass依赖2-显示资源状态和转换3-显示图形、异步计算的重叠。|
|**r.RDG.ExtendResourceLifetimes**|RDG将把资源生命周期扩展到图的全部长度。会增加内存的占用。|
|**r.RDG.ImmediateMode**|在创建Pass时执行Pass。当在Pass的Lambda中崩溃时连接代码的调用堆栈非常有用。|
|**r.RDG.MergeRenderPasses**|图形将合并相同的、连续的渲染通道到一个单一的渲染通道。0-禁用1-开启(默认)。|
|**r.RDG.OverlapUAVs**|RDG将在需要时重叠UAV的工作。如果禁用UAV屏障总是插入。|
|**r.RDG.TransitionLog**|输出资源转换到控制台。|
|**r.RDG.VerboseCSVStats**|控制RDG的CSV分析统计的详细程度。0-为图形执行生成一个CSV配置文件1-为图形执行的每个阶段生成一个CSV文件。|
除了以上列出的RDG控制台还有一些命令可以显示RDG系统运行过程中的有用信息。
`vis`列出所有有效的纹理,输入之后可能显示如下所示的信息:

View File

@@ -0,0 +1,9 @@
---
title: 剖析虚幻渲染体系17- 实时光线追踪
date: 2024-02-05 22:02:57
excerpt:
tags:
rating: ⭐
---
# 前言
原文地址:https://www.cnblogs.com/timlly/p/16687324.html