Gestion des couleurs

Qu'est-ce qu'un espace couleur ?

Chaque espace couleur est une collection de plusieurs décisions de conception, choisies ensemble pour prendre en charge une vaste gamme de couleurs tout en satisfaisant aux contraintes techniques liées à la précision et aux technologies d'affichage. Lors de la création d'un actif 3D, ou de l'assemblage d'actifs 3D dans une scène, il est important de connaître ces propriétés et la façon dont les propriétés d'un espace couleur sont liées aux autres espaces couleur de la scène.

Couleurs sRGB et point blanc (D65) affichés dans le diagramme de chromaticité de référence CIE 1931. La région colorée représente une projection 2D du gamut sRGB, qui est un volume 3D. Source : Wikipedia
  • Primaires de couleur : Les couleurs primaires (par exemple rouge, vert, bleu) ne sont pas absolues ; elles sont sélectionnées à partir du spectre visible en fonction des contraintes de précision limitée et des capacités des périphériques d'affichage disponibles. Les couleurs sont exprimées comme un ratio des couleurs primaires.
  • Point blanc : La plupart des espaces couleur sont conçus de telle sorte qu'une somme pondérée égale des primaires R = G = B apparaisse sans couleur, ou "achromatique". L'apparence des valeurs achromatiques (comme le blanc ou le gris) dépend de la perception humaine, qui à son tour dépend fortement du contexte de l'observateur. Un espace couleur spécifie son "point blanc" pour équilibrer ces besoins. Le point blanc défini par l'espace couleur sRGB est [link:https://en.wikipedia.org/wiki/Illuminant_D65 D65].
  • Fonctions de transfert : Après avoir choisi le gamut et un modèle de couleur, nous devons encore définir des correspondances ("fonctions de transfert") des valeurs numériques vers/depuis l'espace couleur. Est-ce que r = 0.5 représente 50 % moins d'éclairage physique que r = 1.0 ? Ou 50 % moins lumineux, tel que perçu par un œil humain moyen ? Ce sont des choses différentes, et cette différence peut être représentée par une fonction mathématique. Les fonctions de transfert peuvent être linéaires ou non linéaires, en fonction des objectifs de l'espace couleur. sRGB définit des fonctions de transfert non linéaires. Ces fonctions sont parfois approximées par des fonctions gamma, mais le terme "gamma" est ambigu et doit être évité dans ce contexte.
Ces trois paramètres — primaires de couleur, point blanc et fonctions de transfert — définissent un espace couleur, chacun choisi pour des objectifs particuliers. Ayant défini les paramètres, quelques termes supplémentaires sont utiles :
  • Modèle de couleur : Syntaxe pour identifier numériquement les couleurs dans le gamut choisi — un système de coordonnées pour les couleurs. Dans three.js, nous sommes principalement concernés par le modèle de couleur RGB, ayant trois coordonnées r, g, b ∈ [0,1] ("domaine fermé") ou r, g, b ∈ [0,∞] ("domaine ouvert") représentant chacune une fraction d'une couleur primaire. D'autres modèles de couleur (HSL, Lab, LCH) sont couramment utilisés pour le contrôle artistique.
  • Gamut de couleur : Une fois que les primaires de couleur et un point blanc ont été choisis, ceux-ci représentent un volume dans le spectre visible (un "gamut"). Les couleurs ne se trouvant pas dans ce volume ("hors gamut") ne peuvent pas être exprimées par des valeurs RGB [0,1] en domaine fermé. Dans le domaine ouvert [0,∞], le gamut est techniquement infini.

Considérez deux espaces couleur très courants : `SRGBColorSpace` ("sRGB") et `LinearSRGBColorSpace` ("Linear-sRGB"). Les deux utilisent les mêmes primaires et le même point blanc, et ont donc le même gamut de couleur. Les deux utilisent le modèle de couleur RGB. Ils ne diffèrent que par les fonctions de transfert — Linear-sRGB est linéaire par rapport à l'intensité lumineuse physique. sRGB utilise les fonctions de transfert sRGB non linéaires, et ressemble plus étroitement à la façon dont l'œil humain perçoit la lumière et à la réactivité des périphériques d'affichage courants.

Cette différence est importante. Les calculs d'éclairage et autres opérations de rendu doivent généralement avoir lieu dans un espace couleur linéaire. Cependant, les couleurs linéaires sont moins efficaces pour être stockées dans une image ou un framebuffer, et ne paraissent pas correctes lorsqu'elles sont visualisées par un observateur humain. En conséquence, les textures d'entrée et l'image finale rendue utiliseront généralement l'espace couleur sRGB non linéaire.

ℹ️ AVIS : Bien que certains écrans modernes prennent en charge des gamuts plus larges comme Display-P3, les API graphiques de la plateforme web reposent largement sur sRGB. Les applications utilisant three.js aujourd'hui utiliseront généralement uniquement les espaces couleur sRGB et Linear-sRGB.

Rôles des espaces couleur

Les flux de travail linéaires — requis pour les méthodes de rendu modernes — impliquent généralement plus d'un espace couleur, chacun assigné à un rôle particulier. Les espaces couleur linéaires et non linéaires sont appropriés pour différents rôles, expliqués ci-dessous.

Espace couleur d'entrée

Les couleurs fournies à three.js — à partir de sélecteurs de couleur, de textures, de modèles 3D et d'autres sources — ont chacune un espace couleur associé. Celles qui ne sont pas déjà dans l'espace couleur de travail Linear-sRGB doivent être converties, et les textures doivent recevoir l'affectation correcte texture.colorSpace. Certaines conversions (pour les couleurs hexadécimales et CSS en sRGB) peuvent être effectuées automatiquement si l'API THREE.ColorManagement est activée avant l'initialisation des couleurs :

THREE.ColorManagement.enabled = true;

THREE.ColorManagement est activé par défaut.

  • Matériaux, lumières et shaders : Les couleurs dans les matériaux, les lumières et les shaders stockent les composants RGB dans l'espace couleur de travail Linear-sRGB.
  • Couleurs de sommet : `BufferAttribute` stockent les composants RGB dans l'espace couleur de travail Linear-sRGB.
  • Textures de couleur : Les `Texture` PNG ou JPEG contenant des informations de couleur (comme .map ou .emissiveMap) utilisent l'espace couleur sRGB en domaine fermé, et doivent être annotées avec texture.colorSpace = SRGBColorSpace. Des formats comme OpenEXR (parfois utilisés pour .envMap ou .lightMap) utilisent l'espace couleur Linear-sRGB indiqué par texture.colorSpace = LinearSRGBColorSpace, et peuvent contenir des valeurs dans le domaine ouvert [0,∞].
  • Textures non couleur : Les textures qui ne stockent pas d'informations de couleur (comme .normalMap ou .roughnessMap) n'ont pas d'espace couleur associé, et utilisent généralement l'annotation de texture (par défaut) de texture.colorSpace = NoColorSpace. Dans de rares cas, les données non couleur peuvent être représentées avec d'autres encodages non linéaires pour des raisons techniques.

⚠️ AVERTISSEMENT : De nombreux formats de modèles 3D ne définissent pas correctement ou de manière cohérente les informations d'espace couleur. Bien que three.js tente de gérer la plupart des cas, les problèmes sont fréquents avec les anciens formats de fichiers. Pour de meilleurs résultats, utilisez glTF 2.0 (`GLTFLoader`) et testez les modèles 3D dans des visualiseurs en ligne tôt pour confirmer que l'actif lui-même est correct.

Espace couleur de travail

Le rendu, l'interpolation et de nombreuses autres opérations doivent être effectuées dans un espace couleur de travail linéaire en domaine ouvert, dans lequel les composants RGB sont proportionnels à l'illumination physique. Dans three.js, l'espace couleur de travail est Linear-sRGB.

Espace couleur de sortie

La sortie vers un périphérique d'affichage, une image ou une vidéo peut impliquer une conversion de l'espace couleur de travail Linear-sRGB en domaine ouvert vers un autre espace couleur. La conversion est définie par (`WebGLRenderer.outputColorSpace`). Lors de l'utilisation du post-traitement, cela nécessite OutputPass.

  • Affichage : Les couleurs écrites sur un canevas WebGL pour l'affichage doivent être dans l'espace couleur sRGB.
  • Image : Les couleurs écrites dans une image doivent utiliser l'espace couleur approprié pour le format et l'utilisation. Les images entièrement rendues écrites dans des textures PNG ou JPEG utilisent généralement l'espace couleur sRGB. Les images contenant de l'émission, des lightmaps ou d'autres données non confinées à la plage [0,1] utiliseront généralement l'espace couleur Linear-sRGB en domaine ouvert, et un format d'image compatible comme OpenEXR.

⚠️ AVERTISSEMENT : Les cibles de rendu peuvent utiliser soit sRGB soit Linear-sRGB. sRGB utilise mieux la précision limitée. Dans le domaine fermé, 8 bits suffisent souvent pour sRGB tandis que ≥12 bits (half float) peuvent être nécessaires pour Linear-sRGB. Si les étapes ultérieures du pipeline nécessitent une entrée Linear-sRGB, les conversions supplémentaires peuvent entraîner un léger coût de performance.

Les matériaux personnalisés basés sur `ShaderMaterial` et `RawShaderMaterial` doivent implémenter leur propre conversion d'espace couleur de sortie. Pour les instances de `ShaderMaterial`, ajouter le chunk de shader `colorspace_fragment` à la fonction `main()` du shader de fragment devrait être suffisant.

Utilisation des instances THREE.Color

Les méthodes lisant ou modifiant des instances `Color` supposent que les données sont déjà dans l'espace couleur de travail de three.js, Linear-sRGB. Les composants RGB et HSL sont des représentations directes des données stockées par l'instance Color, et ne sont jamais convertis implicitement. Les données de couleur peuvent être explicitement converties avec .convertLinearToSRGB() ou .convertSRGBToLinear().

// RGB components (no change).
color.r = color.g = color.b = 0.5;
console.log( color.r ); // → 0.5

// Manual conversion.
color.r = 0.5;
color.convertSRGBToLinear();
console.log( color.r ); // → 0.214041140

Avec ColorManagement.enabled = true (recommandé), certaines conversions sont effectuées automatiquement. Comme les couleurs hexadécimales et CSS sont généralement sRGB, les méthodes `Color` convertiront automatiquement ces entrées de sRGB vers Linear-sRGB dans les setters, ou convertiront de Linear-sRGB vers sRGB lors du retour d'une sortie hexadécimale ou CSS à partir des getters.

// Hexadecimal conversion.
color.setHex( 0x808080 );
console.log( color.r ); // → 0.214041140
console.log( color.getHex() ); // → 0x808080

// CSS conversion.
color.setStyle( 'rgb( 0.5, 0.5, 0.5 )' );
console.log( color.r ); // → 0.214041140

// Override conversion with 'colorSpace' argument.
color.setHex( 0x808080, LinearSRGBColorSpace );
console.log( color.r ); // → 0.5
console.log( color.getHex( LinearSRGBColorSpace ) ); // → 0x808080
console.log( color.getHex( SRGBColorSpace ) ); // → 0xBCBCBC

Erreurs courantes

Lorsqu'une couleur ou une texture individuelle est mal configurée, elle apparaîtra plus foncée ou plus claire que prévu. Lorsque l'espace couleur de sortie du renderer est mal configuré, toute la scène peut apparaître plus foncée (par exemple, conversion manquante vers sRGB) ou plus claire (par exemple, une double conversion vers sRGB avec post-traitement). Dans chaque cas, le problème peut ne pas être uniforme, et simplement augmenter/diminuer l'éclairage ne le résout pas.

Un problème plus subtil apparaît lorsque à la fois les espaces couleur d'entrée et les espaces couleur de sortie sont incorrects — les niveaux de luminosité globaux peuvent être corrects, mais les couleurs peuvent changer de manière inattendue sous différents éclairages, ou l'ombrage peut sembler plus brûlé et moins doux que prévu. Ces deux erreurs ne font pas une seule bonne chose, et il est important que l'espace couleur de travail soit linéaire ("référé à la scène") et l'espace couleur de sortie soit non linéaire ("référé à l'affichage").

Pour aller plus loin