Updates to Color Management in three.js r152

Thanks for the quick response to my previous questions! That is very helpful.

I am currently struggling with a render target/EffectComposer issue. I noticed that when rendering to a texture, there are subtle color differences to the regular scene. This is consequential when designing something like a mirror. I made a POC but I am struggling to get modules in the ‘examples’ folder, such as EffectComposer, to work correctly in JSFiddle. If anyone wants to give me guidance, I will gladly make a JSFiddle example.

The POC is very simple. The left plane is the regular scene. The dark portion is not part of the image - it is a black plane with 0.5 opacity. I am using an EffectComposer and RenderPass to render the left plane to a texture and display it on the right plane. Notice that the color differences are most noticeable in the dark portion, where the rendered texture is slightly brighter.

A few more notes:

  • I am using the default render target, but have experimented with custom render target parameters to no avail. I see no difference in the output.
  • I am using all default values. The only time I specify a color space is when loading the jpg texture.
  • I confirmed that this issue doesn’t happen in 0.151.0

Here is the most relevant code snippet:

    effectComposer = new EffectComposer(renderer);
    effectComposer.addPass(new RenderPass(scene, rtCamera));
    effectComposer.renderToScreen = false;

    const panelGeometry = new PlaneGeometry(0.7, 1);
    const planeGeometry = new PlaneGeometry(1.6, 1);
    const planeMaterial = new MeshBasicMaterial({color: 0xffffff, side: DoubleSide});
    const rtPlaneMaterial = new MeshBasicMaterial({color: 0xffffff, side: DoubleSide});
    const panelMaterial = new MeshBasicMaterial({color: 0x000000, side: DoubleSide, transparent: true, opacity: 0.5});
    plane = new Mesh(planeGeometry, planeMaterial);
    rtPlane = new Mesh(planeGeometry, rtPlaneMaterial);
    panel = new Mesh(panelGeometry, panelMaterial);
    
    scene.add(plane, rtPlane, panel);

    // The right plane simply displays the output of the EffectComposer, which is rendering the left plane.
    rtPlaneMaterial.map = effectComposer.readBuffer.texture;

    new TextureLoader().load("https://www.psdstack.com/wp-content/uploads/2019/08/copyright-free-images-750x420.jpg", texture => {
      texture.colorSpace = SRGBColorSpace;
      planeMaterial.map = texture;
    });

It might be best to start a new thread for debugging this issue. I will just note that when using three.js’s post-processing, you should generally set renderer.outputColorSpace = THREE.LinearSRGBColorSpace, and add a GammaCorrectionShader as the last pass when rendering to the screen. Configuration of the renderer and the render targets are very important.

2 Likes

Thanks Don! I created a new topic here: Color Management - Slight color differences when using EffectComposer r152. I was able to get my example working in JSFiddle as well.

1 Like

Something extremely weird is happening with my code.
I upgraded to v152.2, however, when I try to set outputColorSpace for the renderer as follows:

renderer.outputColorSpace = SRGBColorSpace;

it throws an error:

Property 'outputColorSpace' does not exist on type 'WebGLRenderer'

Am I doing something wrong?

Assigning a property should not throw a runtime error, even if the property does not exist. Perhaps you’re using TypeScript with an outdated copy of @types/three, or some other type-checking system?

2 Likes

You are right! I forgot to upgrade @types/three. Thanks a lot!

1 Like

I’ve upgraded to 152, but why is the color getting darker relative to 151? And how to solve this problem?

You have to provide more information so we can investigate your use case. Consider to demonstrate the issue with a small live example: three.js dev template - module - JSFiddle - Code Playground

Not getting dark is not normal

Threejs release 152 renders accurate as blender, the colors are vibrant and accurate as the texture RGBs.

There are no lags even with SSAO sample are set to 64

Live

House Interior

why does the couch have shadow, but the person does not?

2 Likes

thats because the environment is in an interior settings, you can check the apartment and edge apartment render quality and performance.

