Dynamically remove textured background?

Hi there, I’m using a CanvasTexture as the scene’s background.

I’d like to dynamically remove it and re-add it but when I do I am getting some Z artifacts or depth / renderOrder issues. Note, this only happens while using postprocessing.

I’m doing scene.background = null to remove it, but nothing actually happens - is there a better way? I’d do scene.remove() but that seems to only accept an Object3D. Looking at background’s source, it looks like it’s using a ShaderMaterial and a Plane under the hood to render the background:

planeMesh = new Mesh(
	new PlaneGeometry( 2, 2 ),
	new ShaderMaterial( {
		name: 'BackgroundMaterial',
		uniforms: cloneUniforms( ShaderLib.background.uniforms ),
		vertexShader: ShaderLib.background.vertexShader,
		fragmentShader: ShaderLib.background.fragmentShader,
		side: FrontSide,
		depthTest: false,
		depthWrite: false,
		fog: false
	} )

Any suggestions on how to fix the artifacts? I’m happy to:

  • Remount the background when necessary (it happens on user input when enabling an effect)
  • Fix renderOrder or sorting issues. I can’t set depthWrite or depthTest to false on the black material (which does seem to fix it, but introduces other issues).
  • Open to other ideas.

Shown in the video: enabling bloom and then disabling it causes the issue

I was unable to reproduce this effect. Setting background to null removed the background without problems and the background became all black. Re-enabling it later showед the texture, again without any problems.

Could you share a minimal online editable example that demonstrates this issue? It is hard to debug a video.

If you are sure the issue is with the special Plane object, you may also try to make the background texture transparent by clearing the 2D canvas (adjust 1024 to your canvas size):

context.clearRect(0,0,1024,1024);
scene.background.needsUpdate = true;

Edit: my test case is here: https://codepen.io/boytchev/pen/oNQVLNg?editors=1111. Most likely it does not recreate the exact situation as with your code, but this is the best I could guess by the description of the setup.

Thank you @PavelBoytchev, I’ll work on a minimal repro to share here.

Indeed I’ve looked at your code and the differences are that (1) I’m using the pmndrs/postprocessing as opposed to the three/addons and (2) I’m unmounting the composer as opposed to disabling the effect.

Edit:

I have a quick sandbox demonstrating my setup and repro. I did learn a few things though:

Very strange. When I ran the CoseSandbox version it worked well for some time. Then it suddenly started to show the glitch. When I was making ready to debug it, the glitched disappeared and I’m no longer able to reproduce it. This is one very smart bug, shows up only to tease you, and then vanishes.

2 Likes

After randomly clicking on the options, it happened again (note: the time is set to 0, so the shape is a square, but only part of it is visible):

Do you have the time to explore whether this happens when using vanilla Three.js (i.e. without react things). R3f tends to do a lot of things under the hood, and I don’t know how to debug it.

Initially I though it might be something with frustum culling, or depth buffers, but this pattern in the snapshot eliminates these cases. It looks like z-fighting, or floating-point precision issues, but could be totally something else.

1 Like

Maybe I found a way to reproduce the bug every time (at least I reproduced it 5 times in a row). The steps are:

  • in line 19 I set ref.current.uniforms.uTime.value = 0 – this is just for convenience, in order to have static image
  • when the code is run, there is a square (snapshot 1)
  • zoom it and rotate it slightly (snapshot 2)
  • switch on composer (snapshot 3)
  • switch off composer and rotate the scene (snapshot 4)


Edit:

After adding clearDepth I’m unable to reproduce the glitch. Could you try it on your local machine?

useFrame((state) => {
  if (ref.current) {
    state.gl.clearDepth( ); // added this line
    ref.current.uniforms.uTime.value = state.clock.elapsedTime
  }
})
2 Likes

Thank you @PavelBoytchev ! gl.clearDepth() did seem to work on my local! I will say though I’m a bit confused as to why that is the case… and is there an overhead of clearing the depth buffer every frame?

Usually the depth buffer is cleared every frame, along with the color buffer. In very rare cases it is not cleared. Maybe here the switching between composer rendering and direct rendering leaves the buffer uncleared?!