diff --git a/src/nodes/display/ColorAdjustment.js b/src/nodes/display/ColorAdjustment.js index 640c9b59fb7787..fbbdb74424cf4c 100644 --- a/src/nodes/display/ColorAdjustment.js +++ b/src/nodes/display/ColorAdjustment.js @@ -1,8 +1,9 @@ -import { dot, mix } from '../math/MathNode.js'; +import { dot, max, mix } from '../math/MathNode.js'; import { add } from '../math/OperatorNode.js'; -import { Fn, float, vec3 } from '../tsl/TSLBase.js'; +import { Fn, If, float, vec3, vec4 } from '../tsl/TSLBase.js'; import { ColorManagement } from '../../math/ColorManagement.js'; import { Vector3 } from '../../math/Vector3.js'; +import { LinearSRGBColorSpace } from '../../constants.js'; export const grayscale = /*@__PURE__*/ Fn( ( [ color ] ) => { @@ -44,3 +45,52 @@ export const luminance = ( ) => dot( color, luminanceCoefficients ); export const threshold = ( color, threshold ) => mix( vec3( 0.0 ), color, luminance( color ).sub( threshold ).max( 0 ) ); + +/** + * Color Decision List (CDL) v1.2 + * + * Compact representation of color grading information, defined by slope, offset, power, and + * saturation. The CDL should be typically be given input in a log space (such as LogC, ACEScc, + * or AgX Log), and will return output in the same space. Output may require clamping >=0. + * + * @param {vec4} color Input (-Infinity < input < +Infinity) + * @param {number | vec3} slope Slope (0 ≤ slope < +Infinity) + * @param {number | vec3} offset Offset (-Infinity < offset < +Infinity; typically -1 < offset < 1) + * @param {number | vec3} power Power (0 < power < +Infinity) + * @param {number} saturation Saturation (0 ≤ saturation < +Infinity; typically 0 ≤ saturation < 4) + * @param {vec3} luminanceCoefficients Luminance coefficients for saturation term, typically Rec. 709 + * @return Output, -Infinity < output < +Infinity + * + * References: + * - ASC CDL v1.2 + * - https://blender.stackexchange.com/a/55239/43930 + * - https://docs.acescentral.com/specifications/acescc/ + */ +export const cdl = /*@__PURE__*/ Fn( ( [ + color, + slope = vec3( 1 ), + offset = vec3( 0 ), + power = vec3( 1 ), + saturation = float( 1 ), + // ASC CDL v1.2 explicitly requires Rec. 709 luminance coefficients. + luminanceCoefficients = vec3( ColorManagement.getLuminanceCoefficients( new Vector3(), LinearSRGBColorSpace ) ) +] ) => { + + // NOTE: The ASC CDL v1.2 defines a [0, 1] clamp on the slope+offset term, and another on the + // saturation term. Per the ACEScc specification and Filament, limits may be omitted to support + // values outside [0, 1], requiring a workaround for negative values in the power expression. + + const luma = color.rgb.dot( vec3( luminanceCoefficients ) ); + + const v = max( color.rgb.mul( slope ).add( offset ), 0.0 ).toVar(); + const pv = v.pow( power ).toVar(); + + If( v.r.greaterThan( 0.0 ), () => { v.r.assign( pv.r ); } ); // eslint-disable-line + If( v.g.greaterThan( 0.0 ), () => { v.g.assign( pv.g ); } ); // eslint-disable-line + If( v.b.greaterThan( 0.0 ), () => { v.b.assign( pv.b ); } ); // eslint-disable-line + + v.assign( luma.add( v.sub( luma ).mul( saturation ) ) ); + + return vec4( v.rgb, color.a ); + +} );