Three.jsのカラー管理
Three.js のカラー管理は r152 から ColorSpace に変わりました.レンダリングにおいて結構大事なところなので調べてみました.
この記事は、前回 Three.js を r116 から r159 にアップデートしたこと の続きです .
Three.js のバージョンを上げたときに WebGLRenderer
の .gammaInput
, .gammaOutput
が無くなっていました.これはガンマ変換を制御するパラメータなのですが、最新版では .outputColorSpace
となっており、 カラー空間を指定するように変更されています.ちなみに、その前は .outputEncoding
という名前でカラーエンコーディングを指定するようになっていました.Three.js のカラー管理については公式のドキュメント1にもあります(今のところ日本語訳はありません).この記事はそのドキュメントに基づいて書いています.また、three.js のソースコードは r159 のものを参考にしています.
Three.js のドキュメントでは、前提知識として原色、白色点、伝達関数、カラーモデル、色域について説明があります.興味がある方は読んでみてください. ここでは詳しく触れません.
リニアワークフロー
カラー管理はリニアワー クフローのためにあります.リニアワークフローについては以下のサイトが参考になるでしょう:
Three.jsでは主に sRGB 空間と Linear-sRGB 空間を扱います(他には Display-P3 があります).基本として内部では Linear-sRGB 空間で処理し、Canvas に描画するときに sRGB 空間に変換して描画します.(Working color space と Output color space 参照1).重要なのは色を扱うときにカラー空間が影響してくるということです.特に THREE.Color(マテリアルやライト、シェーダ、頂点カラー)やテクスチャを扱っている場合に問題になってくることがあります.
Three.js のカラー管理
Three.js のカラー管理は標準で有効になっています.
export const ColorManagement = {
enabled: true,
(...)
}
実際、以下のようにログを出力して確認すると true
になっています.
console.log( THREE.ColorManagement.enabled ); // true
先ほど述べたように Three.js の内部では Linear-sRGB で処理しています.また、レンダリングの出力では標準で sRGB となっています.
const renderer = new THREE.WebGLRenderer();
console.log( this.renderer.outputColorSpace ); // srgb
次に、カラースペースの定義は次のようになっています.
// Color space string identifiers, matching CSS Color Module Level 4 and WebGPU names where available.
export const NoColorSpace = '';
export const SRGBColorSpace = 'srgb';
export const LinearSRGBColorSpace = 'srgb-linear';
export const DisplayP3ColorSpace = 'display-p3';
export const LinearDisplayP3ColorSpace = 'display-p3-linear';
THREE.Color
カラー管理が何を行っているのか、THREE.Color を例に見てみましょう.
以下は setHex
メソッドの実装です.
setHex( hex, colorSpace = SRGBColorSpace ) {
hex = Math.floor( hex );
this.r = ( hex >> 16 & 255 ) / 255;
this.g = ( hex >> 8 & 255 ) / 255;
this.b = ( hex & 255 ) / 255;
ColorManagement.toWorkingColorSpace( this, colorSpace );
return this;
}
ColorManagement.toWorkingColorSpace
でカラー空間を調整しています.Working color space とは Three.js 内部でのカラー空間のことを指しています.
Three.js 内部のカラー空間は標準で Linear-sRGB 空間でした.
export const ColorManagement = {
(...)
_workingColorSpace: LinearSRGBColorSpace,
get workingColorSpace() {
return this._workingColorSpace;
},
(...)
}
sexHex
では、入力カラー空間 colorSpace
から Three.js 内部でのカラー空間への変換が行われていることがわかります.
実際の相互変換処理は Linear-sRGB 空間を経由して行われています.sRGB も Display P3 も Linear-sRGB 空間に変換してから、別のカラー空間に変換しています.
もちろん、カラー管理が無効になっていたり、入力と出力が同じカラー空間であれば何もしません.
setHex
や setStyle
, setColorName
は入力カラー空間が sRGB
になっているのに対して、setRGB
や setScalar
, setHSL
は Working color space になっていることに注意が必要です.
setRGB( r, g, b, colorSpace = ColorManagement.workingColorSpace ) {
this.r = r;
this.g = g;
this.b = b;
ColorManagement.toWorkingColorSpace( this, colorSpace );
return this;
}
THREE.Color には sRGB 空間と Linear-sRGB 空間への変換メソッドが用意されています.
copySRGBToLinear( color ) {
this.r = SRGBToLinear( color.r );
this.g = SRGBToLinear( color.g );
this.b = SRGBToLinear( color.b );
return this;
}
copyLinearToSRGB( color ) {
this.r = LinearToSRGB( color.r );
this.g = LinearToSRGB( color.g );
this.b = LinearToSRGB( color.b );
return this;
}
convertSRGBToLinear() {
this.copySRGBToLinear( this );
return this;
}
convertLinearToSRGB() {
this.copyLinearToSRGB( this );
return this;
}
LinearToSRGB
と SRGBToLinear
は次のようになっています.
export function SRGBToLinear( c ) {
return ( c < 0.04045 ) ? c * 0.0773993808 : Math.pow( c * 0.9478672986 + 0.0521327014, 2.4 );
}
export function LinearToSRGB( c ) {
return ( c < 0.0031308 ) ? c * 12.92 : 1.055 * ( Math.pow( c, 0.41666 ) ) - 0.055;
}
set系のメソッドを見てきましたが、get系メソッドでも同様の変換処理が行われています.
ちなみに、このカラー管理を有効にすることは推奨となっています.
これからわかるように例えば #123456
の値を setHex
で設定したものと、#123456
をRGBに変換して 255 で割った値をカラー空間を適切に設定せずにそのまま setRGB で設定すると結果が異なります.
マテリアルやライトは?
これらは Working color space (標準では Linear-sRGB) での値と見なされます.
BufferAttributes
での頂点カラーも同様です.