NanoRT, single header only modern ray tracing kernel.
C++
1,120
544 commits
updated Oct 1, 2025

Path tracing example contributed by https://github.com/daseyb
NanoRT is simple single header only ray tracing kernel.
c89 branch https://github.com/lighttransport/nanort/tree/c89NanoRT is a linear array and does not have pointers, thus it is suited for GPU raytracing (GPU ray traversal).nanort::Ray represents ray. The origin org, the direction dir (not necessarily normalized), the minimum hit distance min_t(usually 0.0) and the maximum hit distance max_t (usually too far, e.g. 1.0e+30) must be filled before shooting ray.
nanort::BVHAccel builds BVH data structure from geometry, and provides the function to find intersection point for a given ray.
nanort::BVHBuildOptions specifies parameters for BVH build. Usually default parameters should work well.
nanort::BVHTraceOptions specifies ray traverse/intersection options.
template<typename T>
class {
T org[3]; // [in] must set
T dir[3]; // [in] must set
T min_t; // [in] must set
T max_t; // [in] must set
unsigned int type; // optional. ray type.
} Ray;
class BVHTraceOptions {
// Trace rays only in face ids range. faceIdsRange[0] < faceIdsRange[1]
// default: 0 to 0x3FFFFFFF(2G faces)
unsigned int prim_ids_range[2];
bool cull_back_face; // default: false
};
nanort::BVHBuildOptions<float> build_options; // BVH build option(optional)
const float *vertices = ...;
const unsigned int *faces = ...;
// Need to specify stride bytes for `vertices`.
// When vertex is stored XYZXYZXYZ... in float type, stride become 12(= sizeof(float) * 3).
nanort::TriangleMesh<float> triangle_mesh(vertices, faces, /* stride */sizeof(float) * 3);
nanort::TriangleSAHPred<float> triangle_pred(vertices, faces, /* stride */sizeof(float) * 3);
nanort::BVHAccel<float> accel;
ret = accel.Build(mesh.num_faces, triangle_mesh, triangle_pred, build_options);
nanort::TriangleIntersector<> triangle_intersecter(vertices, faces, /* stride */sizeof(float) * 3);
nanort::Ray<float> ray;
// fill ray org and ray dir.
...
// fill minimum and maximum hit distance.
ray.min_t = 0.0f;
ray.max_t = 1.0e+30f;
nanort::TriangleIntersection<float> isect;
// Store nearest hit point to `isect` and returns true if the hit point found.
BVHTraceOptions trace_options; // optional
bool hit = accel.Traverse(ray, triangle_intersecter, &isect, trace_options);
Application must prepare geometric information and store it in linear array.
For a builtin Triangle intersector,
vertices : The array of triangle vertices (e.g. xyz * numVertices)faces : The array of triangle face indices (3 * numFaces)stride : Byte stride of each vertex dataare required attributes.
// NanoRT defines template based class, so no NANORT_IMPLEMENTATION anymore.
#include "nanort.h"
Mesh mesh;
// load mesh data...
nanort::BVHBuildOptions<float> options; // Use default option
nanort::TriangleMesh<float> triangle_mesh(mesh.vertices, mesh.faces, /* stride */sizeof(float) * 3);
nanort::TriangleSAHPred<float> triangle_pred(mesh.vertices, mesh.faces, /* stride */sizeof(float) * 3);
nanort::BVHAccel<float> accel;
ret = accel.Build(mesh.vertices, mesh.faces, mesh.num_faces, options);
assert(ret);
nanort::BVHBuildStatistics stats = accel.GetStatistics();
printf(" BVH statistics:\n");
printf(" # of leaf nodes: %d\n", stats.num_leaf_nodes);
printf(" # of branch nodes: %d\n", stats.num_branch_nodes);
printf(" Max tree depth : %d\n", stats.max_tree_depth);
std::vector<float> rgb(width * height * 3, 0.0f);
const float tFar = 1.0e+30f;
// Shoot rays.
#ifdef _OPENMP
#pragma omp parallel for
#endif
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
BVHTraceOptions trace_options;
// Simple camera. change eye pos and direction fit to .obj model.
nanort::Ray<float> ray;
ray.min_t = 0.0f;
ray.max_t = tFar;
ray.org[0] = 0.0f;
ray.org[1] = 5.0f;
ray.org[2] = 20.0f;
float3 dir;
dir[0] = (x / (float)width) - 0.5f;
dir[1] = (y / (float)height) - 0.5f;
dir[2] = -1.0f;
dir.normalize();
ray.dir[0] = dir[0];
ray.dir[1] = dir[1];
ray.dir[2] = dir[2];
nanort::TriangleIntersector<> triangle_intersecter(mesh.vertices, mesh.faces, /* stride */sizeof(float) * 3);
nanort::TriangleIntersection<> isect,
bool hit = accel.Traverse(ray, triangle_intersector, &isect, trace_options);
if (hit) {
// Write your shader here.
float3 normal;
unsigned int fid = triangle_intersector.intersect.prim_id;
normal[0] = mesh.facevarying_normals[3*3*fid+0]; // @todo { interpolate normal }
normal[1] = mesh.facevarying_normals[3*3*fid+1];
normal[2] = mesh.facevarying_normals[3*3*fid+2];
// Flip Y
rgb[3 * ((height - y - 1) * width + x) + 0] = fabsf(normal[0]);
rgb[3 * ((height - y - 1) * width + x) + 1] = fabsf(normal[1]);
rgb[3 * ((height - y - 1) * width + x) + 2] = fabsf(normal[2]);
}
}
}
NANORT_USE_CPP11_FEATURE : Enable C++11 feature
NANORT_ENABLE_PARALLEL_BUILD : Enable parallel BVH build(OpenMP version is not yet fully tested).
See examples directory for example renderer using NanoRT.
Here is an example of custom geometry.
examples/particle_primitive/examples/curves_primitive/examples/cylinder_primitive/And plesae see API at wiki: https://github.com/lighttransport/nanort/wiki/API
nanort.h is licensed under MIT license.
NanoRT uses stack_container.h which is licensed under:
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
NanoRT examples use some external third party libraries. Licenses for such third party libraries obey their own license.
Lucy statue model is from The Stanford 3D Scanning Repository http://graphics.stanford.edu/data/3Dscanrep/
PR are always welcome!
1,045 followers · starred Dec 2018
1,861 followers · starred Aug 2022
2,034 followers · starred Aug 2019
2,541 followers · starred Apr 2018
NanoRT, single header only modern ray tracing kernel.
C++
1,120
544 commits
updated Oct 1, 2025

