Implement CPU-side Naive Surface Nets for smooth voxel surfaces (SmoothStone, Snow) coexisting with blocky voxels (Grass, Dirt, Stone, Sand). Key features: - SmoothMesher with binary SDF, centroid vertex placement, per-axis boundary clamping to align with blocky grid at smooth↔blocky transitions - Cross-chunk connectivity: PAD=2 SDF grid, vertex range [-1, CHUNK_SIZE), canonical edge ownership (no duplicate triangles, no z-fighting) - Face normals oriented by edge axis+sign (robust with binary SDF, unlike SDF gradient dot or centroid sampling approaches) - Y-axis winding fix: sharing cells have different spatial arrangement, requiring opposite winding from X and Z axes - GPU mesher treats smooth neighbors as solid (no blocky faces toward smooth) - Material blending: primary (smooth-only) + secondary (all counts) per vertex - Dedicated shaders: voxelSmoothVS (vertex pulling t6) + voxelSmoothPS (triplanar + lerp blending between two materials) - Separate render pass with LoadOp::LOAD after voxels+topings - New materials: SmoothStone (mat 6), blocky Stone (mat 3) and Dirt patches added to world generation for boundary testing
143 lines
5.6 KiB
C++
143 lines
5.6 KiB
C++
#pragma once
|
|
#include <cstdint>
|
|
#include <functional>
|
|
|
|
namespace voxel {
|
|
|
|
// ── Voxel Data (16 bits per voxel, as per spec) ─────────────────
|
|
// Layout: 8 bits material ID | 4 bits flags | 4 bits metadata
|
|
struct VoxelData {
|
|
uint16_t packed = 0;
|
|
|
|
VoxelData() = default;
|
|
explicit VoxelData(uint8_t materialID, uint8_t flags = 0, uint8_t meta = 0) {
|
|
packed = (uint16_t(materialID) << 8) | (uint16_t(flags & 0xF) << 4) | (meta & 0xF);
|
|
}
|
|
|
|
uint8_t getMaterialID() const { return uint8_t(packed >> 8); }
|
|
uint8_t getFlags() const { return uint8_t((packed >> 4) & 0xF); }
|
|
uint8_t getMetadata() const { return uint8_t(packed & 0xF); }
|
|
|
|
bool isEmpty() const { return packed == 0; }
|
|
bool isSmooth() const { return (getFlags() & FLAG_SMOOTH) != 0; }
|
|
bool isTransparent() const { return (getFlags() & FLAG_TRANSPARENT) != 0; }
|
|
bool isEmissive() const { return (getFlags() & FLAG_EMISSIVE) != 0; }
|
|
|
|
static constexpr uint8_t FLAG_SMOOTH = 0x1;
|
|
static constexpr uint8_t FLAG_TRANSPARENT = 0x2;
|
|
static constexpr uint8_t FLAG_EMISSIVE = 0x4;
|
|
static constexpr uint8_t FLAG_CUSTOM = 0x8;
|
|
};
|
|
|
|
// ── Chunk Constants ─────────────────────────────────────────────
|
|
static constexpr int CHUNK_SIZE = 32;
|
|
static constexpr int CHUNK_VOLUME = CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE;
|
|
|
|
// ── Packed Vertex for Greedy Mesh Quads (8 bytes per quad) ──────
|
|
// Layout:
|
|
// [5:0] posX (6 bits)
|
|
// [11:6] posY (6 bits)
|
|
// [17:12] posZ (6 bits)
|
|
// [23:18] width (6 bits)
|
|
// [29:24] height (6 bits)
|
|
// [32:30] face (3 bits, split across lo/hi)
|
|
// [40:33] materialID (8 bits)
|
|
// [48:41] blendMatID (8 bits) — neighbor material for height-based blending
|
|
// [59:49] chunkIndex (11 bits, GPU mesh path)
|
|
// [63:60] blendEdges (4 bits) — which edges have the blend material (+U,-U,+V,-V)
|
|
struct PackedQuad {
|
|
uint64_t data;
|
|
|
|
static PackedQuad create(
|
|
uint8_t x, uint8_t y, uint8_t z,
|
|
uint8_t w, uint8_t h, uint8_t face,
|
|
uint8_t materialID, uint8_t blendMatID = 0,
|
|
uint16_t chunkIndex = 0, uint8_t blendEdges = 0
|
|
) {
|
|
PackedQuad q;
|
|
q.data =
|
|
(uint64_t(x & 0x3F)) |
|
|
(uint64_t(y & 0x3F) << 6) |
|
|
(uint64_t(z & 0x3F) << 12) |
|
|
(uint64_t(w & 0x3F) << 18) |
|
|
(uint64_t(h & 0x3F) << 24) |
|
|
(uint64_t(face & 0x7) << 30) |
|
|
(uint64_t(materialID) << 33) |
|
|
(uint64_t(blendMatID) << 41) |
|
|
(uint64_t(chunkIndex & 0x7FF) << 49) |
|
|
(uint64_t(blendEdges & 0xF) << 60);
|
|
return q;
|
|
}
|
|
|
|
uint8_t getX() const { return uint8_t(data & 0x3F); }
|
|
uint8_t getY() const { return uint8_t((data >> 6) & 0x3F); }
|
|
uint8_t getZ() const { return uint8_t((data >> 12) & 0x3F); }
|
|
uint8_t getWidth() const { return uint8_t((data >> 18) & 0x3F); }
|
|
uint8_t getHeight() const { return uint8_t((data >> 24) & 0x3F); }
|
|
uint8_t getFace() const { return uint8_t((data >> 30) & 0x7); }
|
|
uint8_t getMaterialID() const { return uint8_t((data >> 33) & 0xFF); }
|
|
uint8_t getBlendMatID() const { return uint8_t((data >> 41) & 0xFF); }
|
|
uint16_t getChunkIndex() const { return uint16_t((data >> 49) & 0x7FF); }
|
|
uint8_t getBlendEdges() const { return uint8_t((data >> 60) & 0xF); }
|
|
};
|
|
|
|
// Face directions: +X, -X, +Y, -Y, +Z, -Z
|
|
enum Face : uint8_t {
|
|
FACE_POS_X = 0,
|
|
FACE_NEG_X = 1,
|
|
FACE_POS_Y = 2,
|
|
FACE_NEG_Y = 3,
|
|
FACE_POS_Z = 4,
|
|
FACE_NEG_Z = 5,
|
|
FACE_COUNT = 6
|
|
};
|
|
|
|
// ── Smooth Surface Nets vertex (Phase 5) ────────────────────────
|
|
// Packed as 32 bytes for GPU StructuredBuffer.
|
|
// Position is in world space (chunk origin already added).
|
|
struct SmoothVertex {
|
|
float px, py, pz; // 12 bytes — world position
|
|
float nx, ny, nz; // 12 bytes — normal (face normal)
|
|
uint8_t materialID; // 1 byte — primary material
|
|
uint8_t secondaryMat; // 1 byte — secondary material for blending
|
|
uint8_t blendWeight; // 1 byte — 0-255 → 0.0-1.0 blend toward secondary
|
|
uint8_t _pad1; // 1 byte alignment
|
|
uint16_t chunkIndex; // 2 bytes — which chunk owns this vertex
|
|
uint16_t _pad2; // 2 bytes alignment
|
|
}; // total = 32 bytes
|
|
static_assert(sizeof(SmoothVertex) == 32, "SmoothVertex must be 32 bytes");
|
|
|
|
// ── Material Descriptor ─────────────────────────────────────────
|
|
struct MaterialDesc {
|
|
uint16_t albedoTextureIndex = 0;
|
|
uint16_t normalTextureIndex = 0;
|
|
uint16_t heightmapTextureIndex = 0;
|
|
uint8_t roughness = 128; // 0-255 mapped to 0.0-1.0
|
|
uint8_t metallic = 0;
|
|
uint8_t flags = 0; // triplanar, blend mode, etc.
|
|
uint8_t _pad = 0;
|
|
|
|
static constexpr uint8_t FLAG_TRIPLANAR = 0x1;
|
|
};
|
|
|
|
// ── Chunk Position Hash ─────────────────────────────────────────
|
|
struct ChunkPos {
|
|
int32_t x, y, z;
|
|
|
|
bool operator==(const ChunkPos& other) const {
|
|
return x == other.x && y == other.y && z == other.z;
|
|
}
|
|
};
|
|
|
|
struct ChunkPosHash {
|
|
size_t operator()(const ChunkPos& p) const {
|
|
// FNV-1a inspired hash
|
|
size_t h = 0x811c9dc5;
|
|
h ^= size_t(p.x); h *= 0x01000193;
|
|
h ^= size_t(p.y); h *= 0x01000193;
|
|
h ^= size_t(p.z); h *= 0x01000193;
|
|
return h;
|
|
}
|
|
};
|
|
|
|
} // namespace voxel
|