Updates to Color Management in three.js r152

This is excellent guidance, really helpful.

How exactly? Can we automate this in other areas like map properties to make things easy so people don’t have to know about color spaces unless they have an advanced need?

The main issue with the new r152+ Three.js is that now we have to manually do it for every color texture, which is a surface area for human error: easy to forget, easy to apply the wrong value.

So I’m wondering if GLTF concepts can be applied in general.

I attempted this (Material: Infer texture color space (WIP) by donmccurdy · Pull Request #25857 · mrdoob/three.js · GitHub), but ultimately decided it would cause bigger problems for three.js users. For a framework on top of three.js (R3F, A-Frame, or Lume?) it might be more feasible, but even Blender requires you to tag color and non-color textures.

So for now — yes, textures must be tagged. Mostly it’s about keeping the color and non-color textures straight. This becomes a very important difference for things like post-processing and node-based materials, so I would encourage three.js users to spend time understanding at least that distinction.

2 Likes

Hi i’m new to color management - noticed my tests started failing with an upgrade to 152 and it is because of the color management.

I have a custom color object which creates a three.js color object eventually

I am picking colors in a straight forward RGB fashion with values between 0…255 for each tuple

In my test I do the following : I assign 10 to my custom color.r value, g and b are both 0

Some time later, the custom color object spits out a hex string based on its values: (0x0a0000), creates a THREE.Color object and calls setHex(‘0x0a0000’)

Before 152 - this would result in the three.js color.r having the value : 0.0392156862745098

With 152 - the result is now (color.r) : 0.0030352698352941175

I fixed this of course now after reading this article, by saying

color.setHex(‘0x0a0000’, THREE.LinearSRGBColorSpace)

Here is my question :slight_smile:

the default of color.setHex is setHex(string, colorspace = SRGBColorSpace) - it does not feel very intuitive to say color.setHex(‘0x0a0000’, THREE.LinearSRGBColorSpace) because the value I am passing is a hex string and in my mind a normal SRGB space hex string.

What am I missing?

does the setHex function read:

a) ‘setHex’ and convert the number to THREE.LinearSRGBColorSpace

or

b) ‘setHex’ and the number I am giving you is of type THREE.LinearSRGBColorSpace

and how should getHex() be interpreted (when read by a human being) ?

Ok no need for an answer - the answer is a) right?

I think I get it now - had to ask the question to know what to look for as an answer but at the top I was confused because I was looking under the ‘im still using renderer.outputEncoding = LinearEncoding’ section, because in my mind the cure would be in this section, rather than the ‘im already using rendered.outputEncoding = sRGBEncoding’ section

If you’re still using renderer.outputEncoding = LinearEncoding for output to the display (and not using post-processing + THREE.OutputPass for color correction later) then your application’s color management is probably “incorrect”. I don’t meant to say you can’t do that, if you’re getting the visual results you want, but it will be very hard to make sense of everything in that workflow, because that color pipeline isn’t self-consistent.

To your question though – the meaning of color.setHex( hex, colorSpace ) is essentially:

“take the color hex, which is in some color space colorSpace, and configure this THREE.Color instance to represent that same color in the three.js working color space.”

If the declared color space of hex is the same as the three.js working color space (Linear-sRGB) then no conversion is needed or applied. If the declared color space is different, and THREE.ColorManagement is enabled, then three.js will convert.

If you’re not ready to have the renderer output to sRGB yet (which is the default now), it may be easiest for you to disable THREE.ColorManagement for the time being, as in the “opt out” section of the original post.

1 Like

Hey thank you very much for the answer. Yes I would like to switch and I would like to use post-processing and the THREE Output pass and happy with the new defaults.

It is like you said, two wrongs don’t make a right - in this case, my tests are now updated to match the current defaults, instead of ‘dumbing down’ my code. I removed the second argument to setHex and I’m now using the updated value of 0.0030352698352941175 as correct.

1 Like

I dont really get why you would handle Hex Colors differently than RGB colors? It makes sense to me that the workflow is Linear now but why would the same color be interpreted differently based on the “Language” its written in?

This is just a convention, hex triplets are usually sRGB. You’ll see the same thing in Blender – its hex input uses sRGB, everything else is linear.

I’m not really sure why that came to be. I could guess — 8-bit red/green/blue components are perhaps not enough precision with Linear-sRGB? — but it’s just a guess, and might be more historical reasons than technical ones.

2 Likes

Ah okay, i never actually noticed that. Really Odd… Thanks though, for the answer!

I figured ill reply here. Im guessing my issue is related to these changes. It seems like render targets have inconsistent results in 152.

Is the opt out strategy guaranteed to reproduce the pre-152 behavior, because that’s not what I’m seeing. After updating from 146 to 172 and opting out, overlaid sprite looks the same, but a 3D model of the earth that uses shader materials now looks very dark. Are there additional opt out requirements for code that uses shaders?


Hard to say how unknown shaders might interact with lighting and color management, a thread with a reproduction might be helpful. I would suggest upgrading in steps of <=10 versions, you’ll miss deprecation warnings otherwise. The lighting changes in r155 might be related to what you’re seeing:

I decided to test 151 versus 152. I did get a different behavior change from what I reported above - kind of like the black level is too white.
Here’s a jsfiddle: 151To152Migration - JSFiddle - Code Playground
If you revert it to 151, it will work properly.
I’ve observed:

  1. The problem doesn’t occur with ambient light.
  2. The problem doesn’t occur with MeshPhongMaterial

151


152

The fiddles must be correctly configured to make a comparison:

r151: 151To152Migration - JSFiddle - Code Playground
r152: 151To152Migration - JSFiddle - Code Playground

Keep in mind that starting from r152 the renderer outputs sRGB by default which was not true for prior versions.

2 Likes