Support writing the scene textures from the forward pass - #9166
Merged
Conversation
Build size reportThis PR changes the size of the minified bundles.
|
Contributor
There was a problem hiding this comment.
Pull request overview
Adds dormant infrastructure for forward-pass scene textures, initially supporting linear depth.
Changes:
- Adds scene-texture shader defines and GLSL/WGSL write chunks.
- Adds material write declarations, attachment masking, and render-pass scoping.
- Adds supporting APIs, preprocessing changes, and unit tests.
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
test/scene/camera-shader-params.test.mjs |
Tests scene-texture parameters. |
test/platform/graphics/blend-state-utils.test.mjs |
Tests attachment masking. |
test/core/array-utils.test.mjs |
Tests array equality. |
src/scene/shader-lib/wgsl/collections/shader-chunks-wgsl.js |
Registers the WGSL chunk. |
src/scene/shader-lib/wgsl/chunks/lit/frag/pass-forward/litForwardMain.js |
Writes WGSL scene depth. |
src/scene/shader-lib/wgsl/chunks/common/frag/scene-textures.js |
Implements WGSL scene-texture writes. |
src/scene/shader-lib/glsl/collections/shader-chunks-glsl.js |
Registers the GLSL chunk. |
src/scene/shader-lib/glsl/chunks/lit/frag/pass-forward/litForwardMain.js |
Writes GLSL scene depth. |
src/scene/shader-lib/glsl/chunks/common/frag/scene-textures.js |
Implements GLSL scene-texture writes. |
src/scene/renderer/render-pass-forward.js |
Scopes scene textures and clears. |
src/scene/renderer/forward-renderer.js |
Masks unsupported material outputs. |
src/scene/particle-system/particle-material.js |
Disables particle writes. |
src/scene/materials/standard-material-options-builder.js |
Enables forward linear depth. |
src/scene/materials/shader-material.js |
Defaults custom shaders to no writes. |
src/scene/materials/material.js |
Adds scene-texture write policy. |
src/scene/materials/lit-material-options-builder.js |
Enables lit linear depth. |
src/scene/constants.js |
Defines the depth texture name. |
src/scene/camera-shader-params.js |
Generates texture defines and hashing. |
src/platform/graphics/render-target.js |
Exposes color attachment count. |
src/platform/graphics/blend-state-utils.js |
Derives attachment-masked blend states. |
src/core/preprocessor.js |
Reorders define substitution. |
Suppressed comments (1)
src/scene/camera-shader-params.js:242
- Returning the backing array allows mutations to bypass both validation and
markDirty(). Afterhashordefineshas been read,params.sceneTextures.push('depth')changes the configured list while leaving both caches stale. Expose an immutable value (for example, freeze the stored copy) or return a defensive snapshot.
get sceneTextures() {
return this._sceneTextures;
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+663
to
+664
| const blendState = (attachmentCount > 1 && !material.sceneTexturesWrite) ? | ||
| getSingleAttachmentBlendState(material.blendState, attachmentCount) : material.blendState; |
Comment on lines
+196
to
+197
| const colorIndex = this.sceneTextures?.length ? 0 : undefined; | ||
| this.setClearColor(fullSizeClearRect && step.clearColor ? camera.clearColor : undefined, colorIndex); |
Comment on lines
+227
to
+231
| Debug.call(() => { | ||
| names.forEach((name) => { | ||
| Debug.assert(/^[a-z]\w*$/i.test(name), `Scene texture name can only contain letters, numbers and underscores and start with a letter: ${name}`); | ||
| }); | ||
| }); |
Comment on lines
+67
to
+68
| params.sceneTextures = ['depth']; | ||
| expect(params.hash).to.equal(hash); |
Comment on lines
+663
to
+664
| const blendState = (attachmentCount > 1 && !material.sceneTexturesWrite) ? | ||
| getSingleAttachmentBlendState(material.blendState, attachmentCount) : material.blendState; |
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.
The scene textures are additional color attachments the scene pass renders alongside the scene color, holding per pixel data - the linear depth to begin with - which the post-processing effects then consume. Their advantage over a depth prepass is that they cost no extra geometry pass, and that blended geometry contributes to them, so gaussian splats can supply a depth the prepass cannot produce at all.
This adds the layer that lets a material write them, and masks off the materials whose shader does not. Nothing enables it: the list of scene textures a camera renders stays empty until a follow up sets up the render target they are attached to, so every generated shader and every render target is unchanged by this on its own.
Changes:
CameraShaderParams.sceneTexturesholds the ordered names of the scene textures the pass renders, the name at index i going to the color attachment at index i + 1. Each name generates a pair of defines following the same naming as the shader passes -'depth'givesSCENE_TEXTURE_DEPTH, enabling the write, and{SCENE_TEXTURE_DEPTH_SLOT}, which the preprocessor substitutes into the name of the output written. The value is compared by value on assignment, as the render pass assigns it around every layer step it renders and an equal value must not invalidate the shader variants of every material.sceneTexturesPSchunk supplies a write function per scene texture, so the attachment index never appears in the calling code. Custom shaders can use it the way they already useshadowCasterPS, by including the chunk and calling the function. On WebGPU the function takes a pointer to the fragment output, as the outputs there are members of the returned struct.Material.sceneTexturesWritedeclares whether a material's shader generates the scene textures, defaulting by material type - true for the opaqueStandardMaterialandLitMaterial, false forShaderMaterialand the particle materials. The additional attachments of a material which does not generate them are masked off using a newgetSingleAttachmentBlendState, as an attachment the fragment shader leaves unwritten makes the draw invalid on both backends.RenderPassForward.sceneTexturesscopes all of this to the passes rendering to a render target the scene textures are attached to, so that a camera's other passes, for example the one rendering the UI to the output render target, do not write them. The same pass also narrows the camera's clear color to attachment 0, leaving the clear values of the scene textures to whoever owns them.stripUnusedColorAttachmentsmatchespcFragColorNwith a literal index, and would otherwise strip the declaration of an output written through a substituted slot. It is safe for the two passes it now precedes -processArraySizerewrites[KEY]for unbraced int defines while the injection substitutes braced{KEY}keys, which are disjoint patterns, and the injection skips lines containing a preprocessor directive, a set neither of the reordered passes alters.linearDepthis now derived in one expression per material type, instead of being assigned separately by the minimal and the full variant of the standard material options.array.equals, which had none.API Changes:
RenderTarget#colorBufferCount- new, the number of color attachments the render target was set up with. The rest of the above is internal (@ignore).Verified on both backends that the dormant path renders identically. The write path itself was exercised by temporarily forcing a scene texture on, confirming on WebGL2 that the slot is substituted and the output declaration survives the color attachment stripping, and on WebGPU that the write through the pointer compiles and that the generated fragment output struct picks up the additional attachment.