Init
This commit is contained in:
@@ -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)
|
77
03-UnrealEngine/Rendering/RenderingPipeline/GPUScene.md
Normal file
77
03-UnrealEngine/Rendering/RenderingPipeline/GPUScene.md
Normal 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的数据FPrimitiveSceneData(SceneData.ush);
|
||||
- Instance:
|
||||
- C++的数据类型FInstanceSceneShaderData(InstanceUniformShaderParameters.h)
|
||||
- Shader的数据FInstanceSceneData(SceneData.ush);
|
||||
- Payload:
|
||||
- C++数据类型FPackedBatch、FPackedItem(InstanceCullingLoadBalancer.h)
|
||||
- Shader的数据类型FPackedInstanceBatch、FPackedInstanceBatchItem(InstanceCullingLoadBalancer.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
|
||||
|
||||

|
||||
|
||||
接下来,对标记为dirty的Primitive进行更新,更新逻辑为找到该Primitive在PrimitiveData Buffer里对应offset位置,对其进行更新。使用模板FUploadDataSourceAdapterScenePrimitives调用UploadGeneral()。
|
||||
|
||||

|
||||
|
||||
初始化5类Buffer的上传任务TaskContext
|
||||
|
||||
后面代码都是在对这个TaskContext进行初始化,然后启动任务,进行数据的上传。
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
2.2.1、并行更新Primitive数据,将需要更新的PrimitiveCopy到Upload Buffer里,后续通过ComputeShader进行显存的上传然后更新到目标Buffer里;
|
||||
|
||||

|
||||
|
||||
2.2.2、同理InstanceSceneData以及InstancePayloadData数据的处理。
|
||||
|
||||
2.2.3、同理InstanceBVHUploader,LightmapUploader。
|
||||
|
||||
2.2.4、最后都会调用每个Uploader的***End()方法进行GPU显存的更新***。
|
||||
|
||||
总结:数据的上传都一样,将需要更新的数据Copy到对应的UploadBuffer对应的位置里,然后通过对应的ComputeShader进行更新到目标Buffer,在函数FRDGAsyncScatterUploadBuffer::End()里实现,截图:
|
||||
|
||||

|
@@ -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);
|
||||
}
|
||||
}
|
||||
```
|
816
03-UnrealEngine/Rendering/RenderingPipeline/Lighting/Lighting.md
Normal file
816
03-UnrealEngine/Rendering/RenderingPipeline/Lighting/Lighting.md
Normal 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;
|
||||
}
|
||||
```
|
@@ -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
|
||||
|
1334
03-UnrealEngine/Rendering/RenderingPipeline/Lighting/Shadow.md
Normal file
1334
03-UnrealEngine/Rendering/RenderingPipeline/Lighting/Shadow.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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⊗Fsrc−Cdst⊗Fdst$$ |
|
||||
| BO_ReverseSubtract | $$C = C_{dst} \otimes F_{dst} - C_{src} \otimes F_{src}C=Cdst⊗Fdst−Csrc⊗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=(1−rsrc,1−gsrc,1−bsrc)$$ | – |
|
||||
| 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=(1−asrc,1−asrc,1−asrc)$$ | $$F=1-a_{src}F=1−asrc$$ |
|
||||
| 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=(1−adst,1−adst,1−adst)$$ | $$F=1-a_{dst}F=1−adst$$ |
|
||||
| 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=(1−rdst,1−gdst,1−bdst)$$ | – |
|
||||
| BF_ConstantBlendFactor | F=(r,g,b)F=(r,g,b) | F=aF=a |
|
||||
| BF_InverseConstantBlendFactor | F=(1-r,1-g,1-b)F=(1−r,1−g,1−b) | F=1-aF=1−a |
|
||||
| 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()进行渲染。
|
@@ -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;
|
||||
}
|
||||
```
|
@@ -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
|
||||
```
|
@@ -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卡顿:FileCache,PreCache和异步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实现
|
||||
|
@@ -0,0 +1,324 @@
|
||||
## 前言
|
||||
>RDG = Rendering Dependency Graph
|
||||
|
||||
RDG主要包含两个重要组件,一个是FRDGBuilder,负责构建Render graph的资源及添加pass等,构建RenderGraph。另一个是FRDGResource,RenderGraph的资源类,所有资源都由它派生。
|
||||
|
||||
官方入门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是四字节对齐的;
|
||||
- FVector2D,FIntPoint是8字节对齐的;
|
||||
- FVector和FVector 4是16字节对齐的。
|
||||
|
||||
>作者(Author):papalqi 链接(URL):https://papalqi.cn/index.php/2020/01/21/rendering-dependency-graph/
|
||||
|
||||
**所以我们需要根据位宽对变量与资源进行排序:**
|
||||

