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#

FieldMeaning
tDistance along the ray in units of d, so the hit point is o + t * d
pWorld-space hit position
geo_nGeometric (face) normal
nShading normal interpolated from the vertex normals; equals geo_n when use_face_normals is set
uvTexture coordinate interpolated from the mesh UVs (zero when the mesh has none)
barycentricBarycentric coordinates of the hit in the triangle
shape_id, prim_id, local_prim_id, global_prim_idMesh, 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:

FlagFields
RayFlags.Geometrict, p, barycentric, the ids, geo_n
RayFlags.ShadingNn
RayFlags.UVuv
RayFlags.Allall 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.