How O‑Voxel Sparse Voxel Representation Works for 3D Generation vs. Traditional Meshes
O‑Voxel is a sparse voxel‑based native 3D representation that replaces continuous surface meshes with a Flexible Dual Grid, storing only occupied voxels with dual vertices solved via Quadratic‑Error Functions for topology‑agnostic, memory‑efficient 3D generation.
Traditional meshes have dominated computer graphics for decades, but they impose strict requirements: watertight manifolds, expensive UV unwrapping, and costly conversions for neural pipelines. The O‑Voxel representation in Microsoft's TRELLIS.2 codebase eliminates these constraints by rethinking how geometry, topology, and materials are stored and processed. This article explains the technical architecture of O‑Voxel and how it compares to mesh‑based workflows.
What Is O‑Voxel Sparse Voxel Representation?
O‑Voxel is a native 3D representation built on three core principles: sparse storage, dual‑grid geometry encoding, and volumetric material attributes. Unlike conventional voxel grids that waste memory on empty space, O‑Voxel stores only occupied voxels—those intersected by the surface—using spatial hashing for compression.
The Flexible Dual Grid (FDG) Architecture
The heart of O‑Voxel is the Flexible Dual Grid, which places one dual vertex inside each occupied voxel and encodes which voxel edges are intersected by the surface. In flexible_dual_grid.py, the mesh_to_flexible_dual_grid function (lines 28–62) performs three operations:
- Voxel occupancy detection – identifies which voxels contain surface geometry.
- QEF solving – computes a dual vertex position by minimizing quadratic error on intersected faces.
- Edge flag packing – stores which of the 12 voxel edges are crossed by the surface.
The dual vertex position is obtained by solving a small Quadratic‑Error Function (QEF) on the intersected faces of the original mesh. This yields sub‑voxel precision and gracefully handles non‑manifold or open geometries that would break traditional mesh processing.
Sparse Storage with Space‑Filling Curves
Memory efficiency comes from Morton (Z‑order) or Hilbert curve encoding. The serialize.py module (lines 6–35) provides encode_seq and decode_seq functions that compress voxel indices into compact, cache‑friendly orderings. This reduces memory usage from O(N³) for dense grids to O(K) where K is the number of occupied voxels.
Volumetric PBR Attributes
O‑Voxel stores per‑voxel material properties directly: base color, metallic, roughness, and opacity. The textured_mesh_to_volumetric_attr function in convert/__init__.py samples texture maps into voxel space, eliminating the need for UV atlases. Materials become editable properties of the volume rather than baked image textures.
Converting Meshes to O‑Voxel Representation
The conversion pipeline in TRELLIS.2 is designed for production use. Here is a complete workflow from mesh to compressed .vxz file:
import trimesh, torch
import o_voxel.convert as ovc
import o_voxel.io as oio
import o_voxel.serialize as ovs
# Load source mesh
asset = trimesh.load("assets/helmet.glb")
vertices = torch.from_numpy(asset.vertices).float().cuda()
faces = torch.from_numpy(asset.faces).long().cuda()
# Step 1: Geometry voxelization via Flexible Dual Grid
voxel_indices, dual_vertices, intersected = ovc.mesh_to_flexible_dual_grid(
vertices, faces,
grid_size=256,
aabb=[[-0.5, -0.5, -0.5], [0.5, 0.5, 0.5]]
)
# Step 2: Align with Morton-code ordering for sparse storage
vid = ovs.encode_seq(voxel_indices)
mapping = torch.argsort(vid)
voxel_indices = voxel_indices[mapping]
dual_vertices = dual_vertices[mapping]
intersected = intersected[mapping]
# Step 3: Material voxelization (volumetric PBR)
voxel_indices_mat, attrs = ovc.textured_mesh_to_volumetric_attr(
asset, grid_size=256, aabb=[[-0.5, -0.5, -0.5], [0.5, 0.5, 0.5]]
)
# Align material voxels to same ordering
vid_mat = ovs.encode_seq(voxel_indices_mat)
mapping_mat = torch.argsort(vid_mat)
attrs = {k: v[mapping_mat] for k, v in attrs.items()}
# Step 4: Pack auxiliary data and export
attrs['dual_vertices'] = (dual_vertices * 256 - voxel_indices).byte()
attrs['intersected'] = (intersected[:,0] + 2*intersected[:,1] + 4*intersected[:,2]).byte()
oio.write("helmet.vxz", voxel_indices, attrs)
This conversion requires no SDF evaluation or iterative optimization—just linear algebra and hash‑based lookup, typically completing in milliseconds according to the TRELLIS.2 source code.
Rendering Sparse Voxels Directly
O‑Voxel includes a GPU‑accelerated sparse voxel rasterizer for training and debugging. The VoxelRenderer class in rasterize.py (lines 35–99) projects voxel centers and blends attributes without converting to mesh:
import o_voxel.rasterize as ovr
import o_voxel.io as oio
coords, data = oio.read("helmet.vxz")
position = (coords.float() / 256 - 0.5).cuda()
base_color = (data['base_color'].float() / 255).cuda()
renderer = ovr.VoxelRenderer({"resolution": 512, "ssaa": 2})
output = renderer.render(
position=position,
attrs=base_color,
voxel_size=1.0/256,
extrinsics=torch.eye(4).cuda(),
intrinsics=torch.tensor([
[500., 0., 256.],
[0., 500., 256.],
[0., 0., 1.]
]).cuda()
)
color_image = output.attr.permute(1, 2, 0).cpu().numpy()
This rasterizer is differentiable, making it suitable for neural 3D generation pipelines including VAEs and flow‑matching models. Mesh rendering in differentiable contexts typically requires expensive approximations or point‑cloud fallbacks.
Converting O‑Voxel Back to Mesh
Bidirectional conversion is a key advantage. The flexible_dual_grid_to_mesh function (lines 42–84 in flexible_dual_grid.py) reconstructs a mesh by reconnecting dual vertices using stored edge flags:
import o_voxel.convert as ovc
import o_voxel.io as oio
import torch
coords, data = oio.read("helmet.vxz")
# Recover dual vertices and edge flags from packed storage
dual_vertices = data['dual_vertices'].float() / 256
intersected = torch.stack([
data['intersected'] % 2,
data['intersected'] // 2 % 2,
data['intersected'] // 4 % 2,
], dim=-1).bool()
verts, faces = ovc.flexible_dual_grid_to_mesh(
coords.cuda(),
dual_vertices.cuda(),
intersected.cuda(),
split_weight=None,
aabb=[[-0.5, -0.5, -0.5], [0.5, 0.5, 0.5]],
grid_size=256
)
import trimesh
mesh = trimesh.Trimesh(
vertices=verts.cpu().numpy(),
faces=faces.cpu().numpy()
)
mesh.export("helmet_recon.glb")
No marching cubes, no SDF evaluation—just direct reconstruction from the dual grid structure.
Production Export with Baked Textures
For asset delivery, the postprocess.py module provides end‑to‑end conversion to standard formats:
import o_voxel.postprocess as ovp
glb_mesh = ovp.to_glb(
vertices=verts,
faces=faces,
attr_volume=torch.cat([
data['base_color'],
data['metallic'],
data['roughness']
], dim=1),
coords=coords,
attr_layout={
'base_color': slice(0, 3),
'metallic': slice(3, 4),
'roughness': slice(4, 5)
},
grid_size=256,
aabb=[[-0.5, -0.5, -0.5], [0.5, 0.5, 0.5]],
decimation_target=200_000,
texture_size=2048,
verbose=True
)
glb_mesh.export("helmet_baked.glb")
This handles cleaning, remeshing, UV unwrapping, and texture baking in one call.
O‑Voxel vs. Traditional Meshes: Key Differences
| Aspect | Traditional Mesh | O‑Voxel Sparse Voxel Representation |
|---|---|---|
| Topology | Requires watertight, manifold surfaces; non‑manifold geometry breaks pipelines | Handles arbitrary topology including open surfaces and non‑manifold edges |
| Memory scaling | Dense vertex/face lists grow with surface area; high detail → large memory | Sparse storage: memory scales with occupied voxels only |
| Detail preservation | Subdivision or adaptive tessellation needed; risks huge face counts | QEF solver provides sub‑voxel precision without triangle explosion |
| Material workflow | UV unwrapping and texture atlases required; material changes need re‑baking | Volumetric attributes stored per voxel; PBR‑ready without UV maps |
| Conversion speed | Mesh ↔ SDF requires expensive marching cubes or optimization | Instant bidirectional conversion via linear algebra |
| Neural pipeline compatibility | Differentiable rendering costly; often approximated | Native sparse voxel rasterizer fully GPU‑accelerated and differentiable |
Summary
O‑Voxel sparse voxel representation reimagines 3D geometry storage through five technical innovations:
- Flexible Dual Grid replaces continuous surfaces with voxel‑centered dual vertices and edge flags.
- Sparse Morton/Hilbert encoding compresses occupied voxels for memory‑efficient storage.
- QEF‑based surface approximation preserves sharp features with sub‑voxel precision.
- Volumetric PBR attributes eliminate UV dependencies and enable direct material editing.
- Differentiable sparse rasterizer integrates seamlessly into neural 3D generation pipelines.
These properties make O‑Voxel particularly valuable for neural 3D generation, where topology flexibility, fast conversion, and differentiable rendering are essential.
Frequently Asked Questions
What file format does O‑Voxel use for storage?
O‑Voxel uses a custom .vxz format, optionally compressed with Z‑order or Hilbert space‑filling curves. The o_voxel.io module also supports .npz and .ply for interoperability. See io/__init__.py for implementation details.
Can O‑Voxel represent animated or deformable geometry?
The core representation stores static volumetric data. For animation, you would store a sequence of O‑Voxel frames or apply deformation fields to the voxel positions. The sparse structure makes frame‑to‑frame differences compressible with the same Morton/Hilbert encoding.
How does O‑Voxel handle extremely thin features like hair or wires?
The QEF solver in each voxel approximates intersected faces. For features thinner than voxel size, the grid_size parameter controls resolution—256³ is typical, but 512³ or higher can capture finer detail. The dual vertex positioning provides sub‑voxel accuracy for surface reconstruction.
Is O‑Voxel suitable for real‑time game engines?
The sparse voxel rasterizer provides fast preview rendering, but production game engines typically require standard mesh formats. The to_glb pipeline converts O‑Voxel to optimized meshes with baked textures for engine ingestion, preserving the authoring benefits while delivering runtime‑friendly assets.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →