1.UE的DistanceField

距离场的生成,有MeshDistanceField和GlobalDistanceField

UE的主要应用是DFShadow、DFAO、材质DistanceFieldToNearest

https://dev.epicgames.com/documentation/en-us/unreal-engine/mesh-distance-fields-in-unreal-engine

2.DFShadow流程

SceneProxy 收集MeshBatch

DistanceField数据上传

这一步上传所有SceneProxy的相关距离场数据,一堆float4的数据。
每帧有Remove,Update,Add的处理。

上传Buffer用的是一个ComputeShader,后来改引擎的时候又加了一个ComputeShader用来单独上传Buffer里的某段数据,来更新东西实现功能。

DFShadow计算

DFShadow在光照阶段计算,共有3步:剔除划分->计算阴影->上采样并合并CSM

1.CullObjectsForLight-剔除划分Tile阶段

CullObjectToFrustum :
使用CSM的视锥平面剔除Object、HISM同步Cull也是在这里剔除掉的

————使用的CullObjectsForShadowCS这个CS剔除,没剔除的就把这些数据Copy到新的Buffer里

ComputeTileStartOffsets:计算每个Tile相交的Object,并把Object分配到这些Tile里,记录下Tile的索引Offset

————第一次ScatterObjectsToShadowTiles:计算出来每个Tile里有多少个Object

————ComputeCulledTileStartOffsetCS:计算每个Tile的索引Offset

CullObjectsToTiles:

————第二次ScatterObjectsToShadowTiles:前面知道了每个Tile的Object数量和Tile的Offset,这里最后主要是把RWShadowTileArrayData[DataIndex]填充了,它的长度是所有Tile的Object数量之和,它的值是ObjectIndex(就是CullObjectToFrustum这步的CopyData的索引)

RenderDoc上看的流程:

2.DFShadow计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
[numthreads(THREADGROUP_SIZEX, THREADGROUP_SIZEY, 1)]
void DistanceFieldShadowingCS(
uint3 GroupId : SV_GroupID,
uint3 DispatchThreadId : SV_DispatchThreadID,
uint3 GroupThreadId : SV_GroupThreadID)
{
//和其他ComputeShader算法一样,转换DispatchThreadId为屏幕UV和屏幕坐标
uint ThreadIndex = GroupThreadId.y * THREADGROUP_SIZEX + GroupThreadId.x;
float2 ScreenUV = float2((DispatchThreadId.xy * DownsampleFactor + ScissorRectMinAndSize.xy + .5f) * View.BufferSizeAndInvSize.zw);
float2 ScreenPosition = (ScreenUV.xy - View.ScreenPositionScaleBias.wz) / View.ScreenPositionScaleBias.xy;

//深度转换为世界坐标
float SceneDepth = CalcSceneDepth(ScreenUV);
float3 OpaqueWorldPosition = mul(float4(ScreenPosition * SceneDepth, SceneDepth, 1), View.ScreenToWorld).xyz;

//得到DFTrace的射线起点和终点,其实就是往灯光方向去打射线,和ContactShadow一样
float3 WorldRayStart = OpaqueWorldPosition + LightDirection * RayStartOffset;
float3 WorldRayEnd = OpaqueWorldPosition + LightDirection * TraceDistance;

//遍历所有的CullObject,先判断射线是否与物体包围盒相交
//IntersectionTimes得到最近交点和最远交点
float2 IntersectionTimes = LineBoxIntersect(...);

BRANCH
if (IntersectionTimes.x < IntersectionTimes.y)
{
//如果有相交,就在包围盒内RayMarching,逐步采样距离场,如果值小于一个阈值,代表很接近这个物体了,可以结束循环,此时距离场信息作为阴影的值。如果已经RayMarch超出包围盒也结束循环。
LOOP
for (; StepIndex < MaxSteps; StepIndex++)
{
float3 SampleVolumePosition = VolumeRayStart + VolumeRayDirection * SampleRayTime;
float3 ClampedSamplePosition = clamp(SampleVolumePosition, -LocalPositionExtent, LocalPositionExtent);
float DistanceToClamped = length(ClampedSamplePosition - SampleVolumePosition);
float3 VolumeUV = DistanceFieldVolumePositionToUV(ClampedSamplePosition, UVScaleAndVolumeScale.xyz, UVAddAndSelfShadowBias.xyz);
float DistanceField = SampleMeshDistanceField(VolumeUV, DistanceFieldMAD).x + DistanceToClamped;

MinDistance = min(MinDistance, DistanceField);

float SphereRadius = clamp(TanLightAngle * SampleRayTime, VolumeMinSphereRadius, VolumeMaxSphereRadius);
float StepVisibility = max(saturate(DistanceField / SphereRadius), SelfShadowVisibility);

MinConeVisibility = min(MinConeVisibility, StepVisibility);

float StepDistance = max(abs(DistanceField), MinStepSize);
SampleRayTime += StepDistance;

// Terminate the trace if we are fully occluded or went past the end of the ray
if (MinConeVisibility < .01f
|| SampleRayTime > IntersectionTimes.y * VolumeRayLength)
{
break;
}
}
MinConeVisibility = min(MinConeVisibility, (1 - StepIndex / (float)MaxSteps));
}

return MinConeVisibility;
}

3.UpSample 和 合并CSM

RenderRayTracedDistanceFieldProjection()
DFshadow半分辨率计算结果,这里需要上采样,中间还穿插了和CSM进行Fade的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void DistanceFieldShadowingUpsamplePS(
in float4 UVAndScreenPos : TEXCOORD0,
in float4 SVPos : SV_POSITION,
out float4 OutColor : SV_Target0)
{
float2 DistanceFieldUVs = UVAndScreenPos.xy - ScissorRectMinAndSize.xy * View.BufferSizeAndInvSize.zw;
float SceneDepth = CalcSceneDepth(UVAndScreenPos.xy);

float Output = Texture2DSampleLevel(ShadowFactorsTexture, ShadowFactorsSampler, DistanceFieldUVs, 0).x;
float FarBlendFactor = 1.0f - saturate((SceneDepth - FadePlaneOffset) * InvFadePlaneLength);
Output = lerp(1, Output, FarBlendFactor);

float NearBlendFactor = saturate((SceneDepth - NearFadePlaneOffset) * InvNearFadePlaneLength);
Output = lerp(1, Output, NearBlendFactor);

OutColor = EncodeLightAttenuation(half4(Output, Output, Output, Output));
}