The upcoming release r152 will contain changes affecting color and lighting. These updates enable a “linear workflow” by default, for better image quality in both physically-based and non-physically-based scenes. By “linear workflow”, we mean that any sRGB input colors (as found in most textures, color pickers, CSS, and HTML) are converted from sRGB to a linear working color space for rendering, and the rendered image is converted back to sRGB for display.
What does that mean for me?
These changes provide a better lighting workflow. However, it’s essential that inputs (like textures) are assigned the correct color space.
Changes to property names:
THREE.WebGLRenderer
property.outputEncoding
renamed to.outputColorSpace
THREE.Texture
property.encoding
renamed to.colorSpace
sRGBEncoding
renamed toSRGBColorSpace
LinearEncoding
renamed toLinearSRGBColorSpace
Changes to defaults:
THREE.WebGLRenderer
property.outputColorSpace
now defaults to sRGB (THREE.SRGBColorSpace)THREE.ColorManagement.enabled
now defaults to true
Read on to learn what to expect from these changes.
Migrating
Updating to three.js r152 can go two ways, depending on whether you’re already using renderer.outputEncoding = sRGBEncoding
or not.
I'm still using renderer.outputEncoding = LinearEncoding
(previous default)
This migration has few required steps, but there will be some differences in scene lighting as you’re moving to a linear workflow. Typically shading will appear softer, with smoother transitions between light and dark.
Recommended steps:
- Ensure that any material textures containing color data (like
material.map
) are assigned a color space. Typically that will betexture.colorSpace = THREE.SRGBColorSpace
. This is not set by default, as it does not apply to textures used for normal maps, roughness maps, or other non-color data.- HDR color textures (.exr, .hdr) should typically use
THREE.LinearSRGBColorSpace
instead, and will already have this set by default.- Ensure that any THREE.ShaderMaterial instances include output color space encoding and tone-mapping, after setting
gl_FragColor
.#include <tonemapping_fragment> #include <encodings_fragment>
- If using three.js-provided post-processing (not pmndrs/postprocessing),
setuserenderer.outputColorSpace = THREE.LinearSRGBColorSpace
and enable a GammaCorrectionShader passOutputPass
.Be aware that your input colors (like
0xFF0000
) are likely already sRGB, and there’s no need to convert them yourself. three.js now recognizes CSS and hex colors as sRGB. If you’re working with other color syntax, without conversions, you may now need to tell three.js when the values are sRGB.material.color.setRGB( r, g, b, THREE.SRGBColorSpace ); material.color.setHSL( h, s, l, THREE.SRGBColorSpace );
These sRGB inputs are converted automatically to the working color space, Linear-sRGB, required for lighting and other operations. Components of THREE.Color objects and vertex colors are always in Linear-sRGB.
The linear workflow corrects bugs in previous workflows, and generally gives better results faster in new projects. Lighting tends to be softer and less harsh. However, if you’ve already fine-tuned lighting exactly to your preference in an existing scene with the old workflow, you may need to adjust lighting or tone mapping to get more contrast now. Increasing strength of directional lighting or adding tone-mapping may help.
I'm already using renderer.outputEncoding = sRGBEncoding
This migration is lossless. The appearance of your scene should not change; you are already using a linear workflow.
- Update your code to use
renderer.outputColorSpace
andtexture.colorSpace
instead of the older encoding-based names. Use the new values,SRGBColorSpace
andLinearSRGBColorSpace
. Non-color textures (normal maps, etc.) should useNoColorSpace
, the default.- Be aware that three.js now interprets CSS and hexadecimal as sRGB colors by default (as in CSS and HTML), and automatically converts them to Linear-sRGB:
// before material.color.setHex( 0x112233 ).convertSRGBToLinear(); // after material.color.setHex( 0x112233 );
If you weren’t already using
convertSRGBToLinear()
, then you should assume that your input colors were Linear-sRGB. Either tell three.js the value is already Linear-sRGB, or switch to sRGB color values if you want consistency with CSS and HTML.// before material.color.setHex( 0x808080 ); // after (option 1) material.color.setHex( 0x808080, THREE.LinearSRGBColorSpace ); // after (option 2) material.color.setHex( 0xbbbbbb );
Finally, remember that CSS and hexadecimal colors are being converted from sRGB to Linear, but other properties and methods of the THREE.Color class are still Linear. As a result,
0x800000
is not the same thing ascolor.r = 0.5
. Most setters and getters in the THREE.Color API accept a color space parameter to specify:color.setRGB( 0.5, 0.5, 0.5 ); console.log( color ); // → r = .5, g = .5, b = .5 (linear) color.setRGB( 0.5, 0.5, 0.5, THREE.SRGBColorSpace ); // (srgb → linear) console.log( color ); // → r = .22, g = .22, b = .22 color.getHex(); // → 0x808080 (linear → srgb) color.getHex( THREE.LinearSRGBColorSpace ); // → 0x373737 (linear)
Vertex colors and the RGB components of THREE.Color instances are expected to be Linear-sRGB.
Can I opt out?
Yes. Although we recommend using the default workflow in new projects, you can still opt out of these defaults:
import * as THREE from 'three'; THREE.ColorManagement.enabled = false; renderer.outputColorSpace = THREE.LinearSRGBColorSpace;
Note that
THREE.ColorManagement
must be disabled before you initializeTHREE.Color
instances, or CSS and hexadecimal values assigned to colors will be converted to Linear-sRGB.
NOTE: If you’re using three.js with React Three Fiber, A-Frame, or Threlte, then you’re probably already using these defaults — property names have changed but little else.
Motivation
We’re confident these changes move the three.js project in the right direction. As WebGL and WebGPU APIs begin adding support for wide-gamut and high dynamic range (HDR) color, having a color workflow that properly separates linear and non-linear colors is critical. While our legacy workflow was similar to what was once common in older games and even 3D authoring tools, the “plasticky CGI” look was always a problem.
The advantages of a linear workflow are well-documented and uncontroversial at this point — see Two Wrongs Don’t Make a Right for a clear illustration of the issues with our previous workflow.
With all of that said, we understand this is a large change. We intend to keep color APIs easy to use, but cannot (and should not) completely hide the distinction between the sRGB and Linear-sRGB color spaces. Any questions are welcome, and we hope to improve documentation further over time.
Questions
For general questions, please reply to this thread. If you would like help updating some existing code, consider starting a new thread with the full context, then including a link in this thread. Refer to the color management guide for deeper technical background.