Subtle colour banding issue, looks like 256 colours when using Bloom with Effect Composer

threejs banding

Struggling to fix this issue, now day 2 of this and I keep running into the same banding issue, with different pass setups.

  renderer.setClearColor('#000000');
  const bloomPass = new UnrealBloomPass(new THREE.Vector2(width, height), 1, 1, 0.75);
  const copyPass = new ShaderPass(CopyShader);
  copyPass.renderToScreen = false;
  bloomPass.renderTargetsHorizontal.forEach((element) => {
    element.texture.type = THREE.FloatType;
  });
  bloomPass.renderTargetsVertical.forEach((element) => {
    element.texture.type = THREE.FloatType;
  });
  const composer = new EffectComposer(renderer);
  composer.renderer.outputEncoding = sRGBEncoding;
  composer.setSize(width, height);
  composer.setPixelRatio(density);
  const normalRender = new RenderPass(scope.scene, scope.cameraManager.camera);
  composer.addPass(normalRender);
  composer.addPass(bloomPass);
  composer.renderer.toneMapping = ACESFilmicToneMapping;

  THREE.ShaderChunk.tonemapping_pars_fragment = THREE.ShaderChunk.tonemapping_pars_fragment.replace(
    'vec3 CustomToneMapping( vec3 color ) { return color; }',
    `#define Uncharted2Helper( x ) max( ( ( x * ( 0.15 * x + 0.10 * 0.50 ) + 0.20 * 0.02 ) / ( x * ( 0.15 * x + 0.50 ) + 0.20 * 0.30 ) ) - 0.02 / 0.30, vec3( 0.0 ) )
    float toneMappingWhitePoint = 1.0;
    vec3 CustomToneMapping( vec3 color ) {
      color *= toneMappingExposure;
      return saturate( Uncharted2Helper( color ) / Uncharted2Helper( vec3( toneMappingWhitePoint ) ) );
    }`,
  );

env map setup

    cubeTextureLoader.loadAsync([`${map}_px.jpg`, `${map}_nx.jpg`, `${map}_py.jpg`, `${map}_ny.jpg`, `${map}_pz.jpg`, `${map}_nz.jpg`]).then((textureCube) => {
      this.scene.environment = textureCube;
      textureCube.encoding = sRGBEncoding;
      this.scene.background = textureCube;
    });

Is there anything I am missing?

Using ThreeJS 133.1

By the way when i add the following to the end, the entire scene becomes dark so thats why its not in there yet :
composer.addPass(copyPass);

Edit: I realised this always happens when you add unreal bloom pass into the effect composer, it just creates a colour banding

Can you please create your effect composer like so:

const parameters = {
	minFilter: THREE.LinearFilter,
	magFilter: THREE.LinearFilter,
	format: THREE.RGBAFormat,
	type: THREE.FloatType
};

const renderTarget = new THREE.WebGLRenderTarget( window.innerWidth, window.innerHeight, parameters );

const composer = new EffectComposer( renderer, renderTarget );

Any improvements?

Don’t do that. When using post processing don’t touch outputEncoding but apply a gamma correction pass at the end of the pass chain like so:

Tone mapping should also be done via post-processing: https://threejs.org/examples/webgl_shaders_tonemapping

2 Likes

Totally lost on this now.


banding 2

Thanks for your help.

Adding the render target, cant see a noticeable difference visually.

  const renderTarget = new WebGLRenderTarget(width, height);
  const composer = new EffectComposer(renderer, renderTarget);
  const normalRender = new RenderPass(scene, cameraManager.camera);
  const adaptiveToneMapping = new AdaptiveToneMappingPass(true, width);
  adaptiveToneMapping.needsSwap = true;
  const gamma = new ShaderPass(GammaCorrectionShader);
  composer.addPass(normalRender);
  composer.addPass(adaptiveToneMapping);
  composer.addPass(bloomPass);

Adding GammaCorrectionShader causes banding and looks like a toonshader, without GammaCorrectionShader the scene looks very dark.
Adding AdaptiveToneMapping doesnt do anything. Scene still looks dark.

I should add I was using Varunesc postprocessing npm package before switching back to THREE after an update, should I switch back? Is the THREE effect composer pipeline buggy?

Later in the day I will create a jsfiddle sample

Sorry to necro, finding a load of dead ends on this when using Bloom Pass I also noticed the colour banding then when I tried Gamma Correction Pass on its own I also found colour banding. Seems the stack is bugged out…

1 Like

