Optimize Global SDF to use 8-bit storage (50% less memory usage)

This commit is contained in:
Wojtek Figat
2024-07-10 13:22:25 +02:00
parent d5dd8e7ecf
commit 6b06f1dbcf
9 changed files with 151 additions and 120 deletions

View File

@@ -10,7 +10,7 @@
/// <summary>
/// Current materials shader version.
/// </summary>
#define MATERIAL_GRAPH_VERSION 167
#define MATERIAL_GRAPH_VERSION 168
class Material;
class GPUShader;

View File

@@ -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;

View File

@@ -15,6 +15,8 @@ public:
GPU_CB_STRUCT(ConstantsData {
Float4 CascadePosDistance[4];
Float4 CascadeVoxelSize;
Float4 CascadeMaxDistance;
Float4 CascadeMaxDistanceMip;
Float2 Padding;
uint32 CascadesCount;
float Resolution;

View File

@@ -170,7 +170,7 @@ const Char* ShaderGraphUtilities::GenerateShaderResources(TextWriterUnicode& wri
format = TEXT("Texture3D {0} : register(t{1});");
break;
case MaterialParameterType::GlobalSDF:
format = TEXT("Texture3D<float> {0}_Tex : register(t{1});\nTexture3D<float> {0}_Mip : register(t{2});");
format = TEXT("Texture3D<snorm float> {0}_Tex : register(t{1});\nTexture3D<snorm float> {0}_Mip : register(t{2});");
zeroOffset = false;
registers = 2;
break;

View File

@@ -73,8 +73,8 @@ uint GetProbeRaysCount(DDGIData data, uint probeState)
RWTexture2D<snorm float4> RWProbesData : register(u0);
RWByteAddressBuffer RWActiveProbes : register(u1);
Texture3D<float> GlobalSDFTex : register(t0);
Texture3D<float> GlobalSDFMip : register(t1);
Texture3D<snorm float> GlobalSDFTex : register(t0);
Texture3D<snorm float> GlobalSDFMip : register(t1);
float3 Remap(float3 value, float3 fromMin, float3 fromMax, float3 toMin, float3 toMax)
{
@@ -288,8 +288,8 @@ void CS_UpdateProbesInitArgs()
RWTexture2D<float4> RWProbesTrace : register(u0);
Texture3D<float> GlobalSDFTex : register(t0);
Texture3D<float> GlobalSDFMip : register(t1);
Texture3D<snorm float> GlobalSDFTex : register(t0);
Texture3D<snorm float> GlobalSDFMip : register(t1);
ByteAddressBuffer GlobalSurfaceAtlasChunks : register(t2);
ByteAddressBuffer RWGlobalSurfaceAtlasCulledObjects : register(t3);
Buffer<float4> GlobalSurfaceAtlasObjects : register(t4);

View File

@@ -91,8 +91,8 @@ Texture2D<snorm float4> ProbesData : register(t5);
Texture2D<float4> ProbesDistance : register(t6);
Texture2D<float4> ProbesIrradiance : register(t7);
#else
Texture3D<float> GlobalSDFTex : register(t5);
Texture3D<float> GlobalSDFMip : register(t6);
Texture3D<snorm float> GlobalSDFTex : register(t5);
Texture3D<snorm float> 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<float> GlobalSDFTex : register(t0);
Texture3D<float> GlobalSDFMip : register(t1);
Texture3D<snorm float> GlobalSDFTex : register(t0);
Texture3D<snorm float> GlobalSDFMip : register(t1);
ByteAddressBuffer GlobalSurfaceAtlasChunks : register(t2);
ByteAddressBuffer GlobalSurfaceAtlasCulledObjects : register(t3);
Buffer<float4> GlobalSurfaceAtlasObjects : register(t4);

View File

@@ -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<float> tex, float3 worldPosition, uint cascade)
float SampleGlobalSDFCascade(const GlobalSDFData data, Texture3D<snorm float> 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<float> tex, float3 worldPosition)
float SampleGlobalSDF(const GlobalSDFData data, Texture3D<snorm float> 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<float> 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<float> tex, Texture3D<float> mip, float3 worldPosition)
float SampleGlobalSDF(const GlobalSDFData data, Texture3D<snorm float> tex, Texture3D<snorm float> 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<float> 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<float> tex, float3 worldPosition, out float distance)
float3 SampleGlobalSDFGradient(const GlobalSDFData data, Texture3D<snorm float> 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<float> 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<float> 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<float> 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<float> tex, Texture3D<float> mip, float3 worldPosition, out float distance)
float3 SampleGlobalSDFGradient(const GlobalSDFData data, Texture3D<snorm float> tex, Texture3D<snorm float> 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<float> 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<float> 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<float> tex, Texture3D<float> mip, const GlobalSDFTrace trace, float cascadeTraceStartBias = 0.0f)
GlobalSDFHit RayTraceGlobalSDF(const GlobalSDFData data, Texture3D<snorm float> tex, Texture3D<snorm float> 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<float> 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<float> 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<float> 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;
}

View File

@@ -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<float> GlobalSDFTex : register(u0);
RWTexture3D<snorm float> GlobalSDFTex : register(u0);
StructuredBuffer<ObjectRasterizeData> ObjectsBuffer : register(t0);
#endif
@@ -213,7 +216,7 @@ void CS_RasterizeHeightfield(uint3 DispatchThreadId : SV_DispatchThreadID)
#if defined(_CS_ClearChunk)
RWTexture3D<float> GlobalSDFTex : register(u0);
RWTexture3D<snorm float> 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<float> GlobalSDFMip : register(u0);
Texture3D<float> GlobalSDFTex : register(t0);
RWTexture3D<snorm float> GlobalSDFMip : register(u0);
Texture3D<snorm float> 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<float> GlobalSDFTex : register(t0);
Texture3D<float> GlobalSDFMip : register(t1);
Texture3D<snorm float> GlobalSDFTex : register(t0);
Texture3D<snorm float> GlobalSDFMip : register(t1);
// Pixel shader for Global SDF debug drawing
META_PS(true, FEATURE_LEVEL_SM5)

View File

@@ -51,8 +51,8 @@ Texture2D Texture0 : register(t4);
Texture2D Texture1 : register(t5);
Texture2D Texture2 : register(t6);
#if USE_GLOBAL_SURFACE_ATLAS
Texture3D<float> GlobalSDFTex : register(t7);
Texture3D<float> GlobalSDFMip : register(t8);
Texture3D<snorm float> GlobalSDFTex : register(t7);
Texture3D<snorm float> GlobalSDFMip : register(t8);
ByteAddressBuffer GlobalSurfaceAtlasChunks : register(t9);
ByteAddressBuffer RWGlobalSurfaceAtlasCulledObjects : register(t10);
Buffer<float4> GlobalSurfaceAtlasObjects : register(t11);