Procedural Mesh Generation: Dual Marching Cubes & Iso-Surfaces in WebGPU
Procedurally generating complex volumetric terrain, fluid surfaces, and CAD geometry inside the browser has historically been constrained by CPU-bound polygon extraction. WebGPU compute pipelines revolutionize procedural generation by porting Dual Marching Cubes (DMC) and Surface Nets directly onto the GPU. By sampling 3D signed distance fields (SDFs) across threadgroups and utilizing atomic stream compaction, WebGPU constructs indexed triangle buffers entirely in VRAM without CPU readbacks.
The Architecture of Dual Marching Cubes on WebGPU
How dual grid topology preserves sharp geometric features and eliminates manifold holes:
Standard Marching Cubes places vertices along voxel edges, resulting in rounded corners and degenerate sliver triangles. Dual Marching Cubes constructs dual vertices inside voxel cells using Quadratic Error Functions (QEF). Connecting dual vertices across active sign-changing edges preserves sharp mechanical creases while guaranteeing 2-manifold closed surface meshes.
Iso-Surface Extraction Algorithms Compared
| Extraction Algorithm | Sharp Feature Preservation | GPU Compute Parallelism | Triangle Topology Quality |
|---|---|---|---|
| Classic Marching Cubes (Lorensen) | Poor (Smooth chamfers only) | High ($O(N^3)$ independent cells) | Sub-optimal (Sliver triangles) |
| Dual Contouring (Ju et al.) | Optimal (QEF feature vertices) | Moderate (Octree traversal overhead) | High (Adaptive quads) |
| WebGPU Dual Marching Cubes | High (Manifold preserving) | Maximum (Atomic Stream Compaction) | Balanced Triangulation |
WGSL Compute Shader Atomic Mesh Compaction
Writing extracted vertices and indices to GPU storage buffers:
struct MeshCounter {
vertexCount: atomic<u32>,
indexCount: atomic<u32>,
};
@group(0) @binding(0) var<storage, read_write> counter: MeshCounter;
@group(0) @binding(1) var<storage, read_write> vertexBuffer: array<vec4<f32>>;
@group(0) @binding(2) var<storage, read_write> indexBuffer: array<u32>;
@compute @workgroup_size(8, 8, 8)
fn computeDualIsoSurface(@builtin(global_invocation_id) id: vec3<u32>) {
// Sample SDF volume at voxel corners
let cellActive = evaluateVoxelIntersection(id);
if (!cellActive) { return; }
// Atomic allocation for vertex and triangle indices
let baseVertIndex = atomicAdd(&counter.vertexCount, 1u);
let baseIdxIndex = atomicAdd(&counter.indexCount, 3u);
vertexBuffer[baseVertIndex] = calculateQEFVertex(id);
indexBuffer[baseIdxIndex + 0u] = baseVertIndex;
indexBuffer[baseIdxIndex + 1u] = baseVertIndex + 1u;
indexBuffer[baseIdxIndex + 2u] = baseVertIndex + 2u;
}
Explore Advanced Real-Time Graphics & Compute
Master cutting-edge GPU rendering pipelines in WebGPU. Read our guide on WebGPU Vertex Pulling & Programmable Assembly, explore CXL NUMA cloud tiering on WinWinHost CXL Infrastructure, review custom memory allocators on WebDesigner.la jemalloc Tuning, or consult with our real-time graphics architects.