Refactor Mesh SDF generation on GPU to use raytracing for more precise results
This commit is contained in:
@@ -1,18 +1,14 @@
|
||||
// Copyright (c) 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
|
||||
#include "./Flax/MeshAccelerationStructure.hlsl"
|
||||
|
||||
META_CB_BEGIN(0, Data)
|
||||
int3 Resolution;
|
||||
uint ResolutionSize;
|
||||
float MaxDistance;
|
||||
uint VertexStride;
|
||||
bool Index16bit;
|
||||
float BackfacesThreshold;
|
||||
uint TriangleCount;
|
||||
float3 VoxelToPosMul;
|
||||
float WorldUnitsPerVoxel;
|
||||
@@ -20,21 +16,9 @@ float3 VoxelToPosAdd;
|
||||
uint ThreadGroupsX;
|
||||
META_CB_END
|
||||
|
||||
RWBuffer<uint> SDF : register(u0);
|
||||
|
||||
uint GetVoxelIndex(uint3 groupId, uint groupIndex)
|
||||
uint GetVoxelIndex(uint3 groupId, uint groupIndex, uint groupSize)
|
||||
{
|
||||
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;
|
||||
return groupIndex + (groupId.x + groupId.y * ThreadGroupsX) * groupSize;
|
||||
}
|
||||
|
||||
float3 GetVoxelPos(int3 coord)
|
||||
@@ -42,12 +26,6 @@ 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;
|
||||
@@ -59,191 +37,90 @@ int3 GetVoxelCoord(uint index)
|
||||
return int3((int)coordX, (int)coordY, (int)coordZ);
|
||||
}
|
||||
|
||||
// Clears SDF texture with the initial distance.
|
||||
#ifdef _CS_Init
|
||||
|
||||
#define THREAD_GROUP_SIZE 64
|
||||
|
||||
RWTexture3D<unorm half> SDFtex : register(u0);
|
||||
|
||||
// Clears SDF texture with the maximum 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);
|
||||
uint voxelIndex = GetVoxelIndex(GroupId, GroupIndex, THREAD_GROUP_SIZE);
|
||||
if (voxelIndex >= ResolutionSize)
|
||||
return;
|
||||
float distance = MaxDistance * 10.0f; // Start with a very large value
|
||||
SDF[voxelIndex] = FloatFlip3(distance);
|
||||
int3 voxelCoord = GetVoxelCoord(voxelIndex);
|
||||
SDFtex[voxelCoord] = 1.0f;
|
||||
}
|
||||
|
||||
// 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]);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _CS_RasterizeTriangle
|
||||
#ifdef _CS_RasterizeTriangles
|
||||
|
||||
#define THREAD_GROUP_SIZE 64
|
||||
|
||||
RWTexture3D<unorm half> SDFtex : register(u0);
|
||||
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));
|
||||
}
|
||||
StructuredBuffer<BVHNode> BVHBuffer : register(t2);
|
||||
|
||||
// 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)
|
||||
void CS_RasterizeTriangles(uint3 GroupId : SV_GroupID, uint3 GroupThreadID : SV_GroupThreadID, uint GroupIndex : SV_GroupIndex)
|
||||
{
|
||||
uint triangleIndex = DispatchThreadId.x;
|
||||
if (triangleIndex >= TriangleCount)
|
||||
uint voxelIndex = GetVoxelIndex(GroupId, GroupIndex, THREAD_GROUP_SIZE);
|
||||
if (voxelIndex >= ResolutionSize)
|
||||
return;
|
||||
int3 voxelCoord = GetVoxelCoord(voxelIndex);
|
||||
float3 voxelPos = GetVoxelPos(voxelCoord);
|
||||
|
||||
// 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);
|
||||
BVHBuffers bvh;
|
||||
bvh.BVHBuffer = BVHBuffer;
|
||||
bvh.VertexBuffer = VertexBuffer;
|
||||
bvh.IndexBuffer = IndexBuffer;
|
||||
bvh.VertexStride = VertexStride;
|
||||
|
||||
// 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);
|
||||
// Point query to find the distance to the closest surface
|
||||
BVHHit hit;
|
||||
PointQueryBVH(bvh, voxelPos, hit, MaxDistance);
|
||||
float sdf = hit.Distance;
|
||||
|
||||
// Rasterize into SDF voxels
|
||||
for (int z = voxelMin.z; z <= voxelMax.z; z++)
|
||||
// Raycast triangles around voxel to count triangle backfaces hit
|
||||
#define CLOSEST_CACHE_SIZE 6
|
||||
float3 closestDirections[CLOSEST_CACHE_SIZE] =
|
||||
{
|
||||
for (int y = voxelMin.y; y <= voxelMax.y; y++)
|
||||
float3(+1, 0, 0),
|
||||
float3(-1, 0, 0),
|
||||
float3(0, +1, 0),
|
||||
float3(0, -1, 0),
|
||||
float3(0, 0, +1),
|
||||
float3(0, 0, -1),
|
||||
};
|
||||
uint hitBackCount = 0;
|
||||
uint minBackfaceHitCount = (uint)(CLOSEST_CACHE_SIZE * BackfacesThreshold);
|
||||
for (uint i = 0; i < CLOSEST_CACHE_SIZE; i++)
|
||||
{
|
||||
float3 rayDir = closestDirections[i];
|
||||
if (RayCastBVH(bvh, voxelPos, rayDir, hit, MaxDistance))
|
||||
{
|
||||
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 0
|
||||
if (distance < -10.0f) // TODO: find a better way to reject negative distance from degenerate triangles that break SDF shape
|
||||
distance = abs(distance);
|
||||
#endif
|
||||
InterlockedMin(SDF[voxelIndex], FloatFlip3(distance));
|
||||
}
|
||||
sdf = min(sdf, hit.Distance);
|
||||
if (hit.IsBackface)
|
||||
hitBackCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#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)
|
||||
if (hitBackCount >= minBackfaceHitCount)
|
||||
{
|
||||
// Ignore if nearby sample is invalid (see CS_Init)
|
||||
// Voxel is inside the geometry so turn it into negative distance to the surface
|
||||
sdf *= -1;
|
||||
}
|
||||
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 = clamp(sdf, -MaxDistance, MaxDistance);
|
||||
sdf = (sdf / MaxDistance) * 0.5f + 0.5f;
|
||||
|
||||
int3 voxelCoord = GetVoxelCoord(voxelIndex);
|
||||
SDFtex[voxelCoord] = sdf;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user