TinyVDB provides lightweight C/C++ libraries for working with OpenVDB data. It includes VDB file I/O, mesh-to-SDF conversion, grid operations, and more — without depending on the full OpenVDB library.
TinyVDB is suitable for genAI, graphics applications, HPC visualization tools, physics simulation, and any project that needs lightweight VDB functionality.
| Header | Language | Description |
|---|---|---|
tinyvdb_io.h |
C11 | OpenVDB file I/O with custom memory allocator support |
tinyvdb_nanovdb.h |
C11 | NanoVDB file I/O (read-only), GPU-friendly sparse volume format |
tinyvdb_mesh.h + tinyvdb_mesh.cc |
C++11 | Mesh-to-SDF, marching cubes, manifold preprocessing |
tinyvdb_ops.h + tinyvdb_ops.cc |
C++11 | Grid operations: morphology, filtering, CSG, differential operators, advection, ray tracing, fracture |
- Dependency-free C11 code (header-only, single file)
- Custom memory allocator interface (arena/pool allocator friendly)
- mmap-based file access with heap-buffer fallback
- UTF-8 path support on all platforms (Windows WideChar + long path
\\?\prefix) - Cross-platform (Linux, macOS, Windows)
- Big endian support (e.g., Power, SPARC)
- Read and write OpenVDB files (version 220 to 225)
- Multiple grid/tree topologies (not limited to
Tree_float_5_4_3) - ZIP compression (via bundled miniz or system zlib)
- BLOSC compression (built-in, using bundled LZ4 — no external blosc dependency)
- Active mask compression (per-node flags 0-6)
- Half-float (FP16) grid support
- PointIndexGrid (
Tree_ptidx32_*) leaf payload read/write - PointDataGrid (
Tree_ptdataidx32_*) topology read + opaque point payload round-trip
- Read NanoVDB files (version 32+)
- Write NanoVDB files (raw data serialization)
- Support for all grid types (Float, Double, Vec3f, Int32, etc.)
- Support for compressed files (ZIP, BLOSC)
- Grid metadata access (voxel size, bounding box, node counts)
- Endianness handling (little and big endian)
- Memory buffer I/O support
- Node size calculation utilities
- Triangle mesh to signed distance field (dense 3D grid)
- SDF to triangle mesh (marching cubes)
- Manifold preprocessing (mesh to SDF to mesh round-trip)
- Configurable sign determination (flood fill or sweep)
- Morphological dilation/erosion, open/close
- Gaussian, mean, and Laplacian SDF filtering
- CSG operations (union, intersection, difference)
- Surface area and volume measurement
- Differential operators (gradient, divergence, Laplacian, curl)
- Finite difference stencils (central, forward, backward)
- Semi-Lagrangian advection (RK2)
- Poisson solver (preconditioned conjugate gradient)
- Ray-SDF intersection (sphere tracing)
- Volume to spheres (greedy adaptive sphere packing)
- Particles to SDF (sphere stamping)
- Level set fracture (cutter-based volume splitting)
| Version | Feature |
|---|---|
| 220 | Selective compression |
| 221 | Float frustum bbox |
| 222 | Node mask compression, per-grid compression flags |
| 223 | BLOSC compression, point index grid |
| 224 | Multipass I/O |
| 225 | Half-float grid type |
Copy src/tinyvdb_io.h, src/miniz.c, src/miniz.h, src/lz4.c, and src/lz4.h to your project. BLOSC compression (LZ4) is built-in — no external dependency needed.
/* In exactly one .c or .cc file: */
#define TINYVDB_IO_IMPLEMENTATION
#include "tinyvdb_io.h"tvdb_file_t file;
tvdb_error_t err = {0};
tvdb_status_t st = tvdb_file_open(&file, "input.vdb", NULL, &err);
if (st != TVDB_OK) { /* handle error */ }
st = tvdb_read_all_grids(&file, &err);
if (st != TVDB_OK) { /* handle error */ }
for (size_t i = 0; i < tvdb_grid_count(&file); i++) {
printf("Grid: %s Type: %s\n",
tvdb_grid_name(&file, i),
tvdb_grid_type_name(&file, i));
}
tvdb_file_close(&file);/* After reading/modifying a file, write it back: */
tvdb_status_t st = tvdb_file_save(&file, "output.vdb",
TVDB_COMPRESS_BLOSC | TVDB_COMPRESS_ACTIVE_MASK,
/*use_mmap=*/0, &err);Or write to a memory buffer:
uint8_t *data = NULL;
size_t data_size = 0;
tvdb_status_t st = tvdb_write_to_memory(&file,
TVDB_COMPRESS_ZIP | TVDB_COMPRESS_ACTIVE_MASK,
&data, &data_size, &err);
/* ... use data ... */
free(data);/* In exactly one .c or .cc file: */
#define TINYVDB_NANOVDB_IMPLEMENTATION
#include "tinyvdb_nanovdb.h"tvdb_nanovdb_file_t file;
tvdb_error_t err;
memset(&err, 0, sizeof(err));
tvdb_status_t st = tvdb_nanovdb_file_open(&file, "input.nvdb", NULL, &err);
if (st != TVDB_OK) { /* handle error */ }
for (size_t i = 0; i < tvdb_nanovdb_grid_count(&file); i++) {
printf("Grid: %s Type: %s\n",
tvdb_nanovdb_grid_name(&file, i),
tvdb_nanovdb_grid_type_name(tvdb_nanovdb_grid_type(&file, i)));
}
tvdb_nanovdb_file_close(&file);tvdb_allocator_t alloc = {
.malloc_fn = my_malloc,
.realloc_fn = my_realloc,
.free_fn = my_free,
.user_ctx = my_arena
};
tvdb_file_open(&file, "input.vdb", &alloc, &err);The allocator passes old_size to realloc_fn and size to free_fn, enabling arena/pool allocators that don't track allocation sizes internally.
Include tinyvdb_mesh.h and compile/link src/tinyvdb_mesh.cc.
// Mesh to SDF
tvdb_mesh::DenseGrid grid;
tvdb_mesh::MeshToSDF(mesh, voxel_size, band_width, &grid);
// SDF to mesh (marching cubes)
tvdb_mesh::TriangleMesh output;
tvdb_mesh::SDFToMesh(grid, 0.0f, &output);
// Manifold preprocessing (mesh -> SDF -> mesh round-trip)
tvdb_mesh::MakeManifold(input, resolution, isovalue, &output);Include tinyvdb_ops.h and compile/link src/tinyvdb_ops.cc.
// CSG union of two SDF grids
tvdb_ops::CSGUnion(grid_a, grid_b, &result);
// Gaussian smoothing
tvdb_ops::GaussianFilter(&grid, /*width=*/1, /*iterations=*/3);
// Ray-SDF intersection
tvdb_ops::RayHit hit;
if (tvdb_ops::RayCastSDF(grid, origin, dir, max_t, &hit)) {
// hit.position, hit.normal, hit.t
}| Flag | Description |
|---|---|
TVDB_USE_SYSTEM_ZLIB |
Use system zlib instead of bundled miniz |
TVDB_NO_MMAP |
Disable mmap, always read into heap buffer |
CMake build is provided for example/test builds.
$ git submodule update --init --recursive --depth 1
$ mkdir build
$ cd build
$ cmake ..
$ make
| Option | Default | Description |
|---|---|---|
TINYVDB_USE_SYSTEM_ZLIB |
OFF |
Use system zlib instead of bundled miniz |
TINYVDB_BUILD_EXAMPLES |
ON |
Build vdbdump and nanovdbdump examples |
TINYVDB_BUILD_VDBRENDER |
ON |
Build the vdbrender volume path tracer |
TINYVDB_BUILD_PYTHON |
OFF |
Build Python extension |
A command-line tool that reads a VDB file and prints its structure:
$ ./vdbdump input.vdb --verbose
File: input.vdb
VDB version: 224 (lib 6.2)
UUID: 1569c382-d056-4c66-aa3e-9b0ca351fe91
Grids: 1
Grid[0]: "surface"
Type: Tree_float_5_4_3
Tree: 4 levels [Root, Internal(log2dim=5), Internal(log2dim=4), Leaf(log2dim=3)]
Background: 0.3
Root: 0 tiles, 8 children
Nodes: 25 total (16 internal, 8 leaf)
Active voxels: 2.08K
Transform: UniformScale
Voxel size: (0.1, 0.1, 0.1)
Compression: blosc+active_mask (0x6)
Write a copy with --write or --write-mmap:
$ ./vdbdump input.vdb --write output.vdb
$ ./vdbdump input.vdb --write-mmap output_mmap.vdb
A command-line tool that reads a NanoVDB file and prints its structure:
$ ./nanovdbdump input.nvdb --verbose
File: input.nvdb
NanoVDB version: 32
Grids: 1
Codec: blosc
Grid[0]: "surface"
Type: Float
Class: LevelSet
Grid size: 123456 bytes
Voxel size: (0.1, 0.1, 0.1)
World bbox: [(0, 0, 0), (100, 100, 100)]
Index bbox: [(0, 0, 0), (999, 999, 999)]
Active voxels: 123456
Nodes: 10 leaf, 5 lower, 2 upper
Tiles: 0 level0, 0 level1, 0 level2
Install the Python extension:
pip install tinyvdbOr build from source:
cd python && pip install .Usage:
import tinyvdb
# Open a NanoVDB file
with tinyvdb.NanoVDBFile("input.nvdb") as f:
print(f"Grids: {f.grid_count()}")
for i in range(f.grid_count()):
print(f"Grid {i}: {f.grid_name(i)}")
print(f" Type: {f.grid_type(i)}")
print(f" BBox: {f.bbox(i)}")
print(f" Voxel size: {f.voxel_size(i, 0)}")
# Get a voxel value
val = f.get(0, 100, 100, 100)
print(f"Value at (100, 100, 100): {val}")Writing:
# Save to file
f.save("output.nvdb", codec=tinyvdb.CODEC_BLOSC)
# Write to bytes
data = f.to_bytes(codec=tinyvdb.CODEC_ZIP)Utility functions:
import tinyvdb
# Get node sizes
leaf_size = tinyvdb.leaf_node_size() # Default: Float
leaf_size = tinyvdb.leaf_node_size(tinyvdb.GRID_TYPE_DOUBLE)
# Get value size
val_size = tinyvdb.value_size() # 4 for Float
val_size = tinyvdb.value_size(tinyvdb.GRID_TYPE_VEC3F) # 12background is a uniform constant value used when there is no voxel data.
Node is composed of Root, Internal, and Leaf.
Leaf contains actual voxel data.
Root and Internal nodes have Value or a pointer to a child node, where Value is a constant value for the node.
There are two bit masks, child mask and value mask, for each internal node.
TinyVDB is licensed under the Apache License, Version 2.0.
Copyright 2026 - Present Syoyo Fujita
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
| Library | License |
|---|---|
| OpenVDB (original I/O logic) | Apache 2.0 |
| LZ4 | BSD 2-Clause |
| miniz | MIT |
| tinyexr (vdbrender example) | BSD 3-Clause |