From cfe2889285ee5de8d322838a4b404d36bcea7bdb Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Thu, 26 Sep 2024 22:19:02 -0400 Subject: [PATCH 1/7] TSL: Add CDLNode --- src/nodes/Nodes.js | 1 + src/nodes/TSL.js | 1 + src/nodes/display/CDLNode.js | 83 ++++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 src/nodes/display/CDLNode.js diff --git a/src/nodes/Nodes.js b/src/nodes/Nodes.js index a3c58eb14f4a62..88b1ff58dd67ab 100644 --- a/src/nodes/Nodes.js +++ b/src/nodes/Nodes.js @@ -83,6 +83,7 @@ export { default as UserDataNode } from './accessors/UserDataNode.js'; // display export { default as BumpMapNode } from './display/BumpMapNode.js'; +export { default as CDLNode } from './display/CDLNode.js'; export { default as ColorSpaceNode } from './display/ColorSpaceNode.js'; export { default as FrontFacingNode } from './display/FrontFacingNode.js'; export { default as NormalMapNode } from './display/NormalMapNode.js'; diff --git a/src/nodes/TSL.js b/src/nodes/TSL.js index ae0d624e14fb2f..c8a6c406d7cff7 100644 --- a/src/nodes/TSL.js +++ b/src/nodes/TSL.js @@ -84,6 +84,7 @@ export * from './accessors/VelocityNode.js'; // display export * from './display/BlendMode.js'; export * from './display/BumpMapNode.js'; +export * from './display/CDLNode.js'; export * from './display/ColorAdjustment.js'; export * from './display/ColorSpaceNode.js'; export * from './display/FrontFacingNode.js'; diff --git a/src/nodes/display/CDLNode.js b/src/nodes/display/CDLNode.js new file mode 100644 index 00000000000000..870d8f83a8cb67 --- /dev/null +++ b/src/nodes/display/CDLNode.js @@ -0,0 +1,83 @@ +import TempNode from '../core/TempNode.js'; +import { Fn, nodeObject, vec3, vec4 } from '../tsl/TSLBase.js'; +import { max } from '../math/MathNode.js'; +import { LinearSRGBColorSpace } from '../../constants.js'; +import { ColorManagement } from '../../math/ColorManagement.js'; +import { Vector3 } from '../../math/Vector3.js'; + +/** + * Color Decision List (CDL) v1.2 + * + * References: + * - ASC CDL v1.2 + * - https://blender.stackexchange.com/a/55239/43930 + * - https://docs.acescentral.com/specifications/acescc/ + */ +class CDLNode extends TempNode { + + static get type() { + + return 'CDLNode'; + + } + + constructor( inputNode, slopeNode, offsetNode, powerNode, saturationNode ) { + + super(); + + this.inputNode = inputNode; + this.slopeNode = slopeNode; + this.offsetNode = offsetNode; + this.powerNode = powerNode; + this.saturationNode = saturationNode; + + // ASC CDL v1.2 explicitly requires Rec. 709 luminance coefficients, without input conversion to Rec. 709. + this.luminanceCoefficients = ColorManagement.getLuminanceCoefficients( new Vector3(), LinearSRGBColorSpace ); + + } + + setup() { + + const { inputNode, slopeNode, offsetNode, powerNode, saturationNode, luminanceCoefficients } = this; + + const cdl = Fn( () => { + + // NOTE: The ASC CDL v1.2 defines a [0, 1] clamp on slope+offset output, + // and another on saturation output. As discussed in ACEScc specification + // notes on CDL application, the limits may be omitted to support values >1 + // if negative inputs to the power expression are avoided. + // + // We use `max( in, 0.0 )` for this reason, but the lower limit may not be + // required in all cases. + + const luma = inputNode.rgb.dot( vec3( luminanceCoefficients ) ); + + // clamp( ( in * slope ) + offset ) ^ power + const output = max( inputNode.rgb.mul( slopeNode ).add( offsetNode ), 0.0 ).pow( powerNode ).toVar(); + + // clamp( luma + sat * ( in - luma ) ) + output.assign( max( luma.add( saturationNode.mul( output.sub( luma ) ) ), 0.0 ) ); + + return vec4( output.rgb, inputNode.a ); + + } ); + + const outputNode = cdl(); + + return outputNode; + + } + +} + +export default CDLNode; + +export const cdl = ( node, slope, offset, power, saturation ) => nodeObject( + new CDLNode( + nodeObject( node ), + nodeObject( slope ), + nodeObject( offset ), + nodeObject( power ), + nodeObject( saturation ) + ) +); From 399097a0944946489c680af2c6436814f78fd9e5 Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Thu, 26 Sep 2024 23:33:23 -0400 Subject: [PATCH 2/7] CDLNode: Add default vec3 inputs --- src/nodes/display/CDLNode.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nodes/display/CDLNode.js b/src/nodes/display/CDLNode.js index 870d8f83a8cb67..e3d37d81bb6069 100644 --- a/src/nodes/display/CDLNode.js +++ b/src/nodes/display/CDLNode.js @@ -21,7 +21,7 @@ class CDLNode extends TempNode { } - constructor( inputNode, slopeNode, offsetNode, powerNode, saturationNode ) { + constructor( inputNode, slopeNode = vec3( 1 ), offsetNode = vec3( 0 ), powerNode = vec3( 1 ), saturationNode = vec3( 1 ) ) { super(); From ee126f206da4a5105239a2301843ecad0602845d Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Thu, 26 Sep 2024 23:37:17 -0400 Subject: [PATCH 3/7] CDLNode: Add default vec3 inputs to cdl() --- src/nodes/display/CDLNode.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nodes/display/CDLNode.js b/src/nodes/display/CDLNode.js index e3d37d81bb6069..1ef7b8acd6b0e4 100644 --- a/src/nodes/display/CDLNode.js +++ b/src/nodes/display/CDLNode.js @@ -72,7 +72,7 @@ class CDLNode extends TempNode { export default CDLNode; -export const cdl = ( node, slope, offset, power, saturation ) => nodeObject( +export const cdl = ( node, slope = vec3( 1 ), offset = vec3( 0 ), power = vec3( 1 ), saturation = vec3( 1 ) ) => nodeObject( new CDLNode( nodeObject( node ), nodeObject( slope ), From 200a0c2abbd27ae2aa251bc8d12a4bb3d6187f40 Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Wed, 2 Oct 2024 23:04:31 -0400 Subject: [PATCH 4/7] move cdl() function to ColorAdjustments.js --- src/nodes/Nodes.js | 1 - src/nodes/TSL.js | 1 - src/nodes/display/CDLNode.js | 83 ---------------------------- src/nodes/display/ColorAdjustment.js | 44 ++++++++++++++- 4 files changed, 42 insertions(+), 87 deletions(-) delete mode 100644 src/nodes/display/CDLNode.js diff --git a/src/nodes/Nodes.js b/src/nodes/Nodes.js index 88b1ff58dd67ab..a3c58eb14f4a62 100644 --- a/src/nodes/Nodes.js +++ b/src/nodes/Nodes.js @@ -83,7 +83,6 @@ export { default as UserDataNode } from './accessors/UserDataNode.js'; // display export { default as BumpMapNode } from './display/BumpMapNode.js'; -export { default as CDLNode } from './display/CDLNode.js'; export { default as ColorSpaceNode } from './display/ColorSpaceNode.js'; export { default as FrontFacingNode } from './display/FrontFacingNode.js'; export { default as NormalMapNode } from './display/NormalMapNode.js'; diff --git a/src/nodes/TSL.js b/src/nodes/TSL.js index c8a6c406d7cff7..ae0d624e14fb2f 100644 --- a/src/nodes/TSL.js +++ b/src/nodes/TSL.js @@ -84,7 +84,6 @@ export * from './accessors/VelocityNode.js'; // display export * from './display/BlendMode.js'; export * from './display/BumpMapNode.js'; -export * from './display/CDLNode.js'; export * from './display/ColorAdjustment.js'; export * from './display/ColorSpaceNode.js'; export * from './display/FrontFacingNode.js'; diff --git a/src/nodes/display/CDLNode.js b/src/nodes/display/CDLNode.js deleted file mode 100644 index 1ef7b8acd6b0e4..00000000000000 --- a/src/nodes/display/CDLNode.js +++ /dev/null @@ -1,83 +0,0 @@ -import TempNode from '../core/TempNode.js'; -import { Fn, nodeObject, vec3, vec4 } from '../tsl/TSLBase.js'; -import { max } from '../math/MathNode.js'; -import { LinearSRGBColorSpace } from '../../constants.js'; -import { ColorManagement } from '../../math/ColorManagement.js'; -import { Vector3 } from '../../math/Vector3.js'; - -/** - * Color Decision List (CDL) v1.2 - * - * References: - * - ASC CDL v1.2 - * - https://blender.stackexchange.com/a/55239/43930 - * - https://docs.acescentral.com/specifications/acescc/ - */ -class CDLNode extends TempNode { - - static get type() { - - return 'CDLNode'; - - } - - constructor( inputNode, slopeNode = vec3( 1 ), offsetNode = vec3( 0 ), powerNode = vec3( 1 ), saturationNode = vec3( 1 ) ) { - - super(); - - this.inputNode = inputNode; - this.slopeNode = slopeNode; - this.offsetNode = offsetNode; - this.powerNode = powerNode; - this.saturationNode = saturationNode; - - // ASC CDL v1.2 explicitly requires Rec. 709 luminance coefficients, without input conversion to Rec. 709. - this.luminanceCoefficients = ColorManagement.getLuminanceCoefficients( new Vector3(), LinearSRGBColorSpace ); - - } - - setup() { - - const { inputNode, slopeNode, offsetNode, powerNode, saturationNode, luminanceCoefficients } = this; - - const cdl = Fn( () => { - - // NOTE: The ASC CDL v1.2 defines a [0, 1] clamp on slope+offset output, - // and another on saturation output. As discussed in ACEScc specification - // notes on CDL application, the limits may be omitted to support values >1 - // if negative inputs to the power expression are avoided. - // - // We use `max( in, 0.0 )` for this reason, but the lower limit may not be - // required in all cases. - - const luma = inputNode.rgb.dot( vec3( luminanceCoefficients ) ); - - // clamp( ( in * slope ) + offset ) ^ power - const output = max( inputNode.rgb.mul( slopeNode ).add( offsetNode ), 0.0 ).pow( powerNode ).toVar(); - - // clamp( luma + sat * ( in - luma ) ) - output.assign( max( luma.add( saturationNode.mul( output.sub( luma ) ) ), 0.0 ) ); - - return vec4( output.rgb, inputNode.a ); - - } ); - - const outputNode = cdl(); - - return outputNode; - - } - -} - -export default CDLNode; - -export const cdl = ( node, slope = vec3( 1 ), offset = vec3( 0 ), power = vec3( 1 ), saturation = vec3( 1 ) ) => nodeObject( - new CDLNode( - nodeObject( node ), - nodeObject( slope ), - nodeObject( offset ), - nodeObject( power ), - nodeObject( saturation ) - ) -); diff --git a/src/nodes/display/ColorAdjustment.js b/src/nodes/display/ColorAdjustment.js index 640c9b59fb7787..153b6158b15965 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,42 @@ 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 + * + * 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 = vec3( 1 ), + // ASC CDL v1.2 explicitly requires Rec. 709 luminance coefficients, without input conversion to Rec. 709. + luminanceCoefficients = vec3( ColorManagement.getLuminanceCoefficients( new Vector3(), LinearSRGBColorSpace ) ) +] ) => { + + // NOTE: The ASC CDL v1.2 defines a [0, 1] clamp on slope+offset output, + // and another on saturation output. As discussed in the ACEScc specification + // and Filament implementation, the limits may be omitted to support values >1 + // if negative inputs to the power expression are avoided. We use `max( in, 0.0 )` + // on final output, but the lower limit may not be required in all cases. + + const luma = color.rgb.dot( vec3( luminanceCoefficients ) ); + + const v = max( color.rgb.mul( slope ).add( offset ), 0.0 ).toVar( 'v' ); + const pv = v.pow( power ).toVar( 'pv' ); + + 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( max( luma.add( saturation.mul( v.sub( luma ) ) ), 0.0 ) ); + + return vec4( v.rgb, color.a ); + +} ); From d90bdd50ac3d9b17940fef334d603d28af515772 Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Thu, 3 Oct 2024 16:36:20 -0400 Subject: [PATCH 5/7] clean up cdl(), add more JSDoc --- src/nodes/display/ColorAdjustment.js | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/nodes/display/ColorAdjustment.js b/src/nodes/display/ColorAdjustment.js index 153b6158b15965..7c6084f9cfbe3f 100644 --- a/src/nodes/display/ColorAdjustment.js +++ b/src/nodes/display/ColorAdjustment.js @@ -49,6 +49,18 @@ export const threshold = ( color, threshold ) => mix( vec3( 0.0 ), color, lumina /** * 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, +Infinity) + * @param {number | vec3} slope [0, +Infinity) + * @param {number | vec3} offset (-Infinity, +Infinity), and typically [-1, 1] + * @param {number | vec3} power (0, +Infinity) + * @param {number} saturation [0, +Infinity), and typically [0, 4] + * @param {vec3} luminanceCoefficients Luminance coefficients for saturation term, typically Rec. 709 + * @return Output on range (-Infinity, +Infinity) + * * References: * - ASC CDL v1.2 * - https://blender.stackexchange.com/a/55239/43930 @@ -59,27 +71,25 @@ export const cdl = /*@__PURE__*/ Fn( ( [ slope = vec3( 1 ), offset = vec3( 0 ), power = vec3( 1 ), - saturation = vec3( 1 ), + saturation = float( 1 ), // ASC CDL v1.2 explicitly requires Rec. 709 luminance coefficients, without input conversion to Rec. 709. luminanceCoefficients = vec3( ColorManagement.getLuminanceCoefficients( new Vector3(), LinearSRGBColorSpace ) ) ] ) => { - // NOTE: The ASC CDL v1.2 defines a [0, 1] clamp on slope+offset output, - // and another on saturation output. As discussed in the ACEScc specification - // and Filament implementation, the limits may be omitted to support values >1 - // if negative inputs to the power expression are avoided. We use `max( in, 0.0 )` - // on final output, but the lower limit may not be required in all cases. + // 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( 'v' ); - const pv = v.pow( power ).toVar( 'pv' ); + 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( max( luma.add( saturation.mul( v.sub( luma ) ) ), 0.0 ) ); + v.assign( luma.add( v.sub( luma ).mul( saturation ) ) ); return vec4( v.rgb, color.a ); From 331939a46f033f5b445b9dd1bdda1247c35da345 Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Thu, 3 Oct 2024 16:44:25 -0400 Subject: [PATCH 6/7] clean up --- src/nodes/display/ColorAdjustment.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/nodes/display/ColorAdjustment.js b/src/nodes/display/ColorAdjustment.js index 7c6084f9cfbe3f..fac372ffb4dcef 100644 --- a/src/nodes/display/ColorAdjustment.js +++ b/src/nodes/display/ColorAdjustment.js @@ -53,13 +53,13 @@ export const threshold = ( color, threshold ) => mix( vec3( 0.0 ), color, lumina * 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, +Infinity) - * @param {number | vec3} slope [0, +Infinity) - * @param {number | vec3} offset (-Infinity, +Infinity), and typically [-1, 1] - * @param {number | vec3} power (0, +Infinity) - * @param {number} saturation [0, +Infinity), and typically [0, 4] + * @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 on range (-Infinity, +Infinity) + * @return Output, -Infinity < output < +Infinity * * References: * - ASC CDL v1.2 From aa0ad970845bbd0f09253f80accb9aa02775328a Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Thu, 3 Oct 2024 16:45:26 -0400 Subject: [PATCH 7/7] clean up --- src/nodes/display/ColorAdjustment.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nodes/display/ColorAdjustment.js b/src/nodes/display/ColorAdjustment.js index fac372ffb4dcef..fbbdb74424cf4c 100644 --- a/src/nodes/display/ColorAdjustment.js +++ b/src/nodes/display/ColorAdjustment.js @@ -72,7 +72,7 @@ export const cdl = /*@__PURE__*/ Fn( ( [ offset = vec3( 0 ), power = vec3( 1 ), saturation = float( 1 ), - // ASC CDL v1.2 explicitly requires Rec. 709 luminance coefficients, without input conversion to Rec. 709. + // ASC CDL v1.2 explicitly requires Rec. 709 luminance coefficients. luminanceCoefficients = vec3( ColorManagement.getLuminanceCoefficients( new Vector3(), LinearSRGBColorSpace ) ) ] ) => {