# How to Export Generated 3D Assets to GLB with PBR Materials Using o_voxel.postprocess.to_glb()

> Export 3D assets to GLB with PBR materials using o_voxel.postprocess.to_glb(). Streamline your workflow with GPU-accelerated mesh cleaning and texture baking.

- Repository: [Microsoft/TRELLIS.2](https://github.com/microsoft/TRELLIS.2)
- Tags: how-to-guide
- Published: 2026-08-04

---

**The `o_voxel.postprocess.to_glb()` function converts raw voxel-derived meshes into production-ready GLB files by performing GPU-accelerated mesh cleaning, UV unwrapping, differentiable texture baking, and PBR material assembly in a single end-to-end pipeline.**

The Microsoft TRELLIS.2 repository provides a complete pipeline for transforming neural voxel representations into industry-standard 3D assets. When you need to export generated 3D assets to GLB with PBR materials using `o_voxel.postprocess.to_glb()`, the function encapsulates the entire post-processing workflow—from mesh decimation to texture baking—leveraging CUDA-accelerated libraries to handle high-resolution geometry efficiently.

## Understanding the GLB Export Pipeline

The implementation in [`o-voxel/o_voxel/postprocess.py`](https://github.com/microsoft/TRELLIS.2/blob/main/o-voxel/o_voxel/postprocess.py) orchestrates a seven-stage pipeline that runs entirely on the GPU. According to the source code, each stage prepares the geometry and materials for standard glTF 2.0 compliance.

### Input Normalization and Grid Setup

The function begins by accepting vertex tensors, face indices, a sparse attribute volume, voxel coordinates, and axis-aligned bounding box (AABB) data. As implemented in lines 60–88 of [`postprocess.py`](https://github.com/microsoft/TRELLIS.2/blob/main/postprocess.py), the routine converts any list, tuple, or NumPy inputs to `torch.Tensor` objects and derives the voxel grid size when only one of `voxel_size` or `grid_size` is explicitly supplied.

### GPU-Accelerated Mesh Processing

All geometry is immediately moved to the GPU and wrapped in a `cumesh.CuMesh` object (lines 99–152). This CUDA-based structure enables fast mesh cleaning, hole-filling, and optional remeshing via dual contouring. The pipeline supports aggressive simplification through the `decimation_target` parameter, which reduces vertex counts to meet performance budgets for web delivery or real-time rendering.

### UV Parameterization with Cone-Angle Clustering

Before texture baking, the cleaned mesh undergoes UV unwrapping using cone-angle clustering (`mesh.uv_unwrap`). Lines 95–112 of the source keep the resulting UV atlas, vertex normals, and vertex-to-face maps resident on GPU memory. This ensures seamless handoff to the rasterization stage without costly CPU-GPU transfers.

### Differentiable Texture Baking

The core sampling logic (lines 124–166) utilizes `nvdiffrast` to rasterize the UV-mapped mesh into a 2D texture of size `texture_size`. For each texel, the renderer recovers the corresponding 3D position, queries a BVH-accelerated lookup against the original high-resolution mesh, and trilinearly samples the sparse attribute volume (`grid_sample_3d`). The sampled channels are then split according to `attr_layout` specifications for Base-Color, Metallic, Roughness, and Alpha.

### PBR Material Assembly

Following the baking stage, the function constructs a `trimesh.visual.material.PBRMaterial` object (lines 87–103). The channel mapping follows standard glTF conventions:
* **Base-Color and Alpha** are combined into a single RGBA texture.
* **Metallic and Roughness** are packed into a single texture where the **Red channel = 0**, **Green channel = Roughness**, and **Blue channel = Metallic**.

The textures undergo in-painting to fill UV seam artifacts before final assembly.

### Coordinate System Conversion

GLTF expects a right-handed Y-up coordinate system. Lines 112–115 of [`postprocess.py`](https://github.com/microsoft/TRELLIS.2/blob/main/postprocess.py) handle the necessary transforms by swapping the Y and Z axes and flipping the V-coordinate of the UV map to ensure correct orientation in standard 3D viewers.

## Complete Usage Example

The repository provides a reference implementation in [`o-voxel/examples/ovox2glb.py`](https://github.com/microsoft/TRELLIS.2/blob/main/o-voxel/examples/ovox2glb.py) that demonstrates the full workflow from voxel file to GLB export.

### Basic Export Workflow

```python
import torch
import o_voxel

# Load voxel-encoded asset

coords, data = o_voxel.io.read("ovoxel_helmet.vxz")
dual_vertices = data["dual_vertices"] / 255
intersected = torch.stack([
    data["intersected"] % 2,
    data["intersected"] // 2 % 2,
    data["intersected"] // 4 % 2,
], dim=-1).bool()

# Convert dual-grid to raw mesh

rec_verts, rec_faces = o_voxel.convert.flexible_dual_grid_to_mesh(
    coords.cuda(),
    dual_vertices.cuda(),
    intersected.cuda(),
    grid_size=512,
    aabb=[[-0.5, -0.5, -0.5], [0.5, 0.5, 0.5]],
)

# Pack PBR attributes into attribute volume

attr_volume = torch.cat([
    data["base_color"].cuda(),
    data["metallic"].cuda(),
    data["roughness"].cuda(),
    data["alpha"].cuda(),
], dim=-1) / 255

attr_layout = {
    "base_color": slice(0, 3),
    "metallic":   slice(3, 4),
    "roughness":  slice(4, 5),
    "alpha":      slice(5, 6),
}

# Export to GLB with PBR materials

mesh = o_voxel.postprocess.to_glb(
    vertices=rec_verts,
    faces=rec_faces,
    attr_volume=attr_volume,
    coords=coords.cuda(),
    attr_layout=attr_layout,
    grid_size=512,
    aabb=[[-0.5, -0.5, -0.5], [0.5, 0.5, 0.5]],
    decimation_target=100_000,
    texture_size=2048,
    verbose=True,
)

mesh.export("rec_helmet.glb")

```

### Customizing Pipeline Parameters

The `to_glb()` function exposes several parameters to control quality and performance:

* **remesh=True** – Enables dual-contouring remeshing before simplification. Use this when extracting non-manifold geometry from complex voxel grids.
* **decimation_target** – Target vertex count after simplification. Set to `30_000` for web-optimized assets or `100_000+` for high-detail cinematic models.
* **texture_size** – Resolution of baked PBR textures. Typical values are `1024` for fast previews and `4096` for photorealistic production assets.
* **mesh_cluster_refine_iterations** – Controls UV chart clustering quality. Increase to `2` or `3` for cleaner UV seams on complex topology.
* **use_tqdm** – Displays a progress bar for monitoring long-running exports in Jupyter notebooks or CLI environments.

```python
mesh = o_voxel.postprocess.to_glb(
    vertices=rec_verts,
    faces=rec_faces,
    attr_volume=attr_volume,
    coords=coords.cuda(),
    attr_layout=attr_layout,
    grid_size=512,
    aabb=[[-0.5, -0.5, -0.5], [0.5, 0.5, 0.5]],
    decimation_target=50_000,
    texture_size=4096,
    remesh=True,
    mesh_cluster_refine_iterations=2,
    verbose=False,
    use_tqdm=True,
)

```

## Key Implementation Files

Understanding the internal architecture helps when debugging or extending the pipeline:

* **[`o-voxel/o_voxel/postprocess.py`](https://github.com/microsoft/TRELLIS.2/blob/main/o-voxel/o_voxel/postprocess.py)** – Contains the core `to_glb()` routine implementing input normalization, mesh cleaning, UV unwrapping, texture baking, and PBR material creation.
* **[`o-voxel/examples/ovox2glb.py`](https://github.com/microsoft/TRELLIS.2/blob/main/o-voxel/examples/ovox2glb.py)** – Reference implementation showing how to load `.vxz` assets, convert dual-grid representations to meshes, and invoke the export pipeline.
* **[`trellis2/renderers/pbr_mesh_renderer.py`](https://github.com/microsoft/TRELLIS.2/blob/main/trellis2/renderers/pbr_mesh_renderer.py)** – Provides helper utilities for rendering PBR meshes and handling texture material definitions used internally by `to_glb()`.
* **[`trellis2/utils/mesh_utils.py`](https://github.com/microsoft/TRELLIS.2/blob/main/trellis2/utils/mesh_utils.py)** – Supplies additional mesh manipulation helpers for cleaning and simplification operations.

## Summary

* **`o_voxel.postprocess.to_glb()`** handles the complete pipeline from raw voxel mesh to GLB export with physically-based rendering materials.
* The function operates entirely on GPU using **CUDA-accelerated** libraries (`cumesh`, `nvdiffrast`) to process hundreds of thousands of faces and high-resolution textures efficiently.
* **Input normalization** automatically converts various data formats to tensors and derives grid parameters.
* **Texture baking** uses differentiable rasterization to sample sparse voxel attributes onto UV-mapped textures following glTF PBR conventions.
* **Coordinate conversion** automatically transforms geometry to the Y-up, right-handed system required by GLTF standards.

## Frequently Asked Questions

### What input formats does to_glb() accept?

The function accepts `torch.Tensor` objects for vertices, faces, and attribute volumes, though it automatically converts lists, tuples, and NumPy arrays to tensors during input normalization. The `attr_layout` dictionary must define slices for `base_color`, `metallic`, `roughness`, and `alpha` channels that match the last dimension of your `attr_volume` tensor.

### How does the texture baking work internally?

According to the source code in lines 124–166 of [`postprocess.py`](https://github.com/microsoft/TRELLIS.2/blob/main/postprocess.py), the pipeline uses `nvdiffrast` to rasterize the UV-unwrapped mesh into a texture atlas. It then employs a BVH accelerator to map each texel back to 3D space and performs trilinear sampling (`grid_sample_3d`) from the sparse attribute volume. This differentiable approach ensures accurate color transfer from the original high-resolution voxel data to the final texture.

### Can I control the mesh simplification level?

Yes, the `decimation_target` parameter specifies the desired number of vertices after simplification. The `cumesh.CuMesh` implementation performs quadric error decimation on the GPU. For web applications, set this to `30_000` or lower; for high-fidelity assets used in offline rendering, you can maintain original density by setting it higher than your input vertex count or omitting the parameter.

### What coordinate system does the exported GLB use?

The exported GLB conforms to the glTF 2.0 specification, which requires a **right-handed coordinate system with Y-up**. Lines 112–115 of [`postprocess.py`](https://github.com/microsoft/TRELLIS.2/blob/main/postprocess.py) automatically swap the Y and Z axes from the voxel grid's native orientation and flip the UV V-coordinates to ensure correct texture mapping in standard 3D viewers like Blender, Three.js, or Unity.