Cook and capture staircase2¶
Convert the locally supplied Modern Hall / staircase2 scene and render it through Ludus's L6 reference renderer. This native installed-SDK sample selects CPU-oracle traversal explicitly. It produces a complete linear HDR capture; its PNG preview is a separate, documented display conversion.

Ludus CPU-oracle capture: 512 × 512, 256 samples/pixel, depth four, seed 871. Preview exposure one; no denoising. Modern Hall by NewSee2l035, CC BY 3.0; conversion and display changes are described below.
This is a scene-specific offline vertical slice. The private sample bundle does not itself implement the project workflow. Stage 5 now reuses its converter inside the installed backend and integrates project-owned assets, saved levels and the native Editor viewport; see the World scene import and saved-document workflow. The standalone commands below are prototype evidence; they do not provide the project → scene → import → viewport → save/reopen product workflow.
Material-preserving project imports¶
New native project imports of this PBRT subset use pbrt-reference-materials-v2.
They retain named material identities and assignments, linear reflectance factors,
original texture UVs and texture references, GGX alpha, conductor RGB eta/k,
coated-diffuse IOR/thickness defaults and smooth dielectric indices. Named material UUIDs derive from the import
namespace and source name; reordering named definitions does not rename those
material assets. Inline definitions retain their source command identity. Per-shape
emission variants reference that source material and keep distinct emission values.
The cooker still combines room geometry with the same source material; it does not
restore every PLY boundary as an editable object.
ludus-cook cook-materials SOURCE OUTPUT writes the same new LUSCN v7 profile
without a project. The cook command and Cook API below remain the frozen
pbrt-reference-v1 compatibility path, including its historical atlas and captures.
Existing project assets are not silently recooked: explicitly reimport to opt into
changed material identities, grouping, source UVs and one-sided area emission.
Triangle/PLY area-light rectangle proxies preserve PBRT's emitting side: authored vertex normals
orient the geometric normal, including when they oppose the index winding.
Normals use the inverse-transpose of the source transform. Without authored
normals, the source face orientation is retained. A rectangle whose normals cross
hemispheres or are tangent to its plane is rejected without replacing previous
outputs. This preserves emitter count, position, area and radiance; it does not
make the sources two-sided. The current import action appends objects: import
into a fresh World to regenerate corrected transforms for existing imports,
including staircase2's corridor-end window. The frozen compatibility cook path
remains unchanged.
The CPU oracle, native-query reference transport and GPU-compute reference support
pure Lambertian diffuse and isotropic rough conductors with complex-IOR Fresnel.
Render-graph GetMaterial exposes the type and optical parameters;
EvaluateStandardBsdf and SampleStandardBsdf evaluate the selected model. Custom
shaders that implement their own metallic/roughness BRDF must explicitly consume
the new model fields to adopt these semantics. Ordinary raster material shaders
continue to use the metallic/roughness approximation in this milestone.
Reference profile 6 also evaluates the wood and tile coatings as a rough dielectric
layer over textured diffuse, with internal reflections and absorption but zero
medium scattering. It uses the retained IOR/thickness and PBRT's default bounded
internal depth, separately from scene bounce depth. See the
layered reference contract.
Spectral transport, scattering coating media, smooth/anisotropic typed interfaces,
remapped source roughness and texture footprint filtering remain outside this
profile. The source's
remaproughness=false values are stored directly as GGX alpha; the legacy raster
roughness approximation does not replace those parameters. All new captures use
reference profile 6; historical reports below retain their original versions.
Full-scene PBRT fidelity and the existing dense native-query parity gap remain
unqualified. Synthetic tests cover preserved source parameters and moved-project
reopen, independent complex Fresnel evaluation and matched CPU/GPU raw radiance.
Prepare the SDK and tools¶
Use the supported native toolchain and SDK workflow and shader tool bootstrap. Install a Development SDK with the L6 renderer, then set these host inputs:
export LUDUS_SDK_PREFIX="$PWD/out/install/linux-clang-development"
export LUDUS_SLANG_COMPILER="$PWD/out/shader-tools/slang/bin/slangc"
export LUDUS_SPIRV_VALIDATOR="$PWD/out/shader-tools/spirv-tools/usr/bin/spirv-val"
export LUDUS_SPIRV_CROSS="$PWD/out/shader-tools/spirv-cross/bin/spirv-cross"
The sample's selectable configure/build/test profiles are
linux-clang-{debug,development,release} and
macos-clang-{debug,development,release}. Select the profile matching your SDK.
On macOS, provide LUDUS_OSX_SYSROOT and LUDUS_LIBCXX_INCLUDE from the prepared
host tools and put its Clang 23, CMake and Ninja on PATH. For unbundled local
SDK installs, supply the matching Conan generator directory through an ignored
CMakeUserPresets.json extending the selected profile's CMAKE_PREFIX_PATH.
Machine paths belong in that local file, never in shared presets. Configuring
reports missing dependencies/tools and does not download or repair them.
From examples/reference-scene:
cmake --list-presets=configure
cmake --list-presets=build
cmake --list-presets=test
cmake --preset linux-clang-development
cmake --build --preset linux-clang-development
ctest --preset linux-clang-development
The tests use synthetic geometry; the third-party scene is not required for CI.
They check bounded source encodings, geometry/UV preservation, a complete
reference capture and malformed-bundle rejection. Complete-scene acceptance also
submits 128 independently encoded objects using every admitted mesh/material,
including 127 offscreen objects and a visible tail emitter. It compares optimized
raster pixels with source-order submission, checks all source identities and
triangle counts, checks an unlit object-ID pass, and finishes the capture after
public resource owners detach.
The acceptance driver's negative tests reject missing geometry, bad identities,
changed pixels, incomplete samples and stale/mismatched provenance. Enable
LUDUS_SCENE_SANITIZERS=ON to instrument the consumer with ASan/UBSan; its SDK
libraries must separately be built with sanitizers for full engine coverage.
Convert and render¶
From the repository root, with the licensed local reference scene present:
out/build/linux-clang-development/modules/content/scene_cook/ludus-cook cook \
references/resources/scenes/staircase2 out/staircase2
examples/reference-scene/out/build/linux-clang-development/reference_scene \
out/staircase2 out/staircase2 256 64
python3 scripts/reference-hdr out/staircase2/capture.luhdr \
--pfm out/staircase2/capture.pfm --manifest out/staircase2/capture.json
The PFM can be viewed with any HDR viewer; the retired tools/scene_cooker/preview.py
PNG tone-mapper has been removed with the Python scene tooling.
The final two capture arguments are square pixel extent (1–1024) and samples per
pixel (1–4096). Rendering uses four path vertices, roulette from depth three,
seed 871, the authored 70-degree camera and the authored area emitters with a
black environment. A capture has a 30-minute deadline and requires zero invalid
contributions before publication. It can take minutes on the CPU. Use 64 8
for a quick framing check before increasing quality.
The output directory must already exist. The cooker creates it; a different
capture destination needs explicit creation. Failed captures preserve the
previous capture.luhdr. Exit steps distinguish usage (1), numeric arguments
(2), bundle acquisition (3), device creation (4), renderer setup (5), reference
setup (6), program readiness (7), bundle admission (8), capture creation (9),
completion/deadline (10), contribution validation (11), output allocation (12),
HDR encoding (13), and publication (14).
Acceptance raster setup, accounting, draw, completion or readback failure is step
15. An optional fifth argument, acceptance, adds raster/submission evidence to
the CPU capture; native requires native ray queries and never falls back.
manifest.json records every consulted source file's SHA-256, the bundle hash,
source and cooked geometry counts, camera, attribution and approximations.
LICENSE.txt accompanies the converted assets. scene.luscn is an explicit
little-endian versioned sample format with bounded counts and finite binary32
fields; it is not a stable shipping asset format. The capture consumer admits
six meshes, eight materials, three textures and up to 128 scene items within a
64 MiB bundle. It rejects unsupported versions, truncation, trailing data,
nonfinite fields and invalid geometry references.
The PFM and LUHDR retain linear radiance. The PNG uses exposure multiplied into
radiance, x / (1 + x) compression, then sRGB encoding. It applies no denoising;
finite-sample noise is expected. Never compare that preview against a different
renderer without matching transport, materials, exposure and display transform.
Conversion profile and limitations¶
The referenced PBRT scene has 19 room PLY meshes, four planar mesh lights, four inline rectangle lights and five disk lights. Only referenced files are cooked. The room surfaces are grouped into four meshes: diffuse, wood, floor tiles and conductors. Shared quad and disk meshes make six renderer meshes and 17 scene items. Three retained RGBA8 textures and four emitter colors fit eight materials. Wallpaper triangles are clipped at repeated UV boundaries before atlas mapping, so repetition survives interpolation. Wood and tile textures retain repeat UVs.
The local source contains 32,967 triangles after quad triangulation, including 2,432 zero-area room triangles. These contribute no surface area and are counted and discarded. The resulting profile stores 28,561 triangles and renders 28,831 triangles after instancing the lights. Geometry is not decimated. Planar light grids become equivalent rectangles; disks use 64 segments.
The converter rejects source directives, parameters and material/texture profiles it cannot represent. It is deliberately restricted to this source's PBRT subset, bounded binary little-endian PLY triangle/convex-quad meshes, uncompressed opaque true-color TGA textures, rigid perspective camera and identity room transforms. It is not a general PBRT importer.
Material approximations are explicit:
- PBRT coated diffuse becomes L6's RGB GGX/Lambert model, without coating layer multiple scattering. Direct GGX alpha is converted to perceptual roughness.
- Conductor eta/k becomes normal-incidence RGB F0 with Schlick angular behavior.
- Diffuse surfaces use the renderer's fixed dielectric F0 of 0.04.
- Textures use one retained mip with bilinear sampling, rather than PBRT's trilinear footprint filtering.
- The supplied PBRT material named
Glassis a conductor, so this conversion does not introduce glass transmission. The included Tungsten preview can therefore differ substantially from the declared PBRT scene. - L6 surface emission is two-sided; PBRT diffuse area lights default to one-sided.
- Source integrator, sampling and film choices are replaced by the explicit L6 capture profile above.
Full prepared-scene renderer acceptance¶
Use the same cooked bundle and installed sample; this check adds no importer or new scene format. From the repository root:
python3 examples/reference-scene/tests/acceptance.py \
--binary examples/reference-scene/out/build/linux-clang-development/reference_scene \
--bundle out/staircase2 --output out/staircase2-acceptance \
--extent 32 --samples 8
Add --native only on a device exposing the optional native query profile. It
requires a second full capture and compares its raw RGB radiance with the CPU
oracle under identical camera, seed, sample, depth and scene-fingerprint settings.
Unsupported native traversal fails visibly. Configure
LUDUS_SCENE_NATIVE_ACCEPTANCE=ON to require the synthetic native parity test in
CTest; the default CPU-oracle tests run on native CI without assuming ray-capable
hardware.
Acceptance verifies all decoded textures, materials, meshes, vertices, stored triangles and instanced triangles against an independent serialized-bundle walk. The source-order raster path disables visibility/LOD/batching and must report every object identity in order. The optimized path must account for each object as visible or culled, emit unique valid identities, and match its raster bytes within one RGBA8 code value. Both paths use the same camera, depth and material profile. These linear, clamped RGBA8 raster images are structural comparisons; they are not compared against indirect path-traced radiance. An additional unlit snapshot encodes a distinct RGB identity for every source object, using the same geometry, transforms and depth. Its source-order and optimized images must also agree, every nonbackground pixel must name a submitted visible object, and the visible-object accounting must match the lit views. This checks dark surfaces without relying on lighting to reveal a missing wall. Both raster and reference captures must observe nonblack surface radiance, so an empty or wrongly framed scene cannot pass solely through matching black images.
Every reference pixel must have exactly the requested sample count and zero
invalid contributions before publication. CPU/native parity permits RGB-vector
error at most 0.002 + 0.0001 * max(length(cpu), length(native)) per pixel; the
report preserves maximum and RMS errors. This is a declared correctness tolerance
for the admitted profile, not convergence to the source renderer or an FPS claim.
acceptance.json records the selected backend and exposed adapter name, host,
binary and bundle SHA-256, complete
resource/triangle/submission counts, sample settings, artifact hashes and native
parity disposition. If a cooker manifest.json exists, its bundle hash and cooked
counts must agree; its source-file hashes, attribution and approximations are
retained in the acceptance report. Each attempt runs in a fresh directory, and
validation failure preserves the previous accepted report and retains the
isolated attempt-*/ artifacts with a failure.txt diagnostic. acceptance.json is
published last; consumers must verify its artifact hashes rather than assume the
output files form an atomic filesystem transaction. The retained files are
cpu/ (and optional native/) capture.luhdr, raster-reference.rgba,
raster-optimized.rgba, adapter-name.txt and versioned scene-acceptance.bin.
The object-ID pass adds identity-reference.rgba, identity-optimized.rgba and
identity-acceptance.bin. run.json retains each path's verified observations;
a failed native comparison additionally retains native-parity.json with failing
pixel count, maximum/RMS error and the largest error locations.
An empty adapter name is recorded as unavailable, not inferred from the backend.
This stage accepts all prepared geometry under the explicitly approximated RGB reflection profile. It does not establish source-PBRT material fidelity, general scene-format support, browser traversal, animated geometry, real-time transport or physical-GPU performance qualification. Unsupported source features remain converter errors; unsupported prepared material/geometry remains renderer admission failure. No fixture is simplified merely to turn an acceptance failure into a pass.
Recorded acceptance and remaining gap¶
The Apple M5 prepared-scene evidence records the exact bundle/binary/capture hashes, source attribution and declared approximations. Staircase2 CPU-oracle acceptance at 64 × 64 and 32 samples/pixel accounts for all 17 objects, 85,554 vertices, 28,561 stored triangles and 28,831 instanced triangles. Six meshes, eight materials and three textures are admitted. Both lit and object-ID optimized raster images match their source-order images with zero byte error. Sixteen objects are camera-visible and one is culled; occluded objects need not appear in the object-ID image.
The 32 × 32, eight-sample native comparison passes with maximum RGB-vector error
0.000239291 and RMS 0.0000106928. The denser 64 × 64, 32-sample comparison is
not qualified: four pixels exceed the unchanged tolerance, maximum error is
0.184945 and RMS is 0.00348437. Diagnostic ray comparisons include near-coincident
primitive choices and near-origin disagreements; their full root cause remains
open. Neither that run nor all resolutions/seeds/native adapters are marked
accepted by the smaller passing fixture. The failing command exits nonzero and
retains its evidence without replacing acceptance.json.
Acceptance exposed a separate coordinate-zero self-intersection defect, corrected by the shared triangle-extent origin allowance described in the reference rendering profile. The shifted-plane transport regression fails with the original calculation and passes with the correction under the existing radiance tolerance. It does not close the denser native qualification gap above.
Attribution¶
Modern Hall is by NewSee2l035, distributed under CC BY 3.0, originally shared on BlendSwap and obtained from Benedikt Bitterli's rendering resources. Preserve the supplied license and attribution when sharing cooked assets or renders; disclose conversion and display changes.
Thanks to Matt Pharr, Wenzel Jakob and Greg Humphreys for the pbrt-v4 input format documentation, especially camera transforms, graphics-state scopes, materials and shapes. The converter is original code, with no PBRT implementation copied.
For interactive placement and gameplay fields use the single
World authoring workflow.
The command-line level conversion/capture remains a runtime asset pipeline,
not a separate editor workspace. Existing .lulevel files open in World through
the documented conversion path.
Interactive raster viewport acceptance¶
The installed consumer accepts reference_scene INPUT OUTPUT SIZE 2 viewport.
It loads the supplied cooked scene and its camera, renders two ordinary raster
frames through RasterViewport, and exports viewport.rgba for offline evidence.
It never enables reference capture or requires compute/ray queries. That file
contains top-left RGBA8Unorm sRGB-encoded tone-mapped RGB; the consumer must
preserve display values without encoding again. The independent emitter fixture checks authored .5 emission
maps to 156/255 after Reinhard/sRGB. This mode supplies no indirect transport, invented
lights or CPU image presentation fallback.
The native renderer viewport regression changes the extracted World snapshot's
material and camera while keeping an earlier image leased, checking changed
pixels, culling, retained-image immutability, backpressure and stale rejection.
A real editor must feed the selected project's current World extraction into
Update, respond to scene/camera/resize changes, and import the completed image
through the RHI/Qt native-sharing bridge. The offline consumer proves the
installed renderer API; it does not prove editor integration. See the
interactive viewport contract.
GPU preview acceptance¶
This is the explicit optional progressive path-tracing reference mode, distinct from the primary interactive raster viewport above.
The installed sample also accepts gpu as its optional mode. This selects the
renderer_gpu_preview_software recipe, requests raster and compute capabilities, and runs
on adapters without native ray-query support. It preserves authored emissive
lighting with a black environment. It performs GPU tracing, shading,
accumulation and display conversion and returns a leased RHI texture. CPU setup
builds, packs and uploads the static scene; it does not trace or shade pixels.
The explicit software recipe intersects triangles in the compute shader, with
linear work per ray; large scenes need separate performance qualification.
examples/reference-scene/out/build/linux-clang-development/reference_scene \
out/staircase2 out/staircase2 64 32 gpu
Offline acceptance writes gpu-preview.rgba (top-left opaque RGBA8, exposure one,
Reinhard then sRGB) and gpu-preview.bin: little-endian width, height, completed
samples and reserved zero as four uint32 values, then two uint64 scene fingerprint
words. This sample deliberately reads back its final leased texture to export
acceptance evidence. A live viewport must share that texture through RHI/Qt with
producer and consumer completion, without running this export. GPU preview failure
is exit step 16. Failed execution never selects the CPU reference renderer.
The source profile and approximation limits above still apply. Numerical failure
is a magenta error pixel; the completed-dispatch count does not assert radiance
validity. scene_gpu_preview tests the installed shaders/API using an independently
encoded emitter fixture with no added lights and verifies the expected display
pixels. Dense-scene accuracy and additional GPU backends require separate
qualification. See the GPU preview contract
for budgets, capability rejection, ownership and external-consumer requirements.
Surface fidelity profile¶
The material and lighting fidelity contract extends renderer-authored materials with a packed linear normal/roughness/metallic map, and admits authored analytic lights in GPU reference rendering. The current LUSCN scene fixture still supplies its declared constant metallic/roughness and base-color textures; this renderer change does not reinterpret source PBRT materials or add map fields to the interchange format. Captures produced now use reference profile version 2. Historical evidence above retains its recorded version; acceptance compares captures from the same profile and current binary.
Full static Sponza and SunTemple acceptance¶
The authored/HDR renderer acceptance uses the supplied main Sponza glTF and
SunTemple FBX under references/resources. Its optional fixture adapter is
test tooling, independent of the installed scene importer. It never downloads
assets or executes supplied .pyscene files. Fixtures, manifests and images stay
in ignored out/; the original supplied resources are read-only.
Prepare fixtures with NumPy, Pillow and Assimp 6 installed on the acceptance host:
python modules/graphics/renderer/tests/prepare_resource_scenes.py \
--resources /absolute/path/to/references/resources \
--output out/resource-acceptance \
--assimp /absolute/path/to/assimp \
--assimp-library /absolute/path/to/libassimp
Assimp exports SunTemple's geometry/material bindings to a private glTF fixture;
its C API supplies the authored FBX camera and 13 lights omitted by that export.
The adapter preserves their transforms and RGB intensities, interpreted under
Ludus's lux/candela and bounded attenuation profile. Point/spot range is explicitly
one million meters for this FBX acceptance profile. It applies the source
SunTemple.pyscene's documented 1000× emissive factor adjustment without running
the script. DDS DirectX normal Y is converted; roughness and metallic come from
the source ORM G/B channels. The supplied FBX contains no tangent/bitangent layers,
so it uses the declared UV-derived fallback. Static base transforms are captured;
its animation is outside this acceptance.
Sponza retains its authored tangents, colors and UV sets. One degenerate tangent vertex is recorded in the manifest and explicitly marked unavailable; no vertex or triangle is dropped. Its 24 glTF punctual lights have authored zero intensity; acceptance retains those zeros. Its supplied HDR environment supplies illumination. The camera is an explicit interior acceptance rig because this glTF supplies no camera. Both scenes retain every primitive, material and mesh instance, with texture dimensions explicitly capped at 512 for this bounded acceptance profile. Source AO is not applied to reference transport; this test does not establish physical transmission, layered-material fidelity or shadow/GI agreement with the original authoring renderer.
Build ludus_renderer_render_tests with the normal prepared native toolchain.
Run the hidden, private-resource test once per fixture:
LUDUS_RESOURCE_SCENE_FIXTURE=out/resource-acceptance/sponza.rscn \
LUDUS_RESOURCE_SCENE_OUTPUT=out/resource-acceptance/sponza.ppm \
/absolute/path/to/ludus_renderer_render_tests '[.resources]'
LUDUS_RESOURCE_SCENE_FIXTURE=out/resource-acceptance/suntemple.rscn \
LUDUS_RESOURCE_SCENE_OUTPUT=out/resource-acceptance/suntemple.ppm \
/absolute/path/to/ludus_renderer_render_tests '[.resources]'
The fixture is a private little-endian test format, not an engine interchange format. At 256×256, acceptance compares ordinary culling/batching/clustered-light execution with source-order, unculled, direct-light-list execution. It requires all submitted items to be accounted for, no dropped admitted lights, nonblack output, no magenta error pixels, and no pixel differences greater than one byte. The output manifest reports actual geometry counts, source/fixture hashes, attribute coverage, HDR dimensions/range and conversion limits. Image metrics include renderer/RHI requested or known peak bytes and setup/frame-plus-readback wall time; these timings are diagnostics, not steady-state frame-rate claims.
| Full static input | Primitives / instances | Materials | Stored vertices | Instanced triangles |
|---|---|---|---|---|
| Main Sponza | 405 / 405 | 28 | 2,049,137 | 3,747,018 |
| SunTemple | 1,056 / 1,056 | 48 | 550,165 | 606,376 |
SunTemple's triangle count also agrees with independent Assimp FBX inspection. Both supplied environments are 4096×2048; Sponza's maximum source radiance is 125,952 and SunTemple's is 2,880. The renderer's scaled binary16 storage preserves these source ranges for lighting; reference transport retains the original HDR. Regular CI exercises small authored-input/HDR fixtures and CPU/GPU reference convergence without depending on these separately supplied assets. Full-scene acceptance here is raster acceptance; it does not qualify large-scene native ray-query parity or GPU path-tracer capacity.