Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WebGLRenderer: Add support for AgX Tone Mapping #27366

Merged
merged 20 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions examples/jsm/postprocessing/OutputPass.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
LinearToneMapping,
ReinhardToneMapping,
CineonToneMapping,
AgXToneMapping,
ACESFilmicToneMapping,
SRGBTransfer
} from 'three';
Expand Down Expand Up @@ -59,6 +60,7 @@ class OutputPass extends Pass {
else if ( this._toneMapping === ReinhardToneMapping ) this.material.defines.REINHARD_TONE_MAPPING = '';
else if ( this._toneMapping === CineonToneMapping ) this.material.defines.CINEON_TONE_MAPPING = '';
else if ( this._toneMapping === ACESFilmicToneMapping ) this.material.defines.ACES_FILMIC_TONE_MAPPING = '';
else if ( this._toneMapping === AgXToneMapping ) this.material.defines.AGX_TONE_MAPPING = '';

this.material.needsUpdate = true;

Expand Down
4 changes: 4 additions & 0 deletions examples/jsm/shaders/OutputShader.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ const OutputShader = {

gl_FragColor.rgb = ACESFilmicToneMapping( gl_FragColor.rgb );

#elif defined( AGX_TONE_MAPPING )

gl_FragColor.rgb = AgXToneMapping( gl_FragColor.rgb );

#endif

// color space
Expand Down
1 change: 1 addition & 0 deletions examples/webgl_tonemapping.html
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
Reinhard: THREE.ReinhardToneMapping,
Cineon: THREE.CineonToneMapping,
ACESFilmic: THREE.ACESFilmicToneMapping,
AgX: THREE.AgXToneMapping,
Custom: THREE.CustomToneMapping
};

Expand Down
1 change: 1 addition & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export const ReinhardToneMapping = 2;
export const CineonToneMapping = 3;
export const ACESFilmicToneMapping = 4;
export const CustomToneMapping = 5;
export const AgXToneMapping = 6;
gkjohnson marked this conversation as resolved.
Show resolved Hide resolved
export const AttachedBindMode = 'attached';
export const DetachedBindMode = 'detached';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,89 @@ vec3 ACESFilmicToneMapping( vec3 color ) {

}

// Matrices for rec 2020 <> rec 709 color space conversion
// matrix provided in row-major order so it has been transposed
// https://www.itu.int/pub/R-REP-BT.2407-2017
const mat3 LINEAR_REC2020_TO_LINEAR_SRGB = mat3(
vec3( 1.6605, - 0.1246, - 0.0182 ),
vec3( - 0.5876, 1.1329, - 0.1006 ),
vec3( - 0.0728, - 0.0083, 1.1187 )
);

const mat3 LINEAR_SRGB_TO_LINEAR_REC2020 = mat3(
vec3( 0.6274, 0.0691, 0.0164 ),
vec3( 0.3293, 0.9195, 0.0880 ),
vec3( 0.0433, 0.0113, 0.8956 )
);

// https://iolite-engine.com/blog_posts/minimal_agx_implementation
// Mean error^2: 3.6705141e-06
vec3 agxDefaultContrastApprox( vec3 x ) {

vec3 x2 = x * x;
vec3 x4 = x2 * x2;

return + 15.5 * x4 * x2
- 40.14 * x4 * x
+ 31.96 * x4
- 6.868 * x2 * x
+ 0.4298 * x2
+ 0.1191 * x
- 0.00232;

}

// Input and output encoded as Linear-sRGB.
vec3 AgXToneMapping( vec3 color ) {

// AgX constants
const mat3 AgXInsetMatrix = mat3(
vec3( 0.856627153315983, 0.137318972929847, 0.11189821299995 ),
vec3( 0.0951212405381588, 0.761241990602591, 0.0767994186031903 ),
vec3( 0.0482516061458583, 0.101439036467562, 0.811302368396859 )
);

// explicit AgXOutsetMatrix generated from Filaments AgXOutsetMatrixInv
const mat3 AgXOutsetMatrix = mat3(
vec3( 1.1271005818144368, - 0.1413297634984383, - 0.14132976349843826 ),
vec3( - 0.11060664309660323, 1.157823702216272, - 0.11060664309660294 ),
vec3( - 0.016493938717834573, - 0.016493938717834257, 1.2519364065950405 )
);

const float AgxMinEv = - 12.47393; // log2(pow(2, LOG2_MIN) * MIDDLE_GRAY)
const float AgxMaxEv = 4.026069; // log2(pow(2, LOG2_MAX) * MIDDLE_GRAY)

// AGX Tone Mapping implementation based on Filament, which is in turn based
// on Blender's implementation for rec 2020 colors:
// https://github.com/google/filament/pull/7236
color = LINEAR_SRGB_TO_LINEAR_REC2020 * color;
color *= toneMappingExposure;

color = AgXInsetMatrix * color;

// Log2 encoding
color = max( color, 1e-10 ); // avoid 0 or negative numbers for log2
color = log2( color );
color = ( color - AgxMinEv ) / ( AgxMaxEv - AgxMinEv );

color = clamp( color, 0.0, 1.0 );

// Apply sigmoid
color = agxDefaultContrastApprox( color );

// Apply AgX look
// v = agxLook(v, look);

color = AgXOutsetMatrix * color;

// Linearize
color = pow( max( vec3( 0.0 ), color ), vec3( 2.2 ) );

color = LINEAR_REC2020_TO_LINEAR_SRGB * color;

return color;

}

vec3 CustomToneMapping( vec3 color ) { return color; }
`;
6 changes: 5 additions & 1 deletion src/renderers/webgl/WebGLProgram.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
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, LinearDisplayP3ColorSpace, DisplayP3ColorSpace, P3Primaries, Rec709Primaries } from '../../constants.js';
import { NoToneMapping, AddOperation, MixOperation, MultiplyOperation, CubeRefractionMapping, CubeUVReflectionMapping, CubeReflectionMapping, PCFSoftShadowMap, PCFShadowMap, VSMShadowMap, AgXToneMapping, ACESFilmicToneMapping, CineonToneMapping, CustomToneMapping, ReinhardToneMapping, LinearToneMapping, GLSL3, LinearSRGBColorSpace, SRGBColorSpace, LinearDisplayP3ColorSpace, DisplayP3ColorSpace, P3Primaries, Rec709Primaries } from '../../constants.js';
import { ColorManagement } from '../../math/ColorManagement.js';

// From https://www.khronos.org/registry/webgl/extensions/KHR_parallel_shader_compile/
Expand Down Expand Up @@ -120,6 +120,10 @@ function getToneMappingFunction( functionName, toneMapping ) {
toneMappingName = 'ACESFilmic';
break;

case AgXToneMapping:
toneMappingName = 'AgX';
break;

case CustomToneMapping:
toneMappingName = 'Custom';
break;
Expand Down
1 change: 1 addition & 0 deletions test/unit/src/constants.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export default QUnit.module( 'Constants', () => {
assert.equal( Constants.CineonToneMapping, 3, 'CineonToneMapping is equal to 3' );
assert.equal( Constants.ACESFilmicToneMapping, 4, 'ACESFilmicToneMapping is equal to 4' );
assert.equal( Constants.CustomToneMapping, 5, 'CustomToneMapping is equal to 5' );
assert.equal( Constants.AgXToneMapping, 6, 'AgXToneMapping is equal to 6' );

assert.equal( Constants.AttachedBindMode, 'attached', 'AttachedBindMode is equal to attached' );
assert.equal( Constants.DetachedBindMode, 'detached', 'DetachedBindMode is equal to detached' );
Expand Down