From 6b06f1dbcf6c40f1e313a0c6718566692a9407e9 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 10 Jul 2024 13:22:25 +0200 Subject: [PATCH] Optimize Global SDF to use 8-bit storage (50% less memory usage) --- .../Graphics/Materials/MaterialShader.h | 2 +- .../Renderer/GlobalSignDistanceFieldPass.cpp | 70 +++++---- .../Renderer/GlobalSignDistanceFieldPass.h | 2 + .../Engine/Visject/ShaderGraphUtilities.cpp | 2 +- Source/Shaders/GI/DDGI.shader | 8 +- Source/Shaders/GI/GlobalSurfaceAtlas.shader | 8 +- Source/Shaders/GlobalSignDistanceField.hlsl | 138 ++++++++++-------- Source/Shaders/GlobalSignDistanceField.shader | 37 +++-- Source/Shaders/SSR.shader | 4 +- 9 files changed, 151 insertions(+), 120 deletions(-) diff --git a/Source/Engine/Graphics/Materials/MaterialShader.h b/Source/Engine/Graphics/Materials/MaterialShader.h index 3bea7a2ec..4b3f296a5 100644 --- a/Source/Engine/Graphics/Materials/MaterialShader.h +++ b/Source/Engine/Graphics/Materials/MaterialShader.h @@ -10,7 +10,7 @@ /// /// Current materials shader version. /// -#define MATERIAL_GRAPH_VERSION 167 +#define MATERIAL_GRAPH_VERSION 168 class Material; class GPUShader; diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index 8e176f849..cf2b26d44 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -21,7 +21,7 @@ #include "Engine/Threading/JobSystem.h" // Some of those constants must match in shader -#define GLOBAL_SDF_FORMAT PixelFormat::R16_Float +#define GLOBAL_SDF_FORMAT PixelFormat::R8_SNorm #define GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT 28 // The maximum amount of models to rasterize at once as a batch into Global SDF. #define GLOBAL_SDF_RASTERIZE_HEIGHTFIELD_MAX_COUNT 2 // The maximum amount of heightfields to store in a single chunk. #define GLOBAL_SDF_RASTERIZE_GROUP_SIZE 8 @@ -71,10 +71,13 @@ GPU_CB_STRUCT(ModelsRasterizeData { int32 CascadeMipResolution; int32 CascadeMipFactor; uint32 Objects[GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT]; - uint32 GenerateMipTexResolution; - uint32 GenerateMipCoordScale; - uint32 GenerateMipTexOffsetX; - uint32 GenerateMipMipOffsetX; + Float2 Padding10; + float MipMaxDistanceLoad; + float MipMaxDistanceStore; + uint32 MipTexResolution; + uint32 MipCoordScale; + uint32 MipTexOffsetX; + uint32 MipMipOffsetX; }); struct RasterizeChunk @@ -133,9 +136,11 @@ struct CascadeData bool Dirty; int32 Index; float ChunkSize; - float MaxDistance; + float MaxDistanceTex; + float MaxDistanceMip; Float3 Position; float VoxelSize; + float Extent; BoundingBox Bounds; BoundingBox CullingBounds; BoundingBox RasterizeBounds; @@ -315,14 +320,14 @@ public: cascade.Dirty = !useCache || RenderTools::ShouldUpdateCascade(FrameIndex, cascadeIndex, cascadesCount, maxCascadeUpdatesPerFrame, updateEveryFrame); if (!cascade.Dirty) continue; - const float cascadeDistance = distanceExtent * CascadesDistanceScales[cascadeIndex]; - const float cascadeMaxDistance = cascadeDistance * 2; - const float cascadeVoxelSize = cascadeMaxDistance / (float)resolution; + const float cascadeExtent = distanceExtent * CascadesDistanceScales[cascadeIndex]; + const float cascadeSize = cascadeExtent * 2; + const float cascadeVoxelSize = cascadeSize / (float)resolution; const float cascadeChunkSize = cascadeVoxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; static_assert(GLOBAL_SDF_RASTERIZE_CHUNK_SIZE % GLOBAL_SDF_RASTERIZE_MIP_FACTOR == 0, "Adjust chunk size to match the mip factor scale."); const Float3 center = Float3::Floor(viewPosition / cascadeChunkSize) * cascadeChunkSize; //const Float3 center = Float3::Zero; - BoundingBox cascadeBounds(center - cascadeDistance, center + cascadeDistance); + BoundingBox cascadeBounds(center - cascadeExtent, center + cascadeExtent); // Clear cascade before rasterization cascade.Chunks.Clear(); @@ -342,8 +347,12 @@ public: // Setup cascade info cascade.Position = center; cascade.VoxelSize = cascadeVoxelSize; - cascade.ChunkSize = cascadeVoxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; - cascade.MaxDistance = cascadeMaxDistance; + cascade.Extent = cascadeExtent; + cascade.ChunkSize = cascadeChunkSize; + cascade.MaxDistanceTex = cascadeChunkSize * 1.5f; // Encodes SDF distance to [-maxDst; +maxDst] to be packed as normalized value, limits the max SDF trace step distance + cascade.MaxDistanceMip = cascade.MaxDistanceTex * 2.0f; // Encode mip distance with less but covers larger area for faster jumps during tracing + cascade.MaxDistanceTex = Math::Min(cascade.MaxDistanceTex, cascadeSize); + cascade.MaxDistanceMip = Math::Min(cascade.MaxDistanceMip, cascadeSize); cascade.Bounds = cascadeBounds; cascade.RasterizeBounds = cascadeBounds; cascade.RasterizeBounds.Minimum += 0.1f; // Adjust to prevent overflowing chunk keys (cascade bounds are used for clamping object bounds) @@ -814,7 +823,7 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex ModelsRasterizeData data; data.CascadeCoordToPosMul = (Float3)cascade.Bounds.GetSize() / (float)resolution; data.CascadeCoordToPosAdd = (Float3)cascade.Bounds.Minimum + cascade.VoxelSize * 0.5f; - data.MaxDistance = cascade.MaxDistance; + data.MaxDistance = cascade.MaxDistanceTex; data.CascadeResolution = resolution; data.CascadeMipResolution = resolutionMip; data.CascadeIndex = cascadeIndex; @@ -986,17 +995,20 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex GPUTextureView* tmpMipView = tmpMip->ViewVolume(); // Tex -> Mip - data.GenerateMipTexResolution = data.CascadeResolution; - data.GenerateMipCoordScale = data.CascadeMipFactor; - data.GenerateMipTexOffsetX = data.CascadeIndex * data.CascadeResolution; - data.GenerateMipMipOffsetX = data.CascadeIndex * data.CascadeMipResolution; + data.MipMaxDistanceLoad = cascade.MaxDistanceTex; // Decode tex distance within chunk (more precision, for detailed tracing nearby geometry) + data.MipMaxDistanceStore = cascade.MaxDistanceMip; // Encode mip distance within whole volume (less precision, for fast jumps over empty spaces) + data.MipTexResolution = data.CascadeResolution; + data.MipCoordScale = data.CascadeMipFactor; + data.MipTexOffsetX = data.CascadeIndex * data.CascadeResolution; + data.MipMipOffsetX = data.CascadeIndex * data.CascadeMipResolution; context->UpdateCB(_cb1, &data); context->BindSR(0, textureView); context->BindUA(0, textureMipView); context->Dispatch(_csGenerateMip, mipDispatchGroups, mipDispatchGroups, mipDispatchGroups); - data.GenerateMipTexResolution = data.CascadeMipResolution; - data.GenerateMipCoordScale = 1; + data.MipTexResolution = data.CascadeMipResolution; + data.MipCoordScale = 1; + data.MipMaxDistanceLoad = data.MipMaxDistanceStore; for (int32 i = 1; i < floodFillIterations; i++) { context->ResetUA(); @@ -1005,16 +1017,16 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex // Mip -> Tmp context->BindSR(0, textureMipView); context->BindUA(0, tmpMipView); - data.GenerateMipTexOffsetX = data.CascadeIndex * data.CascadeMipResolution; - data.GenerateMipMipOffsetX = 0; + data.MipTexOffsetX = data.CascadeIndex * data.CascadeMipResolution; + data.MipMipOffsetX = 0; } else { // Tmp -> Mip context->BindSR(0, tmpMipView); context->BindUA(0, textureMipView); - data.GenerateMipTexOffsetX = 0; - data.GenerateMipMipOffsetX = data.CascadeIndex * data.CascadeMipResolution; + data.MipTexOffsetX = 0; + data.MipMipOffsetX = data.CascadeIndex * data.CascadeMipResolution; } context->UpdateCB(_cb1, &data); context->Dispatch(_csGenerateMip, mipDispatchGroups, mipDispatchGroups, mipDispatchGroups); @@ -1038,17 +1050,17 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++) { auto& cascade = sdfData.Cascades[cascadeIndex]; - const float cascadeDistance = distanceExtent * sdfData.CascadesDistanceScales[cascadeIndex]; - const float cascadeMaxDistance = cascadeDistance * 2; - const float cascadeVoxelSize = cascadeMaxDistance / (float)resolution; - const Float3 center = cascade.Position; - result.Constants.CascadePosDistance[cascadeIndex] = Vector4(center, cascadeDistance); - result.Constants.CascadeVoxelSize.Raw[cascadeIndex] = cascadeVoxelSize; + const float cascadeExtent = distanceExtent * sdfData.CascadesDistanceScales[cascadeIndex]; + result.Constants.CascadePosDistance[cascadeIndex] = Vector4(cascade.Position, cascadeExtent); + result.Constants.CascadeVoxelSize.Raw[cascadeIndex] = cascade.VoxelSize; + result.Constants.CascadeMaxDistance.Raw[cascadeIndex] = cascade.MaxDistanceTex; + result.Constants.CascadeMaxDistanceMip.Raw[cascadeIndex] = cascade.MaxDistanceMip; } for (int32 cascadeIndex = cascadesCount; cascadeIndex < 4; cascadeIndex++) { result.Constants.CascadePosDistance[cascadeIndex] = result.Constants.CascadePosDistance[cascadesCount - 1]; result.Constants.CascadeVoxelSize.Raw[cascadeIndex] = result.Constants.CascadeVoxelSize.Raw[cascadesCount - 1]; + result.Constants.CascadeMaxDistance.Raw[cascadeIndex] = result.Constants.CascadeMaxDistance.Raw[cascadesCount - 1]; } result.Constants.Resolution = (float)resolution; result.Constants.CascadesCount = cascadesCount; diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h index 104aae790..52a3708b8 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h @@ -15,6 +15,8 @@ public: GPU_CB_STRUCT(ConstantsData { Float4 CascadePosDistance[4]; Float4 CascadeVoxelSize; + Float4 CascadeMaxDistance; + Float4 CascadeMaxDistanceMip; Float2 Padding; uint32 CascadesCount; float Resolution; diff --git a/Source/Engine/Visject/ShaderGraphUtilities.cpp b/Source/Engine/Visject/ShaderGraphUtilities.cpp index 89e25d9d6..57d8f55ae 100644 --- a/Source/Engine/Visject/ShaderGraphUtilities.cpp +++ b/Source/Engine/Visject/ShaderGraphUtilities.cpp @@ -170,7 +170,7 @@ const Char* ShaderGraphUtilities::GenerateShaderResources(TextWriterUnicode& wri format = TEXT("Texture3D {0} : register(t{1});"); break; case MaterialParameterType::GlobalSDF: - format = TEXT("Texture3D {0}_Tex : register(t{1});\nTexture3D {0}_Mip : register(t{2});"); + format = TEXT("Texture3D {0}_Tex : register(t{1});\nTexture3D {0}_Mip : register(t{2});"); zeroOffset = false; registers = 2; break; diff --git a/Source/Shaders/GI/DDGI.shader b/Source/Shaders/GI/DDGI.shader index 4eb71a7ae..9344600b5 100644 --- a/Source/Shaders/GI/DDGI.shader +++ b/Source/Shaders/GI/DDGI.shader @@ -73,8 +73,8 @@ uint GetProbeRaysCount(DDGIData data, uint probeState) RWTexture2D RWProbesData : register(u0); RWByteAddressBuffer RWActiveProbes : register(u1); -Texture3D GlobalSDFTex : register(t0); -Texture3D GlobalSDFMip : register(t1); +Texture3D GlobalSDFTex : register(t0); +Texture3D GlobalSDFMip : register(t1); float3 Remap(float3 value, float3 fromMin, float3 fromMax, float3 toMin, float3 toMax) { @@ -288,8 +288,8 @@ void CS_UpdateProbesInitArgs() RWTexture2D RWProbesTrace : register(u0); -Texture3D GlobalSDFTex : register(t0); -Texture3D GlobalSDFMip : register(t1); +Texture3D GlobalSDFTex : register(t0); +Texture3D GlobalSDFMip : register(t1); ByteAddressBuffer GlobalSurfaceAtlasChunks : register(t2); ByteAddressBuffer RWGlobalSurfaceAtlasCulledObjects : register(t3); Buffer GlobalSurfaceAtlasObjects : register(t4); diff --git a/Source/Shaders/GI/GlobalSurfaceAtlas.shader b/Source/Shaders/GI/GlobalSurfaceAtlas.shader index a81383dcf..2a762f5be 100644 --- a/Source/Shaders/GI/GlobalSurfaceAtlas.shader +++ b/Source/Shaders/GI/GlobalSurfaceAtlas.shader @@ -91,8 +91,8 @@ Texture2D ProbesData : register(t5); Texture2D ProbesDistance : register(t6); Texture2D ProbesIrradiance : register(t7); #else -Texture3D GlobalSDFTex : register(t5); -Texture3D GlobalSDFMip : register(t6); +Texture3D GlobalSDFTex : register(t5); +Texture3D GlobalSDFMip : register(t6); #endif // Pixel shader for Global Surface Atlas shading @@ -289,8 +289,8 @@ void CS_CullObjects(uint3 DispatchThreadId : SV_DispatchThreadID) #ifdef _PS_Debug -Texture3D GlobalSDFTex : register(t0); -Texture3D GlobalSDFMip : register(t1); +Texture3D GlobalSDFTex : register(t0); +Texture3D GlobalSDFMip : register(t1); ByteAddressBuffer GlobalSurfaceAtlasChunks : register(t2); ByteAddressBuffer GlobalSurfaceAtlasCulledObjects : register(t3); Buffer GlobalSurfaceAtlasObjects : register(t4); diff --git a/Source/Shaders/GlobalSignDistanceField.hlsl b/Source/Shaders/GlobalSignDistanceField.hlsl index cafb02869..95f8e6f5b 100644 --- a/Source/Shaders/GlobalSignDistanceField.hlsl +++ b/Source/Shaders/GlobalSignDistanceField.hlsl @@ -3,8 +3,10 @@ #include "./Flax/Common.hlsl" #include "./Flax/Collisions.hlsl" +// This must match C++ #define GLOBAL_SDF_RASTERIZE_CHUNK_SIZE 32 #define GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN 4 +#define GLOBAL_SDF_RASTERIZE_MIP_FACTOR 4 #define GLOBAL_SDF_MIP_FLOODS 5 #define GLOBAL_SDF_WORLD_SIZE 60000.0f #define GLOBAL_SDF_MIN_VALID 0.9f @@ -15,6 +17,8 @@ struct GlobalSDFData { float4 CascadePosDistance[4]; float4 CascadeVoxelSize; + float4 CascadeMaxDistanceTex; + float4 CascadeMaxDistanceMip; float2 Padding; uint CascadesCount; float Resolution; @@ -61,13 +65,13 @@ struct GlobalSDFHit } }; -void GetGlobalSDFCascadeUV(const GlobalSDFData data, uint cascade, float3 worldPosition, out float cascadeMaxDistance, out float3 cascadeUV, out float3 textureUV) +void GetGlobalSDFCascadeUV(const GlobalSDFData data, uint cascade, float3 worldPosition, out float cascadeSize, out float3 cascadeUV, out float3 textureUV) { float4 cascadePosDistance = data.CascadePosDistance[cascade]; float3 posInCascade = worldPosition - cascadePosDistance.xyz; - cascadeMaxDistance = cascadePosDistance.w * 2; - cascadeUV = saturate(posInCascade / cascadeMaxDistance + 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 + 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 } // Gets the Global SDF cascade index for the given world location. @@ -75,9 +79,9 @@ uint GetGlobalSDFCascade(const GlobalSDFData data, float3 worldPosition) { for (uint cascade = 0; cascade < data.CascadesCount; cascade++) { - float cascadeMaxDistance; + float cascadeSize; float3 cascadeUV, textureUV; - GetGlobalSDFCascadeUV(data, cascade, worldPosition, cascadeMaxDistance, cascadeUV, textureUV); + GetGlobalSDFCascadeUV(data, cascade, worldPosition, cascadeSize, cascadeUV, textureUV); if (all(cascadeUV > 0) && all(cascadeUV < 1)) return cascade; } @@ -85,33 +89,35 @@ uint GetGlobalSDFCascade(const GlobalSDFData data, float3 worldPosition) } // Samples the Global SDF cascade and returns the distance to the closest surface (in world units) at the given world location. -float SampleGlobalSDFCascade(const GlobalSDFData data, Texture3D tex, float3 worldPosition, uint cascade) +float SampleGlobalSDFCascade(const GlobalSDFData data, Texture3D tex, float3 worldPosition, uint cascade) { float distance = GLOBAL_SDF_WORLD_SIZE; - float cascadeMaxDistance; + float cascadeSize; float3 cascadeUV, textureUV; - GetGlobalSDFCascadeUV(data, cascade, worldPosition, cascadeMaxDistance, cascadeUV, textureUV); - float cascadeDistance = tex.SampleLevel(SamplerLinearClamp, textureUV, 0); - if (cascadeDistance < GLOBAL_SDF_MIN_VALID && all(cascadeUV > 0) && all(cascadeUV < 1)) - distance = cascadeDistance * cascadeMaxDistance; + GetGlobalSDFCascadeUV(data, cascade, worldPosition, cascadeSize, cascadeUV, textureUV); + float maxDistanceTex = data.CascadeMaxDistanceTex[cascade]; + float distanceTex = tex.SampleLevel(SamplerLinearClamp, textureUV, 0); + if (distanceTex < GLOBAL_SDF_MIN_VALID && all(cascadeUV > 0) && all(cascadeUV < 1)) + distance = distanceTex * maxDistanceTex; return distance; } // Samples the Global SDF and returns the distance to the closest surface (in world units) at the given world location. -float SampleGlobalSDF(const GlobalSDFData data, Texture3D tex, float3 worldPosition) +float SampleGlobalSDF(const GlobalSDFData data, Texture3D tex, float3 worldPosition) { float distance = data.CascadePosDistance[3].w * 2.0f; if (distance <= 0.0f) return GLOBAL_SDF_WORLD_SIZE; for (uint cascade = 0; cascade < data.CascadesCount; cascade++) { - float cascadeMaxDistance; + float cascadeSize; float3 cascadeUV, textureUV; - GetGlobalSDFCascadeUV(data, cascade, worldPosition, cascadeMaxDistance, cascadeUV, textureUV); - float cascadeDistance = tex.SampleLevel(SamplerLinearClamp, textureUV, 0); - if (cascadeDistance < GLOBAL_SDF_MIN_VALID && all(cascadeUV > 0) && all(cascadeUV < 1)) + GetGlobalSDFCascadeUV(data, cascade, worldPosition, cascadeSize, cascadeUV, textureUV); + float maxDistanceTex = data.CascadeMaxDistanceTex[cascade]; + float distanceTex = tex.SampleLevel(SamplerLinearClamp, textureUV, 0); + if (distanceTex < GLOBAL_SDF_MIN_VALID && all(cascadeUV > 0) && all(cascadeUV < 1)) { - distance = cascadeDistance * cascadeMaxDistance; + distance = distanceTex * maxDistanceTex; break; } } @@ -119,25 +125,24 @@ float SampleGlobalSDF(const GlobalSDFData data, Texture3D tex, float3 wor } // Samples the Global SDF and returns the distance to the closest surface (in world units) at the given world location. -float SampleGlobalSDF(const GlobalSDFData data, Texture3D tex, Texture3D mip, float3 worldPosition) +float SampleGlobalSDF(const GlobalSDFData data, Texture3D tex, Texture3D mip, float3 worldPosition) { float distance = data.CascadePosDistance[3].w * 2.0f; if (distance <= 0.0f) return GLOBAL_SDF_WORLD_SIZE; - float chunkSizeDistance = (float)GLOBAL_SDF_RASTERIZE_CHUNK_SIZE / data.Resolution; // Size of the chunk in SDF distance (0-1) - float chunkMarginDistance = GLOBAL_SDF_CHUNK_MARGIN_SCALE * (float)GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN / data.Resolution; // Size of the chunk margin in SDF distance (0-1) for (uint cascade = 0; cascade < data.CascadesCount; cascade++) { - float cascadeMaxDistance; + float cascadeSize; float3 cascadeUV, textureUV; - GetGlobalSDFCascadeUV(data, cascade, worldPosition, cascadeMaxDistance, cascadeUV, textureUV); - float cascadeDistance = mip.SampleLevel(SamplerLinearClamp, textureUV, 0); - if (cascadeDistance < chunkSizeDistance && all(cascadeUV > 0) && all(cascadeUV < 1)) + GetGlobalSDFCascadeUV(data, cascade, worldPosition, cascadeSize, cascadeUV, textureUV); + float distanceMip = mip.SampleLevel(SamplerLinearClamp, textureUV, 0); + if (distanceMip < GLOBAL_SDF_MIN_VALID && all(cascadeUV > 0) && all(cascadeUV < 1)) { - float cascadeDistanceTex = tex.SampleLevel(SamplerLinearClamp, textureUV, 0); - if (cascadeDistanceTex < chunkMarginDistance) - cascadeDistance = cascadeDistanceTex; - distance = cascadeDistance * cascadeMaxDistance; + distance = distanceMip * cascadeSize; + float maxDistanceTex = data.CascadeMaxDistanceTex[cascade]; + float distanceTex = tex.SampleLevel(SamplerLinearClamp, textureUV, 0); + if (distanceTex < GLOBAL_SDF_MIN_VALID) + distance = distanceTex * maxDistanceTex; break; } } @@ -145,7 +150,7 @@ float SampleGlobalSDF(const GlobalSDFData data, Texture3D tex, Texture3D< } // Samples the Global SDF and returns the gradient vector (derivative) at the given world location. Normalize it to get normal vector. -float3 SampleGlobalSDFGradient(const GlobalSDFData data, Texture3D tex, float3 worldPosition, out float distance) +float3 SampleGlobalSDFGradient(const GlobalSDFData data, Texture3D tex, float3 worldPosition, out float distance) { float3 gradient = float3(0, 0.00001f, 0); distance = GLOBAL_SDF_WORLD_SIZE; @@ -153,11 +158,11 @@ float3 SampleGlobalSDFGradient(const GlobalSDFData data, Texture3D tex, f return gradient; for (uint cascade = 0; cascade < data.CascadesCount; cascade++) { - float cascadeMaxDistance; + float cascadeSize; float3 cascadeUV, textureUV; - GetGlobalSDFCascadeUV(data, cascade, worldPosition, cascadeMaxDistance, cascadeUV, textureUV); - float cascadeDistance = tex.SampleLevel(SamplerLinearClamp, textureUV, 0); - if (cascadeDistance < GLOBAL_SDF_MIN_VALID && all(cascadeUV > 0) && all(cascadeUV < 1)) + GetGlobalSDFCascadeUV(data, cascade, worldPosition, cascadeSize, cascadeUV, textureUV); + float distanceTex = tex.SampleLevel(SamplerLinearClamp, textureUV, 0); + if (distanceTex < GLOBAL_SDF_MIN_VALID && all(cascadeUV > 0) && all(cascadeUV < 1)) { float texelOffset = 1.0f / data.Resolution; float xp = tex.SampleLevel(SamplerLinearClamp, float3(textureUV.x + texelOffset, textureUV.y, textureUV.z), 0).x; @@ -166,8 +171,8 @@ float3 SampleGlobalSDFGradient(const GlobalSDFData data, Texture3D tex, f float yn = tex.SampleLevel(SamplerLinearClamp, float3(textureUV.x, textureUV.y - texelOffset, textureUV.z), 0).x; float zp = tex.SampleLevel(SamplerLinearClamp, float3(textureUV.x, textureUV.y, textureUV.z + texelOffset), 0).x; float zn = tex.SampleLevel(SamplerLinearClamp, float3(textureUV.x, textureUV.y, textureUV.z - texelOffset), 0).x; - gradient = float3(xp - xn, yp - yn, zp - zn) * cascadeMaxDistance; - distance = cascadeDistance * cascadeMaxDistance; + gradient = float3(xp - xn, yp - yn, zp - zn) * cascadeSize; + distance = distanceTex * cascadeSize; break; } } @@ -175,25 +180,29 @@ float3 SampleGlobalSDFGradient(const GlobalSDFData data, Texture3D tex, f } // Samples the Global SDF and returns the gradient vector (derivative) at the given world location. Normalize it to get normal vector. -float3 SampleGlobalSDFGradient(const GlobalSDFData data, Texture3D tex, Texture3D mip, float3 worldPosition, out float distance) +float3 SampleGlobalSDFGradient(const GlobalSDFData data, Texture3D tex, Texture3D mip, float3 worldPosition, out float distance) { float3 gradient = float3(0, 0.00001f, 0); distance = GLOBAL_SDF_WORLD_SIZE; if (data.CascadePosDistance[3].w <= 0.0f) return gradient; - float chunkSizeDistance = (float)GLOBAL_SDF_RASTERIZE_CHUNK_SIZE / data.Resolution; // Size of the chunk in SDF distance (0-1) - float chunkMarginDistance = GLOBAL_SDF_CHUNK_MARGIN_SCALE * (float)GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN / data.Resolution; // Size of the chunk margin in SDF distance (0-1) for (uint cascade = 0; cascade < data.CascadesCount; cascade++) { - float cascadeMaxDistance; + float cascadeSize; float3 cascadeUV, textureUV; - GetGlobalSDFCascadeUV(data, cascade, worldPosition, cascadeMaxDistance, cascadeUV, textureUV); - float cascadeDistance = mip.SampleLevel(SamplerLinearClamp, textureUV, 0); - if (cascadeDistance < chunkSizeDistance && all(cascadeUV > 0) && all(cascadeUV < 1)) + GetGlobalSDFCascadeUV(data, cascade, worldPosition, cascadeSize, cascadeUV, textureUV); + 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(SamplerLinearClamp, textureUV, 0) * maxDistanceMip; + if (distanceMip < chunkSize && all(cascadeUV > 0) && all(cascadeUV < 1)) { - float cascadeDistanceTex = tex.SampleLevel(SamplerLinearClamp, textureUV, 0); - if (cascadeDistanceTex < chunkMarginDistance) - cascadeDistance = cascadeDistanceTex; + distance = distanceMip; + float maxDistanceTex = data.CascadeMaxDistanceTex[cascade]; + float distanceTex = tex.SampleLevel(SamplerLinearClamp, textureUV, 0) * maxDistanceTex; + if (distanceTex < chunkMargin) + distance = distanceTex; float texelOffset = 1.0f / data.Resolution; float xp = tex.SampleLevel(SamplerLinearClamp, float3(textureUV.x + texelOffset, textureUV.y, textureUV.z), 0).x; float xn = tex.SampleLevel(SamplerLinearClamp, float3(textureUV.x - texelOffset, textureUV.y, textureUV.z), 0).x; @@ -201,8 +210,7 @@ float3 SampleGlobalSDFGradient(const GlobalSDFData data, Texture3D tex, T float yn = tex.SampleLevel(SamplerLinearClamp, float3(textureUV.x, textureUV.y - texelOffset, textureUV.z), 0).x; float zp = tex.SampleLevel(SamplerLinearClamp, float3(textureUV.x, textureUV.y, textureUV.z + texelOffset), 0).x; float zn = tex.SampleLevel(SamplerLinearClamp, float3(textureUV.x, textureUV.y, textureUV.z - texelOffset), 0).x; - gradient = float3(xp - xn, yp - yn, zp - zn) * cascadeMaxDistance; - distance = cascadeDistance * cascadeMaxDistance; + gradient = float3(xp - xn, yp - yn, zp - zn) * maxDistanceTex; break; } } @@ -211,12 +219,10 @@ float3 SampleGlobalSDFGradient(const GlobalSDFData data, Texture3D tex, T // Ray traces the Global SDF. // cascadeTraceStartBias - scales the trace start position offset (along the trace direction) by cascade voxel size (reduces artifacts on far cascades). Use it for shadow rays to prevent self-occlusion when tracing from object surface that looses quality in far cascades. -GlobalSDFHit RayTraceGlobalSDF(const GlobalSDFData data, Texture3D tex, Texture3D mip, const GlobalSDFTrace trace, float cascadeTraceStartBias = 0.0f) +GlobalSDFHit RayTraceGlobalSDF(const GlobalSDFData data, Texture3D tex, Texture3D mip, const GlobalSDFTrace trace, float cascadeTraceStartBias = 0.0f) { GlobalSDFHit hit = (GlobalSDFHit)0; hit.HitTime = -1.0f; - float chunkSizeDistance = (float)GLOBAL_SDF_RASTERIZE_CHUNK_SIZE / data.Resolution; // Size of the chunk in SDF distance (0-1) - float chunkMarginDistance = GLOBAL_SDF_CHUNK_MARGIN_SCALE * (float)GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN / data.Resolution; // Size of the chunk margin in SDF distance (0-1) float nextIntersectionStart = trace.MinDistance; float traceMaxDistance = min(trace.MaxDistance, data.CascadePosDistance[3].w * 2); float3 traceEndPosition = trace.WorldPosition + trace.WorldDirection * traceMaxDistance; @@ -246,6 +252,10 @@ GlobalSDFHit RayTraceGlobalSDF(const GlobalSDFData data, Texture3D tex, T // Walk over the cascade SDF uint step = 0; float stepTime = intersections.x; + float chunkSize = voxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; + float chunkMargin = voxelSize * (GLOBAL_SDF_CHUNK_MARGIN_SCALE * GLOBAL_SDF_RASTERIZE_CHUNK_MARGIN); + float maxDistanceTex = data.CascadeMaxDistanceTex[cascade]; + float maxDistanceMip = data.CascadeMaxDistanceMip[cascade]; LOOP for (; step < 250 && stepTime < intersections.y && hit.HitTime < 0.0f; step++) { @@ -253,28 +263,30 @@ GlobalSDFHit RayTraceGlobalSDF(const GlobalSDFData data, Texture3D tex, T float stepScale = trace.StepScale; // Sample SDF - float cascadeMaxDistance; + float stepDistance, cascadeSize, voxelSizeScale = (float)GLOBAL_SDF_RASTERIZE_MIP_FACTOR; float3 cascadeUV, textureUV; - GetGlobalSDFCascadeUV(data, cascade, stepPosition, cascadeMaxDistance, cascadeUV, textureUV); - float stepDistance = mip.SampleLevel(SamplerLinearClamp, textureUV, 0); - if (stepDistance < chunkSizeDistance) + GetGlobalSDFCascadeUV(data, cascade, stepPosition, cascadeSize, cascadeUV, textureUV); + float distanceMip = mip.SampleLevel(SamplerLinearClamp, textureUV, 0) * maxDistanceMip; + if (distanceMip < chunkSize) { - float stepDistanceTex = tex.SampleLevel(SamplerLinearClamp, textureUV, 0); - if (stepDistanceTex < chunkMarginDistance) + stepDistance = distanceMip; + float distanceTex = tex.SampleLevel(SamplerLinearClamp, textureUV, 0) * maxDistanceTex; + if (distanceTex < chunkMargin) { - stepDistance = stepDistanceTex; + stepDistance = distanceTex; + voxelSizeScale = 1.0f; stepScale *= 0.63f; // Perform smaller steps nearby geometry } } else { - // Assume no SDF nearby so perform a jump - stepDistance = chunkSizeDistance; + // Assume no SDF nearby so perform a jump tto the next chunk + stepDistance = chunkSize; + voxelSizeScale = 1.0f; } - stepDistance *= cascadeMaxDistance; // Detect surface hit - float minSurfaceThickness = voxelExtent * saturate(stepTime / voxelSize); + float minSurfaceThickness = voxelSizeScale * voxelExtent * saturate(stepTime / voxelSize); if (stepDistance < minSurfaceThickness) { // Surface hit @@ -308,5 +320,5 @@ GlobalSDFHit RayTraceGlobalSDF(const GlobalSDFData data, Texture3D tex, T float GetGlobalSurfaceAtlasThreshold(const GlobalSDFData data, const GlobalSDFHit hit) { // Scale the threshold based on the hit cascade (less precision) - return data.CascadeVoxelSize[hit.HitCascade] * 1.1f; + return data.CascadeVoxelSize[hit.HitCascade] * 1.17f; } diff --git a/Source/Shaders/GlobalSignDistanceField.shader b/Source/Shaders/GlobalSignDistanceField.shader index d92f98acf..b0a058cd3 100644 --- a/Source/Shaders/GlobalSignDistanceField.shader +++ b/Source/Shaders/GlobalSignDistanceField.shader @@ -43,10 +43,13 @@ float CascadeVoxelSize; int CascadeMipResolution; int CascadeMipFactor; uint4 Objects[GLOBAL_SDF_RASTERIZE_MODEL_MAX_COUNT / 4]; -uint GenerateMipTexResolution; -uint GenerateMipCoordScale; -uint GenerateMipTexOffsetX; -uint GenerateMipMipOffsetX; +float2 Padding20; +float MipMaxDistanceLoad; +float MipMaxDistanceStore; +uint MipTexResolution; +uint MipCoordScale; +uint MipTexOffsetX; +uint MipMipOffsetX; META_CB_END float CombineDistanceToSDF(float sdf, float distanceToSDF) @@ -71,7 +74,7 @@ float CombineSDF(float oldSdf, float newSdf) #if defined(_CS_RasterizeModel) || defined(_CS_RasterizeHeightfield) -RWTexture3D GlobalSDFTex : register(u0); +RWTexture3D GlobalSDFTex : register(u0); StructuredBuffer ObjectsBuffer : register(t0); #endif @@ -213,7 +216,7 @@ void CS_RasterizeHeightfield(uint3 DispatchThreadId : SV_DispatchThreadID) #if defined(_CS_ClearChunk) -RWTexture3D GlobalSDFTex : register(u0); +RWTexture3D GlobalSDFTex : register(u0); // Compute shader for clearing Global SDF chunk META_CS(true, FEATURE_LEVEL_SM5) @@ -229,19 +232,21 @@ void CS_ClearChunk(uint3 DispatchThreadId : SV_DispatchThreadID) #if defined(_CS_GenerateMip) -RWTexture3D GlobalSDFMip : register(u0); -Texture3D GlobalSDFTex : register(t0); +RWTexture3D GlobalSDFMip : register(u0); +Texture3D GlobalSDFTex : register(t0); float SampleSDF(uint3 voxelCoordMip, int3 offset) { // Sample SDF - voxelCoordMip = (uint3)clamp((int3)(voxelCoordMip * GenerateMipCoordScale) + offset, int3(0, 0, 0), (int3)(GenerateMipTexResolution - 1)); - voxelCoordMip.x += GenerateMipTexOffsetX; + voxelCoordMip = (uint3)clamp((int3)(voxelCoordMip * MipCoordScale) + offset, int3(0, 0, 0), (int3)(MipTexResolution - 1)); + voxelCoordMip.x += MipTexOffsetX; float result = GlobalSDFTex[voxelCoordMip].r; + if (result >= GLOBAL_SDF_MIN_VALID) + return MipMaxDistanceStore; // No valid distance so use the limit + result *= MipMaxDistanceLoad; // Decode normalized distance to world-units // Extend by distance to the sampled texel location - float distanceInWorldUnits = length((float3)offset) * (MaxDistance / (float)GenerateMipTexResolution); - float distanceToVoxel = distanceInWorldUnits / MaxDistance; + float distanceToVoxel = length((float3)offset) * CascadeVoxelSize * ((float)CascadeResolution / (float)MipTexResolution); result = CombineDistanceToSDF(result, distanceToVoxel); return result; @@ -263,16 +268,16 @@ void CS_GenerateMip(uint3 DispatchThreadId : SV_DispatchThreadID) minDistance = min(minDistance, SampleSDF(voxelCoordMip, int3(0, -1, 0))); minDistance = min(minDistance, SampleSDF(voxelCoordMip, int3(0, 0, -1))); - voxelCoordMip.x += GenerateMipMipOffsetX; - GlobalSDFMip[voxelCoordMip] = minDistance; + voxelCoordMip.x += MipMipOffsetX; + GlobalSDFMip[voxelCoordMip] = clamp(minDistance / MipMaxDistanceStore, -1, 1); } #endif #ifdef _PS_Debug -Texture3D GlobalSDFTex : register(t0); -Texture3D GlobalSDFMip : register(t1); +Texture3D GlobalSDFTex : register(t0); +Texture3D GlobalSDFMip : register(t1); // Pixel shader for Global SDF debug drawing META_PS(true, FEATURE_LEVEL_SM5) diff --git a/Source/Shaders/SSR.shader b/Source/Shaders/SSR.shader index a813bdf36..a3ee880fe 100644 --- a/Source/Shaders/SSR.shader +++ b/Source/Shaders/SSR.shader @@ -51,8 +51,8 @@ Texture2D Texture0 : register(t4); Texture2D Texture1 : register(t5); Texture2D Texture2 : register(t6); #if USE_GLOBAL_SURFACE_ATLAS -Texture3D GlobalSDFTex : register(t7); -Texture3D GlobalSDFMip : register(t8); +Texture3D GlobalSDFTex : register(t7); +Texture3D GlobalSDFMip : register(t8); ByteAddressBuffer GlobalSurfaceAtlasChunks : register(t9); ByteAddressBuffer RWGlobalSurfaceAtlasCulledObjects : register(t10); Buffer GlobalSurfaceAtlasObjects : register(t11);