Update geometry in place
Move vertices, change mesh and instance transforms, and refit a built RayD scene with sync() instead of rebuilding it.
Optimization loops and moving scenes change geometry every iteration. Rebuilding the scene each time is slow. This guide shows how to mark meshes as dynamic, queue edits, and apply them with sync(), which refits the acceleration structures in place.
Mark the meshes you will change#
Only meshes added with dynamic=True accept updates after build(). Keep everything else static, because static meshes get a faster acceleration structure.
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()
floor = scene.add_mesh(quad(0.0)) # static
lid = scene.add_mesh(quad(2.0), dynamic=True) # can move
scene.build()
print(scene.version) # 1Move vertices#
update_mesh_vertices(mesh_id, positions) replaces all vertex positions of a dynamic mesh. The vertex count and the faces must stay the same. The edit is queued until sync():
scene.update_mesh_vertices(lid, Array3f([-1, 1, 1, -1], [-1, -1, 1, 1], [3, 3, 3, 3]))
print(scene.has_pending_updates()) # True
scene.sync()
print(scene.has_pending_updates(), scene.version) # False 2
print(scene.last_sync_profile.updated_meshes) # 1Calling update_mesh_vertices on a static mesh raises RuntimeError: Scene::update_mesh_vertices(): target mesh is not dynamic.
In the Torch backend the call is the same, with an (V, 3) CUDA float32 tensor:
scene.update_mesh_vertices(lid, new_vertices)
scene.sync()Change a transform#
In the Dr.Jit backend, Scene.set_mesh_transform(mesh_id, mat, set_left=True) replaces a dynamic mesh's left (or right) transform factor, and append_mesh_transform(mesh_id, mat, append_left=True) multiplies onto it. Use a drjit.cuda.ad.Matrix4f with column vectors. An AD matrix makes the transform differentiable:
import drjit as dr
from drjit.cuda.ad import Array3f as Array3fAD, Float as FloatAD, Matrix4f
scene = rd.Scene()
mid = scene.add_mesh(quad(0.0), dynamic=True)
scene.build()
dz = FloatAD(0.0)
dr.enable_grad(dz)
T = Matrix4f([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]])
T[2][3] = dz # translate along z
scene.set_mesh_transform(mid, T)
scene.sync()
hit = scene.intersect(rd.RayAD(Array3fAD([0.2], [0.1], [1.0]), Array3fAD([0.0], [0.0], [-1.0])))
dr.backward(dr.sum(hit.t))
print(dr.grad(dz)) # [-1]: raising the quad shortens the rayAdd instances#
add_instance(geometry_id, transform, dynamic=True) places another copy of a static mesh. It shares the source geometry and gets its own shape id and its own range of global primitive ids.
import drjit as dr
from drjit.cuda.ad import Matrix4f
scene = rd.Scene()
src = scene.add_mesh(quad(0.0))
T = dr.scalar.Matrix4f([[1, 0, 0, 3], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]])
inst = scene.add_instance(src, Matrix4f(T)) # copy shifted 3 along x
scene.build()
ray = rd.Ray(Array3f([0.0, 3.0], [0.0, 0.0], [1.0, 1.0]), Array3f([0, 0], [0, 0], [-1, -1]))
hit = scene.intersect(ray)
print(hit.shape_id, hit.global_prim_id) # [0, 1] [1, 3]A dynamic instance takes transform-only updates. Move it with Scene.set_mesh_transform(inst, ...) in Dr.Jit or Scene.set_instance_transform(inst, transform) in Torch, then sync(). You cannot instance a dynamic mesh or another instance, and you cannot change an instance's vertices. The Torch backend does not support instances in a multi-device scene.
When to rebuild instead#
Call build() again when the topology changes: a mesh is added, a face list changes, or the vertex count changes. Adding a mesh to a built scene invalidates it until the next build().
Inspect what a sync did#
In Dr.Jit, scene.last_sync_profile is a SceneSyncProfile with the number of updated meshes and the time spent in each stage. scene.version increases on every geometry change and scene.edge_version when the edge set changes; use them to invalidate your own caches.