three.js icon indicating copy to clipboard operation
three.js copied to clipboard

Auto shader output definitions for MRT

Open vanruesc opened this issue 1 year ago • 3 comments

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 onBeforeCompile hook, so care must be taken to preserve it.
  • All objects need to be tracked to avoid assigning onBeforeCompile hooks 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 onBeforeCompile hooks.

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.~~

vanruesc avatar Feb 22 '24 23:02 vanruesc

📦 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

github-actions[bot] avatar Feb 22 '24 23:02 github-actions[bot]

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 ...

puxiao avatar Feb 24 '24 07:02 puxiao

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.

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.

vanruesc avatar Apr 12 '25 22:04 vanruesc