Widen the kernel's SIMD: mask-driven BVH4 traversal and 4-wide triangle packets - #116
Merged
Conversation
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? |
Pure refactor, no behaviour change: all six sample scenes with lights render pixel-identical (verified with exr_diff). `Light::sample_point(u, v) -> Vec3A` returns a point in space, and a `DistantLight` or `DomeLight` has none — they are directions at infinity. Replaced with a PBRT-shaped `sample_li(from, u, v) -> Option<LightSample>` carrying direction, shadow-ray distance, radiance and solid-angle pdf together, which is also what lets a light decline to be sampled at all (`None` below a dome's horizon). MIS needed splitting to match. Every light has two ways of being found: NEE samples it, or a ray arrives by chance, and both sides must evaluate the same density. That second path differs by light kind, so it is now two methods: `pdf_at_point` for a bounce ray hitting a light's geometry (what `bounce_emission_weight` already did), and `escaped` for a ray leaving the scene along a direction an infinite light covers. Area lights implement the first, infinite lights the second, and the trait documents the pairing since getting it wrong double-counts emission. `LightSample::pdf` is a plain f32 rather than an optional delta marker: a zero-`angle` DistantLight will be widened to a small real cone instead of being made singular, so the integrator keeps one MIS path instead of two. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019Q7RXTdHvPFM5weuxWYLYU
…need A distant light has no geometry, so a bounce ray can only find it by *leaving* the scene along a direction inside its cone. That is the mirror of `bounce_emission_weight` and it did not exist: escaped rays went straight to the hard-coded sky gradient. `escaped_emission` now weights what a departing ray picks up against the same density NEE used, with full weight where NEE did not run (primary rays, post-carried-medium scatters) exactly as the bounce-hit path already did. When nothing at infinity covers the direction the sky gradient still applies, so scenes without an environment light are untouched — verified pixel-identical on cornellbox, veach_mis and smoke. Two conventions, both easy to get backwards, so both are pinned by tests: - The cone is always real. `inputs:angle` is an angular diameter and authors may set it to zero for sharp shadows; rather than a delta light needing a second MIS path, zero widens to MIN_DISTANT_ANGLE_DEG. The penumbra is sub-pixel, and MIS handles a bounce ray straying into the cone — the light pdf is enormous there, so the bounce weight collapses and no firefly survives. - `intensity × color` is irradiance, not radiance; radiance is derived as E / Ω. Widening the angle then softens shadows without changing exposure, which is what Hydra normalizes to. Read as radiance instead, a sun-sized source would come out ~5 orders of magnitude too dark — `distant_light_irradiance_is_angle_invariant` is the guard. The other two tests pin that `sample_li` and `escaped` agree on both the covered set of directions and the pdf (disagreement would double-count), and that a zero angle keeps a finite pdf. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019Q7RXTdHvPFM5weuxWYLYU
Completes the pair of lights at infinity. A dome covers every direction, so once one exists it *is* the background — `Light::escaped` answers for every ray that leaves and the built-in sky gradient stops applying. Scenes without one are untouched (cornellbox, veach_mis and smoke stay pixel-identical). crust-core still decodes nothing. `inputs:texture:file` is resolved against the USD layer's directory and handed to the host through a new `AssetLoader` trait (`Scene::from_usd_with_assets`); `Scene::from_usd` passes `NoAssets`, which warns and falls back to the dome's uniform colour. `crust-render` implements the trait with `exr` for OpenEXR and `image` for `.hdr` and LDR, un-gamma'ing the latter to linear. That seam is where general texture support should grow, and it keeps the crate's no-image-dependencies property intact. The sampling is the part worth reviewing. A lat-long map is importance- sampled by luminance **times sin θ**: the image spends as many pixels on a degree near the pole as near the equator, but those texels cover far less solid angle, so omitting the Jacobian over-samples the poles and biases the estimate. `poles_are_not_oversampled` and `uniform_map_has_uniform_solid_angle_pdf` pin it — the latter evaluated at texel centres, where the density's piecewise-constant discretization vanishes and a uniform map must read exactly 1/4π. Away from centres it legitimately differs, so what is pinned everywhere instead is normalization (E[1/pdf] = 4π over 200k samples) and that `sample` and `pdf` agree, since those are the two MIS sides and disagreement double-counts. Without any of this a small bright sun in an HDRI is a firefly farm: the sample scene's sun disc covers ~1/9000 of the map's texels at 400x its surroundings, and renders clean at 96 spp. Verified end to end on samples/domelight.usda, whose chrome sphere reflects the sky, the ground band and the sun disc — which is the lat-long mapping, the dome's rotation and the importance sampling all being right at once. Tests cover the light-list/geometry split, that the resolved asset path reaches the host exactly once, that both infinite lights agree between `sample_li` and `escaped`, and — in the CLI, which is the only crate that can — an EXR round trip and the sRGB-to-linear conversion. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019Q7RXTdHvPFM5weuxWYLYU
doubleailes
force-pushed
the
claude/usd-dome-distant-lights
branch
from
July 27, 2026 08:12
a81231b to
7f2088b
Compare
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.
Adds the two
UsdLuxlights that have no scene geometry —DistantLightandDomeLight— and the integrator machinery they need. Outdoor scenes can now be lit correctly instead of falling back to the hard-coded sky gradient.Three commits, each independently reviewable.
1.
Lightbecomes direction-based (pure refactor)Light::sample_point(u, v) -> Vec3Areturns a point in space, and a light at infinity has none. It becomes a PBRT-shapedsample_li(from, u, v) -> Option<LightSample>carrying direction, shadow-ray distance, radiance and solid-angle pdf together — which also lets a light decline to be sampled at all (Nonebelow a dome's horizon).MIS splits to match. Every light has two ways of being found — NEE samples it, or a ray arrives by chance — and both sides must evaluate the same density or emission double-counts. That second path differs by light kind, so it is now two methods:
pdf_at_pointfor a bounce ray hitting a light's geometry (whatbounce_emission_weightalready did), andescapedfor a ray leaving the scene along a direction an infinite light covers.No behaviour change: all six lit sample scenes render pixel-identical, verified with
exr_diff.2.
UsdLuxDistantLight, and the escaped-ray MIS pathA distant light can only be found by a ray leaving the scene inside its cone. That mirror of
bounce_emission_weightdid not exist — escaped rays went straight to the sky gradient.escaped_emissionnow weights what a departing ray picks up against the same density NEE used, at full weight where NEE did not run (primary rays, post-carried-medium scatters), exactly as the bounce-hit path already did. Where nothing at infinity covers the direction the sky gradient still applies, so scenes without an environment light are untouched.Two conventions worth reviewing, both easy to get backwards, both pinned by tests:
inputs:angleis an angular diameter and authors may set it to zero for sharp shadows. Rather than a delta light needing a second MIS path, zero widens toMIN_DISTANT_ANGLE_DEG. The penumbra is sub-pixel, and MIS handles a bounce ray straying into the cone — the light pdf is enormous there, so the bounce weight collapses and no firefly survives.intensity × coloris irradiance, not radiance; radiance is derived asE / Ω. Widening the angle then softens shadows without changing exposure, which is what Hydra normalizes to. Read as radiance instead, a sun-sized source comes out ~5 orders of magnitude too dark —distant_light_irradiance_is_angle_invariantis the guard.3.
UsdLuxDomeLightwith importance-sampled HDRIA dome covers every direction, so once one exists it is the background —
escapedanswers for every ray that leaves and the sky gradient stops applying.crust-core still decodes nothing.
inputs:texture:fileis resolved against the USD layer's directory and handed to the host through a newAssetLoadertrait (Scene::from_usd_with_assets);Scene::from_usdpassesNoAssets, which warns and falls back to the dome's uniform colour.crust-renderimplements it withexrfor OpenEXR andimagefor.hdrand LDR, un-gamma'ing the latter to linear. That seam preserves the crate's documented no-image-dependencies property, and is where general texture support should grow.The sampling is the part most worth a look. A lat-long map is importance-sampled by luminance × sin θ: the image spends as many pixels on a degree near the pole as near the equator, but those texels cover far less solid angle, so omitting the Jacobian over-samples the poles and biases the estimate. Without any of this a small bright sun is a firefly farm — the sample scene's sun disc covers ~1/9000 of the map's texels at 400× its surroundings, and renders clean at 96 spp.
Verification
cargo testgreen across the workspace; ~20 new tests.cornellbox,veach_misandsmokerender pixel-identical tomain, so nothing outside the new lighting paths moved.samples/domelight.usda: the chrome sphere reflects the sky, the ground band and the sun disc — the lat-long mapping, the dome's rotation and the importance sampling all being right at once.E[1/pdf] = 4πover 200k samples), agreement betweensampleandpdf(the two MIS sides), and thesin θJacobian viapoles_are_not_oversampled.uniform_map_has_uniform_solid_angle_pdfis evaluated at texel centres, where the piecewise-constant discretization vanishes and a uniform map must read exactly1/4π; away from centres it legitimately differs, which is why normalization and MIS agreement are what hold everywhere.crust-render, the only crate with a decoder: an EXR round trip and the sRGB→linear conversion.Known gaps (documented in CLAUDE.md)
Nearest-texel dome lookup, so a low-resolution HDRI shows texel edges in a mirror;
texture:formatvalues other thanlatlongare refused rather than mapped wrongly; light picking stays uniform, so a dim dome costs as many shadow rays as a bright sun;DiskLightandCylinderLightare still skipped; and neither infinite light is visible to the guiding field's spatial structure, since they have no position.