Updates to Color Management in three.js r152

The PBR material are more accurately close to blender display, and the release r152 three.module.min.js file reduction feels lite and fast.

2 Likes

when pmndrs post-processing was used
renderer.outputEncoding = THREE.LinearEncoding
otherwise
renderer.outputEncoding = THREE.sRGBEncoding

Okay, so assuming you’re renderer has already outputted sRGB then you ideally did this before:

material.color.setHex( 0x112233 ).convertSRGBToLinear();

Meaning 0x112233 was a sRGB color value but you had to convert it manually into the linear-srgb working color space. With enabled color management (the new default), this step isn’t necessary anymore. You can just do the following because the engine takes care of the conversion now:

material.color.setHex( 0x112233 );

When you call getHex() in order to retrieve the hex representation from the color object, you will get 0x112233 (meaning sRGB) again. So there is no need for a manual conversion via convertLinearToSRGB().

Did that answer your question?

4 Likes

sweet!

so if i set the color like this

material1.color.setHex( 0x112233 )
material2.color.set('#112233' )

and if i do

material1.color.getHex()
material2.color.getHexString()

i should get 0x112233 and '112233' right ?

Yes, just give it a try :wink: : three.js dev template - module - JSFiddle - Code Playground

2 Likes

For input of 0x112233 and #112233
it’s printing
1122867
&
"112233"

I’m guessing 1122867 is somehow same as 112233 ?

1122867 is the decimal value of the hexadecimal 0x112233. Just type 0x112233 into the browser console to see the conversion.

3 Likes

On r152.2 Any reason why the autocomplete does not show the new keys ?

i get the warning
THREE.WebGLRenderer: Property .outputEncoding has been removed. Use .outputColorSpace instead.

but

No sorry. But I guess the TS type declaration file isn’t up-to-date.

2 Likes

Type definitions for the TypeScript language are maintained separately. The updates have been merged there as well, and will likely be released soon:

2 Likes

Hello, I have been migrating my codebase to incorporate the change in defaults from getHex and setHex calls and trying to figure out how to handle serialized files between different versions.
So, Material.toJSON, Object3D.toJSON(for scene.background) and probably Node.toJSON, are using getHex to serialize colors, which can then be deserialized with MaterialLoader, ObjectLoader etc.
Right now there is no difference in the JSON generated by old and new versions, so while loading we have no idea what color space the values are in.
To fix this, I have thought of adding a new parameter to the json metadata generated for new files so that it can be determined during load. But this doesn’t seem like a good solution, since we wont be able to use the json from unpatched three.js projects.

It’s not a problem with colors GLTF export and imports as it uses toArray, which uses direct RGB values.

Is there anything in three.js internally to fix this other than disabling ColorManagement? or any distinguishable field I have missed in the source? Or is there plans to change the version number in the metadata in any upcoming version?

Prior to r152, there was no guarantee colors in JSON exports were in a particular color space. The two scenarios described in the OP would lead to either sRGB or Linear-sRGB values, depending on the author of the scene. So I’m not sure if bumping the metadata version is going to give the certainty you’re hoping for. Probably it is worth filing an issue to discuss options here.

It’s not a problem with colors GLTF export and imports as it uses toArray…

glTF has always defined material colors in Linear-sRGB, so this is not an accident.

Got it, thanks. I ended up changing my implementation by always exporting colors in linear space, by disabling ColorManagement during the export and import process.

If you are aware, I would love to know how three.js editor handles this, when creating an object with some color in r151 editor and importing that object in r152, it looks the same, but default output encoding in the renderer have changed to srgb from linear. Also, the value in the editor text box for the color is equal.

ColorManagement has been enabled in the editor since r149 — we are not making special assumptions based on the version of three.js that exported the file (which is not reliably possible).

I ended up changing my implementation by always exporting colors in linear space, by disabling ColorManagement during the export and import process.

I can’t really recommend that, though I see it could work if you’re only importing/exporting files to/from your own application. If you’d be at least as happy with a version bump in the JSON schema, please feel free to propose that!

