Embree comparison, adopt its ideas (occlusion/watertight/instancing/SBVH/BVH4/masks/motion/curves), and extract the crust-rt kernel crate - #113
Merged
Conversation
Analyzes Embree 4.4.x (geometry types, BVH builders, query API, ISA/SYCL support) against crust's intersection layer, documents what crust covers that Embree doesn't, and ranks Embree ideas worth adopting natively. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011XTp9J7yinpSvfjX5tmh4F
Hittable::hit_any defaults to hit().is_some(); Bvh overrides it with an early-exit traversal (no front-to-back ordering, first confirmed hit wins), HittableList and Mesh short-circuit likewise. NEE shadow tests in shadow_transmittance now use it instead of searching for the closest hit - Embree's rtcIntersect/rtcOccluded split. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011XTp9J7yinpSvfjX5tmh4F
Replaces the epsilon-based Möller-Trumbore test with the watertight scheme: winding-preserving dominant-axis permutation, shear onto +Z, 2D signed edge functions with an f64 fallback on exact-zero ties, and a division-free t range test. Triangle and SmoothTriangle now share one intersector that also yields the barycentrics for normal interpolation. Pinned by shared-edge/shared-vertex pinhole tests (10k edge samples). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011XTp9J7yinpSvfjX5tmh4F
Ray gains time (shutter position in [0,1)) and mask (Embree-style visibility category bit) with builder setters and a transformed() helper for instancing. The camera stamps primary rays (new QMC domain K_TIME feeds the shutter), and the integrator stamps every secondary ray with the path's time and the indirect category, shadow rays with the shadow category. Rays built outside the integrator default to MASK_ALL, so nothing changes until geometry opts into a narrower mask. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011XTp9J7yinpSvfjX5tmh4F
New Instance primitive holds an Arc'd Hittable plus a local-to-world placement: rays transform into local space (direction unnormalized so t carries over), normals map back through the inverse-transpose, and an optional end-of-shutter transform linearly interpolates the placement at the ray's time (transform motion blur; the bbox is the conservative union of both endpoints). The USD importer now builds every mesh's triangle BVH in local space and shares it across prims with identical points/topology/material (content hash + memoized material Arcs, so binding paths compare by pointer): N placements of a mesh cost one copy of its triangles. Non-invertible transforms fall back to world-space baking. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011XTp9J7yinpSvfjX5tmh4F
Rebuilds the BVH constructor in the SBVH mold (Stich et al. 2009): subtrees own PrimRef lists (bbox + primitive index), each node evaluates the binned object SAH split and - when the object children overlap by more than alpha=1e-5 of the root surface area - a binned spatial split with entry/exit counting, chopping straddling references at the plane and duplicating them into both children. Clipped bounds come from a new Hittable::clipped_aabb (default bbox-cap-slab; exact Sutherland-Hodgman polygon clipping for triangles). Leaves now index a shared indices array so a primitive can sit in several leaves while being stored once. Subtrees above 4096 references build on parallel rayon::join tasks; every split decision is input-only, so the tree stays deterministic (pinned by a build-twice test). Diagonal-shard tests pin that spatial splits actually fire and agree with a linear scan. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011XTp9J7yinpSvfjX5tmh4F
The SBVH build now feeds a post-build collapse: each wide node adopts its binary node's children and repeatedly expands the largest-area internal child until four lanes fill. Nodes store child slab bounds in SoA Vec4 lanes, so traversal tests all four boxes per visit with vector min/max; direction reciprocals are clamped finite (safe_inv) so 0-dir components cannot produce NaN lanes, and empty lanes carry +INF/+INF bounds the swapped slab test can never pass. Closest-hit traversal processes hit lanes near-to-far (leaf lanes immediately, internal lanes pushed far-to-near); occlusion traversal stays unordered and early-exits. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011XTp9J7yinpSvfjX5tmh4F
Ray masks: a Masked wrapper gates intersection on the ray's category bit (camera / shadow / indirect) against the geometry's crust:rayMask attr - e.g. rayMask = 6 makes a shadow card that darkens the floor without appearing in the image. Motion blur: crust:motion:translate on a Mesh or Sphere prim rides the prim on an Instance whose end-of-shutter transform is the authored world-space translation; primary rays already carry a QMC-drawn shutter time, so moving geometry streaks with zero cost to static scenes. Curves: new RoundCurveSegment primitive (sphere-swept cone: tangent frustum + spherical caps, Quilez's rounded-cone intersector evaluated on a normalized direction and rescaled). UsdGeomBasisCurves imports linear curves directly and flattens cubic bezier/bspline/catmullRom spans to 8-segment polylines, with vertex/uniform/constant width resolution; segments live in local space under an Instance like meshes. Sample scenes samples/curves.usda and samples/motionblur.usda, pinned by integration tests (strand hit t, shutter-time hits, mask filtering). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011XTp9J7yinpSvfjX5tmh4F
Contributor
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
README gains a Geometry & acceleration section; CLAUDE.md's core-traits and USD-import sections now describe hit_any, watertight triangles, Instance/Masked/RoundCurveSegment, the parallel SBVH+BVH4 build, and the crust:rayMask / crust:motion:translate attrs, with new caveats under known incomplete work. The Embree comparison doc's head-to-head table and Route B section are updated to reflect the implemented items. The binary build node's axis field was only consumed by the old ordered binary traversal - BVH4 lane ordering replaced it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011XTp9J7yinpSvfjX5tmh4F
Generates a ~383k-triangle scene (36 identical 9.8k-tri spheres for the instancing dedup, a 30k thin-shard field for spatial splits, and an occluder slab for shadow-ray early-exit) used to benchmark the Embree-adoption pass. The generated file is ~15 MB and not checked in. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011XTp9J7yinpSvfjX5tmh4F
Factors the ray tracing kernel out of crust-core the way openqmc-rs factored out sampling, behind a deliberately Embree-shaped API: Geometry objects (triangle meshes with optional per-vertex shading normals, analytic spheres, round curve segments, single-level instances with transform motion blur) attach to a SceneBuilder with per-geometry visibility masks, commit() builds the acceleration structure, and Scene::intersect / Scene::occluded mirror rtcIntersect1 / rtcOccluded1. Hits are plain Copy data carrying geom_id / prim_id - the application owns the mapping from IDs to materials; the kernel never sees shading state. Internals are the ported SBVH build (parallel, deterministic, spatial splits with exact triangle clipping) collapsed to BVH4 with Vec4 slab tests, the watertight Woop-2013 triangle test, and the rounded-cone curve intersector. 27 unit tests + doctest; nothing depends on the crate yet - the crust-core port lands separately. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011XTp9J7yinpSvfjX5tmh4F
crust-core no longer owns any intersection code: the Hittable trait, HittableList, Bvh, and the primitive types are gone, replaced by rt_world.rs - a WorldBuilder that pairs every attached rt::Geometry with its material (attach() returns the geom_id) and a committed World whose intersect() resolves kernel hits back to (HitRecord, &dyn Material, geom_id, prim_id) and whose occluded() is the shadow-ray query. Light attribution moves from Arc-address identity to geometry ids: AreaLight records the geom_id of its emissive geometry and the integrator matches bounce-hit emitters via LightList::find_by_geom. crust_core::Ray wraps the kernel ray plus the renderer-side carried medium. The USD importer attaches kernel geometries directly (meshes as local-space inner scenes shared via instances, curves as RoundCurves batches, rect lights as one two-triangle mesh geometry - world.count() now counts geometries), and world.rs builds the procedural scene through the same WorldBuilder. Renders are pixel-identical to the pre-port build on cornellbox (32 spp) and the 383k-triangle stress scene (16 spp), with unchanged wall time. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011XTp9J7yinpSvfjX5tmh4F
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Starts with a deep analysis of Embree 4.4.x vs crust's intersection layer (
docs/embree_comparison.md), implements all seven of the doc's "adopt natively" recommendations in safe Rust, then completes the "openqmc move": the whole intersection layer extracted into a dedicatedcrust-rtcrate behind an Embree-shaped, ID-based API.The comparison
docs/embree_comparison.mdanalyzes Embree's geometry types, BVH builders, query API, and ISA/SYCL support against crust's intersection layer, documents what crust covers that Embree doesn't (the whole renderer above the query line), and ranks which Embree ideas were worth adopting. Its head-to-head table and addenda reflect the final state of this branch.Adopted, one commit each
rtcIntersect/rtcOccludedsplit).rayon::join; split decisions are input-only, so the tree is byte-identical run-to-run (pinned by a build-twice test).Vec4min/max (clamped reciprocals keep zero-direction lanes NaN-free), near-to-far lane ordering for closest-hit, early exit for occlusion.crust:rayMaskvisibility categories (camera/shadow/indirect);crust:motion:translatetransform motion blur (QMC shutter time on primary rays, inherited along the path; instances lerp their placement per ray with a conservative union-of-endpoints bbox);RoundCurveSegmentsphere-swept cones withUsdGeomBasisCurvesimport (linear direct, cubic bezier/bspline/catmullRom flattened).The
crust-rtkernel crateThe intersection layer is now its own crate (the way
openqmc-rsfactored out sampling), deliberately Embree-shaped so Embree bindings could slot in behind the same seam:Geometryvariants (triangle meshes with optional per-vertex shading normals, spheres, round curves, single-level instances with motion) attach to aSceneBuilderwith per-geometry masks;commit()builds;Scene::intersect/Scene::occludedmirrorrtcIntersect1/rtcOccluded1.CopyRayHits carryinggeom_id/prim_id— the kernel never sees materials. Depends only on glam + rayon; 27 unit tests + doctest.rt_world.rs:WorldBuilder::attach(geometry, material) -> geom_idand a committedWorldthat resolves hits back to(HitRecord, &dyn Material, geom_id, prim_id). Light attribution moved fromArc-address identity togeom_id(LightList::find_by_geom) — cleaner and faster than pointer comparison. The oldHittable/HittableList/Bvh/primitive types are gone from crust-core.Measured performance
Stress scene from
scripts/gen_stress_scene.py(383k world-baked triangles: 36 identical 9.8k-tri spheres, a 30k thin-shard field, an occluder slab for shadow rays), 640×360, median of 3 runs:021d8dd)b4a48ab)3736eb3)3671527)4.1× end-to-end at 16 spp (4.3× on pure per-sample render cost, 2.7× on startup), with the final image matching the pre-adoption render to a mean abs pixel difference of 1.5e-5, and the kernel extraction adding zero overhead (pixel-identical renders, unchanged wall time). Peak RSS rose from ~78 MB to ~96 MB on this scene (build-transient allocations). On the small bundled samples (shading-bound) wall time is unchanged.
Verification
samples/curves.usda,samples/motionblur.usda) asserting exact strand-hit distances, shutter-time hits/misses, and mask filtering.Known caveats (documented in CLAUDE.md)
Motion blur is transform-only with linear matrix lerp (no deformation/quaternion motion); cubic curves are flattened rather than intersected exactly; mesh sharing requires identical geometry and material —
UsdGeomPointInstancer/instanceablearcs are not consumed; emissive curves/instances are not light-list entries.world.count()now counts geometries (a rect light's two triangles are one mesh geometry).🤖 Generated with Claude Code
https://claude.ai/code/session_011XTp9J7yinpSvfjX5tmh4F