This document describes the current public Python API exposed by rayd.
RayD also ships an optional parallel API under rayd.torch. It mirrors the same object model while using CUDA torch.Tensor inputs and outputs instead of Dr.Jit arrays.
RayD is a geometry-only differentiable ray-intersection package built on Dr.Jit and OptiX.
Public top-level exports:
MeshSceneSceneSyncProfileCameraRayIntersectionNearestPointEdgeNearestRayEdgePrimaryEdgeSampleSecondaryEdgeInfo
All array-valued inputs and outputs use Dr.Jit CUDA arrays or tensors.
For the optional rayd.torch module:
- install with
pip install "rayd[torch]" - all array inputs and outputs must be CUDA
torch.Tensor - vectors use
(N, 3)/(N, 2)layout - index tensors use
(F, 3) - images use
(H, W) - transforms use
(4, 4) - CPU tensors are rejected instead of being moved implicitly
AD mode is inferred automatically from the inputs:
- if any input tensor carries gradient (
requires_gradin torch, ordr.grad_enabledin Dr.Jit), the operation runs in AD mode - otherwise, the operation runs detached
Typical input types:
- detached scalar/array types:
drjit.cuda.Floatdrjit.cuda.Array2fdrjit.cuda.Array3fdrjit.cuda.Array3idrjit.cuda.Matrix4f
- differentiable types:
drjit.cuda.ad.Floatdrjit.cuda.ad.Array2fdrjit.cuda.ad.Array3fdrjit.cuda.ad.Matrix4f
- differentiable tensor output:
drjit.cuda.ad.TensorXf
Batched queries are represented by dynamic Dr.Jit arrays.
Mesh stores triangle geometry, optional UVs, transforms, and precomputed edge data.
Construction:
mesh = rd.Mesh(v, f)
mesh = rd.Mesh(v, f, uv)
mesh = rd.Mesh(v, f, uv, f_uv)
mesh = rd.Mesh(v, f, uv, f_uv, verbose=True)Parameters:
v: vertex positions, usuallyArray3forad.Array3ff: triangle vertex indices,Array3iuv: optional vertex UVs,Array2ff_uv: optional UV indices,Array3iverbose: print basic mesh loading statistics
Methods:
build()- builds derived geometry caches and GPU buffers
set_transform(mat, set_left=True)append_transform(mat, append_left=True)edge_indices()- returns a 5-tuple of integer arrays describing mesh edges
secondary_edges()- returns
SecondaryEdgeInfo
- returns
Properties:
num_verticesnum_facesto_worldto_world_leftto_world_rightvertex_positionsvertex_positions_worldvertex_normalsvertex_uvface_indicesface_uv_indicesuse_face_normalsedges_enabled
Notes:
- call
build()before adding the mesh to a scene if you want mesh-local caches immediately Scene.build()will also build meshes added to that scene- updating geometry or transforms marks derived data dirty until rebuilt or synced through
Scene
Scene owns meshes plus the OptiX and edge acceleration data used by queries.
Construction:
scene = rd.Scene()Methods:
add_mesh(mesh, dynamic=False) -> int- adds a mesh and returns its scene-local mesh id
build()- builds scene-wide triangle and edge acceleration data
update_mesh_vertices(mesh_id, positions)- only valid for meshes added with
dynamic=True
- only valid for meshes added with
set_mesh_transform(mesh_id, mat, set_left=True)append_mesh_transform(mesh_id, mat, append_left=True)sync()- pushes pending dynamic mesh updates into scene acceleration structures
is_ready() -> boolhas_pending_updates() -> boolintersect(ray, active=True) -> Intersectionnearest_edge(point, active=True) -> NearestPointEdgenearest_edge(ray, active=True) -> NearestRayEdge
Properties:
num_mesheslast_sync_profile
Notes:
- a scene must be built before queries
- if
has_pending_updates()is true, queries will raise untilsync()is called - camera primary-edge caches are invalidated when the scene is rebuilt or synced
Timing and update counters from the last Scene.sync() call.
Fields:
mesh_update_mstriangle_scatter_mstriangle_eval_msoptix_sync_mstotal_msoptix_gas_update_msoptix_ias_update_msupdated_meshesupdated_vertex_meshesupdated_transform_meshes
Camera provides primary-ray sampling, primary-edge preparation, point-sampled depth rendering, and primary-edge gradient rendering.
Construction with horizontal FOV:
camera = rd.Camera.perspective(fov_x=45.0)
camera = rd.Camera(45.0, 1e-4, 1e4) # positional form also acceptedConstruction with calibrated intrinsics:
camera = rd.Camera.from_intrinsics(fx=100, fy=100, cx=64, cy=64)
camera = rd.Camera(fx, fy, cx, cy, 1e-4, 1e4) # positional form also acceptedMethods:
build(cache=True)- rebuilds projection and world/sample transform caches
prepare_edges(scene)- preprocesses primary image-space edges for the current scene
sample_ray(sample) -> Raysample_edge(sample1) -> PrimaryEdgeSamplerender(scene, background=0.0) -> TensorXf- point-sampled depth image
render_grad(scene, spp=4, background=0.0) -> TensorXf- primary-edge visibility gradient image
set_transform(mat, set_left=True)append_transform(mat, append_left=True)
Properties:
widthheightto_worldto_world_leftto_world_rightcamera_to_samplesample_to_cameraworld_to_samplesample_to_world
Notes:
- call
build()after changing camera parameters or transforms render_grad()depends on the camera鈥檚 primary-edge pipeline and is connected directly to the Dr.Jit AD graphprepare_edges(scene)must be rerun after scene updates that affect visibility
Ray container. AD mode is inferred from whether any field carries gradient.
Fields:
odtmax
Methods:
reversed()
Result of Scene.intersect(ray, ...).
Fields:
tpngeo_nuvbarycentricshape_idprim_id
Methods:
is_valid()
Result of Scene.nearest_edge(point, ...).
Fields:
distancepointedge_tedge_pointshape_idedge_idis_boundary
Methods:
is_valid()
Notes:
pointis the original query pointedge_tis the closest-point parameter along the edge segment
Result of Scene.nearest_edge(ray, ...).
Fields:
distanceray_tpointedge_tedge_pointshape_idedge_idis_boundary
Methods:
is_valid()
Notes:
ray_tis the closest-point parameter along the query ray or finite ray segmentpointis the closest point on the query ray
Result of Camera.sample_edge(sample1).
Fields:
x_dot_nidxray_nray_ppdf
Interpretation:
idx: flattened pixel indexray_nandray_p: primary rays on opposite sides of the sampled image-space edgex_dot_n: signed boundary termpdf: sampling density at the sampled edge point
Per-edge geometric information precomputed on a mesh.
Fields:
startedgenormal0normal1oppositeis_boundary
Methods:
size()
Interpretation:
start: first endpoint in world spaceedge: edge vector, so the second endpoint isstart + edgenormal0: face normal on one sidenormal1: face normal on the opposite sideopposite: third vertex of thenormal0face
import rayd as rd
import drjit as dr
mesh = rd.Mesh(
dr.cuda.Array3f([0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
[0.0, 0.0, 0.0]),
dr.cuda.Array3i([0], [1], [2]),
)
scene = rd.Scene()
scene.add_mesh(mesh)
scene.build()
ray = rd.Ray(
dr.cuda.Array3f([0.25], [0.25], [-1.0]),
dr.cuda.Array3f([0.0], [0.0], [1.0]),
)
its = scene.intersect(ray)import rayd as rd
import drjit as dr
points = dr.cuda.Array3f([0.25], [0.1], [0.0])
edge = scene.nearest_edge(points)import rayd as rd
import drjit as dr
ray = rd.Ray(
dr.cuda.Array3f([0.2], [0.4], [1.0]),
dr.cuda.Array3f([0.0], [0.0], [-1.0]),
)
ray.tmax = dr.cuda.Float([2.0])
edge = scene.nearest_edge(ray)import rayd as rd
camera = rd.Camera.perspective(fov_x=45.0)
camera.width = 128
camera.height = 128
camera.build()
depth = camera.render(scene)image = camera.render_grad(scene, spp=8)This returns a TensorXf directly from the C++ extension.