|
||||
|
||||

|
||||
|
||||
进行手动位宽对齐,以减少额外的内存与带宽占用。
|
||||
|
||||
### 资源的初始化与绑定
|
||||
其中资源分为使用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设置变量的步骤:
|
||||

|
||||
|
||||
>当增加一个RGPass它必须带有Shader参数,可以是任何Shader参数比如UnifromBuffer,Texture等。且参数使用" 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:
|
||||

|
||||

|
||||
|
||||
**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。**
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### 绑定RenderTarget
|
||||
如需使用RenderTarget,只要在FParameter结构体声明中加一行RENDER_TARGET_BINDING_SLOTS()即可。
|
||||

|
||||
|
||||
之后就可以进行RT的绑定。
|
||||
|
||||
**绑定储存颜色数据的RT**
|
||||

|
||||
颜色数据RT绑定时需要注意数组下标号。
|
||||
|
||||
**绑定深度模板缓存**
|
||||

|
||||
|
||||
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。如下图。
|
||||
|
||||

|
||||
|
||||
>Check out GRenderTargetPool.CreateUntrackedElement()to get a TRefCountPtr<IPooledRenderTarget>if need to register a different from RHI resource (for instance the very old FRenderTarget)
|
||||
|
||||

|
||||
|
||||
## 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);
|
||||
});
|
||||
```
|
@@ -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是FScreenVertexShaderVS,usf为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;
|
||||
```
|
@@ -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);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## 如何使用
|
||||
直接在蓝图中调用即可
|
||||
|
||||

|
||||
|
||||
注意RenderTarget的格式需要与UAV的格式一一对应。
|
||||
|
||||

|
||||
|
||||
结果:
|
||||
|
||||

