
Extraction pipeline
Every stage of one extraction, from input mesh to extracted surface.
from conquer3d.data_structure import (
create_voxel_grid_from_tmesh,
)
from conquer3d.ops import dmc
lo, hi, res = [-1.0] * 3, [1.0] * 3, [256] * 3
# A narrow band around the surface, not a dense lattice.
gv, vox, nrm = create_voxel_grid_from_tmesh(
grid_min=lo, grid_max=hi, res=res,
tmesh=mesh, pad=1, return_normals=True,
)
mesh.build_flood_fill_data(lo, hi, res)
sdf = mesh.query_points(gv, return_sdf=True, sign_mode=3)[-1]
verts, faces = dmc(gv, vox, sdf.contiguous(), iso=0.0)[:2]
Isosurface extraction
One signed distance field meshed by four different extractors.
from conquer3d.ops import (
compute_hermite_from_mesh, dc, dmc, marching_cubes, mca,
)
# Exact edge crossings, so the dual methods can put a vertex
# on the crease instead of rounding it off.
ep, en = compute_hermite_from_mesh(mesh, gv, vox, sdf)
mc = marching_cubes(gv, vox, sdf, iso=0.0)[:2]
asym = mca(gv, vox, sdf, iso=0.0)[:2]
dual = dc(gv, vox, sdf, grid_normals=nrm, iso=0.0,
edge_points=ep, edge_normals=en)[:2]
dual_mc = dmc(gv, vox, sdf, iso=0.0,
edge_points=ep, edge_normals=en)[:2]
Sharp features
Exact Hermite data lets the dual methods reconstruct a crease instead of rounding it.
from conquer3d.ops import compute_hermite_from_mesh, dc
mesh.compute_triangle_normals()
# Where each grid edge crosses the surface and the normal
# there, read off the mesh rather than off the field.
ep, en = compute_hermite_from_mesh(mesh, gv, vox, sdf)
rounded = dc(gv, vox, sdf, grid_normals=nrm, iso=0.0)[:2]
sharp = dc(gv, vox, sdf, grid_normals=nrm, iso=0.0,
edge_points=ep, edge_normals=en)[:2]
Grid resolution
The same model extracted from 64³ up to 2048³, with the error measured at each step.
from conquer3d.data_structure import (
create_voxel_grid_from_tmesh,
)
from conquer3d.ops import chamfer_distance, dmc
reference = mesh.sample_points(200_000)[0].contiguous()
for res in (64, 128, 256, 512, 1024, 2048):
gv, vox, _ = create_voxel_grid_from_tmesh(
grid_min=[-1.0] * 3, grid_max=[1.0] * 3,
res=[res] * 3, tmesh=mesh, pad=1,
)
sdf = mesh.query_points(gv, return_sdf=True,
sign_mode=3)[-1]
verts, faces = dmc(gv, vox, sdf.contiguous(),
iso=0.0)[:2]
error = chamfer_distance(samples, reference,
squared=False)
Sign modes
One slice through each of two meshes, signed by all seven ways of deciding inside.
# 0 ray parity 1 winding number 2 pseudonormal
# 3 flood fill 4 hybrid consensus 5 coarse-fine
# 6 band fill
lo, hi, res = [-1.0] * 3, [1.0] * 3, [512] * 3
# Each lattice mode needs its own structure built first.
mesh.build_flood_fill_data(lo, hi, res) # mode 3
mesh.build_flood_fill_cf_data(lo, hi, res) # mode 5
mesh.build_flood_fill_band_data( # mode 6
lo, hi, res, dilation_radius=2, cavity_max_voxels=125)
for mode in range(7):
sdf = mesh.query_points(points, return_sdf=True,
sign_mode=mode)[-1]
Surface normals
Normals from five extractors, coloured by direction and compared to the source.
from conquer3d.ops import (
dc, dmc, marching_cubes, marching_tetrahedra_grid, mca,
)
mesh.fix_normals() # consistent winding first
mesh.compute_triangle_normals()
extracted = {
"Marching Cubes": marching_cubes(gv, vox, sdf, iso=0.0),
"MC Asymptotic": mca(gv, vox, sdf, iso=0.0),
"Dual Contouring": dc(gv, vox, sdf, grid_normals=nrm,
iso=0.0),
"Dual Marching Cubes": dmc(gv, vox, sdf, iso=0.0),
}
Normal modes
The three ways of supplying normals to Dual Contouring, and what each one produces.
from conquer3d.data_structure import (
create_voxel_grid_from_tmesh,
)
from conquer3d.ops import dc
# 0 area-weighted 1 angle-weighted 2 from the field
for normal_mode in (0, 1, 2):
gv, vox, nrm = create_voxel_grid_from_tmesh(
grid_min=[-1.0] * 3, grid_max=[1.0] * 3,
res=[128] * 3, tmesh=mesh, pad=1,
return_normals=True, normal_mode=normal_mode,
)
sdf = mesh.query_points(gv, return_sdf=True,
sign_mode=3)[-1]
verts, faces = dc(gv, vox, sdf.contiguous(),
grid_normals=nrm, iso=0.0)[:2]
Signed distance field
Slices through the field a mesh generates, with the zero level set drawn on top.
import torch
# A plane of query points cut through the model.
res, z = 512, -0.08
xs = torch.linspace(-1.0, 1.0, res, device="cuda")
xx, yy = torch.meshgrid(xs, xs, indexing="xy")
points = torch.stack([
xx.reshape(-1), yy.reshape(-1),
torch.full((res * res,), z, device="cuda"),
], dim=-1).contiguous()
mesh.build_flood_fill_data([-1.0] * 3, [1.0] * 3, [512] * 3)
sdf = mesh.query_points(points, return_sdf=True,
sign_mode=3)[-1]
Differentiable rendering
A field of noise optimised into the Happy Buddha by multi-view mask and depth loss alone, with diff_marching_cubes carrying the gradient.
import torch
from conquer3d.data_structure import create_voxel_grid
from conquer3d.ops import diff_marching_cubes
gv, vox, _ = create_voxel_grid(
grid_min=[-1.0] * 3, grid_max=[1.0] * 3,
res=[128] * 3, device="cuda")
# Start from noise; only the field is optimised.
sdf = torch.nn.Parameter(torch.rand_like(gv[:, 0]) - 0.1)
optimizer = torch.optim.Adam([sdf], lr=1e-2)
for step in range(1000):
optimizer.zero_grad()
# Differentiable, so mask and depth losses on the
# render reach the field through the extractor.
verts, faces = diff_marching_cubes(gv, vox, sdf,
iso=0.0)[:2]
render_and_compare(verts, faces).backward()
optimizer.step()