Skip to content

Add MeshToGrid: CUDA triangle mesh to NanoVDB UDF/IndexGrid converter#2178

Open
sifakis wants to merge 10 commits intoAcademySoftwareFoundation:masterfrom
sifakis:mesh-to-grid
Open

Add MeshToGrid: CUDA triangle mesh to NanoVDB UDF/IndexGrid converter#2178
sifakis wants to merge 10 commits intoAcademySoftwareFoundation:masterfrom
sifakis:mesh-to-grid

Conversation

@sifakis
Copy link
Copy Markdown
Contributor

@sifakis sifakis commented Mar 16, 2026

Summary

Introduces nanovdb::tools::cuda::MeshToGrid<BuildT>, a GPU-accelerated rasterizer that converts a triangle soup (vertex list + index list) into a sparse NanoVDB grid on the device. Two output modes are implemented:

  • getHandle() — produces a ValueOnIndex (topology-only) IndexGrid
  • getHandleAndUDF() — produces the same grid plus a device sidecar buffer of per-active-voxel unsigned distances (world-space UDF, ordered by active voxel index)

SDF (signed distance field) output is planned future work.

Pipeline

  1. transformTriangles() — vertices transformed to NanoVDB index space
  2. processRootTrianglePairs() — enumerates (root tile, triangle) pairs whose padded AABBs overlap, via CUB prefix-sum + scatter (no atomics)
  3. processLeafTrianglePairs() — 3-level 8× subdivision (4096→512→64→8), refining the pair list at each level using triangle-AABB / SAT intersection tests (1 CTA per pair, 512 threads, warp ballot + CUB block reduce)
  4. Topology assembly — unique leaf origins extracted, NanoVDB ValueOnIndex tree built via TopologyBuilder, empty leaves pruned via PruneGrid
  5. UDF computation (getHandleAndUDF only) — ComputeUDFFunctor computes per-voxel point-to-triangle distances via atomic-min, finalized to world-space in a second pass

Files

New:

  • nanovdb/nanovdb/tools/cuda/MeshToGrid.cuh — main converter
  • nanovdb/nanovdb/math/Proximity.h — point/triangle proximity utilities
  • nanovdb/nanovdb/util/cuda/Rasterization.cuhComputeUDFFunctor and helpers

Modified:

  • nanovdb/nanovdb/tools/cuda/TopologyBuilder.cuh — adds InitGridTreeRootFunctor for constructing grids from scratch (no source grid to copy metadata from)
  • nanovdb/nanovdb/util/cuda/Util.h — adds operatorKernelInstance for launching pre-constructed device functors carrying data members
  • nanovdb/nanovdb/examples/CMakeLists.txt — registers ex_mesh_to_grid_cuda

Tests

Two unit tests added to TestNanoVDB.cu:

  • MeshToGrid_EmptyMesh — verifies an empty triangle list produces a valid zero-active-voxel grid
  • MeshToGrid_UnitTetrahedron — verifies topology and UDF accuracy against a CPU brute-force reference on a unit tetrahedron

Known TODOs

  • The Triangle POD struct (currently in MeshToGrid.cuh) is a candidate for migration to a dedicated geometric-primitives header in nanovdb/math/ once such a header is introduced.

Introduces nanovdb::tools::cuda::MeshToGrid<BuildT>, a GPU-accelerated
rasterizer that converts a triangle soup (vertex list + index list) into
a sparse NanoVDB grid on the device. Two output modes are implemented:

  - getHandle()         — produces a ValueOnIndex (topology-only) grid
  - getHandleAndUDF()   — produces the same grid plus a device sidecar
                          buffer of per-active-voxel unsigned distances
                          (world-space UDF, ordered by active voxel index)

SDF (signed distance field) output is planned future work.

The pipeline uses a hierarchical top-down subdivision approach:
1. transformTriangles()       — vertices transformed to NanoVDB index space
                                via nanovdb::Map::applyInverseMap()
2. processRootTrianglePairs() — enumerates (root tile, triangle) pairs whose
                                padded AABBs overlap, via CUB prefix-sum +
                                scatter (no atomics)
3. processLeafTrianglePairs() — 3-level 8x subdivision (4096→512→64→8),
                                refining the pair list at each level using
                                triangle-AABB / SAT intersection tests
                                (evaluateAndCountSubBoxesKernel: 1 CTA per
                                pair, 512 threads, warp ballot + CUB reduce)
4. Topology assembly          — unique leaf origins extracted, NanoVDB
                                ValueOnIndex tree built via TopologyBuilder,
                                empty leaves pruned via PruneGrid
