What Is Iso-Surface Extraction?

Shapes as Functions

A surface can be described without storing any geometry. Take a function \(f\) that assigns a number to every point in space, and pick a threshold \(c\). The points where \(f(p) = c\) form a surface: the iso-surface of \(f\) at the iso-level \(c\). Points where \(f(p) < c\) count as inside, points where \(f(p) > c\) as outside. A function used this way is called a level set function, and the shape it carries is implicit: no vertices, no faces, just the rule.

The simplest example is \(f(p) = \lVert p \rVert\), the distance from the origin. Its iso-surface at level 1 is the unit sphere; at level 2 it is a sphere of radius 2. One function holds every sphere at once, and the iso-level picks which one to look at. Below is that function sampled on a grid and extracted at three levels; the level= argument selects the shell:

Hide code cell source

import torch
import isoext
from isoext import viewer

field = isoext.UniformGrid([48, 48, 48], aabb_min=[-1.2] * 3, aabb_max=[1.2] * 3)
field.set_values(field.get_points().norm(dim=-1))  # f(p) = |p|

def shells(s):
    for level, color in [(1.0, (0.27, 0.51, 0.71)), (0.7, (0.24, 0.55, 0.36))]:
        v, f = isoext.marching_cubes(field, level=level)
        viewer.add_mesh(s, v, f, color=color, wireframe=True, name=f"/level_{level}")
        viewer.add_label(s, f"level = {level}", (0.0, 0.0, level + 0.12))
    viewer.add_label(s, "level = 0.4", (0.0, 0.0, 0.52))

v, f = isoext.marching_cubes(field, level=0.4)
viewer.embed(v, f, color="coral", height=360, draw=shells,
             frame=torch.tensor([[-1.15, -1.15, -1.15], [1.15, 1.15, 1.15]], device="cuda"))

Signed Distance Functions

A signed distance function (SDF) is a level set function with an extra promise: its value at \(p\) is the distance from \(p\) to the surface, made negative inside. The sphere of radius \(r\) has the SDF \(f(p) = \lVert p \rVert - r\): zero exactly on the sphere, \(-r\) at the center, growing by one for every unit moved away. A general level set function only promises the sign. Away from the surface its values can be anything, and the same shape has infinitely many level set functions but only one SDF.

The distance meaning buys several things. Interpolated edge crossings land close to the true surface, because distance changes at a steady unit rate. Extracting at level \(d\) gives the surface offset outward by exactly \(d\), which is what the sphere shells above show. The gradient of an SDF is the unit surface normal, which Dual Contouring uses to reconstruct sharp features. And a renderer can march a ray through an SDF by stepping the value at each point, since no surface can be closer than the distance says.

Extraction itself does not require true distances. Any level set function works: a neural network’s output, an occupancy volume from a scan, the level set of a fluid solver. What matters is that the values change sign across the surface.

Sampling on a Grid

A computer cannot evaluate \(f\) at every point, so the field is sampled on a regular grid, and everything downstream sees only those samples. Below is the smallest possible case: a single grid cell near a sphere of radius 1.2 centered at one of its corners. Each corner is labeled with its sampled value, which is that corner’s signed distance to the sphere. The center corner reads -1.2 and is inside (red); the other seven are positive and outside (blue). The surface passes through the cell even though no sample sits exactly on it:

Hide code cell source

cell = isoext.UniformGrid([2, 2, 2])
center = torch.tensor([-1.0, -1.0, -1.0], device="cuda")
values = (cell.get_points() - center).norm(dim=-1) - 1.2
cell.set_values(values)

def value_labels(s):
    up = torch.tensor([0.0, 0.0, 0.17], device="cuda")
    for p, val in zip(cell.get_points().reshape(-1, 3), values.reshape(-1)):
        viewer.add_label(s, f"{val:+.1f}", p + up)

viewer.embed(grid=cell, height=340, draw=value_labels)

Extraction

Iso-surface extraction rebuilds the surface from the samples as a triangle mesh. Wherever an edge of the cell connects an inside corner to an outside one, the field must cross zero somewhere along it, and interpolating the two corner values estimates where. Meshing those crossings gives, for this cell, a single triangle that separates the red corner from the rest:

v, f = isoext.marching_cubes(cell)  # level=0 by default
viewer.embed(v, f, color="steelblue", flat_shading=True, side="double",
             height=340, grid=cell, draw=value_labels)

Zooming Out

A real grid repeats this in every cell. Eight cells per side is already enough to recognize a sphere; the Quick Start example uses 256 and produces hundreds of thousands of triangles. The extraction methods differ in where they place the vertices and how they connect them: each method’s page explains its algorithm, and Method Comparisons compares them side by side.

grid = isoext.UniformGrid([9, 9, 9])
grid.set_values(grid.get_points().norm(dim=-1) - 0.8)

v, f = isoext.marching_cubes(grid)
viewer.embed(v, f, wireframe=True, height=340, grid=grid)

From here, Quick Start runs the full pipeline on a real shape, Working with Grids covers the data structures, and SDF Utilities provides ready-made fields to experiment with.