i would move towards pmndrs/postprocessing by vanruesc. it figures out color management automatically, you don’t need gamma correction.

2 Likes

Thanks, just switched to it and now my colour space looks correct, no more hideous colour banding with Bloom or Gamma Correction pass. The post processing stack in Three.js should be replaced by pmndrs post processing stack imo!

1 Like

The key thing in getting rid of banding (or the easiest way, at least) is to ensure all render targets use >=16 bits of precision. By default they use 8 bits, and it’s more efficient for older devices but also much harder to avoid precision problems.

I’m a big fan of what Raoul is doing with pmndrs/postprocessing, it’s good work. The project is still evolving actively though, with a bunch of breaking changes (compared to the original EffectComposer API) planned for v7 – including requiring WebGL 2. I think it would be best to leave the base three.js post-processing alone until pmndrs/postprocessing v7 has stabilized, and decide next steps from there.

2 Likes

Related:

1 Like

vanruesc is currently adding a new pipeline API for V7, he’s been working on adding MRT for while now.

at some point i think it would make sense for threejs to recognize postprocessing. this project is doing good, this is an actively maintained composer with a clear roadmap and obvious benefits (not just the uber shader, per mesh selection, alpha background, color management, stencil, there are numerous improvements currently). whatever the plan is for 2023, i hope you all can get in touch.

There is no official discussion at Github yet but I personally favor the new node material as the basis for the upcoming post processing. sunag has already done some terrific work with the node material this year and I believe we can expand the new system towards post-processing like the former node material.

The idea is to use the new node syntax to define passes similar to how we define lights now. Meaning without any specific shader language and depedencies to the actual 3D API. The respective WebGL or WebGPU node builder can then construct the final shader based on the material and post processing definitions. Expect to see some examples of this next year.

3 Likes

I’d love to have this discussion (on GitHub or elsewhere), yes.

I suspect that composing shaders with NodeMaterial, and composing passes with the proposed render pipelines in pmndrs/postprocessing, are two orthogonal features and we can do both if we choose.

I do worry that the official EffectComposer does not get the fixes and features it needs lately, and there is no one investing in it enough to make hard decisions about improving its API. Support for HDR, color management, WebXR, etc. are just not moving at all (and that’s no one’s fault, it all takes time). Raoul is putting in work here, and I think it’s worth trying to support that if we can.

1 Like

I’ve planned to improve the color management and fix the banding issue in EffectComposer but first I’d like to finish the color management tasks regarding the core (meaning Roadmap for a color-managed workflow in three.js · Issue #23614 · mrdoob/three.js · GitHub). IMO, that has to be implemented first before working on addon classes.

FWIW I have been making issues and pull requests to pmndrs/postprocessing, and building three-filmic on top of its EffectPass. It has a clear roadmap that I’m excited about.

2 Likes

I personally favor the new node material as the basis for the upcoming post processing. sunag has already done some terrific work with the node material this year and I believe we can expand the new system towards post-processing like the former node material.

I’m looking forward to node materials, but I’m worried about the prospect of post processing getting integrated deeper into three. I think that node materials and post processing are different and complex enough to be separate systems. Is there any thread that explains the reasoning behind mixing them together?

The idea is to use the new node syntax to define passes similar to how we define lights now. Meaning without any specific shader language and depedencies to the actual 3D API. The respective WebGL or WebGPU node builder can then construct the final shader based on the material and post processing definitions. Expect to see some examples of this next year.

I’m excited for the demos and curious to see what the post processing definitions will look like. However, I don’t think node materials can truly be language-agnostic when WebGPU and WebGL support different feature sets.

I don’t want to appear overbearing, but I think that foundational libraries like three should be flexible, open and extensible. Integrating complex systems like post processing into three seems wrong to me because it goes against the principle of separation of concerns. The same applies to WebXR and dynamic shadows. In my opinion, those systems should be modular components with the option to replace them with something else. Not only would this further encourage community efforts to build great plugins for three, but it would allow three to focus on providing a solid API that covers abstractions for all WebGL/WebGPU features. Three could, of course, still provide default solutions for advanced modules, but those should be built on top of it, not into it.

5 Likes

How should I ensure my render targets use >=16 bits of precision? I’ve been looking three.js docs but I can’t figure out how to do that…

Thanks

Since r153, EffectComposer uses half float by default so the banding should not be an issue anymore.

Ah ok, I thought it might be the FloatType and HalfFloatType constants but wasn’t sure. I guess it’s a problem in my shader then :slight_smile: thank you so much for the quick reply!