5. UDF computation (optional) — ComputeUDFFunctor computes per-voxel
                                point-to-triangle distances via atomic-min,
                                finalized to world-space in a second pass

The example driver (ex_mesh_to_grid_cuda) reads an OBJ file, builds an
OpenVDB UDF as reference, and runs a thorough correctness validation:
  - Active voxel set comparison (false negatives, false positive rate)
  - Per-voxel UDF error histogram vs. OpenVDB reference (in voxel units)
  - CPU brute-force ground-truth check on the worst-error voxels

New files:
  nanovdb/nanovdb/tools/cuda/MeshToGrid.cuh
  nanovdb/nanovdb/math/Proximity.h         — point/triangle proximity utils
  nanovdb/nanovdb/util/cuda/Rasterization.cuh — ComputeUDFFunctor and helpers

Modified files:
  nanovdb/nanovdb/tools/cuda/TopologyBuilder.cuh — adds InitGridTreeRootFunctor
    for constructing grids from scratch (no source grid to copy metadata from)
  nanovdb/nanovdb/util/cuda/Util.h — adds operatorKernelInstance for launching
    pre-constructed device functors carrying data members
  nanovdb/nanovdb/examples/CMakeLists.txt — registers ex_mesh_to_grid_cuda

Signed-off-by: Efty Sifakis <esifakis@nvidia.com>
sifakis and others added 5 commits March 16, 2026 12:59
Signed-off-by: Efty Sifakis <esifakis@nvidia.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Efty Sifakis <esifakis@nvidia.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Efty Sifakis <esifakis@nvidia.com>
Replace throws-on-zero with early returns throughout the pipeline so
that getHandle() and getHandleAndUDF() return a valid empty grid when
triangleCount == 0. Kernel launches over null sets are explicitly
guarded before any buffer pointer is dereferenced.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Efty Sifakis <esifakis@nvidia.com>
MeshToGrid_EmptyMesh: verifies that getHandle() returns a valid grid
with zero active voxels when the triangle list is empty.

MeshToGrid_UnitTetrahedron: constructs a unit tetrahedron (4 vertices,
4 faces) with voxel size 0.1. Tests both getHandle() (topology-only)
and getHandleAndUDF() (with sidecar). Checks:
  - Topology: checksum from getHandle() equals that from getHandleAndUDF(),
    confirming both paths produce identical grids.
  - Per-voxel UDF: sidecar values match CPU brute-force (index-space
    pointToTriangleDistSqr * voxelSize) within 1e-3 voxels.
  - No false positives: every active voxel lies within the narrow band.
  - No false negatives: every voxel with CPU UDF strictly inside the band
    is active (checked via leaf-iteration active set).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Efty Sifakis <esifakis@nvidia.com>
sifakis and others added 3 commits March 17, 2026 17:28
mesh_to_grid_cuda.cpp:
  - Remove unused #include <openvdb/tools/Morphology.h>
  - Fix cout label "openvdb_triangles" -> "triangles"
  - Replace atof(argv[3]) with std::stof
  - Remove spurious blank lines

mesh_to_grid_cuda_kernels.cu:
  - Remove unused #include <array> (leftover from std::array<Vec3f,3> era)
  - Collapse triple blank line after getHandleAndUDF() to single
  - Replace the detailed per-voxel histogram, top-N worst-voxel heap,
    and inline Ericson brute-force with a compact summary: active voxel
    counts, false negative/positive counts, and a single UDF quality
    line (max error + count exceeding 0.1 voxels vs OpenVDB reference)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Efty Sifakis <esifakis@nvidia.com>
All mVerbose>=2 guarded timer restarts and printf diagnostics have been
removed. Verbosity level 1 (function-scale start/stop timing) is retained.

Signed-off-by: Efty Sifakis <esifakis@nvidia.com>
…accessors

- getHandle: parameter renamed from pool to buffer, matching the
  convention used throughout NanoVDB tools (PointsToGrid, DilateGrid, etc.)
- getHandleAndUDF: parameters renamed from gridPool/sidecarPool to
  buffer/sidecarBuffer for the same reason
- Local raw-buffer variables renamed to gridBuffer to avoid shadowing
  the (now renamed) buffer parameter
- Remove getPairCount(), getDevicePairs(), getRootTileCount(), and
  getDeviceRootOrigins() — unused diagnostic-only accessors

Signed-off-by: Efty Sifakis <esifakis@nvidia.com>
@sifakis sifakis marked this pull request as ready for review March 17, 2026 22:51
@sifakis sifakis requested a review from kmuseth as a code owner March 17, 2026 22:51
Signed-off-by: Efty Sifakis <esifakis@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant