diff --git a/examples/files.json b/examples/files.json index cbc164b4216590..b72521ae65d266 100644 --- a/examples/files.json +++ b/examples/files.json @@ -213,6 +213,7 @@ "webgl_sprites", "webgl_test_memory", "webgl_test_memory2", + "webgl_test_wide_gamut", "webgl_tonemapping", "webgl_video_kinect", "webgl_video_panorama_equirectangular", diff --git a/examples/jsm/capabilities/WebGL.js b/examples/jsm/capabilities/WebGL.js index 08666feb103f91..2abf2614fccbc0 100644 --- a/examples/jsm/capabilities/WebGL.js +++ b/examples/jsm/capabilities/WebGL.js @@ -30,6 +30,23 @@ class WebGL { } + static isColorSpaceAvailable( colorSpace ) { + + try { + + const canvas = document.createElement( 'canvas' ); + const ctx = window.WebGL2RenderingContext && canvas.getContext( 'webgl2' ); + ctx.drawingBufferColorSpace = colorSpace; + return ctx.drawingBufferColorSpace === colorSpace; // deepscan-disable-line SAME_OPERAND_VALUE + + } catch ( e ) { + + return false; + + } + + } + static getWebGLErrorMessage() { return this.getErrorMessage( 1 ); diff --git a/examples/jsm/postprocessing/OutputPass.js b/examples/jsm/postprocessing/OutputPass.js index d2207624d9ed95..09e36614b25325 100644 --- a/examples/jsm/postprocessing/OutputPass.js +++ b/examples/jsm/postprocessing/OutputPass.js @@ -1,11 +1,12 @@ import { + ColorManagement, RawShaderMaterial, UniformsUtils, LinearToneMapping, ReinhardToneMapping, CineonToneMapping, ACESFilmicToneMapping, - SRGBColorSpace + SRGBTransfer } from 'three'; import { Pass, FullScreenQuad } from './Pass.js'; import { OutputShader } from '../shaders/OutputShader.js'; @@ -51,7 +52,7 @@ class OutputPass extends Pass { this.material.defines = {}; - if ( this._outputColorSpace == SRGBColorSpace ) this.material.defines.SRGB_COLOR_SPACE = ''; + if ( ColorManagement.getTransfer( this._outputColorSpace ) === SRGBTransfer ) this.material.defines.SRGB_TRANSFER = ''; if ( this._toneMapping === LinearToneMapping ) this.material.defines.LINEAR_TONE_MAPPING = ''; else if ( this._toneMapping === ReinhardToneMapping ) this.material.defines.REINHARD_TONE_MAPPING = ''; diff --git a/examples/jsm/shaders/GammaCorrectionShader.js b/examples/jsm/shaders/GammaCorrectionShader.js index ca0732605d92f8..719960497b6008 100644 --- a/examples/jsm/shaders/GammaCorrectionShader.js +++ b/examples/jsm/shaders/GammaCorrectionShader.js @@ -34,7 +34,7 @@ const GammaCorrectionShader = { vec4 tex = texture2D( tDiffuse, vUv ); - gl_FragColor = LinearTosRGB( tex ); + gl_FragColor = sRGBTransferOETF( tex ); }` diff --git a/examples/jsm/shaders/OutputShader.js b/examples/jsm/shaders/OutputShader.js index d15c4dd143aef8..cf7a18ae7f9de8 100644 --- a/examples/jsm/shaders/OutputShader.js +++ b/examples/jsm/shaders/OutputShader.js @@ -65,9 +65,9 @@ const OutputShader = { // color space - #ifdef SRGB_COLOR_SPACE + #ifdef SRGB_TRANSFER - gl_FragColor = LinearTosRGB( gl_FragColor ); + gl_FragColor = sRGBTransferOETF( gl_FragColor ); #endif diff --git a/examples/screenshots/webgl_test_wide_gamut.jpg b/examples/screenshots/webgl_test_wide_gamut.jpg new file mode 100644 index 00000000000000..a1250f0ee67f13 Binary files /dev/null and b/examples/screenshots/webgl_test_wide_gamut.jpg differ diff --git a/examples/textures/wide_gamut/logo_p3.png b/examples/textures/wide_gamut/logo_p3.png new file mode 100644 index 00000000000000..20d97bc59106b3 Binary files /dev/null and b/examples/textures/wide_gamut/logo_p3.png differ diff --git a/examples/textures/wide_gamut/logo_srgb.png b/examples/textures/wide_gamut/logo_srgb.png new file mode 100644 index 00000000000000..4dfc4d1a3743a9 Binary files /dev/null and b/examples/textures/wide_gamut/logo_srgb.png differ diff --git a/examples/webgl_test_wide_gamut.html b/examples/webgl_test_wide_gamut.html new file mode 100644 index 00000000000000..641c80685d88e6 --- /dev/null +++ b/examples/webgl_test_wide_gamut.html @@ -0,0 +1,232 @@ + + + + three.js webgl - test - wide gamut + + + + + + + + +
+ three.js - wide gamut test
+
+ +
+
+

sRGB

+

Display P3

+
+ + + + + + + + + + diff --git a/src/constants.js b/src/constants.js index d30a7713755a3a..aab485cb35f31c 100644 --- a/src/constants.js +++ b/src/constants.js @@ -158,6 +158,12 @@ 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 d15d3d3539c98e..296334d2929ac4 100644 --- a/src/math/ColorManagement.js +++ b/src/math/ColorManagement.js @@ -1,18 +1,6 @@ -import { SRGBColorSpace, LinearSRGBColorSpace, DisplayP3ColorSpace, } from '../constants.js'; +import { SRGBColorSpace, LinearSRGBColorSpace, DisplayP3ColorSpace, LinearDisplayP3ColorSpace, Rec709Primaries, P3Primaries, SRGBTransfer, LinearTransfer, NoColorSpace, } from '../constants.js'; import { Matrix3 } from './Matrix3.js'; -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; - -} - /** * Matrices converting P3 <-> Rec. 709 primaries, without gamut mapping * or clipping. Based on W3C specifications for sRGB and Display P3, @@ -25,50 +13,57 @@ export function LinearToSRGB( c ) { * - http://www.russellcottrell.com/photo/matrixCalculator.htm */ -const LINEAR_SRGB_TO_LINEAR_DISPLAY_P3 = /*@__PURE__*/ new Matrix3().fromArray( [ - 0.8224621, 0.0331941, 0.0170827, - 0.1775380, 0.9668058, 0.0723974, - - 0.0000001, 0.0000001, 0.9105199 -] ); - -const LINEAR_DISPLAY_P3_TO_LINEAR_SRGB = /*@__PURE__*/ new Matrix3().fromArray( [ - 1.2249401, - 0.0420569, - 0.0196376, - - 0.2249404, 1.0420571, - 0.0786361, - 0.0000001, 0.0000000, 1.0982735 -] ); - -function DisplayP3ToLinearSRGB( color ) { +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, +); - // Display P3 uses the sRGB transfer functions - return color.convertSRGBToLinear().applyMatrix3( LINEAR_DISPLAY_P3_TO_LINEAR_SRGB ); +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 +); -} - -function LinearSRGBToDisplayP3( color ) { - - // Display P3 uses the sRGB transfer functions - return color.applyMatrix3( LINEAR_SRGB_TO_LINEAR_DISPLAY_P3 ).convertLinearToSRGB(); - -} - -// Conversions from to Linear-sRGB reference space. -const TO_LINEAR = { - [ LinearSRGBColorSpace ]: ( color ) => color, - [ SRGBColorSpace ]: ( color ) => color.convertSRGBToLinear(), - [ DisplayP3ColorSpace ]: DisplayP3ToLinearSRGB, +/** + * 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, + toReference: ( color ) => color, + fromReference: ( color ) => color, + }, + [ SRGBColorSpace ]: { + transfer: SRGBTransfer, + primaries: Rec709Primaries, + toReference: ( color ) => color.convertSRGBToLinear(), + fromReference: ( color ) => color.convertLinearToSRGB(), + }, + [ LinearDisplayP3ColorSpace ]: { + transfer: LinearTransfer, + primaries: P3Primaries, + 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, + toReference: ( color ) => color.convertSRGBToLinear().applyMatrix3( LINEAR_DISPLAY_P3_TO_LINEAR_SRGB ), + fromReference: ( color ) => color.applyMatrix3( LINEAR_SRGB_TO_LINEAR_DISPLAY_P3 ).convertLinearToSRGB(), + }, }; -// Conversions to from Linear-sRGB reference space. -const FROM_LINEAR = { - [ LinearSRGBColorSpace ]: ( color ) => color, - [ SRGBColorSpace ]: ( color ) => color.convertLinearToSRGB(), - [ DisplayP3ColorSpace ]: LinearSRGBToDisplayP3, -}; +const SUPPORTED_WORKING_COLOR_SPACES = new Set( [ LinearSRGBColorSpace, LinearDisplayP3ColorSpace ] ); export const ColorManagement = { enabled: true, + _workingColorSpace: LinearSRGBColorSpace, + get legacyMode() { console.warn( 'THREE.ColorManagement: .legacyMode=false renamed to .enabled=true in r150.' ); @@ -87,13 +82,19 @@ export const ColorManagement = { get workingColorSpace() { - return LinearSRGBColorSpace; + return this._workingColorSpace; }, set workingColorSpace( colorSpace ) { - console.warn( 'THREE.ColorManagement: .workingColorSpace is readonly.' ); + if ( ! SUPPORTED_WORKING_COLOR_SPACES.has( colorSpace ) ) { + + throw new Error( `Unsupported working color space, "${ colorSpace }".` ); + + } + + this._workingColorSpace = colorSpace; }, @@ -105,29 +106,50 @@ export const ColorManagement = { } - const sourceToLinear = TO_LINEAR[ sourceColorSpace ]; - const targetFromLinear = FROM_LINEAR[ targetColorSpace ]; + const sourceToReference = COLOR_SPACES[ sourceColorSpace ].toReference; + const targetFromReference = COLOR_SPACES[ targetColorSpace ].fromReference; - if ( sourceToLinear === undefined || targetFromLinear === undefined ) { + return targetFromReference( sourceToReference( color ) ); - throw new Error( `Unsupported color space conversion, "${ sourceColorSpace }" to "${ targetColorSpace }".` ); + }, - } + fromWorkingColorSpace: function ( color, targetColorSpace ) { - return targetFromLinear( sourceToLinear( color ) ); + return this.convert( color, this._workingColorSpace, targetColorSpace ); }, - fromWorkingColorSpace: function ( color, targetColorSpace ) { + toWorkingColorSpace: function ( color, sourceColorSpace ) { - return this.convert( color, this.workingColorSpace, targetColorSpace ); + return this.convert( color, sourceColorSpace, this._workingColorSpace ); }, - toWorkingColorSpace: function ( color, sourceColorSpace ) { + getPrimaries: function ( colorSpace ) { + + return COLOR_SPACES[ colorSpace ].primaries; + + }, + + getTransfer: function ( colorSpace ) { - return this.convert( color, sourceColorSpace, this.workingColorSpace ); + if ( colorSpace === NoColorSpace ) return LinearTransfer; + + return COLOR_SPACES[ colorSpace ].transfer; }, }; + + +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; + +} diff --git a/src/renderers/WebGLRenderer.js b/src/renderers/WebGLRenderer.js index 4c675bddf54f2a..35b215c3e2198d 100644 --- a/src/renderers/WebGLRenderer.js +++ b/src/renderers/WebGLRenderer.js @@ -21,7 +21,9 @@ import { UnsignedInt248Type, UnsignedShort4444Type, UnsignedShort5551Type, - WebGLCoordinateSystem + WebGLCoordinateSystem, + DisplayP3ColorSpace, + LinearDisplayP3ColorSpace } from '../constants.js'; import { Color } from '../math/Color.js'; import { Frustum } from '../math/Frustum.js'; @@ -59,6 +61,7 @@ import { WebXRManager } from './webxr/WebXRManager.js'; import { WebGLMaterials } from './webgl/WebGLMaterials.js'; import { WebGLUniformsGroups } from './webgl/WebGLUniformsGroups.js'; import { createCanvasElement } from '../utils.js'; +import { ColorManagement } from '../math/ColorManagement.js'; class WebGLRenderer { @@ -140,7 +143,7 @@ class WebGLRenderer { // physically based shading - this.outputColorSpace = SRGBColorSpace; + this._outputColorSpace = SRGBColorSpace; // physical lights @@ -2417,6 +2420,22 @@ class WebGLRenderer { } + get outputColorSpace() { + + return this._outputColorSpace; + + } + + set outputColorSpace( colorSpace ) { + + this._outputColorSpace = colorSpace; + + const gl = this.getContext(); + gl.drawingBufferColorSpace = colorSpace === DisplayP3ColorSpace ? 'display-p3' : 'srgb'; + gl.unpackColorSpace = ColorManagement.workingColorSpace === LinearDisplayP3ColorSpace ? 'display-p3' : 'srgb'; + + } + get physicallyCorrectLights() { // @deprecated, r150 console.warn( 'THREE.WebGLRenderer: The property .physicallyCorrectLights has been removed. Set renderer.useLegacyLights instead.' ); diff --git a/src/renderers/shaders/ShaderChunk/colorspace_pars_fragment.glsl.js b/src/renderers/shaders/ShaderChunk/colorspace_pars_fragment.glsl.js index 26371675293d38..4b52daad72fb8e 100644 --- a/src/renderers/shaders/ShaderChunk/colorspace_pars_fragment.glsl.js +++ b/src/renderers/shaders/ShaderChunk/colorspace_pars_fragment.glsl.js @@ -1,11 +1,44 @@ export default /* glsl */` -vec4 LinearToLinear( in vec4 value ) { +// 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; } -vec4 LinearTosRGB( in vec4 value ) { +vec4 sRGBTransferOETF( in vec4 value ) { return vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a ); } +// @deprecated, r156 +vec4 LinearToLinear( in vec4 value ) { + return value; +} + +// @deprecated, r156 +vec4 LinearTosRGB( in vec4 value ) { + return sRGBTransferOETF( value ); +} `; diff --git a/src/renderers/shaders/UniformsUtils.js b/src/renderers/shaders/UniformsUtils.js index b8628bbee035c1..14025664a38c1d 100644 --- a/src/renderers/shaders/UniformsUtils.js +++ b/src/renderers/shaders/UniformsUtils.js @@ -1,4 +1,4 @@ -import { LinearSRGBColorSpace } from '../../constants.js'; +import { ColorManagement } from '../../math/ColorManagement.js'; /** * Uniform Utilities @@ -93,7 +93,7 @@ export function getUnlitUniformColorSpace( renderer ) { } - return LinearSRGBColorSpace; + return ColorManagement.workingColorSpace; } diff --git a/src/renderers/webgl/WebGLBackground.js b/src/renderers/webgl/WebGLBackground.js index 041fec87767671..c236a0c2039a89 100644 --- a/src/renderers/webgl/WebGLBackground.js +++ b/src/renderers/webgl/WebGLBackground.js @@ -1,8 +1,9 @@ -import { BackSide, FrontSide, CubeUVReflectionMapping, SRGBColorSpace } from '../../constants.js'; +import { BackSide, FrontSide, CubeUVReflectionMapping, SRGBTransfer } from '../../constants.js'; import { BoxGeometry } from '../../geometries/BoxGeometry.js'; import { PlaneGeometry } from '../../geometries/PlaneGeometry.js'; import { ShaderMaterial } from '../../materials/ShaderMaterial.js'; import { Color } from '../../math/Color.js'; +import { ColorManagement } from '../../math/ColorManagement.js'; import { Mesh } from '../../objects/Mesh.js'; import { ShaderLib } from '../shaders/ShaderLib.js'; import { cloneUniforms, getUnlitUniformColorSpace } from '../shaders/UniformsUtils.js'; @@ -108,7 +109,7 @@ function WebGLBackground( renderer, cubemaps, cubeuvmaps, state, objects, alpha, boxMesh.material.uniforms.flipEnvMap.value = ( background.isCubeTexture && background.isRenderTargetTexture === false ) ? - 1 : 1; boxMesh.material.uniforms.backgroundBlurriness.value = scene.backgroundBlurriness; boxMesh.material.uniforms.backgroundIntensity.value = scene.backgroundIntensity; - boxMesh.material.toneMapped = ( background.colorSpace === SRGBColorSpace ) ? false : true; + boxMesh.material.toneMapped = ColorManagement.getTransfer( background.colorSpace ) !== SRGBTransfer; if ( currentBackground !== background || currentBackgroundVersion !== background.version || @@ -164,7 +165,7 @@ function WebGLBackground( renderer, cubemaps, cubeuvmaps, state, objects, alpha, planeMesh.material.uniforms.t2D.value = background; planeMesh.material.uniforms.backgroundIntensity.value = scene.backgroundIntensity; - planeMesh.material.toneMapped = ( background.colorSpace === SRGBColorSpace ) ? false : true; + planeMesh.material.toneMapped = ColorManagement.getTransfer( background.colorSpace ) !== SRGBTransfer; if ( background.matrixAutoUpdate === true ) { diff --git a/src/renderers/webgl/WebGLProgram.js b/src/renderers/webgl/WebGLProgram.js index ec5cd753dbf7dc..01614ad5a22079 100644 --- a/src/renderers/webgl/WebGLProgram.js +++ b/src/renderers/webgl/WebGLProgram.js @@ -1,7 +1,8 @@ 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, ACESFilmicToneMapping, CineonToneMapping, CustomToneMapping, ReinhardToneMapping, LinearToneMapping, GLSL3, LinearSRGBColorSpace, SRGBColorSpace } from '../../constants.js'; +import { NoToneMapping, AddOperation, MixOperation, MultiplyOperation, CubeRefractionMapping, CubeUVReflectionMapping, CubeReflectionMapping, PCFSoftShadowMap, PCFShadowMap, VSMShadowMap, ACESFilmicToneMapping, CineonToneMapping, CustomToneMapping, ReinhardToneMapping, LinearToneMapping, GLSL3, LinearSRGBColorSpace, SRGBColorSpace, LinearDisplayP3ColorSpace, DisplayP3ColorSpace, P3Primaries, Rec709Primaries } from '../../constants.js'; +import { ColorManagement } from '../../math/ColorManagement.js'; let programIdCount = 0; @@ -26,15 +27,38 @@ function handleSource( string, errorLine ) { function getEncodingComponents( colorSpace ) { + const workingPrimaries = ColorManagement.getPrimaries( ColorManagement.workingColorSpace ); + const encodingPrimaries = ColorManagement.getPrimaries( colorSpace ); + + let gamutMapping; + + if ( workingPrimaries === encodingPrimaries ) { + + gamutMapping = ''; + + } else if ( workingPrimaries === P3Primaries && encodingPrimaries === Rec709Primaries ) { + + gamutMapping = 'LinearDisplayP3ToLinearSRGB'; + + } else if ( workingPrimaries === Rec709Primaries && encodingPrimaries === P3Primaries ) { + + gamutMapping = 'LinearSRGBToLinearDisplayP3'; + + } + switch ( colorSpace ) { case LinearSRGBColorSpace: - return [ 'Linear', '( value )' ]; + case LinearDisplayP3ColorSpace: + return [ gamutMapping, 'LinearTransferOETF' ]; + case SRGBColorSpace: - return [ 'sRGB', '( value )' ]; + case DisplayP3ColorSpace: + return [ gamutMapping, 'sRGBTransferOETF' ]; + default: console.warn( 'THREE.WebGLProgram: Unsupported color space:', colorSpace ); - return [ 'Linear', '( value )' ]; + return [ gamutMapping, 'LinearTransferOETF' ]; } @@ -67,7 +91,7 @@ function getShaderErrors( gl, shader, type ) { function getTexelEncodingFunction( functionName, colorSpace ) { const components = getEncodingComponents( colorSpace ); - return 'vec4 ' + functionName + '( vec4 value ) { return LinearTo' + components[ 0 ] + components[ 1 ] + '; }'; + return `vec4 ${functionName}( vec4 value ) { return ${components[ 0 ]}( ${components[ 1 ]}( value ) ); }`; } diff --git a/src/renderers/webgl/WebGLPrograms.js b/src/renderers/webgl/WebGLPrograms.js index 0457aa0ebb1616..3546ed5823243c 100644 --- a/src/renderers/webgl/WebGLPrograms.js +++ b/src/renderers/webgl/WebGLPrograms.js @@ -1,9 +1,10 @@ -import { BackSide, DoubleSide, CubeUVReflectionMapping, ObjectSpaceNormalMap, TangentSpaceNormalMap, NoToneMapping, NormalBlending, LinearSRGBColorSpace, SRGBColorSpace } from '../../constants.js'; +import { BackSide, DoubleSide, CubeUVReflectionMapping, ObjectSpaceNormalMap, TangentSpaceNormalMap, NoToneMapping, NormalBlending, LinearSRGBColorSpace, SRGBTransfer } from '../../constants.js'; import { Layers } from '../../core/Layers.js'; import { WebGLProgram } from './WebGLProgram.js'; import { WebGLShaderCache } from './WebGLShaderCache.js'; import { ShaderLib } from '../shaders/ShaderLib.js'; import { UniformsUtils } from '../shaders/UniformsUtils.js'; +import { ColorManagement } from '../../math/ColorManagement.js'; function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities, bindingStates, clipping ) { @@ -335,7 +336,7 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities toneMapping: toneMapping, useLegacyLights: renderer._useLegacyLights, - decodeVideoTexture: HAS_MAP && ( material.map.isVideoTexture === true ) && ( material.map.colorSpace === SRGBColorSpace ), + decodeVideoTexture: HAS_MAP && ( material.map.isVideoTexture === true ) && ( ColorManagement.getTransfer( material.map.colorSpace ) === SRGBTransfer ), premultipliedAlpha: material.premultipliedAlpha, diff --git a/src/renderers/webgl/WebGLTextures.js b/src/renderers/webgl/WebGLTextures.js index 5065c3d94056b9..61bb37effa4ef6 100644 --- a/src/renderers/webgl/WebGLTextures.js +++ b/src/renderers/webgl/WebGLTextures.js @@ -1,7 +1,8 @@ -import { LinearFilter, LinearMipmapLinearFilter, LinearMipmapNearestFilter, NearestFilter, NearestMipmapLinearFilter, NearestMipmapNearestFilter, RGBAFormat, DepthFormat, DepthStencilFormat, UnsignedShortType, UnsignedIntType, UnsignedInt248Type, FloatType, HalfFloatType, MirroredRepeatWrapping, ClampToEdgeWrapping, RepeatWrapping, UnsignedByteType, _SRGBAFormat, NoColorSpace, LinearSRGBColorSpace, SRGBColorSpace, NeverCompare, AlwaysCompare, LessCompare, LessEqualCompare, EqualCompare, GreaterEqualCompare, GreaterCompare, NotEqualCompare, DisplayP3ColorSpace } from '../../constants.js'; +import { LinearFilter, LinearMipmapLinearFilter, LinearMipmapNearestFilter, NearestFilter, NearestMipmapLinearFilter, NearestMipmapNearestFilter, RGBAFormat, DepthFormat, DepthStencilFormat, UnsignedShortType, UnsignedIntType, UnsignedInt248Type, FloatType, HalfFloatType, MirroredRepeatWrapping, ClampToEdgeWrapping, RepeatWrapping, UnsignedByteType, _SRGBAFormat, NoColorSpace, LinearSRGBColorSpace, NeverCompare, AlwaysCompare, LessCompare, LessEqualCompare, EqualCompare, GreaterEqualCompare, GreaterCompare, NotEqualCompare, SRGBTransfer, LinearTransfer } from '../../constants.js'; import * as MathUtils from '../../math/MathUtils.js'; import { ImageUtils } from '../../extras/ImageUtils.js'; import { createElementNS } from '../../utils.js'; +import { ColorManagement } from '../../math/ColorManagement.js'; function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info ) { @@ -178,9 +179,11 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, if ( glFormat === _gl.RGBA ) { + const transfer = forceLinearTransfer ? LinearTransfer : ColorManagement.getTransfer( colorSpace ); + if ( glType === _gl.FLOAT ) internalFormat = _gl.RGBA32F; if ( glType === _gl.HALF_FLOAT ) internalFormat = _gl.RGBA16F; - if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = ( colorSpace === SRGBColorSpace && forceLinearTransfer === false ) ? _gl.SRGB8_ALPHA8 : _gl.RGBA8; + if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = ( transfer === SRGBTransfer ) ? _gl.SRGB8_ALPHA8 : _gl.RGBA8; if ( glType === _gl.UNSIGNED_SHORT_4_4_4_4 ) internalFormat = _gl.RGBA4; if ( glType === _gl.UNSIGNED_SHORT_5_5_5_1 ) internalFormat = _gl.RGB5_A1; @@ -735,10 +738,14 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, state.activeTexture( _gl.TEXTURE0 + slot ); + const workingPrimaries = ColorManagement.getPrimaries( ColorManagement.workingColorSpace ); + const texturePrimaries = texture.colorSpace === NoColorSpace ? null : ColorManagement.getPrimaries( texture.colorSpace ); + const unpackConversion = texture.colorSpace === NoColorSpace || workingPrimaries === texturePrimaries ? _gl.NONE : _gl.BROWSER_DEFAULT_WEBGL; + _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY ); _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha ); _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, texture.unpackAlignment ); - _gl.pixelStorei( _gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, _gl.NONE ); + _gl.pixelStorei( _gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, unpackConversion ); const needsPowerOfTwo = textureNeedsPowerOfTwo( texture ) && isPowerOfTwo( texture.image ) === false; let image = resizeImage( texture.image, needsPowerOfTwo, false, maxTextureSize ); @@ -1149,10 +1156,14 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, state.activeTexture( _gl.TEXTURE0 + slot ); + const workingPrimaries = ColorManagement.getPrimaries( ColorManagement.workingColorSpace ); + const texturePrimaries = texture.colorSpace === NoColorSpace ? null : ColorManagement.getPrimaries( texture.colorSpace ); + const unpackConversion = texture.colorSpace === NoColorSpace || workingPrimaries === texturePrimaries ? _gl.NONE : _gl.BROWSER_DEFAULT_WEBGL; + _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY ); _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha ); _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, texture.unpackAlignment ); - _gl.pixelStorei( _gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, _gl.NONE ); + _gl.pixelStorei( _gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, unpackConversion ); const isCompressed = ( texture.isCompressedTexture || texture.image[ 0 ].isCompressedTexture ); const isDataTexture = ( texture.image[ 0 ] && texture.image[ 0 ].isDataTexture ); @@ -2033,7 +2044,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, // sRGB - if ( colorSpace === SRGBColorSpace || colorSpace === DisplayP3ColorSpace ) { + if ( ColorManagement.getTransfer( colorSpace ) === SRGBTransfer ) { if ( isWebGL2 === false ) { diff --git a/src/renderers/webgl/WebGLUtils.js b/src/renderers/webgl/WebGLUtils.js index 986bf0a86e4e93..989c84989c9c0b 100644 --- a/src/renderers/webgl/WebGLUtils.js +++ b/src/renderers/webgl/WebGLUtils.js @@ -1,7 +1,5 @@ -import { RGBA_ASTC_4x4_Format, RGBA_ASTC_5x4_Format, RGBA_ASTC_5x5_Format, RGBA_ASTC_6x5_Format, RGBA_ASTC_6x6_Format, RGBA_ASTC_8x5_Format, RGBA_ASTC_8x6_Format, RGBA_ASTC_8x8_Format, RGBA_ASTC_10x5_Format, RGBA_ASTC_10x6_Format, RGBA_ASTC_10x8_Format, RGBA_ASTC_10x10_Format, RGBA_ASTC_12x10_Format, RGBA_ASTC_12x12_Format, RGB_ETC1_Format, RGB_ETC2_Format, RGBA_ETC2_EAC_Format, RGBA_PVRTC_2BPPV1_Format, RGBA_PVRTC_4BPPV1_Format, RGB_PVRTC_2BPPV1_Format, RGB_PVRTC_4BPPV1_Format, RGBA_S3TC_DXT5_Format, RGBA_S3TC_DXT3_Format, RGBA_S3TC_DXT1_Format, RGB_S3TC_DXT1_Format, DepthFormat, DepthStencilFormat, LuminanceAlphaFormat, LuminanceFormat, RedFormat, RGBAFormat, AlphaFormat, RedIntegerFormat, RGFormat, RGIntegerFormat, RGBAIntegerFormat, HalfFloatType, FloatType, UnsignedIntType, IntType, UnsignedShortType, ShortType, ByteType, UnsignedInt248Type, UnsignedShort5551Type, UnsignedShort4444Type, UnsignedByteType, RGBA_BPTC_Format, RGB_BPTC_SIGNED_Format, RGB_BPTC_UNSIGNED_Format, _SRGBAFormat, RED_RGTC1_Format, SIGNED_RED_RGTC1_Format, RED_GREEN_RGTC2_Format, SIGNED_RED_GREEN_RGTC2_Format, SRGBColorSpace, NoColorSpace, DisplayP3ColorSpace } from '../../constants.js'; - -const LinearTransferFunction = 0; -const SRGBTransferFunction = 1; +import { RGBA_ASTC_4x4_Format, RGBA_ASTC_5x4_Format, RGBA_ASTC_5x5_Format, RGBA_ASTC_6x5_Format, RGBA_ASTC_6x6_Format, RGBA_ASTC_8x5_Format, RGBA_ASTC_8x6_Format, RGBA_ASTC_8x8_Format, RGBA_ASTC_10x5_Format, RGBA_ASTC_10x6_Format, RGBA_ASTC_10x8_Format, RGBA_ASTC_10x10_Format, RGBA_ASTC_12x10_Format, RGBA_ASTC_12x12_Format, RGB_ETC1_Format, RGB_ETC2_Format, RGBA_ETC2_EAC_Format, RGBA_PVRTC_2BPPV1_Format, RGBA_PVRTC_4BPPV1_Format, RGB_PVRTC_2BPPV1_Format, RGB_PVRTC_4BPPV1_Format, RGBA_S3TC_DXT5_Format, RGBA_S3TC_DXT3_Format, RGBA_S3TC_DXT1_Format, RGB_S3TC_DXT1_Format, DepthFormat, DepthStencilFormat, LuminanceAlphaFormat, LuminanceFormat, RedFormat, RGBAFormat, AlphaFormat, RedIntegerFormat, RGFormat, RGIntegerFormat, RGBAIntegerFormat, HalfFloatType, FloatType, UnsignedIntType, IntType, UnsignedShortType, ShortType, ByteType, UnsignedInt248Type, UnsignedShort5551Type, UnsignedShort4444Type, UnsignedByteType, RGBA_BPTC_Format, RGB_BPTC_SIGNED_Format, RGB_BPTC_UNSIGNED_Format, _SRGBAFormat, RED_RGTC1_Format, SIGNED_RED_RGTC1_Format, RED_GREEN_RGTC2_Format, SIGNED_RED_GREEN_RGTC2_Format, NoColorSpace, SRGBTransfer } from '../../constants.js'; +import { ColorManagement } from '../../math/ColorManagement.js'; function WebGLUtils( gl, extensions, capabilities ) { @@ -11,7 +9,7 @@ function WebGLUtils( gl, extensions, capabilities ) { let extension; - const transferFunction = ( colorSpace === SRGBColorSpace || colorSpace === DisplayP3ColorSpace ) ? SRGBTransferFunction : LinearTransferFunction; + const transfer = ColorManagement.getTransfer( colorSpace ); if ( p === UnsignedByteType ) return gl.UNSIGNED_BYTE; if ( p === UnsignedShort4444Type ) return gl.UNSIGNED_SHORT_4_4_4_4; @@ -79,7 +77,7 @@ function WebGLUtils( gl, extensions, capabilities ) { if ( p === RGB_S3TC_DXT1_Format || p === RGBA_S3TC_DXT1_Format || p === RGBA_S3TC_DXT3_Format || p === RGBA_S3TC_DXT5_Format ) { - if ( transferFunction === SRGBTransferFunction ) { + if ( transfer === SRGBTransfer ) { extension = extensions.get( 'WEBGL_compressed_texture_s3tc_srgb' ); @@ -164,8 +162,8 @@ function WebGLUtils( gl, extensions, capabilities ) { if ( extension !== null ) { - if ( p === RGB_ETC2_Format ) return ( transferFunction === SRGBTransferFunction ) ? extension.COMPRESSED_SRGB8_ETC2 : extension.COMPRESSED_RGB8_ETC2; - if ( p === RGBA_ETC2_EAC_Format ) return ( transferFunction === SRGBTransferFunction ) ? extension.COMPRESSED_SRGB8_ALPHA8_ETC2_EAC : extension.COMPRESSED_RGBA8_ETC2_EAC; + if ( p === RGB_ETC2_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ETC2 : extension.COMPRESSED_RGB8_ETC2; + if ( p === RGBA_ETC2_EAC_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ETC2_EAC : extension.COMPRESSED_RGBA8_ETC2_EAC; } else { @@ -187,20 +185,20 @@ function WebGLUtils( gl, extensions, capabilities ) { if ( extension !== null ) { - if ( p === RGBA_ASTC_4x4_Format ) return ( transferFunction === SRGBTransferFunction ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR : extension.COMPRESSED_RGBA_ASTC_4x4_KHR; - if ( p === RGBA_ASTC_5x4_Format ) return ( transferFunction === SRGBTransferFunction ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR : extension.COMPRESSED_RGBA_ASTC_5x4_KHR; - if ( p === RGBA_ASTC_5x5_Format ) return ( transferFunction === SRGBTransferFunction ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR : extension.COMPRESSED_RGBA_ASTC_5x5_KHR; - if ( p === RGBA_ASTC_6x5_Format ) return ( transferFunction === SRGBTransferFunction ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR : extension.COMPRESSED_RGBA_ASTC_6x5_KHR; - if ( p === RGBA_ASTC_6x6_Format ) return ( transferFunction === SRGBTransferFunction ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR : extension.COMPRESSED_RGBA_ASTC_6x6_KHR; - if ( p === RGBA_ASTC_8x5_Format ) return ( transferFunction === SRGBTransferFunction ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR : extension.COMPRESSED_RGBA_ASTC_8x5_KHR; - if ( p === RGBA_ASTC_8x6_Format ) return ( transferFunction === SRGBTransferFunction ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR : extension.COMPRESSED_RGBA_ASTC_8x6_KHR; - if ( p === RGBA_ASTC_8x8_Format ) return ( transferFunction === SRGBTransferFunction ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR : extension.COMPRESSED_RGBA_ASTC_8x8_KHR; - if ( p === RGBA_ASTC_10x5_Format ) return ( transferFunction === SRGBTransferFunction ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR : extension.COMPRESSED_RGBA_ASTC_10x5_KHR; - if ( p === RGBA_ASTC_10x6_Format ) return ( transferFunction === SRGBTransferFunction ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR : extension.COMPRESSED_RGBA_ASTC_10x6_KHR; - if ( p === RGBA_ASTC_10x8_Format ) return ( transferFunction === SRGBTransferFunction ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR : extension.COMPRESSED_RGBA_ASTC_10x8_KHR; - if ( p === RGBA_ASTC_10x10_Format ) return ( transferFunction === SRGBTransferFunction ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR : extension.COMPRESSED_RGBA_ASTC_10x10_KHR; - if ( p === RGBA_ASTC_12x10_Format ) return ( transferFunction === SRGBTransferFunction ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR : extension.COMPRESSED_RGBA_ASTC_12x10_KHR; - if ( p === RGBA_ASTC_12x12_Format ) return ( transferFunction === SRGBTransferFunction ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR : extension.COMPRESSED_RGBA_ASTC_12x12_KHR; + if ( p === RGBA_ASTC_4x4_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR : extension.COMPRESSED_RGBA_ASTC_4x4_KHR; + if ( p === RGBA_ASTC_5x4_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR : extension.COMPRESSED_RGBA_ASTC_5x4_KHR; + if ( p === RGBA_ASTC_5x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR : extension.COMPRESSED_RGBA_ASTC_5x5_KHR; + if ( p === RGBA_ASTC_6x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR : extension.COMPRESSED_RGBA_ASTC_6x5_KHR; + if ( p === RGBA_ASTC_6x6_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR : extension.COMPRESSED_RGBA_ASTC_6x6_KHR; + if ( p === RGBA_ASTC_8x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR : extension.COMPRESSED_RGBA_ASTC_8x5_KHR; + if ( p === RGBA_ASTC_8x6_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR : extension.COMPRESSED_RGBA_ASTC_8x6_KHR; + if ( p === RGBA_ASTC_8x8_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR : extension.COMPRESSED_RGBA_ASTC_8x8_KHR; + if ( p === RGBA_ASTC_10x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR : extension.COMPRESSED_RGBA_ASTC_10x5_KHR; + if ( p === RGBA_ASTC_10x6_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR : extension.COMPRESSED_RGBA_ASTC_10x6_KHR; + if ( p === RGBA_ASTC_10x8_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR : extension.COMPRESSED_RGBA_ASTC_10x8_KHR; + if ( p === RGBA_ASTC_10x10_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR : extension.COMPRESSED_RGBA_ASTC_10x10_KHR; + if ( p === RGBA_ASTC_12x10_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR : extension.COMPRESSED_RGBA_ASTC_12x10_KHR; + if ( p === RGBA_ASTC_12x12_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR : extension.COMPRESSED_RGBA_ASTC_12x12_KHR; } else { @@ -218,7 +216,7 @@ function WebGLUtils( gl, extensions, capabilities ) { if ( extension !== null ) { - if ( p === RGBA_BPTC_Format ) return ( transferFunction === SRGBTransferFunction ) ? extension.COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT : extension.COMPRESSED_RGBA_BPTC_UNORM_EXT; + if ( p === RGBA_BPTC_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT : extension.COMPRESSED_RGBA_BPTC_UNORM_EXT; if ( p === RGB_BPTC_SIGNED_Format ) return extension.COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT; if ( p === RGB_BPTC_UNSIGNED_Format ) return extension.COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT;