Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WebGLRenderer: Proof of concept for copying transmission pass, improving rendering performance #28423

Open
wants to merge 5 commits into
base: dev
Choose a base branch
from

Conversation

gkjohnson
Copy link
Collaborator

@gkjohnson gkjohnson commented May 19, 2024

Related issue: --

Description

Currently WebGLRenderer renders all opaque objects twice when rendering a transmission material - once for the transmissive pass and once for the main render pass. This PR demonstrates the performance improvements gained if we render the opaque materials to a render target with a depth attachment and then copy both the depth and color values to the final target instead of rerendering the objects.

To exaggerate the performance gains I added a ball of 10,000 cubes to the transmission example:

image

Before this change calling "render" takes ~27-28ms and after it takes ~15-16ms on my machine. An improvement of ~42%.

The limitations of this approach are that if the background is not cleared then unique styles of blending will not work correctly with existing render target / canvas contents. And likewise if stencil rendering is used then it will not be able to work with existing target values and they cannot be copied across buffers.

We could handle the cases specially, though, and skip copying the transmission buffer contents if stencil rendering is used on opaque rendered materials or color buffer is not cleared.

Edit: I guess we'll need to account for the case background colors are handled differently, as well?

@gkjohnson gkjohnson changed the title WebGLRenderer: Proof of concept for copying transmission pass, improving rendering performance WebGLRenderer: Proof of concept for copying transmission pass, improving rendering performance over 40% May 19, 2024
@gkjohnson gkjohnson changed the title WebGLRenderer: Proof of concept for copying transmission pass, improving rendering performance over 40% WebGLRenderer: Proof of concept for copying transmission pass, improving rendering performance May 19, 2024
Copy link

github-actions bot commented May 19, 2024

📦 Bundle size

Full ESM build, minified and gzipped.

Filesize dev Filesize PR Diff
677.8 kB (168 kB) 678.7 kB (168.3 kB) +956 B

🌳 Bundle size after tree-shaking

Minimal build including a renderer, camera, empty scene, and dependencies.

Filesize dev Filesize PR Diff
455.8 kB (110.1 kB) 456.8 kB (110.4 kB) +956 B

# Conflicts:
#	src/renderers/WebGLRenderer.js
@Mugen87
Copy link
Collaborator

Mugen87 commented May 21, 2024

The limitations of this approach are that if the background is not cleared then unique styles of blending will not work correctly with existing render target / canvas contents. And likewise if stencil rendering is used then it will not be able to work with existing target values and they cannot be copied across buffers.

I'm not yet sure what all these limitations mean. Any chances to show with live examples what use cases would break? E.g. there are users who use stencil with transmission so would the new code path break their apps?

Besides, you did not update the array camera/XR code path which also calls renderTransmissionPass(). Is that an oversight? Ideally, we handle both code paths uniformly.

@gkjohnson
Copy link
Collaborator Author

gkjohnson commented May 21, 2024

I'm not yet sure what all these limitations mean. Any chances to show with live examples what use cases would break? E.g. there are users who use stencil with transmission so would the new code path break their apps?

Stencil Buffers

Right now we render all the geometry twice which means we're writing stencil state to both the canvas buffer and the transmission render target buffer. That means that after rendering with transmission the canvas stencil buffer has stencil state remaining that can be used again in a subsequent "render" call as long as the buffer is not cleared.

However generally the stencil buffer cannot be copied between buffers. That means if we transition to copying contents from the rendered transmission buffer then we cannot copy the stencil state, and stencil buffer contents will not be present after calling "render". Internally it won't even be available after the opaque rendering as it was previously so transparent objects won't be able to use the stencil state, either.

Here's a quick example that wouldn't work afterward. It's a scene that renders a sphere to the stencil buffer & renders a transmissive box. Then afterward renders a blue quad which colors in the pixel with stencil values:

https://jsfiddle.net/n15tdLv2/1/

Blending

Again if we're rendering a scene without autoclear and the scene includes objects in the opaque queue that use custom blending (additive, multiplicative, etc) then they will not be blended properly with existing canvas contents. This is because we're rendering all contents to the transmission buffer (which will likely not have the same contents as the canvas) so the blending results will not be the same. And when copying data from the transmission buffer to the canvas we cannot perform the appropriate blending operations per pixel.

Unique Backgrounds

It's not always the case that the background we're rendering in the transmission buffer is the same as the one we want when rendering to the canvas. As commented in #28434 & #25819 - we want to render the background as transparent white when clear alpha is not set to 1. But we'll still want to respect the use set clear color and alpha when rendering the canvas background.

Besides, you did not update the array camera/XR code path which also calls renderTransmissionPass()

Once these other questions are figured out I'll update the PR.

@gkjohnson
Copy link
Collaborator Author

cc @Mugen87 @mrdoob - any other thoughts on this?

@mrdoob
Copy link
Owner

mrdoob commented May 29, 2024

Hmm, these limitations seem like something developers will have to battle with and we'll have to explain every time...

@gkjohnson
Copy link
Collaborator Author

Hmm, these limitations seem like something developers will have to battle with and we'll have to explain every time...

I agree to an extent - on the other hand it's a pretty huge performance gain. And people already complain about transmission performance, no? (1, 2, 3, #28073, #27108, ...more) Also I think it's the role of documentation to explain in what conditions the "slow" path kicks in so users can decide what to do. Right now everything is slow even though it doesn't need to be. Transmissive materials seem almost unusable in seemingly simple use cases since it nearly doubles the render time for even a small transmissive surface. We had to remove it from our application due to this which is why I took a look at what could be done.

Something like Batching geometry may help with draw calls but it still won't help performance degradation due to rendering expensive shaders twice. It would just be nice to be able to use this in some way without such a large performance hit because it's otherwise a nice effect. Even if the solution is less "smart" - like adding a flag to the renderer.

@mrdoob mrdoob modified the milestones: r165, r166 May 31, 2024
@gkjohnson
Copy link
Collaborator Author

It occurs to me that we could render everything to the target buffer first (canvas, render target) and then copy that into the transmission buffer using a FrameBufferTexture. If we did that then:

  • All stencil effects would be written to the target buffer and interact with any existing state correctly.
  • All blending effects would be written to the target buffer and blend with existing state correctly.
  • Transmission effects would be able to take advantage of uncleared target buffer color state (which doesn't work right now)
  • Transmission buffer no longer needs a depth or stencil attachment.

The only thing that isn't immediately clear is how to address is the forced white background clear from #25819 in order to ensure the transmission tint can be rendered. Some possible solutions:

  • Composite / blend the copied framebuffer on top of a 50% transparent white background before rendering (requires another render target?)
  • Blend with a 50% transparent white color in the shader when sampling the transmission texture.

Both solutions would mean we would retain the existing background tint in addition to the white color which I think is okay (and possibly an improvement). I'm not sure if there's a way to copy directly from the canvas frame buffer and immediately blend on a white background otherwise this would be the easiest approach. Do you know of one @Mugen87?

If we don't foresee any issues for merging the above approach and we can agree on a solution to the background issue then I can adjust this PR to try it out and see how it works.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants