257 lines
8.9 KiB
HLSL
257 lines
8.9 KiB
HLSL
|
|
// BVLE Voxels - GPU Smooth Mesher Pass 1: Centroid + Solid Grid
|
||
|
|
// Computes centroid position + material for each surface cell and writes
|
||
|
|
// to a per-chunk centroid grid buffer (34^3 entries for cells [-1..32]).
|
||
|
|
//
|
||
|
|
// IMPORTANT: Also writes a "solid" flag for ALL cells (surface or not).
|
||
|
|
// The emit shader (pass 2) reads ONLY from this grid — no voxel buffer access.
|
||
|
|
// This makes the emit shader much simpler and faster to compile.
|
||
|
|
//
|
||
|
|
// Grid format (float4 per cell):
|
||
|
|
// xyz = chunk-local position (only valid for surface cells)
|
||
|
|
// w = asfloat(packed):
|
||
|
|
// bits [7:0] = primaryMat
|
||
|
|
// bits [15:8] = secondaryMat
|
||
|
|
// bits [23:16] = blendWeight
|
||
|
|
// bit 24 = valid (has centroid, is surface cell)
|
||
|
|
// bit 25 = solid (readVox != 0 at this cell position)
|
||
|
|
//
|
||
|
|
// Dispatch: 5x5x5 groups of 8x8x8 threads per chunk (covers 40^3, clipped to 34^3)
|
||
|
|
|
||
|
|
#include "voxelCommon.hlsli"
|
||
|
|
|
||
|
|
struct SmoothPush {
|
||
|
|
uint chunkIndex;
|
||
|
|
uint voxelBufferOffset;
|
||
|
|
uint maxOutputVerts;
|
||
|
|
uint centroidGridOffset;
|
||
|
|
uint pad[8];
|
||
|
|
};
|
||
|
|
[[vk::push_constant]] ConstantBuffer<SmoothPush> push : register(b999);
|
||
|
|
|
||
|
|
StructuredBuffer<uint> voxelData : register(t0);
|
||
|
|
StructuredBuffer<GPUChunkInfo> chunkInfo : register(t1);
|
||
|
|
RWStructuredBuffer<float4> centroidGrid : register(u0);
|
||
|
|
|
||
|
|
static const uint CSIZE = 32;
|
||
|
|
static const uint GRID_DIM = 34;
|
||
|
|
static const uint WORDS_PER_CHUNK = CSIZE * CSIZE * CSIZE / 2;
|
||
|
|
|
||
|
|
static const int3 cornerOff[8] = {
|
||
|
|
int3(0,0,0), int3(1,0,0), int3(0,1,0), int3(1,1,0),
|
||
|
|
int3(0,0,1), int3(1,0,1), int3(0,1,1), int3(1,1,1)
|
||
|
|
};
|
||
|
|
static const float3 cornerOffF[8] = {
|
||
|
|
float3(0,0,0), float3(1,0,0), float3(0,1,0), float3(1,1,0),
|
||
|
|
float3(0,0,1), float3(1,0,1), float3(0,1,1), float3(1,1,1)
|
||
|
|
};
|
||
|
|
static const uint2 edgePairs[12] = {
|
||
|
|
uint2(0,1), uint2(2,3), uint2(4,5), uint2(6,7),
|
||
|
|
uint2(0,2), uint2(1,3), uint2(4,6), uint2(5,7),
|
||
|
|
uint2(0,4), uint2(1,5), uint2(2,6), uint2(3,7)
|
||
|
|
};
|
||
|
|
static const int3 dirs6[6] = {
|
||
|
|
int3(1,0,0), int3(-1,0,0), int3(0,1,0), int3(0,-1,0), int3(0,0,1), int3(0,0,-1)
|
||
|
|
};
|
||
|
|
|
||
|
|
// ── Voxel reading (with cross-chunk neighbor support) ───────────────
|
||
|
|
uint readVoxelAt(uint bufferOffset, uint flatIndex) {
|
||
|
|
uint pairIndex = flatIndex >> 1;
|
||
|
|
uint shift = (flatIndex & 1) * 16;
|
||
|
|
return (voxelData[bufferOffset + pairIndex] >> shift) & 0xFFFF;
|
||
|
|
}
|
||
|
|
|
||
|
|
uint readVoxel(uint flatIndex) {
|
||
|
|
return readVoxelAt(push.voxelBufferOffset, flatIndex);
|
||
|
|
}
|
||
|
|
|
||
|
|
uint readVox(int3 p) {
|
||
|
|
int3 localP = p;
|
||
|
|
int3 chunkOff = int3(0, 0, 0);
|
||
|
|
if (p.x < 0) { chunkOff.x = -1; localP.x += CSIZE; }
|
||
|
|
else if (p.x >= (int)CSIZE) { chunkOff.x = 1; localP.x -= CSIZE; }
|
||
|
|
if (p.y < 0) { chunkOff.y = -1; localP.y += CSIZE; }
|
||
|
|
else if (p.y >= (int)CSIZE) { chunkOff.y = 1; localP.y -= CSIZE; }
|
||
|
|
if (p.z < 0) { chunkOff.z = -1; localP.z += CSIZE; }
|
||
|
|
else if (p.z >= (int)CSIZE) { chunkOff.z = 1; localP.z -= CSIZE; }
|
||
|
|
|
||
|
|
if (chunkOff.x == 0 && chunkOff.y == 0 && chunkOff.z == 0) {
|
||
|
|
uint fi = (uint)localP.x + (uint)localP.y * CSIZE + (uint)localP.z * CSIZE * CSIZE;
|
||
|
|
return readVoxel(fi);
|
||
|
|
}
|
||
|
|
int axisCount = abs(chunkOff.x) + abs(chunkOff.y) + abs(chunkOff.z);
|
||
|
|
if (axisCount > 1) return 0;
|
||
|
|
|
||
|
|
uint neighborFace;
|
||
|
|
if (chunkOff.x > 0) neighborFace = 0;
|
||
|
|
else if (chunkOff.x < 0) neighborFace = 1;
|
||
|
|
else if (chunkOff.y > 0) neighborFace = 2;
|
||
|
|
else if (chunkOff.y < 0) neighborFace = 3;
|
||
|
|
else if (chunkOff.z > 0) neighborFace = 4;
|
||
|
|
else neighborFace = 5;
|
||
|
|
|
||
|
|
GPUChunkInfo ci = chunkInfo[push.chunkIndex];
|
||
|
|
uint nIdx = getNeighborIdx(ci, neighborFace);
|
||
|
|
if (nIdx == 0xFFFFFFFF) return 0;
|
||
|
|
|
||
|
|
uint fi = (uint)localP.x + (uint)localP.y * CSIZE + (uint)localP.z * CSIZE * CSIZE;
|
||
|
|
return readVoxelAt(nIdx * WORDS_PER_CHUNK, fi);
|
||
|
|
}
|
||
|
|
|
||
|
|
bool isSmooth(uint voxel) { return (voxel != 0) && ((voxel >> 4) & 0x1); }
|
||
|
|
uint getMatID(uint voxel) { return (voxel >> 8) & 0xFF; }
|
||
|
|
|
||
|
|
uint gridIndex(int3 cellPos) {
|
||
|
|
return push.centroidGridOffset +
|
||
|
|
(uint)(cellPos.z + 1) * GRID_DIM * GRID_DIM +
|
||
|
|
(uint)(cellPos.y + 1) * GRID_DIM +
|
||
|
|
(uint)(cellPos.x + 1);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Flags byte (bits 24-31 of packed w):
|
||
|
|
// bit 0 (24): valid centroid
|
||
|
|
// bit 1 (25): cell is solid
|
||
|
|
static const uint FLAG_VALID = 1u;
|
||
|
|
static const uint FLAG_SOLID = 2u;
|
||
|
|
|
||
|
|
[RootSignature(VOXEL_ROOTSIG)]
|
||
|
|
[numthreads(8, 8, 8)]
|
||
|
|
void main(uint3 DTid : SV_DispatchThreadID)
|
||
|
|
{
|
||
|
|
int3 cellPos = int3(DTid) - 1;
|
||
|
|
if (any(cellPos > 32)) return;
|
||
|
|
|
||
|
|
uint idx = gridIndex(cellPos);
|
||
|
|
|
||
|
|
// Determine if cell center is solid (needed by emit shader for edge sign checks)
|
||
|
|
uint voxAtCell = readVox(cellPos);
|
||
|
|
uint solidFlag = (voxAtCell != 0) ? FLAG_SOLID : 0u;
|
||
|
|
|
||
|
|
// Read SDF at 8 corners
|
||
|
|
float corner[8];
|
||
|
|
bool hasPos = false, hasNeg = false;
|
||
|
|
bool hasSmoothFlag = false;
|
||
|
|
|
||
|
|
[unroll]
|
||
|
|
for (uint c = 0; c < 8; c++) {
|
||
|
|
int3 cp = cellPos + cornerOff[c];
|
||
|
|
uint vox = readVox(cp);
|
||
|
|
corner[c] = (vox == 0) ? 1.0 : -1.0;
|
||
|
|
if (corner[c] < 0.0) hasNeg = true;
|
||
|
|
else hasPos = true;
|
||
|
|
if (isSmooth(vox)) hasSmoothFlag = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Not a surface cell → write only solid flag
|
||
|
|
if (!hasPos || !hasNeg) {
|
||
|
|
centroidGrid[idx] = float4(0, 0, 0, asfloat(solidFlag << 24));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Must be near smooth voxels
|
||
|
|
if (!hasSmoothFlag) {
|
||
|
|
bool nearSmooth = false;
|
||
|
|
[unroll]
|
||
|
|
for (uint c = 0; c < 8 && !nearSmooth; c++) {
|
||
|
|
int3 cp = cellPos + cornerOff[c];
|
||
|
|
[unroll]
|
||
|
|
for (uint d = 0; d < 6 && !nearSmooth; d++) {
|
||
|
|
uint nv = readVox(cp + dirs6[d]);
|
||
|
|
if (isSmooth(nv)) nearSmooth = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (!nearSmooth) {
|
||
|
|
centroidGrid[idx] = float4(0, 0, 0, asfloat(solidFlag << 24));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Compute centroid from edge crossings
|
||
|
|
float3 sum = float3(0, 0, 0);
|
||
|
|
uint crossCount = 0;
|
||
|
|
[unroll]
|
||
|
|
for (uint e = 0; e < 12; e++) {
|
||
|
|
float s0 = corner[edgePairs[e].x];
|
||
|
|
float s1 = corner[edgePairs[e].y];
|
||
|
|
if ((s0 < 0.0) == (s1 < 0.0)) continue;
|
||
|
|
float t = clamp(s0 / (s0 - s1), 0.01, 0.99);
|
||
|
|
sum += cornerOffF[edgePairs[e].x] + t * (cornerOffF[edgePairs[e].y] - cornerOffF[edgePairs[e].x]);
|
||
|
|
crossCount++;
|
||
|
|
}
|
||
|
|
if (crossCount == 0) {
|
||
|
|
centroidGrid[idx] = float4(0, 0, 0, asfloat(solidFlag << 24));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
float3 cen = sum / (float)crossCount;
|
||
|
|
|
||
|
|
// Boundary clamping
|
||
|
|
[unroll]
|
||
|
|
for (uint c = 0; c < 8; c++) {
|
||
|
|
if (corner[c] >= 0.0) continue;
|
||
|
|
int3 cp = cellPos + cornerOff[c];
|
||
|
|
uint vox = readVox(cp);
|
||
|
|
if (vox != 0 && !isSmooth(vox)) {
|
||
|
|
if (cornerOff[c].x == 0) cen.x = max(cen.x, 0.5);
|
||
|
|
else cen.x = min(cen.x, 0.5);
|
||
|
|
if (cornerOff[c].y == 0) cen.y = max(cen.y, 0.5);
|
||
|
|
else cen.y = min(cen.y, 0.5);
|
||
|
|
if (cornerOff[c].z == 0) cen.z = max(cen.z, 0.5);
|
||
|
|
else cen.z = min(cen.z, 0.5);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
float3 localPos = float3(cellPos) + float3(0.5, 0.5, 0.5) + cen;
|
||
|
|
|
||
|
|
// Material determination
|
||
|
|
uint matIDs[8];
|
||
|
|
bool matIsSmooth[8];
|
||
|
|
uint solidCount = 0;
|
||
|
|
[unroll]
|
||
|
|
for (uint cc = 0; cc < 8; cc++) {
|
||
|
|
if (corner[cc] >= 0.0) continue;
|
||
|
|
int3 cp = cellPos + cornerOff[cc];
|
||
|
|
uint vox = readVox(cp);
|
||
|
|
if (vox == 0) continue;
|
||
|
|
matIDs[solidCount] = getMatID(vox);
|
||
|
|
matIsSmooth[solidCount] = isSmooth(vox);
|
||
|
|
solidCount++;
|
||
|
|
}
|
||
|
|
|
||
|
|
uint bestMat = 6, bestCount = 0, smoothSolidCount = 0;
|
||
|
|
[unroll] for (uint i = 0; i < 8; i++) {
|
||
|
|
if (i >= solidCount) break;
|
||
|
|
if (matIsSmooth[i]) smoothSolidCount++;
|
||
|
|
}
|
||
|
|
[unroll] for (uint i = 0; i < 8; i++) {
|
||
|
|
if (i >= solidCount) break;
|
||
|
|
bool useSmooth = (smoothSolidCount > 0);
|
||
|
|
if (useSmooth && !matIsSmooth[i]) continue;
|
||
|
|
uint mat = matIDs[i];
|
||
|
|
uint cnt = 0;
|
||
|
|
[unroll] for (uint j = 0; j < 8; j++) {
|
||
|
|
if (j >= solidCount) break;
|
||
|
|
if (useSmooth && !matIsSmooth[j]) continue;
|
||
|
|
if (matIDs[j] == mat) cnt++;
|
||
|
|
}
|
||
|
|
if (cnt > bestCount) { bestCount = cnt; bestMat = mat; }
|
||
|
|
}
|
||
|
|
|
||
|
|
uint secMat = bestMat, secCount = 0;
|
||
|
|
[unroll] for (uint i = 0; i < 8; i++) {
|
||
|
|
if (i >= solidCount) break;
|
||
|
|
if (matIDs[i] == bestMat) continue;
|
||
|
|
uint mat = matIDs[i];
|
||
|
|
uint cnt = 0;
|
||
|
|
[unroll] for (uint j = 0; j < 8; j++) {
|
||
|
|
if (j >= solidCount) break;
|
||
|
|
if (matIDs[j] == mat && mat != bestMat) cnt++;
|
||
|
|
}
|
||
|
|
if (cnt > secCount) { secCount = cnt; secMat = mat; }
|
||
|
|
}
|
||
|
|
|
||
|
|
uint blendW = (secCount > 0 && secMat != bestMat) ? 255 : 0;
|
||
|
|
uint flags = FLAG_VALID | solidFlag;
|
||
|
|
uint packed = (bestMat & 0xFF) | ((secMat & 0xFF) << 8) | ((blendW & 0xFF) << 16) | (flags << 24);
|
||
|
|
centroidGrid[idx] = float4(localPos, asfloat(packed));
|
||
|
|
}
|