Auto shader output definitions for MRT
Description
This PR aims to make Multiple Render Targets (MRT) easier to use by implementing automatic shader output definitions for built-in materials and ShaderMaterial.
// Example output definitions:
layout(location = 0) out lowp vec4 out_FragData0;
#define out_Color out_FragData0
layout(location = 1) out mediump vec4 out_FragData1;
#define out_Normal out_FragData1
Context
For MRT workflows it's up to the user to define what data should be written to the respective textures. The MRT example promotes the use of RawShaderMaterial to achieve this. However, MRT is most useful for rendering out additional pixel data during the main render pass. In most situations the main scene will consist of meshes that use built-in materials like MeshStandardMaterial, but those materials are currently difficult to use with MRT.
Improving compatibility
It's possible to use onBeforeCompile on every object in a scene to modify built-in materials, but this has some downsides:
- An object's material may already have an
onBeforeCompilehook, so care must be taken to preserve it. - All objects need to be tracked to avoid assigning
onBeforeCompilehooks multiple times. - Objects and materials may be added or removed at runtime which requires additional scene processing to ensure that all materials have the required
onBeforeCompilehooks.
If it wasn't necessary to define shader outputs, we could avoid using onBeforeCompile altogether and modify the ShaderChunk library instead (example). But at the moment there's no way around onBeforeCompile.
Meeting halfway
With the changes in this PR, three can define shader outputs automatically based on the current render target. To avoid breakage, this is only done for built-in materials and for ShaderMaterial if the shader doesn't already define shader outputs. This implementation also uses the names of the MRT textures to define macros of the form out_${name} for convenience which allows for conditional MRT code like the following:
#ifdef out_Normal
out_Normal = vec4(normal, 1.0);
#endif
I've considered using the texture names as-is but opted for a prefix approach to reduce the risk of name conflicts.
Open issue
~~This PR is in draft mode because the material/program doesn't get updated after switching the render target. I've tried modifying WebGLPrograms.getProgramCacheKey to account for the render target, but that didn't work out. Setting material.needsUpdate to true also didn't trigger a program update.~~
📦 Bundle size
Full ESM build, minified and gzipped.
| Before | After | Diff | |
|---|---|---|---|
| WebGL | 336.49 78.38 |
337.41 78.71 |
+917 B +329 B |
| WebGPU | 542.05 150.13 |
542.05 150.13 |
+0 B +0 B |
| WebGPU Nodes | 541.51 150.03 |
541.51 150.03 |
+0 B +0 B |
🌳 Bundle size after tree-shaking
Minimal build including a renderer, camera, empty scene, and dependencies.
| Before | After | Diff | |
|---|---|---|---|
| WebGL | 465.42 112.22 |
466.35 112.55 |
+924 B +331 B |
| WebGPU | 614.89 166.17 |
614.89 166.17 |
+0 B +0 B |
| WebGPU Nodes | 569.88 155.58 |
569.88 155.58 |
+0 B +0 B |
Some checks were not successful
DeepScan:1 new and 1 fixed issues
Local variable 'xr' is not used.
editor/js/Viewport.js
....
@vanruesc This issue has been fixed, you need to pull the code again.
Solution:
git checkout auto-shader-outputs
git remote add upstream https://github.com/threejs/three.js.git
git fetch upstream
git merge upstream/dev
... All checks have passed ...
This PR is in draft mode because the material/program doesn't get updated after switching the render target. I've tried modifying
WebGLPrograms.getProgramCacheKeyto account for the render target, but that didn't work out. Settingmaterial.needsUpdatetotruealso didn't trigger a program update.
I've fixed this now by including the render target in the material properties to detect if the current render target differs from the one that was previously associated with the material. The needsUpdate flag did in fact work before, but I didn't realize that I had to call render() manually in the demo I was testing with.
I've adjusted webgl_multiple_rendertargets to test switching between render targets and it appears to be working as expected. One downside is that render targets are only checked superficially. If the texture attachments are changed, materials will not be updated unless needsUpdate is set.