diff --git a/examples/jsm/loaders/KTX2Loader.js b/examples/jsm/loaders/KTX2Loader.js index d402f44af27afc..dd29578a0a6f8c 100644 --- a/examples/jsm/loaders/KTX2Loader.js +++ b/examples/jsm/loaders/KTX2Loader.js @@ -18,14 +18,12 @@ import { CompressedCubeTexture, Data3DTexture, DataTexture, - DisplayP3ColorSpace, FileLoader, FloatType, HalfFloatType, NoColorSpace, LinearFilter, LinearMipmapLinearFilter, - LinearDisplayP3ColorSpace, LinearSRGBColorSpace, Loader, RedFormat, @@ -72,6 +70,7 @@ import { KHR_DF_PRIMARIES_DISPLAYP3 } from '../libs/ktx-parse.module.js'; import { ZSTDDecoder } from '../libs/zstddec.module.js'; +import { DisplayP3ColorSpace, LinearDisplayP3ColorSpace } from '../math/ColorSpaces.js'; const _taskCache = new WeakMap(); diff --git a/examples/jsm/math/ColorSpaces.js b/examples/jsm/math/ColorSpaces.js new file mode 100644 index 00000000000000..f75c22aaec1722 --- /dev/null +++ b/examples/jsm/math/ColorSpaces.js @@ -0,0 +1,75 @@ +import { LinearTransfer, Matrix3, SRGBTransfer } from 'three'; + +// Reference: http://www.russellcottrell.com/photo/matrixCalculator.htm + +const P3_PRIMARIES = [ 0.680, 0.320, 0.265, 0.690, 0.150, 0.060 ]; +const P3_LUMINANCE_COEFFICIENTS = [ 0.2289, 0.6917, 0.0793 ]; +const REC2020_PRIMARIES = [ 0.708, 0.292, 0.170, 0.797, 0.131, 0.046 ]; +const REC2020_LUMINANCE_COEFFICIENTS = [ 0.2627, 0.6780, 0.0593 ]; +const D65 = [ 0.3127, 0.3290 ]; + +/****************************************************************************** + * Display P3 definitions + */ + +const LINEAR_DISPLAY_P3_TO_XYZ = /*@__PURE__*/ new Matrix3().set( + 0.4865709, 0.2656677, 0.1982173, + 0.2289746, 0.6917385, 0.0792869, + 0.0000000, 0.0451134, 1.0439444 +); + +const XYZ_TO_LINEAR_DISPLAY_P3 = /*@__PURE__*/ new Matrix3().set( + 2.4934969, - 0.9313836, - 0.4027108, + - 0.8294890, 1.7626641, 0.0236247, + 0.0358458, - 0.0761724, 0.9568845 +); + +export const DisplayP3ColorSpace = 'display-p3'; +export const LinearDisplayP3ColorSpace = 'display-p3-linear'; + +export const DisplayP3ColorSpaceImpl = { + primaries: P3_PRIMARIES, + whitePoint: D65, + transfer: SRGBTransfer, + toXYZ: LINEAR_DISPLAY_P3_TO_XYZ, + fromXYZ: XYZ_TO_LINEAR_DISPLAY_P3, + luminanceCoefficients: P3_LUMINANCE_COEFFICIENTS, + outputColorSpaceConfig: { drawingBufferColorSpace: DisplayP3ColorSpace } +}; + +export const LinearDisplayP3ColorSpaceImpl = { + primaries: P3_PRIMARIES, + whitePoint: D65, + transfer: LinearTransfer, + toXYZ: LINEAR_DISPLAY_P3_TO_XYZ, + fromXYZ: XYZ_TO_LINEAR_DISPLAY_P3, + luminanceCoefficients: P3_LUMINANCE_COEFFICIENTS, + workingColorSpaceConfig: { unpackColorSpace: DisplayP3ColorSpace } +}; + +/****************************************************************************** + * Rec. 2020 definitions + */ + +const LINEAR_REC2020_TO_XYZ = /*@__PURE__*/ new Matrix3().set( + 0.6369580, 0.1446169, 0.1688810, + 0.2627002, 0.6779981, 0.0593017, + 0.0000000, 0.0280727, 1.0609851 +); + +const XYZ_TO_LINEAR_REC2020 = /*@__PURE__*/ new Matrix3().set( + 1.7166512, - 0.3556708, - 0.2533663, + - 0.6666844, 1.6164812, 0.0157685, + 0.0176399, - 0.0427706, 0.9421031 +); + +export const LinearRec2020ColorSpace = 'rec2020-linear'; + +export const LinearRec2020ColorSpaceImpl = { + primaries: REC2020_PRIMARIES, + whitePoint: D65, + transfer: LinearTransfer, + toXYZ: LINEAR_REC2020_TO_XYZ, + fromXYZ: XYZ_TO_LINEAR_REC2020, + luminanceCoefficients: REC2020_LUMINANCE_COEFFICIENTS, +}; diff --git a/examples/webgl_test_wide_gamut.html b/examples/webgl_test_wide_gamut.html index fd467609fb354e..9d652865f3f818 100644 --- a/examples/webgl_test_wide_gamut.html +++ b/examples/webgl_test_wide_gamut.html @@ -89,6 +89,8 @@ import * as THREE from 'three'; + import { DisplayP3ColorSpace, DisplayP3ColorSpaceImpl, LinearDisplayP3ColorSpace, LinearDisplayP3ColorSpaceImpl } from 'three/addons/math/ColorSpaces.js'; + import WebGL from 'three/addons/capabilities/WebGL.js'; let container, camera, renderer, loader; @@ -98,11 +100,18 @@ const slider = document.querySelector( '.slider' ); - const isP3Context = WebGL.isColorSpaceAvailable( THREE.DisplayP3ColorSpace ); + const isP3Context = WebGL.isColorSpaceAvailable( DisplayP3ColorSpace ); + + THREE.ColorManagement.define( { + + [ DisplayP3ColorSpace ]: DisplayP3ColorSpaceImpl, + [ LinearDisplayP3ColorSpace ]: LinearDisplayP3ColorSpaceImpl + + } ); if ( isP3Context ) { - THREE.ColorManagement.workingColorSpace = THREE.LinearDisplayP3ColorSpace; + THREE.ColorManagement.workingColorSpace = LinearDisplayP3ColorSpace; } @@ -132,7 +141,7 @@ if ( isP3Context && window.matchMedia( '( color-gamut: p3 )' ).matches ) { - renderer.outputColorSpace = THREE.DisplayP3ColorSpace; + renderer.outputColorSpace = DisplayP3ColorSpace; } @@ -149,7 +158,7 @@ textureR = await loader.loadAsync( path.replace( '{colorSpace}', 'p3' ) ); textureL.colorSpace = THREE.SRGBColorSpace; - textureR.colorSpace = THREE.DisplayP3ColorSpace; + textureR.colorSpace = DisplayP3ColorSpace; sceneL.background = THREE.TextureUtils.contain( textureL, window.innerWidth / window.innerHeight ); sceneR.background = THREE.TextureUtils.contain( textureR, window.innerWidth / window.innerHeight ); @@ -213,7 +222,7 @@ function onGamutChange( { matches } ) { - renderer.outputColorSpace = isP3Context && matches ? THREE.DisplayP3ColorSpace : THREE.SRGBColorSpace; + renderer.outputColorSpace = isP3Context && matches ? DisplayP3ColorSpace : THREE.SRGBColorSpace; textureL.needsUpdate = true; textureR.needsUpdate = true; diff --git a/src/constants.js b/src/constants.js index c1aa52c77adde4..930eae16caceda 100644 --- a/src/constants.js +++ b/src/constants.js @@ -163,15 +163,10 @@ export const ObjectSpaceNormalMap = 1; export const NoColorSpace = ''; export const SRGBColorSpace = 'srgb'; export const LinearSRGBColorSpace = 'srgb-linear'; -export const DisplayP3ColorSpace = 'display-p3'; -export const LinearDisplayP3ColorSpace = 'display-p3-linear'; export const LinearTransfer = 'linear'; export const SRGBTransfer = 'srgb'; -export const Rec709Primaries = 'rec709'; -export const P3Primaries = 'p3'; - export const ZeroStencilOp = 0; export const KeepStencilOp = 7680; export const ReplaceStencilOp = 7681; diff --git a/src/math/ColorManagement.js b/src/math/ColorManagement.js index abd804d6485af2..629a1ace55eaca 100644 --- a/src/math/ColorManagement.js +++ b/src/math/ColorManagement.js @@ -1,121 +1,82 @@ -import { SRGBColorSpace, LinearSRGBColorSpace, DisplayP3ColorSpace, LinearDisplayP3ColorSpace, Rec709Primaries, P3Primaries, SRGBTransfer, LinearTransfer, NoColorSpace, } from '../constants.js'; +import { SRGBColorSpace, LinearSRGBColorSpace, SRGBTransfer, LinearTransfer, NoColorSpace } from '../constants.js'; import { Matrix3 } from './Matrix3.js'; -/** - * Matrices converting P3 <-> Rec. 709 primaries, without gamut mapping - * or clipping. Based on W3C specifications for sRGB and Display P3, - * and ICC specifications for the D50 connection space. Values in/out - * are _linear_ sRGB and _linear_ Display P3. - * - * Note that both sRGB and Display P3 use the sRGB transfer functions. - * - * Reference: - * - http://www.russellcottrell.com/photo/matrixCalculator.htm - */ - -const LINEAR_SRGB_TO_LINEAR_DISPLAY_P3 = /*@__PURE__*/ new Matrix3().set( - 0.8224621, 0.177538, 0.0, - 0.0331941, 0.9668058, 0.0, - 0.0170827, 0.0723974, 0.9105199, -); - -const LINEAR_DISPLAY_P3_TO_LINEAR_SRGB = /*@__PURE__*/ new Matrix3().set( - 1.2249401, - 0.2249404, 0.0, - - 0.0420569, 1.0420571, 0.0, - - 0.0196376, - 0.0786361, 1.0982735 -); - -/** - * Defines supported color spaces by transfer function and primaries, - * and provides conversions to/from the Linear-sRGB reference space. - */ -const COLOR_SPACES = { - [ LinearSRGBColorSpace ]: { - transfer: LinearTransfer, - primaries: Rec709Primaries, - luminanceCoefficients: [ 0.2126, 0.7152, 0.0722 ], - toReference: ( color ) => color, - fromReference: ( color ) => color, - }, - [ SRGBColorSpace ]: { - transfer: SRGBTransfer, - primaries: Rec709Primaries, - luminanceCoefficients: [ 0.2126, 0.7152, 0.0722 ], - toReference: ( color ) => color.convertSRGBToLinear(), - fromReference: ( color ) => color.convertLinearToSRGB(), - }, - [ LinearDisplayP3ColorSpace ]: { - transfer: LinearTransfer, - primaries: P3Primaries, - luminanceCoefficients: [ 0.2289, 0.6917, 0.0793 ], - toReference: ( color ) => color.applyMatrix3( LINEAR_DISPLAY_P3_TO_LINEAR_SRGB ), - fromReference: ( color ) => color.applyMatrix3( LINEAR_SRGB_TO_LINEAR_DISPLAY_P3 ), - }, - [ DisplayP3ColorSpace ]: { - transfer: SRGBTransfer, - primaries: P3Primaries, - luminanceCoefficients: [ 0.2289, 0.6917, 0.0793 ], - toReference: ( color ) => color.convertSRGBToLinear().applyMatrix3( LINEAR_DISPLAY_P3_TO_LINEAR_SRGB ), - fromReference: ( color ) => color.applyMatrix3( LINEAR_SRGB_TO_LINEAR_DISPLAY_P3 ).convertLinearToSRGB(), - }, -}; - -const SUPPORTED_WORKING_COLOR_SPACES = new Set( [ LinearSRGBColorSpace, LinearDisplayP3ColorSpace ] ); - export const ColorManagement = { enabled: true, - _workingColorSpace: LinearSRGBColorSpace, + workingColorSpace: LinearSRGBColorSpace, + + /** + * Implementations of supported color spaces. + * + * Required: + * - primaries: chromaticity coordinates [ rx ry gx gy bx by ] + * - whitePoint: reference white [ x y ] + * - transfer: transfer function (pre-defined) + * - toXYZ: Matrix3 RGB to XYZ transform + * - fromXYZ: Matrix3 XYZ to RGB transform + * - luminanceCoefficients: RGB luminance coefficients + * + * Optional: + * - outputColorSpaceConfig: { drawingBufferColorSpace: ColorSpace } + * - workingColorSpaceConfig: { unpackColorSpace: ColorSpace } + * + * Reference: + * - https://www.russellcottrell.com/photo/matrixCalculator.htm + */ + spaces: {}, - get workingColorSpace() { + convert: function ( color, sourceColorSpace, targetColorSpace ) { - return this._workingColorSpace; + if ( this.enabled === false || sourceColorSpace === targetColorSpace || ! sourceColorSpace || ! targetColorSpace ) { - }, + return color; - set workingColorSpace( colorSpace ) { + } - if ( ! SUPPORTED_WORKING_COLOR_SPACES.has( colorSpace ) ) { + if ( this.spaces[ sourceColorSpace ].transfer === SRGBTransfer ) { - throw new Error( `Unsupported working color space, "${ colorSpace }".` ); + color.r = SRGBToLinear( color.r ); + color.g = SRGBToLinear( color.g ); + color.b = SRGBToLinear( color.b ); } - this._workingColorSpace = colorSpace; + if ( this.spaces[ sourceColorSpace ].primaries !== this.spaces[ targetColorSpace ].primaries ) { - }, + color.applyMatrix3( this.spaces[ sourceColorSpace ].toXYZ ); + color.applyMatrix3( this.spaces[ targetColorSpace ].fromXYZ ); - convert: function ( color, sourceColorSpace, targetColorSpace ) { + } - if ( this.enabled === false || sourceColorSpace === targetColorSpace || ! sourceColorSpace || ! targetColorSpace ) { + if ( this.spaces[ targetColorSpace ].transfer === SRGBTransfer ) { - return color; + color.r = LinearToSRGB( color.r ); + color.g = LinearToSRGB( color.g ); + color.b = LinearToSRGB( color.b ); } - const sourceToReference = COLOR_SPACES[ sourceColorSpace ].toReference; - const targetFromReference = COLOR_SPACES[ targetColorSpace ].fromReference; - - return targetFromReference( sourceToReference( color ) ); + return color; }, fromWorkingColorSpace: function ( color, targetColorSpace ) { - return this.convert( color, this._workingColorSpace, targetColorSpace ); + return this.convert( color, this.workingColorSpace, targetColorSpace ); }, toWorkingColorSpace: function ( color, sourceColorSpace ) { - return this.convert( color, sourceColorSpace, this._workingColorSpace ); + return this.convert( color, sourceColorSpace, this.workingColorSpace ); }, getPrimaries: function ( colorSpace ) { - return COLOR_SPACES[ colorSpace ].primaries; + return this.spaces[ colorSpace ].primaries; }, @@ -123,16 +84,44 @@ export const ColorManagement = { if ( colorSpace === NoColorSpace ) return LinearTransfer; - return COLOR_SPACES[ colorSpace ].transfer; + return this.spaces[ colorSpace ].transfer; + + }, + + getLuminanceCoefficients: function ( target, colorSpace = this.workingColorSpace ) { + + return target.fromArray( this.spaces[ colorSpace ].luminanceCoefficients ); + + }, + + define: function ( colorSpaces ) { + + Object.assign( this.spaces, colorSpaces ); + + }, + + // Internal APIs + + _getMatrix: function ( targetMatrix, sourceColorSpace, targetColorSpace ) { + + return targetMatrix + .copy( this.spaces[ sourceColorSpace ].toXYZ ) + .multiply( this.spaces[ targetColorSpace ].fromXYZ ); }, - getLuminanceCoefficients: function ( target, colorSpace = this._workingColorSpace ) { + _getDrawingBufferColorSpace: function ( colorSpace ) { - return target.fromArray( COLOR_SPACES[ colorSpace ].luminanceCoefficients ); + return this.spaces[ colorSpace ].outputColorSpaceConfig.drawingBufferColorSpace; }, + _getUnpackColorSpace: function ( colorSpace = this.workingColorSpace ) { + + return this.spaces[ colorSpace ].workingColorSpaceConfig.unpackColorSpace; + + } + }; @@ -147,3 +136,47 @@ export function LinearToSRGB( c ) { return ( c < 0.0031308 ) ? c * 12.92 : 1.055 * ( Math.pow( c, 0.41666 ) ) - 0.055; } + +/****************************************************************************** + * sRGB definitions + */ + +const REC709_PRIMARIES = [ 0.640, 0.330, 0.300, 0.600, 0.150, 0.060 ]; +const REC709_LUMINANCE_COEFFICIENTS = [ 0.2126, 0.7152, 0.0722 ]; +const D65 = [ 0.3127, 0.3290 ]; + +const LINEAR_REC709_TO_XYZ = /*@__PURE__*/ new Matrix3().set( + 0.4123908, 0.3575843, 0.1804808, + 0.2126390, 0.7151687, 0.0721923, + 0.0193308, 0.1191948, 0.9505322 +); + +const XYZ_TO_LINEAR_REC709 = /*@__PURE__*/ new Matrix3().set( + 3.2409699, - 1.5373832, - 0.4986108, + - 0.9692436, 1.8759675, 0.0415551, + 0.0556301, - 0.2039770, 1.0569715 +); + +ColorManagement.define( { + + [ LinearSRGBColorSpace ]: { + primaries: REC709_PRIMARIES, + whitePoint: D65, + transfer: LinearTransfer, + toXYZ: LINEAR_REC709_TO_XYZ, + fromXYZ: XYZ_TO_LINEAR_REC709, + luminanceCoefficients: REC709_LUMINANCE_COEFFICIENTS, + workingColorSpaceConfig: { unpackColorSpace: SRGBColorSpace } + }, + + [ SRGBColorSpace ]: { + primaries: REC709_PRIMARIES, + whitePoint: D65, + transfer: SRGBTransfer, + toXYZ: LINEAR_REC709_TO_XYZ, + fromXYZ: XYZ_TO_LINEAR_REC709, + luminanceCoefficients: REC709_LUMINANCE_COEFFICIENTS, + outputColorSpaceConfig: { drawingBufferColorSpace: SRGBColorSpace } + }, + +} ); diff --git a/src/renderers/WebGLRenderer.js b/src/renderers/WebGLRenderer.js index 1cf6a6a565b4e7..69a6c343038300 100644 --- a/src/renderers/WebGLRenderer.js +++ b/src/renderers/WebGLRenderer.js @@ -17,9 +17,7 @@ import { UnsignedInt248Type, UnsignedShort4444Type, UnsignedShort5551Type, - WebGLCoordinateSystem, - DisplayP3ColorSpace, - LinearDisplayP3ColorSpace + WebGLCoordinateSystem } from '../constants.js'; import { Color } from '../math/Color.js'; import { Frustum } from '../math/Frustum.js'; @@ -2828,8 +2826,8 @@ class WebGLRenderer { this._outputColorSpace = colorSpace; const gl = this.getContext(); - gl.drawingBufferColorSpace = colorSpace === DisplayP3ColorSpace ? 'display-p3' : 'srgb'; - gl.unpackColorSpace = ColorManagement.workingColorSpace === LinearDisplayP3ColorSpace ? 'display-p3' : 'srgb'; + gl.drawingBufferColorspace = ColorManagement._getDrawingBufferColorSpace( colorSpace ); + gl.unpackColorSpace = ColorManagement._getUnpackColorSpace(); } diff --git a/src/renderers/shaders/ShaderChunk/colorspace_pars_fragment.glsl.js b/src/renderers/shaders/ShaderChunk/colorspace_pars_fragment.glsl.js index 2b9523d8751a7b..53c473b5ed0ade 100644 --- a/src/renderers/shaders/ShaderChunk/colorspace_pars_fragment.glsl.js +++ b/src/renderers/shaders/ShaderChunk/colorspace_pars_fragment.glsl.js @@ -1,29 +1,5 @@ export default /* glsl */` -// http://www.russellcottrell.com/photo/matrixCalculator.htm - -// Linear sRGB => XYZ => Linear Display P3 -const mat3 LINEAR_SRGB_TO_LINEAR_DISPLAY_P3 = mat3( - vec3( 0.8224621, 0.177538, 0.0 ), - vec3( 0.0331941, 0.9668058, 0.0 ), - vec3( 0.0170827, 0.0723974, 0.9105199 ) -); - -// Linear Display P3 => XYZ => Linear sRGB -const mat3 LINEAR_DISPLAY_P3_TO_LINEAR_SRGB = mat3( - vec3( 1.2249401, - 0.2249404, 0.0 ), - vec3( - 0.0420569, 1.0420571, 0.0 ), - vec3( - 0.0196376, - 0.0786361, 1.0982735 ) -); - -vec4 LinearSRGBToLinearDisplayP3( in vec4 value ) { - return vec4( value.rgb * LINEAR_SRGB_TO_LINEAR_DISPLAY_P3, value.a ); -} - -vec4 LinearDisplayP3ToLinearSRGB( in vec4 value ) { - return vec4( value.rgb * LINEAR_DISPLAY_P3_TO_LINEAR_SRGB, value.a ); -} - vec4 LinearTransferOETF( in vec4 value ) { return value; } diff --git a/src/renderers/webgl/WebGLProgram.js b/src/renderers/webgl/WebGLProgram.js index 1dda0576a120af..c85d3e0dc57f2d 100644 --- a/src/renderers/webgl/WebGLProgram.js +++ b/src/renderers/webgl/WebGLProgram.js @@ -1,9 +1,10 @@ import { WebGLUniforms } from './WebGLUniforms.js'; import { WebGLShader } from './WebGLShader.js'; import { ShaderChunk } from '../shaders/ShaderChunk.js'; -import { NoToneMapping, AddOperation, MixOperation, MultiplyOperation, CubeRefractionMapping, CubeUVReflectionMapping, CubeReflectionMapping, PCFSoftShadowMap, PCFShadowMap, VSMShadowMap, AgXToneMapping, ACESFilmicToneMapping, NeutralToneMapping, CineonToneMapping, CustomToneMapping, ReinhardToneMapping, LinearToneMapping, GLSL3, LinearSRGBColorSpace, SRGBColorSpace, LinearDisplayP3ColorSpace, DisplayP3ColorSpace, P3Primaries, Rec709Primaries } from '../../constants.js'; +import { NoToneMapping, AddOperation, MixOperation, MultiplyOperation, CubeRefractionMapping, CubeUVReflectionMapping, CubeReflectionMapping, PCFSoftShadowMap, PCFShadowMap, VSMShadowMap, AgXToneMapping, ACESFilmicToneMapping, NeutralToneMapping, CineonToneMapping, CustomToneMapping, ReinhardToneMapping, LinearToneMapping, GLSL3, LinearTransfer, SRGBTransfer } from '../../constants.js'; import { ColorManagement } from '../../math/ColorManagement.js'; import { Vector3 } from '../../math/Vector3.js'; +import { Matrix3 } from '../../math/Matrix3.js'; // From https://www.khronos.org/registry/webgl/extensions/KHR_parallel_shader_compile/ const COMPLETION_STATUS_KHR = 0x91B1; @@ -29,40 +30,25 @@ function handleSource( string, errorLine ) { } -function getEncodingComponents( colorSpace ) { - - const workingPrimaries = ColorManagement.getPrimaries( ColorManagement.workingColorSpace ); - const encodingPrimaries = ColorManagement.getPrimaries( colorSpace ); - - let gamutMapping; - - if ( workingPrimaries === encodingPrimaries ) { +const _m0 = /*@__PURE__*/ new Matrix3(); - gamutMapping = ''; - - } else if ( workingPrimaries === P3Primaries && encodingPrimaries === Rec709Primaries ) { - - gamutMapping = 'LinearDisplayP3ToLinearSRGB'; - - } else if ( workingPrimaries === Rec709Primaries && encodingPrimaries === P3Primaries ) { +function getEncodingComponents( colorSpace ) { - gamutMapping = 'LinearSRGBToLinearDisplayP3'; + ColorManagement._getMatrix( _m0, ColorManagement.workingColorSpace, colorSpace ); - } + const encodingMatrix = `mat3( ${ _m0.elements.map( ( v ) => v.toFixed( 4 ) ) } )`; - switch ( colorSpace ) { + switch ( ColorManagement.getTransfer( colorSpace ) ) { - case LinearSRGBColorSpace: - case LinearDisplayP3ColorSpace: - return [ gamutMapping, 'LinearTransferOETF' ]; + case LinearTransfer: + return [ encodingMatrix, 'LinearTransferOETF' ]; - case SRGBColorSpace: - case DisplayP3ColorSpace: - return [ gamutMapping, 'sRGBTransferOETF' ]; + case SRGBTransfer: + return [ encodingMatrix, 'sRGBTransferOETF' ]; default: - console.warn( 'THREE.WebGLProgram: Unsupported color space:', colorSpace ); - return [ gamutMapping, 'LinearTransferOETF' ]; + console.warn( 'THREE.WebGLProgram: Unsupported color space: ', colorSpace ); + return [ encodingMatrix, 'LinearTransferOETF' ]; } @@ -95,7 +81,16 @@ function getShaderErrors( gl, shader, type ) { function getTexelEncodingFunction( functionName, colorSpace ) { const components = getEncodingComponents( colorSpace ); - return `vec4 ${functionName}( vec4 value ) { return ${components[ 0 ]}( ${components[ 1 ]}( value ) ); }`; + + return [ + + `vec4 ${functionName}( vec4 value ) {`, + + ` return ${components[ 1 ]}( vec4( value.rgb * ${components[ 0 ]}, value.a ) );`, + + '}', + + ].join( '\n' ); } diff --git a/test/unit/addons/math/ColorSpaces.tests.js b/test/unit/addons/math/ColorSpaces.tests.js new file mode 100644 index 00000000000000..804e68119284ad --- /dev/null +++ b/test/unit/addons/math/ColorSpaces.tests.js @@ -0,0 +1,89 @@ +import { Color } from '../../../../src/math/Color.js'; +import { ColorManagement } from '../../../../src/math/ColorManagement.js'; +import { LinearSRGBColorSpace } from '../../../../src/constants.js'; +import { + DisplayP3ColorSpace, + DisplayP3ColorSpaceImpl, + LinearDisplayP3ColorSpace, + LinearDisplayP3ColorSpaceImpl, + LinearRec2020ColorSpace, + LinearRec2020ColorSpaceImpl +} from '../../../../examples/jsm/math/ColorSpaces.js'; + +// Reference: https://apps.colorjs.io/convert/ + +export default QUnit.module( 'Maths', () => { + + QUnit.module( 'ColorSpaces', () => { + + ColorManagement.define( { + + [ DisplayP3ColorSpace ]: DisplayP3ColorSpaceImpl, + [ LinearDisplayP3ColorSpace ]: LinearDisplayP3ColorSpaceImpl, + [ LinearRec2020ColorSpace ]: LinearRec2020ColorSpaceImpl + + } ); + + QUnit.test( 'DisplayP3ColorSpace', ( assert ) => { + + const c = new Color().setRGB( 0.3, 0.5, 0.7 ); + + ColorManagement.convert( c, LinearSRGBColorSpace, DisplayP3ColorSpace ); + + assert.equal( c.r.toFixed( 3 ), 0.614, 'Red: ' + c.r + ' (display-p3, in gamut)' ); + assert.equal( c.g.toFixed( 3 ), 0.731, 'Green: ' + c.g + ' (display-p3, in gamut)' ); + assert.equal( c.b.toFixed( 3 ), 0.843, 'Blue: ' + c.b + ' (display-p3, in gamut)' ); + + c.setRGB( 1.0, 0.5, 0.01, DisplayP3ColorSpace ); + + assert.equal( c.r.toFixed( 3 ), 1.177, 'Red: ' + c.r + ' (srgb-linear, out of gamut)' ); + assert.equal( c.g.toFixed( 3 ), 0.181, 'Green: ' + c.g + ' (srgb-linear, out of gamut)' ); + assert.equal( c.b.toFixed( 3 ), - 0.036, 'Blue: ' + c.b + ' (srgb-linear, out of gamut)' ); + + assert.equal( c.getStyle( DisplayP3ColorSpace ), 'color(display-p3 1.000 0.500 0.010)', 'style: display-p3' ); + + } ); + + QUnit.test( 'LinearDisplayP3ColorSpace', ( assert ) => { + + const c = new Color().setRGB( 0.3, 0.5, 0.7 ); + + ColorManagement.convert( c, LinearSRGBColorSpace, LinearDisplayP3ColorSpace ); + + assert.equal( c.r.toFixed( 3 ), 0.336, 'Red: ' + c.r + ' (display-p3-linear, in gamut)' ); + assert.equal( c.g.toFixed( 3 ), 0.493, 'Green: ' + c.g + ' (display-p3-linear, in gamut)' ); + assert.equal( c.b.toFixed( 3 ), 0.679, 'Blue: ' + c.b + ' (display-p3-linear, in gamut)' ); + + c.setRGB( 1.0, 0.5, 0.01, LinearDisplayP3ColorSpace ); + + assert.equal( c.r.toFixed( 3 ), 1.112, 'Red: ' + c.r + ' (srgb-linear, out of gamut)' ); + assert.equal( c.g.toFixed( 3 ), 0.479, 'Green: ' + c.g + ' (srgb-linear, out of gamut)' ); + assert.equal( c.b.toFixed( 3 ), - 0.048, 'Blue: ' + c.b + ' (srgb-linear, out of gamut)' ); + + assert.equal( c.getStyle( LinearDisplayP3ColorSpace ), 'color(display-p3-linear 1.000 0.500 0.010)', 'style: display-p3-linear' ); + + } ); + + QUnit.test( 'LinearRec2020ColorSpace', ( assert ) => { + + const c = new Color().setRGB( 0.3, 0.5, 0.7 ); + + ColorManagement.convert( c, LinearSRGBColorSpace, LinearRec2020ColorSpace ); + + assert.equal( c.r.toFixed( 3 ), 0.383, 'Red: ' + c.r + ' (rec2020-linear, in gamut)' ); + assert.equal( c.g.toFixed( 3 ), 0.488, 'Green: ' + c.g + ' (rec2020-linear, in gamut)' ); + assert.equal( c.b.toFixed( 3 ), 0.676, 'Blue: ' + c.b + ' (rec2020-linear, in gamut)' ); + + c.setRGB( 1.0, 0.5, 0.01, LinearRec2020ColorSpace ); + + assert.equal( c.r.toFixed( 3 ), 1.366, 'Red: ' + c.r + ' (srgb-linear, out of gamut)' ); + assert.equal( c.g.toFixed( 3 ), 0.442, 'Green: ' + c.g + ' (srgb-linear, out of gamut)' ); + assert.equal( c.b.toFixed( 3 ), - 0.057, 'Blue: ' + c.b + ' (srgb-linear, out of gamut)' ); + + assert.equal( c.getStyle( LinearRec2020ColorSpace ), 'color(rec2020-linear 1.000 0.500 0.010)', 'style: rec2020-linear' ); + + } ); + + } ); + +} ); diff --git a/test/unit/src/constants.tests.js b/test/unit/src/constants.tests.js index 313b5f9a2eb02f..1904ff7d973dc2 100644 --- a/test/unit/src/constants.tests.js +++ b/test/unit/src/constants.tests.js @@ -179,7 +179,6 @@ export default QUnit.module( 'Constants', () => { assert.equal( Constants.NoColorSpace, '', 'NoColorSpace is equal to ""' ); assert.equal( Constants.SRGBColorSpace, 'srgb', 'SRGBColorSpace is equal to srgb' ); assert.equal( Constants.LinearSRGBColorSpace, 'srgb-linear', 'LinearSRGBColorSpace is equal to srgb-linear' ); - assert.equal( Constants.DisplayP3ColorSpace, 'display-p3', 'DisplayP3ColorSpace is equal to display-p3' ); assert.equal( Constants.ZeroStencilOp, 0, 'ZeroStencilOp is equal to 0' ); assert.equal( Constants.KeepStencilOp, 7680, 'KeepStencilOp is equal to 7680' ); diff --git a/test/unit/src/math/Color.tests.js b/test/unit/src/math/Color.tests.js index b6c9a19688b320..8a6d967c0f4f16 100644 --- a/test/unit/src/math/Color.tests.js +++ b/test/unit/src/math/Color.tests.js @@ -4,7 +4,7 @@ import { Color } from '../../../../src/math/Color.js'; import { ColorManagement } from '../../../../src/math/ColorManagement.js'; import { eps } from '../../utils/math-constants.js'; import { CONSOLE_LEVEL } from '../../utils/console-wrapper.js'; -import { DisplayP3ColorSpace, SRGBColorSpace } from '../../../../src/constants.js'; +import { SRGBColorSpace } from '../../../../src/constants.js'; export default QUnit.module( 'Maths', () => { @@ -127,18 +127,6 @@ export default QUnit.module( 'Maths', () => { assert.equal( c.g.toFixed( 3 ), 0.214, 'Green: ' + c.g + ' (srgb)' ); assert.equal( c.b.toFixed( 3 ), 0.448, 'Blue: ' + c.b + ' (srgb)' ); - c.setRGB( 0.614, 0.731, 0.843, DisplayP3ColorSpace ); - - assert.numEqual( c.r.toFixed( 2 ), 0.3, 'Red: ' + c.r + ' (display-p3, in gamut)' ); - assert.numEqual( c.g.toFixed( 2 ), 0.5, 'Green: ' + c.g + ' (display-p3, in gamut)' ); - assert.numEqual( c.b.toFixed( 2 ), 0.7, 'Blue: ' + c.b + ' (display-p3, in gamut)' ); - - c.setRGB( 1.0, 0.5, 0.0, DisplayP3ColorSpace ); - - assert.numEqual( c.r.toFixed( 3 ), 1.179, 'Red: ' + c.r + ' (display-p3, out of gamut)' ); - assert.numEqual( c.g.toFixed( 3 ), 0.181, 'Green: ' + c.g + ' (display-p3, out of gamut)' ); - assert.numEqual( c.b.toFixed( 3 ), - 0.036, 'Blue: ' + c.b + ' (display-p3, out of gamut)' ); - } ); QUnit.test( 'setHSL', ( assert ) => { @@ -343,12 +331,6 @@ export default QUnit.module( 'Maths', () => { assert.equal( t.g.toFixed( 3 ), ( 160 / 255 ).toFixed( 3 ), 'g (srgb)' ); assert.equal( t.b.toFixed( 3 ), ( 221 / 255 ).toFixed( 3 ), 'b (srgb)' ); - c.getRGB( t, DisplayP3ColorSpace ); - - assert.equal( t.r.toFixed( 3 ), 0.831, 'r (display-p3)' ); - assert.equal( t.g.toFixed( 3 ), 0.637, 'g (display-p3)' ); - assert.equal( t.b.toFixed( 3 ), 0.852, 'b (display-p3)' ); - } ); QUnit.test( 'getStyle', ( assert ) => { @@ -358,7 +340,6 @@ export default QUnit.module( 'Maths', () => { const c = new Color( 'plum' ); assert.equal( c.getStyle(), 'rgb(221,160,221)', 'style: srgb' ); - assert.equal( c.getStyle( DisplayP3ColorSpace ), 'color(display-p3 0.831 0.637 0.852)', 'style: display-p3' ); } ); diff --git a/test/unit/three.addons.unit.js b/test/unit/three.addons.unit.js index c5c9e123cb94da..049188251191fe 100644 --- a/test/unit/three.addons.unit.js +++ b/test/unit/three.addons.unit.js @@ -1,3 +1,4 @@ //addons/utils import './addons/utils/BufferGeometryUtils.tests.js'; +import './addons/math/ColorSpaces.tests.js';