bvle-voxels/shaders/voxelSmoothCentroidCS.hlsl

257 lines
8.9 KiB
HLSL
Raw Normal View History

// 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));
}