Three.js Color Space and Gamma Correction Guide
This article provides a clear overview of gamma correction and explains how modern Three.js handles output color spaces. You will learn the fundamental physics behind linear and non-linear color perception, why gamma correction is necessary for realistic 3D rendering, and how to correctly configure color spaces for renderers and textures in recent versions of Three.js.
Understanding Gamma Correction
Computer monitors display colors non-linearly, meaning doubling the input voltage does not double the physical brightness of the screen. At the same time, human vision is highly sensitive to changes in dark tones but less sensitive to changes in bright tones. To account for this, digital images are encoded in a non-linear format (usually sRGB) to allocate more data to the dark tones humans can actually perceive.
However, 3D engines must perform lighting calculations (shading, reflections, and blending) in a mathematically accurate “linear” color space where light behaves naturally (double the light energy equals double the physical brightness).
Gamma correction is the process of translating between these spaces: 1. Decoding: Non-linear input textures (like sRGB color maps) are converted to linear space so the renderer can perform accurate lighting calculations. 2. Encoding (Gamma Correction): The final linear render is converted back to sRGB space before being sent to the monitor, ensuring the output looks correct to the viewer. Without this final correction, renders often appear washed out, overly dark, or incorrectly saturated.
Color Space Management in Modern Three.js
In older versions of Three.js, developers managed encoding using
renderer.outputEncoding. In modern Three.js (version r152
and later), the API has been streamlined to use standardized color space
terminology.
1. The Renderer’s Output Color Space
By default, modern Three.js configures the WebGL renderer to output
to the sRGB color space. This means gamma correction is automatically
applied to the final image. The renderer property used to control this
is outputColorSpace:
const renderer = new THREE.WebGLRenderer();
// This is set to THREE.SRGBColorSpace by default in modern versions
renderer.outputColorSpace = THREE.SRGBColorSpace; If you are rendering to a post-processing pipeline that expects
linear input, you may temporarily set this to
THREE.LinearSRGBColorSpace, but the final output shown on
screen should almost always be THREE.SRGBColorSpace.
2. Texture Color Spaces
To ensure the renderer does calculations in linear space, you must tell Three.js how to interpret incoming textures.
Color/Diffuse Textures: Textures that contain visible colors (like
.mapor.emissiveMap) are created in sRGB space. You must explicitly set their color space so Three.js can decode them to linear space for rendering:const textureLoader = new THREE.TextureLoader(); const colorTexture = textureLoader.load('color_map.png'); colorTexture.colorSpace = THREE.SRGBColorSpace;Data Textures: Textures that store numerical data rather than visual colors (such as
.normalMap,.roughnessMap,.metalnessMap, or.aoMap) must not be gamma-corrected. They should remain in linear space:const normalTexture = textureLoader.load('normal_map.png'); normalTexture.colorSpace = THREE.NoColorSpace; // default value
By properly defining the input color spaces of your textures and letting the renderer handle the output color space, modern Three.js guarantees physically accurate lighting, consistent colors, and visually correct 3D scenes.