How to Implement Ray Tracing Using Mesh and Accel Acceleration Structures in Luisa Compute
To implement ray tracing in Luisa Compute, you upload vertex and index buffers to create a Mesh, wrap it in an Accel structure with instance transforms, build both structures, then query the AccelVar inside a kernel using intersect().
Luisa Compute is a high-performance, data-parallel GPU computing framework developed by the Luisa Group. Implementing hardware-accelerated ray tracing requires understanding its two-level acceleration structure system: the bottom-level Mesh for geometry and the top-level Accel for scene instances.
Core Architecture of Ray Tracing in Luisa Compute
The ray tracing pipeline in Luisa Compute separates geometry definition from scene instantiation:
-
Mesh– Represents a vertex-buffer/index-buffer pair. It validates geometry layouts and emits build commands to construct the bottom-level acceleration structure (BLAS). Implemented insrc/runtime/rtx/mesh.cppviaMesh::build(). -
Accel– The top-level acceleration structure (TLAS) that stores instances of meshes, curves, or procedural primitives. Manages instance metadata, visibility masks, and transform matrices. Implemented insrc/runtime/rtx/accel.cppviaAccel::emplace_back()andAccel::build(). -
AccelVar– The DSL-level handle passed into kernels. Exposes theintersect()method used to query the TLAS during ray traversal.
Step-by-Step Implementation Guide
The following complete example is derived from the official test suite in src/tests/test_rtx.cpp. It demonstrates device initialization, geometry upload, acceleration structure construction, and kernel dispatch.
Initialize Device and Upload Geometry
First, create a device context and upload your vertex and index data to GPU buffers:
#include <luisa/luisa-compute.h>
#include <luisa/dsl/sugar.h>
using namespace luisa;
using namespace luisa::compute;
int main(int argc, char *argv[]) {
Context ctx{argv[0]};
Device dev = ctx.create_device(argv[1]); // "cuda", "metal", etc.
// Define triangle geometry
std::array<float3> verts{
float3{-0.5f, -0.5f, 0.0f},
float3{ 0.5f, -0.5f, 0.0f},
float3{ 0.0f, 0.5f, 0.0f}};
std::array<uint> inds{0, 1, 2};
// Upload to GPU
Buffer<float3> vbuf = dev.create_buffer<float3>(3);
Buffer<Triangle> ibuf = dev.create_buffer<Triangle>(1);
Stream s = dev.create_stream();
s << vbuf.copy_from(verts.data())
<< ibuf.copy_from(inds.data());
Build the Mesh Acceleration Structure
Wrap the buffers in a Mesh object and build the bottom-level structure:
// Build Mesh (BLAS)
Mesh mesh = dev.create_mesh(vbuf, ibuf);
s << mesh.build(); // Emits MeshBuildCommand, see mesh.cpp
The Mesh::build() method in src/runtime/rtx/mesh.cpp validates the vertex and index layouts using detail::check_mesh_* functions before issuing the build command.
Create the Top-Level Acceleration Structure (Accel)
Instantiate the mesh one or more times with transforms:
// Create TLAS and add instances
Accel accel = dev.create_accel();
accel.emplace_back(mesh, scaling(1.5f)); // First instance, scaled
// Second instance with translation and rotation
float4x4 transform = translation(float3{-0.25f, 0.0f, 0.1f}) *
rotation(float3{0, 0, 1}, 0.5f);
accel.emplace_back(mesh, transform);
s << accel.build(); // Generates BVH, see Accel::build in accel.cpp
The Accel::emplace_back() method in src/runtime/rtx/accel.cpp supports overloads for meshes, curves, procedural primitives, and motion instances.
Write the Ray Tracing Kernel
Define a kernel that accepts an AccelVar and traces rays:
// Ray tracing kernel
Kernel2D rt = [&](BufferFloat4 out, AccelVar as, UInt frame) noexcept {
UInt2 id = dispatch_id().xy();
Float2 uv = (make_float2(id) + make_float2(0.5f)) /
make_float2(dispatch_size().xy());
// Camera setup
Float3 ro = make_float3(0.0f, 0.0f, -2.0f);
Float3 rd = normalize(make_float3(uv * 2.0f - 1.0f, 1.0f));
Ray ray = make_ray(ro, rd);
auto hit = as.intersect(ray, {}); // Query TLAS
// Shade: blue sky if miss, red if hit
Float3 col = hit->miss() ? make_float3(0.3f, 0.5f, 0.7f)
: make_float3(1.0f, 0.0f, 0.0f);
out.write(id.y * dispatch_size_x() + id.x, make_float4(col, 1.0f));
};
The as.intersect(ray, {}) call maps to the underlying hardware ray-tracing API (e.g., NVIDIA RTX) via the AccelVar DSL wrapper.
Dispatch and Render
Compile the kernel and dispatch it:
// Compile and execute
auto rt_shader = dev.compile(rt);
Buffer<float4> img = dev.create_buffer<float4>(512 * 512);
for (UInt f = 0; f < 256; f++) {
s << rt_shader(img, accel, f).dispatch(512, 512);
}
s << synchronize();
}
Advanced Usage Patterns
Dynamic Instance Updates
You can update instance transforms without rebuilding the entire TLAS:
// Update transform for instance 0
accel.set_instance_transform(0, new_transform);
s << accel.update_instance_buffer(); // Fast update, see accel.cpp
This pattern is implemented in Accel::update_instance_buffer in src/runtime/rtx/accel.cpp, which uses a PREFER_UPDATE hint to avoid full BVH reconstruction.
Supporting Multiple Primitive Types
The Accel structure supports heterogeneous scenes:
accel.emplace_back(curve, transform, visibility_mask); // Hair/fur
accel.emplace_back(procedural, transform, user_id); // Custom AABBs
accel.emplace_back(motion_mesh, transform, time_samples); // Motion blur
These overloads are defined in src/runtime/rtx/accel.cpp (lines 46-62).
Motion Blur and Animation
For motion blur, create a MotionInstance from a Mesh with per-keyframe vertex buffers:
auto motion_mesh = dev.create_motion_instance(mesh, keyframe_count, time_samples);
accel.emplace_back(motion_mesh, transform);
The test suite in src/tests/test_motion_blur.cpp demonstrates this pattern for animated geometry.
Key Source Files and Implementation Details
| File | Purpose | Key Functions |
|---|---|---|
src/runtime/rtx/mesh.cpp |
Bottom-level geometry construction | Mesh::build() – validates buffers and emits MeshBuildCommand |
src/runtime/rtx/accel.cpp |
Top-level structure management | Accel::emplace_back(), Accel::build(), Accel::update_instance_buffer() |
src/tests/test_rtx.cpp |
Complete working example | Demonstrates setup, kernel dispatch, and instance animation |
include/luisa/runtime/rtx/accel.h |
Public C++ API | Class definitions for Accel and Mesh |
include/luisa/runtime/rtx/mesh.h |
Mesh public interface | Mesh class declaration and buffer requirements |
Summary
- Create a
Meshfrom vertex and index buffers usingdev.create_mesh(), then callmesh.build()to construct the bottom-level acceleration structure. - Instantiate geometry by creating an
Accelobject and adding mesh instances withaccel.emplace_back(mesh, transform), supporting multiple transforms and visibility masks. - Build the TLAS by calling
accel.build(), which generates the top-level BVH used for ray traversal. - Trace rays inside kernels by passing an
AccelVarto your shader and callingas.intersect(ray, {})to query hits against the acceleration structure. - Update dynamically using
accel.set_instance_transform()andaccel.update_instance_buffer()to animate scenes without full rebuilds.
Frequently Asked Questions
How do I update instance transforms without rebuilding the entire acceleration structure?
Use accel.set_instance_transform(instance_id, new_transform) to modify the transform matrix, then submit accel.update_instance_buffer() to the stream. This operation is significantly faster than a full rebuild because it updates only the instance buffer metadata while preserving the existing BVH structure, as implemented in src/runtime/rtx/accel.cpp.
Can I mix different geometry types in the same acceleration structure?
Yes, the Accel class supports heterogeneous scenes. You can add mesh instances, curves, procedural primitives with custom AABBs, and motion instances to the same Accel object using the various emplace_back() overloads defined in src/runtime/rtx/accel.cpp. Each instance type is tracked with appropriate flags and user IDs for identification inside the ray tracing kernel.
What is the difference between Mesh and Accel in Luisa Compute?
Mesh represents a single piece of geometry—a combination of vertex and index buffers that defines triangles. It builds the bottom-level acceleration structure (BLAS). Accel is the top-level acceleration structure (TLAS) that contains one or more instances of Mesh (or other primitives), each with its own transform matrix and visibility properties. You trace rays against the Accel, not individual Mesh objects directly.
How do I implement motion blur with the acceleration structures?
Create a MotionInstance using dev.create_motion_instance(mesh, keyframe_count, time_samples) where you provide multiple vertex buffers representing the geometry at different time steps. Add this motion instance to your Accel using emplace_back(). Inside the kernel, the hardware interpolation between keyframes occurs automatically when you trace rays against the AccelVar, as demonstrated in src/tests/test_motion_blur.cpp.
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 →