Path tracing example contributed by https://github.com/daseyb
NanoRT is simple single header only ray tracing kernel.
c89 branch https://github.com/lighttransport/nanort/tree/c89NanoRT is a linear array and does not have pointers, thus it is suited for GPU raytracing (GPU ray traversal).nanort::Ray represents ray. The origin org, the direction dir (not necessarily normalized), the minimum hit distance min_t(usually 0.0) and the maximum hit distance max_t (usually too far, e.g. 1.0e+30) must be filled before shooting ray.
nanort::BVHAccel builds BVH data structure from geometry, and provides the function to find intersection point for a given ray.
nanort::BVHBuildOptions specifies parameters for BVH build. Usually default parameters should work well.
nanort::BVHTraceOptions specifies ray traverse/intersection options.
template<typename T>
class {
T org[3]; // [in] must set
T dir[3]; // [in] must set
T min_t; // [in] must set
T max_t; // [in] must set
unsigned int type; // optional. ray type.
} Ray;
class BVHTraceOptions {
// Trace rays only in face ids range. faceIdsRange[0] < faceIdsRange[1]
// default: 0 to 0x3FFFFFFF(2G faces)
unsigned int prim_ids_range[2];
bool cull_back_face; // default: false
};
nanort::BVHBuildOptions<float> build_options; // BVH build option(optional)
const float *vertices = ...;
const unsigned int *faces = ...;
// Need to specify stride bytes for `vertices`.
// When vertex is stored XYZXYZXYZ... in float type, stride become 12(= sizeof(float) * 3).
nanort::TriangleMesh<float> triangle_mesh(vertices, faces, /* stride */sizeof(float) * 3);
nanort::TriangleSAHPred<float> triangle_pred(vertices, faces, /* stride */sizeof(float) * 3);
nanort::BVHAccel<float> accel;
ret = accel.Build(mesh.num_faces, triangle_mesh, triangle_pred, build_options);
nanort::TriangleIntersector<> triangle_intersecter(vertices, faces, /* stride */sizeof(float) * 3);
nanort::Ray<float> ray;
// fill ray org and ray dir.
...
// fill minimum and maximum hit distance.
ray.min_t = 0.0f;
ray.max_t = 1.0e+30f;
nanort::TriangleIntersection<float> isect;
// Store nearest hit point to `isect` and returns true if the hit point found.
BVHTraceOptions trace_options; // optional
bool hit = accel.Traverse(ray, triangle_intersecter, &isect, trace_options);
Application must prepare geometric information and store it in linear array.
For a builtin Triangle intersector,
vertices : The array of triangle vertices (e.g. xyz * numVertices)faces : The array of triangle face indices (3 * numFaces)stride : Byte stride of each vertex dataare required attributes.
// NanoRT defines template based class, so no NANORT_IMPLEMENTATION anymore.
#include "nanort.h"
Mesh mesh;
// load mesh data...
nanort::BVHBuildOptions<float> options; // Use default option
nanort::TriangleMesh<float> triangle_mesh(mesh.vertices, mesh.faces, /* stride */sizeof(float) * 3);
nanort::TriangleSAHPred<float> triangle_pred(mesh.vertices, mesh.faces, /* stride */sizeof(float) * 3);
nanort::BVHAccel<float> accel;
ret = accel.Build(mesh.vertices, mesh.faces, mesh.num_faces, options);
assert(ret);
nanort::BVHBuildStatistics stats = accel.GetStatistics();
printf(" BVH statistics:\n");
printf(" # of leaf nodes: %d\n", stats.num_leaf_nodes);
printf(" # of branch nodes: %d\n", stats.num_branch_nodes);
printf(" Max tree depth : %d\n", stats.max_tree_depth);
std::vector<float> rgb(width * height * 3, 0.0f);
const float tFar = 1.0e+30f;
// Shoot rays.
#ifdef _OPENMP
#pragma omp parallel for
#endif
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
BVHTraceOptions trace_options;
// Simple camera. change eye pos and direction fit to .obj model.
nanort::Ray<float> ray;
ray.min_t = 0.0f;
ray.max_t = tFar;
ray.org[0] = 0.0f;
ray.org[1] = 5.0f;
ray.org[2] = 20.0f;
float3 dir;
dir[0] = (x / (float)width) - 0.5f;
dir[1] = (y / (float)height) - 0.5f;
dir[2] = -1.0f;
dir.normalize();
ray.dir[0] = dir[0];
ray.dir[1] = dir[1];
ray.dir[2] = dir[2];
nanort::TriangleIntersector<> triangle_intersecter(mesh.vertices, mesh.faces, /* stride */sizeof(float) * 3);
nanort::TriangleIntersection<> isect,
bool hit = accel.Traverse(ray, triangle_intersector, &isect, trace_options);
if (hit) {
// Write your shader here.
float3 normal;
unsigned int fid = triangle_intersector.intersect.prim_id;
normal[0] = mesh.facevarying_normals[3*3*fid+0]; // @todo { interpolate normal }
normal[1] = mesh.facevarying_normals[3*3*fid+1];
normal[2] = mesh.facevarying_normals[3*3*fid+2];
// Flip Y
rgb[3 * ((height - y - 1) * width + x) + 0] = fabsf(normal[0]);
rgb[3 * ((height - y - 1) * width + x) + 1] = fabsf(normal[1]);
rgb[3 * ((height - y - 1) * width + x) + 2] = fabsf(normal[2]);
}
}
}
NANORT_USE_CPP11_FEATURE : Enable C++11 feature
NANORT_ENABLE_PARALLEL_BUILD : Enable parallel BVH build(OpenMP version is not yet fully tested).
See examples directory for example renderer using NanoRT.
Here is an example of custom geometry.
examples/particle_primitive/examples/curves_primitive/examples/cylinder_primitive/And plesae see API at wiki: https://github.com/lighttransport/nanort/wiki/API
nanort.h is licensed under MIT license.
NanoRT uses stack_container.h which is licensed under:
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
NanoRT examples use some external third party libraries. Licenses for such third party libraries obey their own license.
Lucy statue model is from The Stanford 3D Scanning Repository http://graphics.stanford.edu/data/3Dscanrep/
PR are always welcome!
1,045 followers · starred Dec 2018
1,861 followers · starred Aug 2022
2,034 followers · starred Aug 2019
2,541 followers · starred Apr 2018