Color Management

Color Management and Output Color Space

Lighting math must run on linear values (sRGB), while CSS colors and image files are sRGB. Since r152 Three.js 30,815 converts for you: THREE.ColorManagement is enabled, the working space is linear sRGB, and renderer.outputColorSpace encodes back to sRGB at the end. A palette color goes in and comes back unchanged:

How THREE.Color stores a BookNest palette color, run in NodeJavaScript
import * as THREE from 'three';
const blue = new THREE.Color('#1f5f8b');               // CSS colors are sRGB
const rgb = (color) => color.toArray().map((c) => +c.toFixed(3));
console.log(THREE.ColorManagement.workingColorSpace, rgb(blue), blue.getHexString());
THREE.ColorManagement.enabled = false;                  // the pre-r152 behavior
console.log('unmanaged', rgb(new THREE.Color('#1f5f8b')));
console.log(new THREE.Texture().colorSpace === THREE.NoColorSpace);
Output
srgb-linear [ 0.014, 0.114, 0.258 ] 1f5f8b
unmanaged [ 0.122, 0.373, 0.545 ]
true

The last line is the trap: a new texture has NoColorSpace, so set texture.colorSpace = THREE.SRGBColorSpace on color maps you load yourself, and leave data maps (normal, roughness) alone; GLTFLoader does both (Texture Types and Color Space). Mistakes give washed-out or dark colors, never an error.

Color management: a CSS swatch against managed and unmanaged renders, and a texture's colorSpaceHTMLLive
<!doctype html>
<style>
  body { margin: 0; padding: 8px; background: #f5f1e8; font: 12px system-ui, sans-serif; color: #263238; }
  .row { display: flex; gap: 8px; flex-wrap: wrap; }
  figure { margin: 0; width: 140px; text-align: center; }
  .swatch, canvas { width: 140px; height: 110px; display: block; }
</style>
<script type="importmap">
{ "imports": {
  "three": "https://cdn.jsdelivr.net/npm/three@0.186.1/build/three.module.js",
  "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.186.1/examples/jsm/" } }
</script>
<div class="row" id="row">
  <figure><div class="swatch" style="background:#1f5f8b"></div>CSS #1f5f8b</figure>
</div>
<p id="out"></p>
<script type="module">
import * as THREE from 'three';

const row = document.getElementById('row');
const rgb = (color) => color.toArray().map((c) => +c.toFixed(3)).join(', ');

// Render one unlit plane per panel; material and texture set by the caller
function panel(caption, material) {
  const renderer = new THREE.WebGLRenderer();
  renderer.setSize(140, 110);
  const figure = row.appendChild(document.createElement('figure'));
  figure.append(renderer.domElement, caption);
  const scene = new THREE.Scene();
  const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 2);
  camera.position.z = 1;
  scene.add(new THREE.Mesh(new THREE.PlaneGeometry(2, 2), material));
  renderer.render(scene, camera);
}

// 1. Managed (the default since r152): the CSS color comes back out unchanged
const managed = new THREE.Color('#1f5f8b');
panel('managed Color', new THREE.MeshBasicMaterial({ color: managed }));

// 2. Unmanaged: the sRGB numbers are treated as linear, then encoded again: too light
THREE.ColorManagement.enabled = false;
const unmanaged = new THREE.Color('#1f5f8b');
THREE.ColorManagement.enabled = true;
panel('ColorManagement off', new THREE.MeshBasicMaterial({ color: unmanaged }));

// 3 and 4. A canvas texture painted with the same color, with and without SRGBColorSpace
function paintedTexture(colorSpace) {
  const canvas = Object.assign(document.createElement('canvas'), { width: 64, height: 64 });
  const g = canvas.getContext('2d');
  g.fillStyle = '#1f5f8b';  g.fillRect(0, 0, 64, 64);
  g.fillStyle = '#e09a10';  g.fillRect(0, 40, 64, 24);
  const texture = new THREE.CanvasTexture(canvas);
  texture.colorSpace = colorSpace;
  return texture;
}
panel('texture: NoColorSpace', new THREE.MeshBasicMaterial({ map: paintedTexture(THREE.NoColorSpace) }));
panel('texture: SRGBColorSpace', new THREE.MeshBasicMaterial({ map: paintedTexture(THREE.SRGBColorSpace) }));

document.getElementById('out').textContent =
  `working space ${THREE.ColorManagement.workingColorSpace}: managed linear [${rgb(managed)}], ` +
  `unmanaged [${rgb(unmanaged)}]. A new Texture has NoColorSpace: ` +
  `${new THREE.Texture().colorSpace === THREE.NoColorSpace}, so color maps look washed out until you set SRGBColorSpace.`;
</script>