Okay, thanks for correcting it, just checked, and there is a difference between r148 and r152.
Perhaps bumping the version might be a good idea because of the inconsistency, but not sure from three.js perspective because it depends on outputEncoding. I will create an issue and the maintainers can decide.

My thought process behind that was to remain consistent with gltf export as I am sometimes embedding custom JSON in extras. (because three.js JSON doesn’t have a fixed specification, it’s better to remain consistent with gltf spec).
One reason I see for keeping SRGB in the JSON files is to keep the values in JSON the same as what you see in the UI, in case they need to be edited directly. Any other reasons for recommending against it?

Originally I was thinking about this as I wanted to support the loading of files from three.js editor(and
it’s forks) in my library and editor. But that doesn’t seem like a good idea anymore because of the lack of spec and so the forks can have any changes wrt color management.

I cannot tell you what color you’re putting in the JSON without knowing how your application is set up. If you’re using all the new defaults defined here, then your colors in the JSON will be sRGB. It’s fine to store colors in whatever color space you want, just so long as you know what color space that is. Given a pre-r152 JSON file, it is not possible to know without prior knowledge of the application’s code.

It seems like passing a hex value to the THREE.Color constructor still requires the use of convertSRGBToLinear(). Such as:

const myColor = new THREE.Color(0xFE5E5E).convertSRGBToLinear();

Can you confirm?

When color management is enabled, 0xFE5E5E is supposed to be a sRGB color value and the conversion into the internal linear-srgb working color space happens automatically. So the call of convertSRGBToLinear() isn’t required anymore.

If you have issues with that workflow, it’s best to share a fiddle with your code so we can have a look.

2 Likes

I’m unfamiliar with color management and have some basic questions that can hopefully be answered.

  1. It sounds like non-color maps like an alpha map don’t need a color space specified, but why not? Since alphaMap uses the green channel and an aoMap uses the red channel, for example, doesn’t it need to be converted into linear space, and thus have texture.colorSpace = SRGBColorSpace set, assuming PNG/JPG images are used?

  2. When using an EffectComposer to render to a render target/texture, is the resulting texture linear SRGB space? Is this true regardless of what renderer.outputColorSpace is set to?

  3. I noticed that when using EffectComposer, there was more banding than in version 0.151.0 (with default color space / encoding values). I solved this by using a render target that has a FloatType value for the ‘type’ property. This did not completely eliminate banding, but brought the quality back up to what I saw in 0.151.0. Why is a float render target more important when using linear space, and should it be assumed that render targets should always use FloatType?

  4. How was THREE 0.151.0 and earlier handling color spaces, assuming all default encoding values were used? Was the entire render chain using non-linear SRGB space?

Non-color maps are not sRGB-encoded, even if they’re PNG or JPEG files. By “non-color” we mean their RGB channels are being used to represent data — normal vectors, opacities, and PBR properties — rather than to represent visible color. It’s also worth noting that while PNG and JPEG files do contain color space metadata (usually claiming to be sRGB) that metadata is inaccurate more often than not, for these files.

Also note: Blender has the same concept of non-color textures, and should export correctly.

When using an EffectComposer to render to a render target/texture, is the resulting texture linear SRGB space?

You can choose the color space for render targets when using THREE.UnsignedByteType storage. With float or half-float storage, the render target cannot be sRGB. This choice is mostly unrelated to the renderer’s output color space.

Why is a float render target more important when using linear space, and should it be assumed that render targets should always use FloatType?

UnsignedByteType (8 bits per channel) is generally not enough to represent Linear-sRGB colors accurately. FloatType or HalfFloatType should be fine – if you still see banding with these choices, I suspect something else is wrong. Using UnsignedByteType with the sRGB color space is also an option to reduce banding, but note there is a related Chromium bug.

How was THREE 0.151.0 and earlier handling color spaces, assuming all default encoding values were used? Was the entire render chain using non-linear SRGB space?

Because the default render chain did no conversions, this depends on what you fed into it. But I think in most cases, the entire render chain used sRGB color space.

However, loaders like GLTFLoader and FBXLoader have been marking their colors with the correct conversions since at least 2019, so if you’ve been using these loaders, you would have needed to at least update the renderer’s output space to get correct results.

2 Likes