Skip to content

Chunked meshing

To mesh a volume larger than memory, cut it into chunks and join the results. serra is designed so that join needs nothing but vertex deduplication — no tolerance, no snapping, no repair pass.

The contract

Each chunk owns a disjoint range of voxels and is passed to mesh() with a one-voxel halo on every side. Neighbouring input arrays therefore overlap by two voxels.

OWN = 512
HALO = 1

lo = index * OWN
hi = lo + OWN
chunk = volume[max(lo - HALO, 0) : hi + HALO, ...]

mesher = serra_mesh.Mesher(voxel_resolution=[32, 32, 40], threads=1)
mesher.mesh(chunk, close=False)

Use close=False for interior chunks. close=True seals the array's faces, which is what you want for a whole volume but would cap a chunk off at its seams.

Why two voxels and not one

serra places a vertex inside each cell — each 2×2×2 block of voxels — rather than on voxel edges. For two chunks to agree, the layer of cells straddling the seam has to exist in both.

With a one-voxel overlap it does not. The lower chunk's last cell ends exactly where the upper chunk's first cell begins, so neither produces the layer in between and the joined surface has a ring-shaped hole.

Marching cubes differs here

zmesh needs only a one-voxel overlap, because its vertices sit on voxel edges rather than inside cells. If you are porting a pipeline, this is the one thing you must change.

Relaxation does not widen the halo

Iterative smoothing normally spreads one cell per iteration, so k iterations would need k + 1 voxels of halo. serra instead pins the outermost layer of cells — precisely the vertices whose one-ring the chunk does not fully contain — so relaxation never reads past the halo.

A chunk's mesh is therefore reproducible from that chunk's own array alone, at any relaxation setting. Objects outside the halo cannot perturb it.

The deliberate trade-off: a chunk's interior smooths slightly more than the band around its seams, so a stitched surface is self-consistent and watertight, but not identical to the same volume meshed in one piece.

Threading

Set threads=1 when running one chunk per process, or every process will try to claim every core:

serra_mesh.Mesher(threads=1)

See the README for the scaling numbers.