// 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 push : register(b999); StructuredBuffer voxelData : register(t0); StructuredBuffer chunkInfo : register(t1); RWStructuredBuffer 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)); }