|
208
03-UnrealEngine/Rendering/RenderingPipeline/ShaderModel添加.md
Normal file
208
03-UnrealEngine/Rendering/RenderingPipeline/ShaderModel添加.md
Normal file
@@ -0,0 +1,208 @@
|
||||
# 参考文章
|
||||
- UE4:https://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.ush,BxDF实现。
|
||||
- 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
|
@@ -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中有用到)
|
||||
- 1041:GBuffer.DiffuseColor = BaseColor - BaseColor * Metallic;
|
||||
- 1059~1072:使用法线(清漆ShaderModel还会计算的底层法线)计算BentNormal以及GBufferAO。(使用SphericalGaussian)
|
||||
|
||||
### 1081~1146:
|
||||
#### 1086~1116:计算DiffuseColorForIndirect
|
||||
DiffuseColorForIndirect(DiffuseDir只在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~1146:SingleLayerWater 覆盖颜色操作
|
||||
```c#
|
||||
GBuffer.DiffuseColor *= BaseMaterialCoverageOverWater;
|
||||
DiffuseColor *= BaseMaterialCoverageOverWater;
|
||||
```
|
||||
### 1148~1211
|
||||
1. 使用ForwardDirectLighting的DiffuseLighting与SpecularLighting累加,Color,THIN_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~1349:SingleLayerWater光照计算
|
||||
计算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~1529:GBuffer相关
|
||||
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;
|
||||
|
||||
### 1553:FinalizeVirtualTextureFeedback
|
||||
|
||||
# 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);
|
||||
}
|
||||
```
|
304
03-UnrealEngine/Rendering/RenderingPipeline/UE4 ToneMapping.md
Normal file
304
03-UnrealEngine/Rendering/RenderingPipeline/UE4 ToneMapping.md
Normal 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 System(ACES)
|
||||
>是一套颜色编码系统,或者说是一个新的颜色空间。它是一个通用的数据交换格式,一方面可以不同的输入设备转成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_A32B32G32R32F;LinearNoToneCurve、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 Transform(RRT)及 Output Device Transform(ODT)渲染的图像。
|
||||
- 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
|
||||
|
||||

|
@@ -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为Value,DefineValue为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
|
122
03-UnrealEngine/Rendering/RenderingPipeline/UE4渲染用贴图资源实时更换.md
Normal file
122
03-UnrealEngine/Rendering/RenderingPipeline/UE4渲染用贴图资源实时更换.md
Normal 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界面中有SubsufaceProfile(SubsufaceProfile类,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;
|
||||
```
|
||||
|
58
03-UnrealEngine/Rendering/RenderingPipeline/UE5大世界坐标转换.md
Normal file
58
03-UnrealEngine/Rendering/RenderingPipeline/UE5大世界坐标转换.md
Normal 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.ush(UE5.3不存在)
|
||||
- DoubleFloat.h(UE5.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);
|
||||
```
|
141
03-UnrealEngine/Rendering/RenderingPipeline/Ue4后处理逻辑简析.md
Normal file
141
03-UnrealEngine/Rendering/RenderingPipeline/Ue4后处理逻辑简析.md
Normal 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
|
16
03-UnrealEngine/Rendering/RenderingPipeline/Ue4延迟渲染流程笔记.md
Normal file
16
03-UnrealEngine/Rendering/RenderingPipeline/Ue4延迟渲染流程笔记.md
Normal file
@@ -0,0 +1,16 @@
|
||||
#### 渲染循环发起以及渲染函数
|
||||
渲染更新由UGameEngine::Tick()发起。
|
||||
```
|
||||
UGameEngine::Tick
|
||||
|
|
||||
-RedrawViewports()
|
||||
|
|
||||
-GameViewport->Viewport->Draw
|
||||
|
|
||||
-EnqueueBeginRenderFrame()
|
||||
SetRequiresVsync()
|
||||
EnqueueEndRenderFrame()
|
||||
```
|
||||
|
||||
#### FDeferredShadingSceneRenderer
|
||||
FDeferredShadingSceneRenderer继承自FSceneRenderer,从Render函数中可以了解到延迟渲染的整个过程。每个Pass的渲染流程。
|
@@ -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 Texture:UE4 运行时虚拟纹理系统,以下简称 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地址。
|
||||
#### 四叉树映射
|
||||

|
||||
这里每个四叉树的节点的内容存的就是bias和scale,这样就可以将虚拟纹理的地址转换成物理纹理的地址了,假如没有找到,也可以用父节点的地址来得到低分辨率的。但是这里要找到对应的节点需要搜索这个四叉树,搜索的速度取决于树的高度,也就是mipmap的层级,在差的低mip的地址上效率会比较差。
|
||||
### Feedback Rendering
|
||||
在Virtual Texture中一个很重要的事情是要有一个可以决定加载哪些page的策略,这个策略就是要有一个叫Feedback Rendering的过程。这个可以设计为一个单独的pass,或者跟Pre-Z或者GBuffer同时。渲染生成的这张texture里面存的就是虚纹理的page坐标,mip level和可能的虚纹理id(用来应对多虚纹理的情况)。
|
||||

|
||||
|
||||
可以看到上图,由于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上去:
|
||||

|
||||
|
||||
这样可以使回读操作更快,处理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加载。但是,这样仍然会出现跳变,由于采样的位置其实发生了突变。
|
||||

|
||||
|
||||
上图可以看到,当分辨率增加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流程。
|
||||
- URuntimeVirtualTexture(UObject)
|
||||
- FRuntimeVirtualTextureRenderResource
|
||||
- UVirtualTexture(UObject)
|
||||
- UVirtualTexture2D(UTexture2D)
|
||||
|
||||
# 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> Texture:BGRA8格式、贴图大小与RVT的Tile数量一致、有全部mipmap。每个像素存储RVT一个Tile中的最小值以及最大值,各为16bit、encode在RGBA的4个通道上。
|
||||
- TObjectPtr<class UTexture2D> LodBiasTexture:G8格式、贴图大小与RTV的Tile数量一致、无mipmap。每个像素存储了Texture对应像素周围3x3blur之后的结果。
|
||||
- TObjectPtr<class UTexture2D> LodBiasMinMaxTexture:BGRA8格式、贴图大小与RTV的Tile数量一致、有全部mipmap。类似于HZB、每个像素存储LodBiasTexture的最小值以及最大值,各为8bit、存在RG两个通道上。
|
||||
- int32 MaxCPULevels:表示共需要在CPU端存储多少层level的数据。
|
||||
- TArray`<FVector2D>` TextureData:CPU端存储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的最小最大值的示意过程图:
|
||||

|
||||
|
||||
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:
|
||||

|
||||
OcclusionVolumes的构建在函数BuildOcclusionVolumes中,其基本思路为取MinMaxTexture中**CPU端的TextureData**的数据、获得每个Tile的高度最小最大值来创建该Tile的Bounds信息。
|
||||
|
||||
可以看到OcclusionVolumes是带有Lod的。当然实际上这里的代码的LodIndex不一定从0开始,因为Component中有一项成员变量**NumOcclusionLod**、表示创建多少层mipmap的OcclusionVolumes。另外有一点需要注意的是,NumOcclusionLod默认值为0、也就是说VHM的遮挡剔除默认没有开启。
|
||||

|
||||
|
||||
由于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中没有工作为止。示意图如下:
|
||||
|
||||

|
||||
|
||||
### RVT相关代码(Pass1:CollectQuad)
|
||||
如果不能细分,那么就会增加一个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代码如下图所示:
|
||||

|
||||
|
||||
c++中则要将这个RWFeedbackBuffer喂给虚幻的函数**SubmitVirtualTextureFeedbackBuffer**:
|
||||

|
||||
|
||||
### 相关代码段
|
||||
```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进行采样。
|
@@ -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);
|
||||
}
|
||||
```
|
170
03-UnrealEngine/Rendering/RenderingPipeline/Yivanlee卡通渲染笔记.md
Normal file
170
03-UnrealEngine/Rendering/RenderingPipeline/Yivanlee卡通渲染笔记.md
Normal 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
|
||||
- ToonDataA:R ShadowOffset GBA 未使用
|
||||
- ToonDataB:RGB OutlineColor A OutlineMask
|
||||
- ToonDataC:RGB IDMap A OutlineWidth
|
||||
- PreIntegratedToonBRDF: R 为NoL 预积分Ramp G 为GGX高光预积分值。
|
||||
- PreIntegratedToonSkinBRDF:RGB为皮肤预积分颜色。
|
||||
- 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*/
|
||||
```
|
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: 剖析虚幻渲染体系(09)- 材质体系
|
||||
date: 2024-02-04 21:44:37
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
# 前言
|
||||
https://www.cnblogs.com/timlly/p/15109132.html
|
@@ -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::EmitPreshaderOrConstant:PreshaderHeader = &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;
|
||||
```
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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。
|
||||
- FOpenGLDynamicRHI:OpenGL的动态RHI。
|
||||
- FD3D11DynamicRHI:D3D11的动态RHI。
|
||||
- FMetalRHICommandContext:Metal平台的命令上下文。
|
||||
- FD3D12CommandContextBase:D3D12的命令上下文。
|
||||
- FVulkanCommandListContext:Vulkan平台的命令队列上下文。
|
||||
- FEmptyDynamicRHI:动态绑定的RHI实现的接口。
|
||||
- FValidationContext:校验上下文。
|
||||
- IRHICommandContextContainer:IRHICommandContextContainer就是包含了IRHICommandContext对象的类型。相当于存储了一个或一组命令上下文的容器,以支持并行化地提交命令队列,只在D3D12、Metal、Vulkan等现代图形API中有实现。
|
||||
- D3D12存储了FD3D12Adapter* Adapter、FD3D12CommandContext* CmdContext、 FD3D12CommandContextRedirector* CmdContextRedirector。
|
||||
- FDynamicRHI:FDynamicRHI是由动态绑定的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()** 看到调用。
|
||||
|
||||
>需要注意的是,传统图形API(D3D11、OpenGL)除了继承FDynamicRHI,还需要继承**IRHICommandContextPSOFallback**,因为需要借助后者的接口处理PSO的数据和行为,以保证传统和现代API对PSO的一致处理行为。也正因为此,现代图形API(D3D12、Vulkan、Metal)不需要继承**IRHICommandContext**的任何继承体系的类型,单单直接继承**FDynamicRHI**就可以处理RHI层的所有数据和操作。
|
||||
既然现代图形API(D3D12、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采样读取。这种纹理采样方式不受任何限制,可以读取任意的领域像素,使用任意的纹理过滤方式。这种方式虽然使用灵活,但在TBR(Tile-Based Renderer)硬件架构的设备中会有较大的消耗:渲染纹理的Pass通常会将渲染结果存储在On-chip的Tile Memory中,待Pass结束后会写回GPU显存(VRAM)中,写回GPU显存是个耗时耗耗电的操作。
|
||||
|
||||

|
||||
|
||||
_传统多Pass之间的内存存取模型,多次发生于On-Chip和全局存储器之间。_
|
||||
|
||||
如果出现一种特殊的纹理使用情况:上一个Pass渲染处理的纹理,立即被下一个Pass使用,并且下一个Pass只采样像素位置自身的数据,而不需要采样邻域像素的位置。这种情况就符合了Subpass的使用情景。使用Subpass渲染的纹理结果只会存储在Tile Memory中,在Subpass结束后不会写回VRAM,而直接提供Tile Memory的数据给下一个Subpass采样读取。这样就避免了传统Pass结束写回GPU显存以及下一个Pass又从GPU显存读数据的耗时耗电操作,从而提升了性能。
|
||||
|
||||

|
||||
|
||||
_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;
|
||||
}
|
||||
};
|
||||
```
|
@@ -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。
|
||||
- FRDGResource:RDG资源并不是直接用RHI资源,而是包裹了RHI资源引用,然后针对不同类型的资源各自封装,且增加了额外的信息。
|
||||
- FRDGUniformBuffer、TRDGUniformBuffer
|
||||
- FRDGParentResource:一种由图跟踪分配生命周期的渲染图资源。可能有引用它的子资源(例如视图)
|
||||
- FRDGView
|
||||
- FRDGBuffer、FRDGBufferSRV、FRDGBufferUAV
|
||||
- FRDGViewableResource:一种由图跟踪分配生命周期的RPGResource。可能有引用它的子资源
|
||||
- FRDGTexture
|
||||
- FRDGView
|
||||
- FRDGUnorderedAccessView、FRDGTextureUAV
|
||||
- FRDGShaderResourceView、FRDGTextureSRV
|
||||
- FRDGTextureDesc:创建渲染纹理的描述信息。
|
||||
- FRDGPooledTexture:贴图池里的贴图资源。
|
||||
- FRDGPooledBuffer:池化的缓冲区。
|
||||
- FRHITransition:用于表示RHI中挂起的资源转换的**不透明表面材质**数据结构。
|
||||
- FRDGBarrierBatch:RDG屏障批。
|
||||
- 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`列出所有有效的纹理,输入之后可能显示如下所示的信息:
|
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: 剖析虚幻渲染体系(17)- 实时光线追踪
|
||||
date: 2024-02-05 22:02:57
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
# 前言
|
||||
原文地址:https://www.cnblogs.com/timlly/p/16687324.html
|
Reference in New Issue
Block a user