Optimize Global SDF tracing to not scale steps nearby geometry and properly trace mip cascade

This commit is contained in:
2025-12-16 16:07:46 +01:00
parent 3c8b80152b
commit 779e8e7169
+36 -41
View File
@@ -32,15 +32,13 @@ struct GlobalSDFTrace
float MinDistance;
float3 WorldDirection;
float MaxDistance;
float StepScale;
void Init(float3 worldPosition, float3 worldDirection, float minDistance, float maxDistance, float stepScale = 1.0f)
void Init(float3 worldPosition, float3 worldDirection, float minDistance, float maxDistance)
{
WorldPosition = worldPosition;
WorldDirection = worldDirection;
MinDistance = minDistance;
MaxDistance = maxDistance;
StepScale = stepScale;
}
};
@@ -73,7 +71,18 @@ void GetGlobalSDFCascadeUV(const GlobalSDFData data, uint cascade, float3 worldP
textureUV = float3(((float)cascade + cascadeUV.x) / (float)data.CascadesCount, cascadeUV.y, cascadeUV.z); // Cascades are placed next to each other on X axis
}
// Clamps Global SDF cascade UV to ensure it can be sued for gradient sampling (clamps first and last pixels).
void GetGlobalSDFCascadeUV(const GlobalSDFData data, uint cascade, float3 worldPosition, out float3 cascadeUV, out float3 textureUV, out float3 textureMipUV)
{
float4 cascadePosDistance = data.CascadePosDistance[cascade];
float3 posInCascade = worldPosition - cascadePosDistance.xyz;
float cascadeSize = cascadePosDistance.w * 2;
cascadeUV = saturate(posInCascade / cascadeSize + 0.5f);
textureUV = float3(((float)cascade + cascadeUV.x) / (float)data.CascadesCount, cascadeUV.y, cascadeUV.z); // Cascades are placed next to each other on X axis
float halfTexelOffsetMip = (GLOBAL_SDF_RASTERIZE_MIP_FACTOR * 0.5f) / data.Resolution;
textureMipUV = textureUV + float3(halfTexelOffsetMip / (float)data.CascadesCount, halfTexelOffsetMip, halfTexelOffsetMip); // Mipmaps are offset by half texel to sample correctly
}
// Clamps Global SDF cascade UV to ensure it can be used for gradient sampling (clamps first and last pixels).
void ClampGlobalSDFTextureGradientUV(const GlobalSDFData data, uint cascade, float texelOffset, inout float3 textureUV)
{
float cascadeSizeUV = 1.0f / data.CascadesCount;
@@ -142,13 +151,13 @@ float SampleGlobalSDF(const GlobalSDFData data, Texture3D<snorm float> tex, Text
startCascade = min(startCascade, data.CascadesCount - 1);
for (uint cascade = startCascade; cascade < data.CascadesCount; cascade++)
{
float3 cascadeUV, textureUV;
GetGlobalSDFCascadeUV(data, cascade, worldPosition, cascadeUV, textureUV);
float3 cascadeUV, textureUV, textureMipUV;
GetGlobalSDFCascadeUV(data, cascade, worldPosition, cascadeUV, textureUV, textureMipUV);
float voxelSize = data.CascadeVoxelSize[cascade];
float chunkSize = voxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE;
float chunkMargin = voxelSize * (GLOBAL_SDF_CHUNK_MARGIN_SCALE * GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN);
float maxDistanceMip = data.CascadeMaxDistanceMip[cascade];
float distanceMip = mip.SampleLevel(GLOBAL_SDF_SAMPLER, textureUV, 0);
float distanceMip = mip.SampleLevel(GLOBAL_SDF_SAMPLER, textureMipUV, 0);
if (distanceMip < chunkSize && all(cascadeUV > 0) && all(cascadeUV < 1))
{
distance = distanceMip * maxDistanceMip;
@@ -206,13 +215,13 @@ float3 SampleGlobalSDFGradient(const GlobalSDFData data, Texture3D<snorm float>
startCascade = min(startCascade, data.CascadesCount - 1);
for (uint cascade = startCascade; cascade < data.CascadesCount; cascade++)
{
float3 cascadeUV, textureUV;
GetGlobalSDFCascadeUV(data, cascade, worldPosition, cascadeUV, textureUV);
float3 cascadeUV, textureUV, textureMipUV;
GetGlobalSDFCascadeUV(data, cascade, worldPosition, cascadeUV, textureUV, textureMipUV);
float voxelSize = data.CascadeVoxelSize[cascade];
float chunkSize = voxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE;
float chunkMargin = voxelSize * (GLOBAL_SDF_CHUNK_MARGIN_SCALE * GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN);
float maxDistanceMip = data.CascadeMaxDistanceMip[cascade];
float distanceMip = mip.SampleLevel(GLOBAL_SDF_SAMPLER, textureUV, 0) * maxDistanceMip;
float distanceMip = mip.SampleLevel(GLOBAL_SDF_SAMPLER, textureMipUV, 0) * maxDistanceMip;
if (distanceMip < chunkSize && all(cascadeUV > 0) && all(cascadeUV < 1))
{
float maxDistanceTex = data.CascadeMaxDistanceTex[cascade];
@@ -234,13 +243,13 @@ float3 SampleGlobalSDFGradient(const GlobalSDFData data, Texture3D<snorm float>
{
distance = distanceMip;
float texelOffset = (float)GLOBAL_SDF_RASTERIZE_MIP_FACTOR / data.Resolution;
ClampGlobalSDFTextureGradientUV(data, cascade, texelOffset, textureUV);
float xp = mip.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureUV.x + texelOffset, textureUV.y, textureUV.z), 0).x;
float xn = mip.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureUV.x - texelOffset, textureUV.y, textureUV.z), 0).x;
float yp = mip.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureUV.x, textureUV.y + texelOffset, textureUV.z), 0).x;
float yn = mip.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureUV.x, textureUV.y - texelOffset, textureUV.z), 0).x;
float zp = mip.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureUV.x, textureUV.y, textureUV.z + texelOffset), 0).x;
float zn = mip.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureUV.x, textureUV.y, textureUV.z - texelOffset), 0).x;
ClampGlobalSDFTextureGradientUV(data, cascade, texelOffset, textureMipUV);
float xp = mip.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureMipUV.x + texelOffset, textureMipUV.y, textureMipUV.z), 0).x;
float xn = mip.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureMipUV.x - texelOffset, textureMipUV.y, textureMipUV.z), 0).x;
float yp = mip.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureMipUV.x, textureMipUV.y + texelOffset, textureMipUV.z), 0).x;
float yn = mip.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureMipUV.x, textureMipUV.y - texelOffset, textureMipUV.z), 0).x;
float zp = mip.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureMipUV.x, textureMipUV.y, textureMipUV.z + texelOffset), 0).x;
float zn = mip.SampleLevel(GLOBAL_SDF_SAMPLER, float3(textureMipUV.x, textureMipUV.y, textureMipUV.z - texelOffset), 0).x;
gradient = float3(xp - xn, yp - yn, zp - zn) * maxDistanceMip;
}
break;
@@ -291,33 +300,19 @@ GlobalSDFHit RayTraceGlobalSDF(const GlobalSDFData data, Texture3D<snorm float>
for (; step < 250 && stepTime < intersections.y && hit.HitTime < 0.0f; step++)
{
float3 stepPosition = trace.WorldPosition + trace.WorldDirection * stepTime;
float stepScale = trace.StepScale;
// Sample SDF
float stepDistance, voxelSizeScale = (float)GLOBAL_SDF_RASTERIZE_MIP_FACTOR;
float3 cascadeUV, textureUV;
GetGlobalSDFCascadeUV(data, cascade, stepPosition, cascadeUV, textureUV);
float distanceMip = mip.SampleLevel(GLOBAL_SDF_SAMPLER, textureUV, 0) * maxDistanceMip;
if (distanceMip < chunkSize)
{
stepDistance = distanceMip;
float distanceTex = tex.SampleLevel(GLOBAL_SDF_SAMPLER, textureUV, 0) * maxDistanceTex;
if (distanceTex < chunkMargin)
{
stepDistance = distanceTex;
voxelSizeScale = 1.0f;
stepScale *= 0.63f; // Perform smaller steps nearby geometry
}
}
else
{
// Assume no SDF nearby so perform a jump to the next chunk
stepDistance = chunkSize;
voxelSizeScale = 1.0f;
}
float stepDistance;
float3 cascadeUV, textureUV, textureMipUV;
GetGlobalSDFCascadeUV(data, cascade, stepPosition, cascadeUV, textureUV, textureMipUV);
stepDistance = min(mip.SampleLevel(GLOBAL_SDF_SAMPLER, textureMipUV, 0) * maxDistanceMip, chunkSize);
float distanceTex = tex.SampleLevel(GLOBAL_SDF_SAMPLER, textureUV, 0) * maxDistanceTex;
FLATTEN
if (distanceTex < chunkMargin)
stepDistance = distanceTex;
// Detect surface hit
float minSurfaceThickness = voxelSizeScale * voxelExtent * saturate(stepTime / voxelSize);
float minSurfaceThickness = voxelExtent * saturate(stepTime / voxelSize);
if (stepDistance < minSurfaceThickness)
{
// Surface hit
@@ -327,7 +322,7 @@ GlobalSDFHit RayTraceGlobalSDF(const GlobalSDFData data, Texture3D<snorm float>
}
// Move forward
stepTime += max(stepDistance * stepScale, voxelSize);
stepTime += max(stepDistance, voxelSize);
}
hit.StepsCount += step;
}