Intersect rays with meshes
Build a scene, trace batches of rays, choose which hit fields to compute, mask lanes, and run shadow tests in both RayD backends.
This guide shows how to cast a batch of rays into a triangle scene and read the hits. It covers the hit record, the flags and active arguments, and shadow tests.
Build the scene and cast rays#
A scene needs at least one mesh and a build() call before any query. The example puts a floor at z = 0 and a dynamic lid at z = 2, then casts one ray up and one ray down from z = 1.
import rayd.drjit as rd
from drjit.cuda import Array3f, Array3i
def quad(z):
return rd.Mesh(Array3f([-1, 1, 1, -1], [-1, -1, 1, 1], [z, z, z, z]),
Array3i([0, 0], [1, 2], [2, 3]))
scene = rd.Scene()
scene.add_mesh(quad(0.0)) # mesh 0: global faces 0-1
scene.add_mesh(quad(2.0)) # mesh 1: global faces 2-3
scene.build()
ray = rd.Ray(Array3f([0, 0], [0, 0], [1, 1]), # origins
Array3f([0, 0], [0, 0], [1, -1])) # directions: up, down
hit = scene.intersect(ray)
print(hit.t, hit.shape_id, hit.global_prim_id) # [1, 1] [1, 0] [2, 1]Each lane of the result belongs to one ray. A ray that misses has t = inf, shape_id = -1, and prim_id = -1; hit.is_valid() returns the hit mask. See Scene build and sync for the meaning of each id.
Read the hit record#
| Field | Meaning |
|---|---|
t | Distance along the ray in units of d, so the hit point is o + t * d |
p | World-space hit position |
geo_n | Geometric (face) normal |
n | Shading normal interpolated from the vertex normals; equals geo_n when use_face_normals is set |
uv | Texture coordinate interpolated from the mesh UVs (zero when the mesh has none) |
barycentric | Barycentric coordinates of the hit in the triangle |
shape_id, prim_id, local_prim_id, global_prim_id | Mesh, local triangle, and scene-global triangle ids |
ray.tmax limits the search. A Dr.Jit Ray has tmax = inf by default; set ray.tmax = Float(...) to stop at a distance. A Torch Ray takes tmax as an optional (N,) tensor.
Compute fewer fields#
flags selects which fields RayD computes. The default RayFlags.All fills everything. Use a smaller set when you only need distances and ids:
| Flag | Fields |
|---|---|
RayFlags.Geometric | t, p, barycentric, the ids, geo_n |
RayFlags.ShadingN | n |
RayFlags.UV | uv |
RayFlags.All | all of the above |
Combine flags with |, for example rd.RayFlags.Geometric | rd.RayFlags.UV. Fields that a flag does not select are not meaningful.
hit = scene.intersect(ray, flags=rd.RayFlags.Geometric)
print(hit.geo_n) # [[0, 0, 1], [0, 0, 1]]Mask lanes#
active turns individual lanes off. In Dr.Jit it is a Bool with one entry per ray (or a Python True for all lanes); in Torch it is an optional (N,) bool tensor. Inactive lanes return a miss.
from drjit.cuda import Bool
hit = scene.intersect(ray, active=Bool([True, False]))Test for occlusion only#
When you only need to know whether a ray hits anything, shadow_test is cheaper than intersect because it stops at the first hit and returns no record. It is available in the Dr.Jit backend.
blocked = scene.shadow_test(ray) # Bool per ray: [True, True]To test whether the segment between two points is clear, use visibility queries instead; they handle the segment length for you.
Differentiate the hit#
Pass a RayAD built from drjit.cuda.ad arrays, or hold vertex positions as AD arrays, to get an IntersectionAD whose continuous fields carry gradients. In Torch, set requires_grad=True on the ray or vertex tensors. The Quickstart runs a complete gradient example, and Differentiation explains what the gradient includes.