Files
UnrealEngine/Engine/Shaders/Private/DistanceFieldVisualization.usf
2025-05-18 13:04:45 +08:00

281 lines
12 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Common.ush"
#include "Visualization.ush"
#include "DeferredShadingCommon.ush"
#include "DistanceFieldLightingShared.ush"
#include "DistanceFieldAOShared.ush"
#include "DistanceField/GlobalDistanceFieldShared.ush"
#include "DistanceField/GlobalDistanceFieldUtils.ush"
#include "ReflectionEnvironmentShared.ush"
RWTexture2D<float4> RWVisualizeMeshDistanceFields;
#define MAX_INTERSECTING_OBJECTS 2048
groupshared uint IntersectingObjectIndices[MAX_INTERSECTING_OBJECTS];
groupshared uint NumIntersectingObjects;
#define SDF_TRACING_TRAVERSE_MIPS 1
void RayTraceThroughTileCulledDistanceFields(float3 TranslatedWorldRayStart, float3 WorldRayDirection, float MaxRayTime, out float MinRayTime, out float TotalStepsTaken, out uint HitCulledObjectIndex)
{
MinRayTime = MaxRayTime;
TotalStepsTaken = 0;
HitCulledObjectIndex = 0xFFFFFFFF;
LOOP
for (uint ListObjectIndex = 0; ListObjectIndex < min(NumIntersectingObjects, (uint)MAX_INTERSECTING_OBJECTS); ListObjectIndex++)
{
uint ObjectIndex = IntersectingObjectIndices[ListObjectIndex];
FDFObjectData DFObjectData = LoadDFObjectData(ObjectIndex);
float4x4 TranslatedWorldToVolume = DFFastToTranslatedWorld(DFObjectData.WorldToVolume, PrimaryView.PreViewTranslation);
float3 VolumeRayStart = mul(float4(TranslatedWorldRayStart, 1.0f), TranslatedWorldToVolume).xyz;
float3 TranslatedWorldRayEnd = TranslatedWorldRayStart + WorldRayDirection * MaxRayTime;
float3 VolumeRayEnd = mul(float4(TranslatedWorldRayEnd, 1.0f), TranslatedWorldToVolume).xyz;
float3 VolumeRayDirection = VolumeRayEnd - VolumeRayStart;
float VolumeRayLength = length(VolumeRayDirection);
VolumeRayDirection /= VolumeRayLength;
float2 VolumeSpaceIntersectionTimes = LineBoxIntersect(VolumeRayStart, VolumeRayEnd, -DFObjectData.VolumePositionExtent, DFObjectData.VolumePositionExtent) * VolumeRayLength;
if (VolumeSpaceIntersectionTimes.x < VolumeSpaceIntersectionTimes.y && VolumeSpaceIntersectionTimes.x < VolumeRayLength)
{
uint ReversedMipIndex = 0;
FDFAssetData DFAssetMipData = LoadDFAssetData(DFObjectData.AssetIndex, ReversedMipIndex);
uint MaxMipIndex = DFAssetMipData.NumMips - 1;
#if !SDF_TRACING_TRAVERSE_MIPS
ReversedMipIndex = MaxMipIndex;
DFAssetMipData = LoadDFAssetData(DFObjectData.AssetIndex, ReversedMipIndex);
#endif
float SampleRayTime = VolumeSpaceIntersectionTimes.x;
uint StepIndex = 0;
uint MaxSteps = 64;
bool bHit = false;
LOOP
for (; StepIndex < MaxSteps; StepIndex++)
{
float3 SampleVolumePosition = VolumeRayStart + VolumeRayDirection * SampleRayTime;
float DistanceField = SampleSparseMeshSignedDistanceField(SampleVolumePosition, DFAssetMipData);
// Expand the surface to find thin features, but only away from the start of the trace where it won't introduce incorrect self-occlusion
// This still causes incorrect self-occlusion at grazing angles
float ExpandSurfaceDistance = DFObjectData.VolumeSurfaceBias;
const float ExpandSurfaceFalloff = 2.0f * ExpandSurfaceDistance;
const float ExpandSurfaceAmount = ExpandSurfaceDistance * saturate(SampleRayTime / ExpandSurfaceFalloff);
#if SDF_TRACING_TRAVERSE_MIPS
float MaxEncodedDistance = DFAssetMipData.DistanceFieldToVolumeScaleBias.x + DFAssetMipData.DistanceFieldToVolumeScaleBias.y;
if (abs(DistanceField) > MaxEncodedDistance && ReversedMipIndex > 0)
{
ReversedMipIndex--;
DFAssetMipData = LoadDFAssetData(DFObjectData.AssetIndex, ReversedMipIndex);
}
else if (abs(DistanceField) < .25f * MaxEncodedDistance && ReversedMipIndex < MaxMipIndex)
{
DistanceField -= 6.0f * DFObjectData.VolumeSurfaceBias;
ReversedMipIndex++;
DFAssetMipData = LoadDFAssetData(DFObjectData.AssetIndex, ReversedMipIndex);
}
else
#endif
// Terminate the trace if we reached a negative area or went past the end of the ray
if (DistanceField < ExpandSurfaceAmount && ReversedMipIndex == MaxMipIndex)
{
bHit = true;
// One more step back to the surface
SampleRayTime = clamp(SampleRayTime + DistanceField - ExpandSurfaceAmount, VolumeSpaceIntersectionTimes.x, VolumeSpaceIntersectionTimes.y);
break;
}
float MinStepSize = 1.0f / (16 * MaxSteps);
float StepDistance = max(DistanceField, MinStepSize);
SampleRayTime += StepDistance;
if (SampleRayTime > VolumeSpaceIntersectionTimes.y + ExpandSurfaceAmount)
{
break;
}
}
if (bHit || StepIndex == MaxSteps)
{
const float NewHitDistance = length(VolumeRayDirection * SampleRayTime * DFObjectData.VolumeToWorldScale);
if (NewHitDistance < MinRayTime)
{
HitCulledObjectIndex = ObjectIndex;
MinRayTime = NewHitDistance;
}
}
TotalStepsTaken += max(StepIndex, 1u);
}
}
}
float2 NumGroups;
[numthreads(THREADGROUP_SIZEX, THREADGROUP_SIZEY, 1)]
void VisualizeMeshDistanceFieldCS(
uint3 GroupId : SV_GroupID,
uint3 DispatchThreadId : SV_DispatchThreadID,
uint3 GroupThreadId : SV_GroupThreadID)
{
uint ThreadIndex = GroupThreadId.y * THREADGROUP_SIZEX + GroupThreadId.x;
float2 ScreenUV = float2((DispatchThreadId.xy * DOWNSAMPLE_FACTOR + View.ViewRectMin.xy + .5f) * View.BufferSizeAndInvSize.zw);
float2 ScreenPosition = (ScreenUV.xy - View.ScreenPositionScaleBias.wz) / View.ScreenPositionScaleBias.xy;
float SceneDepth = CalcSceneDepth(ScreenUV);
float3 TranslatedWorldPosition = mul(float4(GetScreenPositionForProjectionType(ScreenPosition, SceneDepth), SceneDepth, 1), View.ScreenToTranslatedWorld).xyz;
float TraceDistance = 100000.0f;
float3 TranslatedWorldRayStart = PrimaryView.TranslatedWorldCameraOrigin;
float3 WorldRayDirection = normalize(TranslatedWorldPosition - View.TranslatedWorldCameraOrigin);
float3 WorldHitNormal = float3(0, 0, 1);
bool bHit = false;
#if USE_GLOBAL_DISTANCE_FIELD
FGlobalSDFTraceInput TraceInput = SetupGlobalSDFTraceInput(TranslatedWorldRayStart, WorldRayDirection, 0.0f, TraceDistance, 1.0f, 1.0f);
FGlobalSDFTraceResult SDFTraceResult = RayTraceGlobalDistanceField(TraceInput);
if (GlobalSDFTraceResultIsHit(SDFTraceResult))
{
float3 TranslatedWorldRayEnd = TranslatedWorldRayStart + WorldRayDirection * SDFTraceResult.HitTime;
WorldHitNormal = ComputeGlobalDistanceFieldNormal(TranslatedWorldRayEnd, SDFTraceResult.HitClipmapIndex, -WorldRayDirection);
bHit = true;
}
#else
if (ThreadIndex == 0)
{
NumIntersectingObjects = 0;
}
GroupMemoryBarrierWithGroupSync();
float3 TileConeTranslatedVertex;
float3 TileConeAxis;
float TileConeAngleCos;
float TileConeAngleSin;
{
float2 ViewSize = float2(1 / View.ViewToClip[0][0], 1 / View.ViewToClip[1][1]);
float3 TileCorner00 = normalize(float3((GroupId.x + 0) / NumGroups.x * ViewSize.x * 2 - ViewSize.x, ViewSize.y - (GroupId.y + 0) / NumGroups.y * ViewSize.y * 2, 1));
float3 TileCorner10 = normalize(float3((GroupId.x + 1) / NumGroups.x * ViewSize.x * 2 - ViewSize.x, ViewSize.y - (GroupId.y + 0) / NumGroups.y * ViewSize.y * 2, 1));
float3 TileCorner01 = normalize(float3((GroupId.x + 0) / NumGroups.x * ViewSize.x * 2 - ViewSize.x, ViewSize.y - (GroupId.y + 1) / NumGroups.y * ViewSize.y * 2, 1));
float3 TileCorner11 = normalize(float3((GroupId.x + 1) / NumGroups.x * ViewSize.x * 2 - ViewSize.x, ViewSize.y - (GroupId.y + 1) / NumGroups.y * ViewSize.y * 2, 1));
float3 ViewSpaceTileConeAxis = normalize(TileCorner00 + TileCorner10 + TileCorner01 + TileCorner11);
TileConeAxis = mul(ViewSpaceTileConeAxis, (float3x3)View.ViewToTranslatedWorld);
TileConeAngleCos = min(min(dot(ViewSpaceTileConeAxis, TileCorner00), dot(ViewSpaceTileConeAxis, TileCorner10)), min(dot(ViewSpaceTileConeAxis, TileCorner01), dot(ViewSpaceTileConeAxis, TileCorner11)));
TileConeAngleSin = sqrt(1 - TileConeAngleCos * TileConeAngleCos);
TileConeTranslatedVertex = PrimaryView.TranslatedWorldCameraOrigin;
}
uint NumCulledObjects = GetCulledNumObjects();
LOOP
for (uint IndexInCulledList = ThreadIndex; IndexInCulledList < NumCulledObjects; IndexInCulledList += THREADGROUP_TOTALSIZE)
{
const uint ObjectIndex = CulledObjectIndices[IndexInCulledList];
FDFObjectBounds ObjectBounds = LoadDFObjectBounds(ObjectIndex);
float4 SphereTranslatedCenterAndRadius = float4(DFFastAddDemote(ObjectBounds.Center, PrimaryView.PreViewTranslation), ObjectBounds.SphereRadius);
BRANCH
if (SphereIntersectCone(SphereTranslatedCenterAndRadius, TileConeTranslatedVertex, TileConeAxis, TileConeAngleCos, TileConeAngleSin))
{
uint ListIndex;
InterlockedAdd(NumIntersectingObjects, 1U, ListIndex);
if (ListIndex < MAX_INTERSECTING_OBJECTS)
{
IntersectingObjectIndices[ListIndex] = ObjectIndex;
}
}
}
GroupMemoryBarrierWithGroupSync();
float MinRayTime;
float TotalStepsTaken;
uint HitCulledObjectIndex;
// Trace once to find the distance to first intersection
RayTraceThroughTileCulledDistanceFields(TranslatedWorldRayStart, WorldRayDirection, TraceDistance, MinRayTime, TotalStepsTaken, HitCulledObjectIndex);
if (HitCulledObjectIndex != 0xFFFFFFFF)
{
FDFObjectData DFObjectData = LoadDFObjectData(HitCulledObjectIndex);
float4x4 TranslatedWorldToVolume = DFFastToTranslatedWorld(DFObjectData.WorldToVolume, PrimaryView.PreViewTranslation);
float3 TranslatedWorldRayEnd = TranslatedWorldRayStart + WorldRayDirection * MinRayTime;
float3 VolumeHitPosition = mul(float4(TranslatedWorldRayEnd, 1.0f), TranslatedWorldToVolume).xyz;
//VolumeHitPosition = clamp(VolumeHitPosition, -DFObjectData.VolumePositionExtent, DFObjectData.VolumePositionExtent);
uint NumMips = LoadDFAssetData(DFObjectData.AssetIndex, 0).NumMips;
FDFAssetData DFAssetData = LoadDFAssetData(DFObjectData.AssetIndex, NumMips - 1);
float3 VolumeGradient = CalculateMeshSDFGradient(VolumeHitPosition, DFAssetData);
float VolumeGradientLength = length(VolumeGradient);
float3 VolumeNormal = VolumeGradientLength > 0.0f ? VolumeGradient / VolumeGradientLength : 0;
float3 WorldGradient = mul(VolumeNormal, transpose((float3x3)DFObjectData.WorldToVolume.M));
float WorldGradientLength = length(WorldGradient);
WorldHitNormal = WorldGradientLength > 0.0f ? WorldGradient / WorldGradientLength : 0;
bHit = true;
}
#endif
float3 Lighting = 0;
float3 DirectionalLightColor = 10;
float3 DirectionalLightDirection = float3(.707f, 0, .707f);
if (ForwardLightStruct.HasDirectionalLight)
{
DirectionalLightColor = ForwardLightStruct.DirectionalLightColor;
DirectionalLightDirection = ForwardLightStruct.DirectionalLightDirection;
}
if (bHit)
{
// Compute simple lighting to help visualize errors in either position or normal in a way that is intuitive to artists
Lighting += DirectionalLightColor * saturate(dot(DirectionalLightDirection, WorldHitNormal));
Lighting += GetSkySHDiffuse(WorldHitNormal) * View.SkyLightColor.rgb;
float3 Albedo = .05f;
#if !USE_GLOBAL_DISTANCE_FIELD && !MONOCHROME_VISUALIZATION
{
FDFObjectData DFObjectData = LoadDFObjectData(HitCulledObjectIndex);
Albedo = 0.1f * IntToColor(DFObjectData.GPUSceneInstanceIndex + 1);
}
#endif
Lighting *= Albedo;
}
else if (ReflectionStruct.SkyLightParameters.y > 0)
{
float SkyAverageBrightness = 1.0f;
float Roughness = 0.0f;
Lighting += GetSkyLightReflection(WorldRayDirection, Roughness, SkyAverageBrightness);
}
float3 Result = Lighting * View.PreExposure;
RWVisualizeMeshDistanceFields[DispatchThreadId.xy + View.ViewRectMin.xy / DOWNSAMPLE_FACTOR] = float4(Result, 0);
}
Texture2D VisualizeDistanceFieldTexture;
SamplerState VisualizeDistanceFieldSampler;
void VisualizeDistanceFieldUpsamplePS(in float4 UVAndScreenPos : TEXCOORD0, out float4 OutColor : SV_Target0)
{
float3 Value = Texture2DSampleLevel(VisualizeDistanceFieldTexture, VisualizeDistanceFieldSampler, UVAndScreenPos.xy, 0).xyz;
OutColor = float4(Value, 1);
}