This thread might be relevant to this I discovered. Wit WebGPU anything other than linear colour space displays colors over exposed. I presume its this gamma correction you speak of. I require to do this on the renderer to display correctly.

renderer.outputColorSpace = LinearSRGBColorSpace
renderer.colorSpace = LinearSRGBColorSpace

@danrossi In most cases, those renderer settings would not be what you want. WebGPURenderer should use the same settings as described for WebGLRenderer in the original post. Compare the official examples for loading a glTF model in WebGL (left) vs. WebGPU (right) —

If your own scene appears “over exposed” it’s likely that some part of the scene’s content is using the wrong color space, and that output to Linear-sRGB is just masking the problem with a second error, as described under The Importance of Being Linear: Two Wrongs Don’t Make a Right. Or perhaps there is a bug that needs to be resolved, that could be investigated separately. In any case – users of WebGPU should use the same guidance given in the original post above.

EDIT: Let’s continue the WebGPU discussion in three.js#26233.

2 Likes

I’ve noticed if I set the linear color space on WebGLRenderer it darkens. Its just a video texture on the scene. So hopefully the video texture can be corrected. It happens to images loaded via TextureLoader also.

@danrossi Assigning Linear-sRGB output color space to WebGLRenderer is the opposite of what this thread is saying you should do – the new default is sRGB, and that’s what we recommend. Loading images works properly in three.js r152, the only bug I’m aware of is an issue with video texture loading in WebGPU only. If I’ve misunderstood your question, I would recommend starting a new thread with complete details about how your scene is set up, if you are not getting the results you expect.

This is exciting, and I understand the importance of it for the quality of rendering in Three.js. :tada:

I have some Q’s and thoughts.


It seems we must not forget to manually set texture.colorSpace = SRGBColorSpace every time for all color maps. From what I can tell, these are the maps that need the treatment:

  • .map
  • .emissiveMap
  • .envMap
  • .specularMap
  • .specularColorMap
  • .sheenColorMap

Did I miss any? Did I include any that shouldn’t be?

And what’s the list of map properties that we must remember to set texture.colorSpace = LinearSRGBColorSpace on?

I wouldn’t memorize a rule for each material texture slot – there is no such restriction in three.js. I’d think of it this way:

  1. If the texture is meant to be used as data, not viewed as a color — like .roughnessMap or .normalMap — then you need NoColorSpace to prevent any color-related conversions.
  2. If the texture contains color, and its range is limited [0,1] — most .png or .jpg files — then the texture is very likely to be in SRGBColorSpace.
  3. If the texture contains color, and its range is outside [0,1] — most .hdr or .exr files — then your texture is very likely to be in LinearSRGBColorSpace.

I say “likely” here because it’s certainly possible to put linear data into .png or .jpg files, or sRGB data into .exr files. But most of the time you’d have to go out of your way to create textures that don’t follow these rules.

For a specific example, .emissiveMap could easily be a Linear-sRGB .exr file, or an sRGB .png file. Either would be fine — .png files generally compress better, but .exr files could be used for a much wider range of emissive values.

7 Likes

@donmccurdy Thanks! While I understand what you’re saying, I think there may be people who probably have no idea (and would like to not learn) what color space each thing is, and would benefit from a generic safe-bet list of which color space value to apply to each particular map with most likelihood of success.

A better question may be: what is the most likely colorSpace value for each particular map property, in practice? (I have never seen or used exr before, so I’m tending to think png and jpg are most likely).

1 Like

I’m not keen to make a list of every material texture slot here, but maybe that would be a good addition to the material API documentation, at least categorizing each as “color” on “non-color”. For most people, LinearSRGBColorSpace will only ever be used for the .envMap, with .exr or .hdr. Everything else would likely be SRGBColorSpace if it’s color, NoColorSpace if it’s data.

Model loaders like GLTFLoader will handle that automatically.

2 Likes