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 in src/runtime/rtx/mesh.cpp via Mesh::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 in src/runtime/rtx/accel.cpp via Accel::emplace_back() and Accel::build().

  • AccelVar – The DSL-level handle passed into kernels. Exposes the intersect() 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 Mesh from vertex and index buffers using dev.create_mesh(), then call mesh.build() to construct the bottom-level acceleration structure.
  • Instantiate geometry by creating an Accel object and adding mesh instances with accel.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 AccelVar to your shader and calling as.intersect(ray, {}) to query hits against the acceleration structure.
  • Update dynamically using accel.set_instance_transform() and accel.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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →