Edges and edge sampling

How RayD extracts mesh edges, builds the scene-global edge BVH, answers nearest-edge queries, and supports edge-sampling gradient estimators.

Edges matter for two reasons in differentiable ray tracing and RF simulation. Diffraction happens at wedges, and the gradient of anything that depends on visibility comes from the edges where visibility changes. RayD gives you edge geometry and fast edge queries. The estimator that turns them into gradients or diffraction fields is yours, or comes from RayD's diffraction operations.

Edge topology#

When a mesh is built, RayD extracts its unique undirected edges. Each edge records its two endpoints, the one or two adjacent faces, the face normals on each side, and the vertex opposite the edge in each face. An edge with only one adjacent face is a boundary edge (is_boundary is true); its second face and normal are undefined. A two-triangle square has five edges: four boundary edges and the shared interior diagonal.

Set Mesh.edges_enabled = False (Dr.Jit) or Mesh(..., edges_enabled=False) (Torch) to leave a mesh out of the edge set, for example a floor whose edges you never query.

The Dr.Jit backend exposes the edge data at two levels:

  • Mesh.secondary_edges() returns a SecondaryEdgeInfo per mesh: start point, edge vector, both normals, the opposite vertex, and the boundary flag.
  • Scene.edge_info() and Scene.edge_topology() return the same data for the whole scene in the scene-global edge index space, with per-mesh and global vertex and face ids. Scene.edge_adjacent_faces(edge_id) and Scene.triangle_edge_indices(prim_id) map between faces and edges.

Nearest-edge queries#

The scene keeps a BVH over all enabled edges and answers three kinds of query on the GPU:

QueryCallResult
Point to edgescene.nearest_edge(points)closest edge, distance, closest point on the edge, edge parameter in [0, 1]
Ray to edgescene.nearest_edge(ray)also the ray parameter ray_t of the closest approach
k nearest edgesscene.nearest_edges(points, k)up to k edges per point, nearest first, k at most 16

A ray with finite tmax is treated as the segment from o to o + tmax * d. A ray with infinite tmax is a half-line. The distances and closest points are differentiable with respect to both the query and the mesh vertices, with the chosen edge held fixed.

Masking edges#

scene.set_edge_mask(mask) takes one boolean per scene-global edge and hides the edges whose entry is false from edge queries. It filters the edge BVH without changing the topology, the edge ids, or the per-mesh offsets, so results keep their usual ids. Typical masks keep only silhouette candidates (edges whose two face normals point to opposite sides of a viewer) or only boundary edges. The mask is a pending update: call scene.sync() before the next edge query. scene.edge_mask() returns the current mask.

Edge sampling for visibility gradients#

The fixed-winner derivative of intersect captures how a hit moves while the same triangle is hit. It misses the change caused by geometry occluding or revealing something, which shows up as a discontinuity along silhouette edges. Edge-sampling estimators such as those in redner and psdr-jit recover that term by sampling points on the relevant edges and evaluating the difference across them.

  • Primary edges are silhouettes as seen from a camera or a transmitter.
  • Secondary edges are silhouettes as seen from a shading point, a receiver, or a reflection point.

RayD supplies the pieces such an estimator needs: the edge list with adjacency and normals to find silhouette edges, nearest-edge and top-k queries to pick edges near a point or a ray, edge masks to restrict the search, visible_edge to test whether a source sees points along an edge, and differentiable intersections on either side. It does not ship a ready-made boundary-gradient integrator.

Choosing the edge backend#

The default edge backend is OptiX, which encodes edges as custom primitives and uses hardware traversal. The software BVH ("drjit" in Dr.Jit, "cuda" in Torch) is the fallback when OptiX is unavailable and gives the same answers. In Dr.Jit, Scene.edge_bvh_stats() reports the edge BVH's primitive and node counts, leaf sizes, height, and surface-area overlap. The node statistics are meaningful for the "drjit" backend; with the OptiX backend they describe only a trivial placeholder tree.