Repository navigation
3D: fog and light shafts, tiled local lights, shadow face cache, glTF material extensions - #164
Merged
Merged
Conversation
… material extensions Stage 5 (atmosphere) - Fog3D in Environment3D: distance and exponential height fog in linear HDR before tone mapping, with sky influence and sun in-scattering. - Sun light shafts: half-resolution raymarch through the fog that samples the cascaded shadow map, composited with a depth-aware upsample. Stage 6 (lighting scale) - Tiled local lights: the CPU bins up to 32 local lights into screen tiles with a 32-bit mask each, and the main pass loops only over the lights in its tile. - Local shadow face cache: unchanged shadow faces keep their atlas tile; a scene digest skips rebuilding per-face lists while casters and light matrices are unchanged. Stage 2 (materials) - KHR_texture_transform, KHR_materials_ior and KHR_materials_specular (factors); the dielectric reflectance replaces the fixed 0.04. - Extension policy: NativeGLTFLoader.supportedExtensions, import warnings for optional extensions that are not implemented, ModelAsset3D.importWarnings. Adds Metal golden frames for fog and light shafts, benchmark configurations, and records measurements and limits in ADR-0027. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…-materials # Conflicts: # Documentation/ArchitectureDecisions/0027-3d-rendering-pipeline-gap-roadmap.md # Sources/AdaCorePipelines/Assets/Shaders/flat3d.glsl # Sources/AdaCorePipelines/CorePipelines/PBR3DUniforms.swift
A clear lacquer layer over the base surface: a second GGX lobe with its own roughness on the geometric normal, a highlight for every light, energy-conserving dimming of the base layer, and an environment reflection with image-based lighting. The importer reads the clearcoat factors (validated to 0...1) and reports clearcoat textures as skipped. Adds the pbr-sphere-clearcoat golden frame (Metal) and records the cost and limits in ADR-0027. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… PBR materials Sheen uses the Charlie distribution and Neubelt visibility from the glTF specification with a constant albedo scale in place of its lookup table. Anisotropy uses anisotropic GGX with the authored tangent or a UV-derivative frame, rotated by anisotropyRotation, and bends image-based reflections toward the stretch. The sun now goes through evaluateDirect, so every layer applies to every light once. The importer validates the factors and reports sheen and anisotropy textures as skipped. Adds pbr-sphere-sheen and pbr-sphere-anisotropy golden frames (Metal) and records the cost (about 8 percent of main-pass geometry with all three layered features, none used) and limits in ADR-0027. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The temporal resolve is not exactly repeatable at a bright highlight: two untouched renders of the probe at the first sphere's highlight differ by one step in some runs and three in others, and the identity-LUT check required at most three steps from the first render. It failed in about half of the full GPU runs and never in isolation. The identity and inverted-LUT checks now allow the drift measured between the two untouched renders on top of their tolerance on the temporal path, so they test the LUT and not the noise floor. Four consecutive full runs pass. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…othing PBRMaterial.shaderFeatures (clearcoat, sheen, anisotropy) joins the PBR pipeline key, and Flat3DPipeline takes a fragment shader variant that contains only those layers. The layer predicates in flat3d.glsl are constant false unless ADA_CLEARCOAT, ADA_SHEEN or ADA_ANISOTROPY is defined, so the plain shader (disk cached, and the only one on the web) has no layer code. Variants are compiled from the file text on first use because the on-disk shader cache is keyed by file and stage. Main-pass geometry on the benchmark scene goes from 1.92 ms back to 1.77 ms (32 lights: 2.40 to 2.17 ms). Records the result and limits in ADR-0027. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…r extensions Eight layer textures (clearcoat, clearcoat roughness and normal, sheen color and roughness, anisotropy direction and strength, specular strength and color) are imported with their UV set, KHR_texture_transform and scale, mapped to PBRMaterial.layerTextures, and read in flat3d.glsl, where each multiplies its factor. They bind at 32 to 39 and only in the shader variant of a material that has them (one feature bit per texture), so other materials and the plain shader are unchanged. Layer textures share the base color sampler because the main pass has no free sampler slot. The dielectric reflectance is now a per-fragment value that the specular textures scale. Adds import, mapping, feature and uniform tests and one GPU test per texture, with equivalence checks against the factor-only results. Records the design and limits in ADR-0027. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ghts Primitives carry their morph targets (POSITION, NORMAL and TANGENT deltas per vertex; sparse accessors expand to dense), meshes carry default weights and the extras.targetNames convention, nodes may override the weights, and animation channels with the weights path import with one value per target and key (three per key for cubic splines), also on matrix-placed nodes. Inconsistent data (weights or names that do not match the targets, primitives with different target counts, accessors of the wrong size, a weights channel on a node whose mesh has no targets) fails with invalidMorphTargets, invalidNode or invalidAnimation. Morphed attributes other than the three deltas are reported as warnings. This is data only: weights channels are not yet played (ModelAsset3D skips them) and nothing is rendered with the targets yet. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…n vectors and bounds Each mesh part packs its morph target deltas into one half-float texture; a draw applies the eight targets with the largest weights, fetched by the vertex shader (Morph.glsl, behind ADA_MORPH) before skinning, so unmorphed meshes keep the plain shaders. The weights live in a per-entity, per-part uniform with the previous frame's targets for motion vectors. The morph feature selects vertex shader variants of the main, motion, directional shadow, local shadow and occlusion prepass shaders (Shadow3DConfiguration joins the shadow pipeline key). Source bounds grow by the displacement the active weights can cause (scaled by the largest joint scale for skinned meshes); a morphed mesh is its own batch, has no LOD alternatives, and counts as dynamic for the shadow face cache. MorphWeights3DComponent holds the weights and ModelAsset3D instantiates it with the file's defaults. Tests compare a weighted shift with moving the entity (sun and local-light shadows included), the eight-target limit, culling of a mesh that rests off screen, normal deltas, the occlusion prepass, the temporal motion pass, previous-frame weights, and morph-before-skin on a glTF built in the test. Records the design and limits in ADR-0027. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…on graph, and script access Weights channels import as MorphWeightsTrack (linear, step, cubic spline), join the clip duration, and are sampled by SkeletalAnimationPlayer into morphWeights by rig node, with rest weights from the file, crossfades, and blend, mask and additive rules matching the poses in the animation graph. SkeletalAnimation3DSystem writes them to the node's MorphWeights3DComponent. The component becomes a reflected, Codable, registered @component; because reflection exposes scalars only it offers a selected target (index or name) and its weight for scripts and the inspector. Adds 17 CPU animation tests, a GPU test that plays an animated glTF end to end, and component reflection and Codable tests. Records the design and what remains (an Editor panel) in ADR-0027. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…tes them Model3DSource gains morphWeights (target name to weight), applied at instantiation to every mesh that has a target of that name; scenes written before it decode to none. The animation system now writes player weights only to nodes some clip animates (SkeletalAnimationPlayer.animatedMorphNodes); before, a rigged model's rest weights from the file would have overwritten authored ones on the first frame. Tests cover the encoding and older scenes, authored weights surviving on a rigged model, and a clip taking over the weights it animates. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ble apply The Animator panel gains a Morph Targets mode that lists the model's named targets with a slider each, shows the result live in the model preview, and applies it to the scene as one undoable transaction stored as a structured morphWeights map on the Model 3D component (a weight equal to the file's default is not an override; clearing removes the key). The Model 3D descriptor decodes the map, EditorComponentRegistry registers MorphWeights3DComponent, and the strings are localized (en, ru). Six Editor tests cover the panel model, the payload, the scene transaction with save, undo and redo, the preview, and the weight list; one more opens the saved scene through the scene loader and is not run on machines where that loader ends the test process (the existing animation graph round trip does the same there). Records the design and gaps in ADR-0027. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
SpectralDragon
marked this pull request as ready for review
October 19, 2026 22:09
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
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.
Summary
Work from the ADR-0027 3D roadmap (Stages 2, 5 and 6). Each part is measured and recorded in the ADR, including its limits.
Stage 5: atmosphere
Fog3DinEnvironment3D: distance and exponential height fog in linear HDR before tone mapping, with sky influence and sun in-scattering. Free when off, and free in the benchmark when on.Stage 6: lighting scale
Stage 3: morph targets
targets, mesh and nodeweights,extras.targetNamesand animatedweightschannels import (sparse accessors included). Each mesh part packs its deltas into one half-float texture; a draw applies the 8 heaviest targets, fetched in the vertex shader before skinning, so unmorphed meshes keep the plain shaders.MorphWeights3DComponentis a reflected component with scalar controls for scripts and the inspector.morphWeightson the Model 3D component (authored weights are kept unless a clip animates that mesh's weights).Stage 2: materials
KHR_texture_transform,KHR_materials_iorandKHR_materials_specularfactors. The dielectric reflectance replaces the fixed 0.04 and equals it by default, so existing frames are unchanged.KHR_materials_clearcoat(factors): a second GGX lobe on the geometric normal with energy-conserving dimming of the base layer and an environment reflection under image-based lighting. Together with sheen and anisotropy the three layers cost about 8 percent of main-pass geometry time on scenes that use none of them (bare1.77 to 1.92 ms on the benchmark scene, most likely register pressure from the larger lighting function); this is now fixed: the main shader is compiled per material from only the layers and layer textures it uses (ADA_CLEARCOAT,ADA_SHEEN,ADA_ANISOTROPYand one macro per texture), which bringsbaregeometry back to 1.77 ms. The plain shader keeps the disk shader cache; layered variants compile in the process on first use (about 1 second each), and web builds have no layers.KHR_materials_sheen(Charlie distribution, Neubelt visibility) andKHR_materials_anisotropy(anisotropic GGX, authored or UV-derivative tangent frame, bent IBL reflections), factors only. Their textures (clearcoat strength, roughness and normal, sheen color and roughness, anisotropy direction and strength, specular strength and color) are read too, each with its own UV set andKHR_texture_transform; they bind only in the shader variant of a material that has them and are sampled with the base color sampler (Metal has no free sampler slot).NativeGLTFLoader.supportedExtensions, import warnings for optional extensions that are not implemented, andModelAsset3D.importWarnings. The ADR also corrects the audit: the loader only rejected required unknown extensions; optional ones were silently ignored.mainwas merged in. The only real conflict was with parallax occlusion mapping (#163 area): the uniform block is now... emission, parallax, transforms, dielectricinflat3d.glsl,directional_shadow_3d.glslandmotion_3d.glsl, and a parallax UV shift is applied before a texture's own transform. Measured on the benchmark scene, the parallax code frommainhad already movedbaremain-pass geometry from 1.69 to 1.77 ms.Not validated / known limits
script/validate_webgpu.shwas not run. The shaders are shared with the Metal path, but the new golden frames exist for Metal only.KHR_lights_punctualis still open; layer textures use the base color sampler; anisotropy on meshes without tangents is faceted on coarse geometry (UV-derivative frame); without image-based lighting the coat shows light highlights but not a sky reflection; the specularF90term is not modeled.SLOPPY_RUNTIME_LOCAL_PATHset).loaderAppliesAuthoredWeightswas not run: Editor tests that go throughEditorSceneFileLoaderend the test process silently on my machine, including the existingEditorAnimationGraphTestsround trip. Also,EditorAgentWorkspaceMCPTools.swift(from #251e688b, not part of this PR) usesMetadata/_meta, which AdaMCP v0.2.0 lacks, so the Editor does not build atmainas is; I built with those three arguments temporarily removed and restored them.Test plan
ADAENGINE_GOLDEN_METAL=1 swift test --filter Render3DGPUTests(80 tests, includes the new fog, light-shaft, light-tile, shadow-cache and material-extension suites and the existing golden frames)Render3DMaterialExtensionConstantTests, light tile math, fog and shadow-cache settings,LocalLighting,VisibleMesh3DListsEquivalence,PBR3DSceneTestsADAENGINE_PERF=1 ADAENGINE_GOLDEN_METAL=1 swift test -c release -Xswiftc -enable-testing --filter Render3DGPUTests/Benchmarksscript/validate_webgpu.shon a machine with Swan/Dawn and Swift 6.3.2A test caveat: mixing the
LocalLightingCPU suite withRender3DGPUTestsin one--filtermakes the GPU tests fail (one process-wide renderer), so run them separately. After changingPBRMateriallayout, clear the dependent module build directories before running GPU tests, or the incremental build can crash.🤖 Generated with Claude Code