Reflections - limztudio/NWBLot GitHub Wiki
Reflections
NWBLot renders reflections by combining hierarchical screen-space tracing with bounded hardware continuation. One graph domain produces per-frame reflection radiance for opaque surfaces and primary glass; the final composite blends that radiance with deferred lighting, AVBOIT accumulation, and the refraction resolve.
The public contract is ReflectionSettings in impl/ecs_render/reflection/settings.h, applied through RendererSystem::setReflectionSettings. ValidateReflectionSettings rejects invalid values before they reach shaders or history state. The fixture-level acceptance contract lives in tests/smoke/REFLECTION.md.
Routes
ReflectionTraceMode selects the route:
Disabled: identity reflection outputs. No classification, tracing, filtering, or statistics work is declared.ScreenSpace: hierarchical screen tracing only.Hardware: the bounded hardware ray queue only.Hybrid(default): screen tracing first; unresolved or uncertain screen samples continue through hardware.
Route selection is per frame and does not change device capability reporting. When hardware preparation is not ready, the graph dispatches its prepared fallback pipeline while retaining the declared resource set, and logs the substitution once.
Settings
Notable ReflectionSettings fields:
temporalEnabledwithtemporalMaxSamples(default 16): strict static-scene accumulation, not motion reprojection. It requires trusted, unchanged view, content, and settings.spatialFilterEnabledwithspatialRadius(default 2): filtering runs separately from temporal accumulation.roughnessCutoff(default 0.8): very rough pixels are excluded.maxHardwareRaysPerFrame(default 262144): bounds the hardware ray queue storage.maxOpticalQueries: total scene queries per admitted path, including optional initial-medium discovery.screenMaxSteps,screenThickness,screenConfidenceThreshold,screenEdgeFade: screen traversal depth, world-space surface uncertainty, acceptance confidence, and viewport-edge fade.maxRayDistance,distanceFadeStart: world-space reflected path limits and fade.environmentTop,environmentBottom: linear HDR radiance for ordinary exterior misses. Unresolved optical paths retain only accumulated radiance.samplingSeed: deterministic sampling seed.debugViewselectsTraceSourceorConfidencevisualizations;diagnosticsEnabledenables completed-frame statistics.
Frame Work
The reflection graph domain (DeclareReflectionTasks in impl/ecs_render/reflection/task_graph_reflection.cpp) declares:
- Depth hierarchy: a min/max visible-depth pyramid in
RG32_FLOAT(RGBA32_FLOATwhen the format query rejects RG32 for a required use; neither means the feature fails closed). Mip zero conservatively bounds a 3x3 neighborhood of camera depth samples; coarser levels shrink both bounds and preserve odd-sized edge coverage. A ray skips a cell when its depth interval is disjoint from the cell's visible-depth interval, including when tracing back toward the camera. - Classification: eligible opaque and glass pixels from the G-buffer and primary-glass capture, split into screen candidates and hardware candidates. Hardware queue storage, counters, and indirect argument buffers are sized by the configured budget.
- Build arguments: an indirect pass derives dispatch dimensions from the live counters.
- Hardware resolve: bounded continuation for screen misses. Actual rays equal the minimum of candidates and effective budget, including on warm-up frames. A zero budget keeps candidate classification truthful without issuing rays. Retained-CSG dispatches are sliced into bounded native packets of
NWB_REFLECTION_TRACE_SLICE_RAYS(8192) rays each (impl/assets/graphics/reflection/frame_constants.h), preserving the complete frame ray budget while letting the driver retire bounded work; shared UAV state also orders CPU recording. - Screen trace: hierarchical traversal with world-space tangent-plane validation against exact camera depth, thickness-based acceptance, and a confidence threshold. Provisional wall-mirror color at a candidate is not an accepted hit until it meets confidence.
- Optical transport: a reflected path that enters an authored optical volume traces bounded secondary transmission (entry/exit, absorption over traveled distance, Fresnel) within
maxOpticalQueries. - Temporal accumulation and spatial filtering as configured.
TemporalTask::Record(impl/ecs_render/reflection/task_graph_postprocess.cpp) resolves the late hardware-readiness/history outcome and returns before render-pass termination, binding, push constants, timing, or dispatch when history is not reusable ortemporalMaxSamplesis 1. Classification and hardware resolve already populated the current bank; the graph task, resource declarations, reservation, and accepted/discarded callbacks stay intact, so accepted frames still publish the correct bank and sample sequence. The spatial kernel (impl/assets/graphics/reflection/spatial_cs.slang) first loads each in-bounds pixel's radiance/specular/depth eligibility inputs, then a workgroup-wide flag selects the path: uniformly ineligible tiles copy source values without halo, position, or normal loads, while active tiles reuse eligibility inputs and populate disjoint center/halo cache entries before the existing bilateral filter. Both paths enforce the radius-zero, alpha, finite-value, roughness, and F0 rules. - Publication: opaque and glass radiance targets plus feedback state are committed only by accepted packets; feedback imports, frozen selectors, and accepted/discarded publication stay inside the reflection graph domain.
Scene metadata uploads are reused across unchanged frames. impl/ecs_render/raytrace/optical_scene_upload.* preserves an immutable CPU payload only after an exact comparison of every header and instance byte, and impl/ecs_render/raytrace/task_graph_optical_scene_upload.* with the buffer-bound RayTracingOpticalUploadState imports the accepted writer completion with zero upload tasks and zero graph upload blobs when the payload is unchanged. ReflectionFrameSnapshot::hasHardwareWork() (hardware enabled and maxHardwareRays > 0) gates the shared scene import on both the task-declaration and frame scene-import boundaries, so a zero hardware-ray budget declares no hardware consumer.
Material surface hooks supply RGB specular F0 and perceptual roughness. These values are packed into a dedicated G-buffer target and captured for primary glass, so reflection quality is material-driven like every other shading input.
Reflection temporal push data contains ten live uint fields (40 bytes), at offsets 0 through 36. Its CPU mirror, shared size macro and pipeline range agree; spatial postprocessing uses 32 bytes within the shared range. See GPU payload packing.
Screen-Miss Feedback
screenFeedbackEnabled lets stable, smooth Hybrid tiles bypass proven screen misses, with periodic probes re-testing the miss so a changed scene recovers. It is disabled by default and can be enabled explicitly through the reflection settings.
Statistics
With diagnosticsEnabled, RendererSystem::tryGetLatestReflectionStatistics returns the latest completed-frame counters without waiting:
- eligible opaque/glass pixels, hardware candidates/rays/hits, screen attempts/accepted hits, and fallback pixels
- source frame, requested mode, dimensions, requested and effective budgets, queue capacity, resource generation, and the accepted submission token including physical queue/device generation
Counters describe completed traversal work; they do not establish GPU speedup. Accepted outcomes satisfy opaquePixels + glassPixels = screenHits + hardwareHits + fallbackPixels, and hardware rays never exceed either the effective budget or the candidate count.
Optical Transport
The current-pose optical bounds step publishes skinning-computed per-meshlet AABB partials reduced into a skinning-owned bounds resource, transformed with the exact TLAS snapshot into GPU-resolved optical metadata. Unchanged accepted poses reuse local bounds; accepted skinned bounds also feed hardware optical traversal. Open character geometry terminates through its explicit unsupported-path policy: stress diagnostics report unsupported-path and exterior-eligible ratios, and unresolved optical paths retain only accumulated radiance.
A frozen-scene Unspecified-boundary specialization (ECSRender_ReflectionUnspecifiedOpticalHardware) omits closed-medium stacks and bootstrap event sorting arrays while keeping ray queries, material reconstruction, alpha handling, unsupported-policy termination, and query/event limits. Any transparent ClosedNested, ClosedPriority, or unknown policy selects the general shader; missing snapshots stay conservative. The specialization retains the admitted ray traversal and optical-policy contract.
The reflection spatial kernel (spatial_cs.slang) accumulates in half precision matching its RGBA16F transport and preserves HDR range. Route, filter, query budget, and optical-boundary controls remain explicit in settings and material data.
Tests
nwb_reflection_smokerenders the reflection fixture;reflection_smoke.pydrives capture suites (baseline,screen,budget,rough,temporal,optical,feedback,feedback-diagnostics-off, andall) as CTest tests such asnwb_reflection_capture_smoke, with a--require-hardwaremode for qualified adapters.reflection_benchmark.pycollects completed GPU samples for route and filter comparisons, with workload and host identity recorded in its report.- Unit suites under
tests/unit/ecs_graphics/cover settings validation, sampling sequences, history, statistics, feedback, the ray surface lighting contract, and the optical scene, including the temporal-omission contract/history tests, the zero-budget behavior test. nwb_reflection_temporal_omission_smokewithnwb_reflection_temporal_omission_analysis_unitproves the cap-one frame issues zero temporal ranges while reusable history keeps normal temporal coverage; it checks every finalized timing report and freezes sources, executable dependencies, and authored resources for both launches.tests/smoke/renderer_ab_benchmark.pywithtests/integration/renderer_ab_benchmark_tests.pyis the reusable frozen baseline/candidate benchmark (explicit executables, separate runtime trees, SHA256 source manifests, shared logserver, balanced AB/BA blocks, completed-sample normalization, publication-skew bound, and control-equivalence gates).
Files
impl/ecs_render/reflection/: settings, system (includingReflectionFrameSnapshot::hasHardwareWork()), task-graph declaration, postprocess (including the temporal-omission early return), feedback, history, sampling sequence, and statistics readback.impl/ecs_render/raytrace/optical_scene_upload.*andtask_graph_optical_scene_upload.*: exact-byte immutable payload preflight, buffer-bound accepted-upload residency (RayTracingOpticalUploadState), and the zero-upload reuse import used by the shared scene-import boundary.impl/assets/graphics/reflection/: Slang compute shaders and shared constants for classify, build args, depth reduce, hardware resolve, temporal, and spatial passes.- Frame wiring:
impl/ecs_render/renderer_frame_pipeline_graph.cpp(with the deferred/lighting stage builders underimpl/ecs_render/deferred/) declares the reflection domain between lighting work and composite, and the refraction resolve depends on its completion.