Files
FlaxEngine/Source/Shaders/SDF.shader

249 lines
7.3 KiB
GLSL

// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
// Mesh SDF generation based on https://github.com/GPUOpen-Effects/TressFX
#include "./Flax/Common.hlsl"
#include "./Flax/ThirdParty/TressFX/TressFXSDF.hlsl"
#define THREAD_GROUP_SIZE 64
META_CB_BEGIN(0, Data)
int3 Resolution;
uint ResolutionSize;
float MaxDistance;
uint VertexStride;
bool Index16bit;
uint TriangleCount;
float3 VoxelToPosMul;
float WorldUnitsPerVoxel;
float3 VoxelToPosAdd;
uint ThreadGroupsX;
META_CB_END
RWBuffer<uint> SDF : register(u0);
uint GetVoxelIndex(uint3 groupId, uint groupIndex)
{
return groupIndex + (groupId.x + groupId.y * ThreadGroupsX) * THREAD_GROUP_SIZE;
}
int3 ClampVoxelCoord(int3 coord)
{
return clamp(coord, 0, Resolution - 1);
}
int GetVoxelIndex(int3 coord)
{
return Resolution.x * Resolution.y * coord.z + Resolution.x * coord.y + coord.x;
}
float3 GetVoxelPos(int3 coord)
{
return float3((float)coord.x, (float)coord.y, (float)coord.z) * VoxelToPosMul + VoxelToPosAdd;
}
int3 GetVoxelCoord(float3 pos)
{
pos = (pos - VoxelToPosAdd) / VoxelToPosMul;
return int3((int)pos.x, (int)pos.y, (int)pos.z);
}
int3 GetVoxelCoord(uint index)
{
uint sizeX = (uint)Resolution.x;
uint sizeY = (uint)(Resolution.x * Resolution.y);
uint coordZ = index / sizeY;
uint coordXY = index % sizeY;
uint coordY = coordXY / sizeX;
uint coordX = coordXY % sizeX;
return int3((int)coordX, (int)coordY, (int)coordZ);
}
// Clears SDF texture with the initial distance.
META_CS(true, FEATURE_LEVEL_SM5)
[numthreads(THREAD_GROUP_SIZE, 1, 1)]
void CS_Init(uint3 GroupId : SV_GroupID, uint GroupIndex : SV_GroupIndex)
{
uint voxelIndex = GetVoxelIndex(GroupId, GroupIndex);
if (voxelIndex >= ResolutionSize)
return;
float distance = MaxDistance * 10.0f; // Start with a very large value
SDF[voxelIndex] = FloatFlip3(distance);
}
// Unpacks SDF texture into distances stores as normal float value (FloatFlip3 is used for interlocked operations on uint).
META_CS(true, FEATURE_LEVEL_SM5)
[numthreads(THREAD_GROUP_SIZE, 1, 1)]
void CS_Resolve(uint3 GroupId : SV_GroupID, uint GroupIndex : SV_GroupIndex)
{
uint voxelIndex = GetVoxelIndex(GroupId, GroupIndex);
if (voxelIndex >= ResolutionSize)
return;
SDF[voxelIndex] = IFloatFlip3(SDF[voxelIndex]);
}
#ifdef _CS_RasterizeTriangle
ByteAddressBuffer VertexBuffer : register(t0);
ByteAddressBuffer IndexBuffer : register(t1);
uint LoadIndex(uint i)
{
if (Index16bit)
{
uint index = IndexBuffer.Load((i >> 1u) << 2u);
index = (i & 1u) == 1u ? (index >> 16) : index;
return index & 0xffff;
}
return IndexBuffer.Load(i << 2u);
}
float3 LoadVertex(uint i)
{
return asfloat(VertexBuffer.Load3(i * VertexStride));
}
// Renders triangle mesh into the SDF texture by writing minimum distance to the triangle into all intersecting voxels.
META_CS(true, FEATURE_LEVEL_SM5)
[numthreads(THREAD_GROUP_SIZE, 1, 1)]
void CS_RasterizeTriangle(uint3 DispatchThreadId : SV_DispatchThreadID)
{
uint triangleIndex = DispatchThreadId.x;
if (triangleIndex >= TriangleCount)
return;
// Load triangle
triangleIndex *= 3;
uint i0 = LoadIndex(triangleIndex + 0);
uint i1 = LoadIndex(triangleIndex + 1);
uint i2 = LoadIndex(triangleIndex + 2);
float3 v0 = LoadVertex(i0);
float3 v1 = LoadVertex(i1);
float3 v2 = LoadVertex(i2);
// Project triangle into SDF voxels
float3 vMargin = float3(WorldUnitsPerVoxel, WorldUnitsPerVoxel, WorldUnitsPerVoxel);
float3 vMin = min(min(v0, v1), v2) - vMargin;
float3 vMax = max(max(v0, v1), v2) + vMargin;
int3 voxelMargin = int3(1, 1, 1);
int3 voxelMin = GetVoxelCoord(vMin) - voxelMargin;
int3 voxelMax = GetVoxelCoord(vMax) + voxelMargin;
voxelMin = ClampVoxelCoord(voxelMin);
voxelMax = ClampVoxelCoord(voxelMax);
// Rasterize into SDF voxels
for (int z = voxelMin.z; z <= voxelMax.z; z++)
{
for (int y = voxelMin.y; y <= voxelMax.y; y++)
{
for (int x = voxelMin.x; x <= voxelMax.x; x++)
{
int3 voxelCoord = int3(x, y, z);
int voxelIndex = GetVoxelIndex(voxelCoord);
float3 voxelPos = GetVoxelPos(voxelCoord);
float distance = SignedDistancePointToTriangle(voxelPos, v0, v1, v2);
if (distance < -10.0f) // TODO: find a better way to reject negative distance from degenerate triangles that break SDF shape
distance = abs(distance);
InterlockedMin(SDF[voxelIndex], FloatFlip3(distance));
}
}
}
}
#endif
#if defined(_CS_FloodFill) || defined(_CS_Encode)
Buffer<uint> InSDF : register(t0);
float GetVoxel(int voxelIndex)
{
return asfloat(InSDF[voxelIndex]);
}
float GetVoxel(int3 coord)
{
coord = ClampVoxelCoord(coord);
int voxelIndex = GetVoxelIndex(coord);
return GetVoxel(voxelIndex);
}
float CombineSDF(float sdf, int3 nearbyCoord, float nearbyDistance)
{
// Sample nearby voxel
float sdfNearby = GetVoxel(nearbyCoord);
// Include distance to that nearby voxel
if (sdfNearby < 0.0f)
nearbyDistance *= -1;
sdfNearby += nearbyDistance;
if (sdfNearby > MaxDistance)
{
// Ignore if nearby sample is invalid (see CS_Init)
}
else if (sdf > MaxDistance)
{
// Use nearby sample if current one is invalid (see CS_Init)
sdf = sdfNearby;
}
else
{
// Use distance closer to 0
sdf = sdf >= 0 ? min(sdf, sdfNearby) : max(sdf, sdfNearby);
}
return sdf;
}
// Fills the voxels with minimum distances to the triangles.
META_CS(true, FEATURE_LEVEL_SM5)
[numthreads(THREAD_GROUP_SIZE, 1, 1)]
void CS_FloodFill(uint3 GroupId : SV_GroupID, uint GroupIndex : SV_GroupIndex)
{
uint voxelIndex = GetVoxelIndex(GroupId, GroupIndex);
if (voxelIndex >= ResolutionSize)
return;
float sdf = GetVoxel(voxelIndex);
// Skip if the distance is already so small that we know that triangle is nearby
if (abs(sdf) > WorldUnitsPerVoxel * 1.2f)
{
int3 voxelCoord = GetVoxelCoord(voxelIndex);
int3 offset = int3(-1, 0, 1);
// Sample nearby voxels
float nearbyDistance = WorldUnitsPerVoxel;
sdf = CombineSDF(sdf, voxelCoord + offset.zyy, nearbyDistance);
sdf = CombineSDF(sdf, voxelCoord + offset.yzy, nearbyDistance);
sdf = CombineSDF(sdf, voxelCoord + offset.yyz, nearbyDistance);
sdf = CombineSDF(sdf, voxelCoord + offset.xyy, nearbyDistance);
sdf = CombineSDF(sdf, voxelCoord + offset.yxy, nearbyDistance);
sdf = CombineSDF(sdf, voxelCoord + offset.yyx, nearbyDistance);
}
SDF[voxelIndex] = asuint(sdf);
}
RWTexture3D<half> SDFtex : register(u1);
// Encodes SDF values into the packed format with normalized distances.
META_CS(true, FEATURE_LEVEL_SM5)
[numthreads(THREAD_GROUP_SIZE, 1, 1)]
void CS_Encode(uint3 GroupId : SV_GroupID, uint GroupIndex : SV_GroupIndex)
{
uint voxelIndex = GetVoxelIndex(GroupId, GroupIndex);
if (voxelIndex >= ResolutionSize)
return;
float sdf = GetVoxel(voxelIndex);
sdf = min(sdf, MaxDistance);
// Pack from range [-MaxDistance; +MaxDistance] to [0; 1]
sdf = (sdf / MaxDistance) * 0.5f + 0.5f;
int3 voxelCoord = GetVoxelCoord(voxelIndex);
SDFtex[voxelCoord] = sdf;
}
#endif