Skip to content

Commit 8c163da

Browse files
feat: adds AgXToneMapping and NeutralToneMapping to encoding SDR Material shader
1 parent 69b60ee commit 8c163da

2 files changed

Lines changed: 128 additions & 3 deletions

File tree

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/encode/materials/SDRMaterial.ts

Lines changed: 126 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ACESFilmicToneMapping, CineonToneMapping, LinearToneMapping, NoBlending, ReinhardToneMapping, ShaderMaterial, Texture, ToneMapping } from 'three'
1+
import { ACESFilmicToneMapping, AgXToneMapping, CineonToneMapping, LinearToneMapping, NeutralToneMapping, NoBlending, ReinhardToneMapping, ShaderMaterial, Texture, ToneMapping } from 'three'
22

33
const vertexShader = /* glsl */`
44
varying vec2 vUv;
@@ -102,6 +102,123 @@ vec3 LinearToneMapping ( vec3 color ) {
102102
return color;
103103
}
104104
105+
// Matrices for rec 2020 <> rec 709 color space conversion
106+
// matrix provided in row-major order so it has been transposed
107+
// https://www.itu.int/pub/R-REP-BT.2407-2017
108+
const mat3 LINEAR_REC2020_TO_LINEAR_SRGB = mat3(
109+
vec3( 1.6605, - 0.1246, - 0.0182 ),
110+
vec3( - 0.5876, 1.1329, - 0.1006 ),
111+
vec3( - 0.0728, - 0.0083, 1.1187 )
112+
);
113+
114+
const mat3 LINEAR_SRGB_TO_LINEAR_REC2020 = mat3(
115+
vec3( 0.6274, 0.0691, 0.0164 ),
116+
vec3( 0.3293, 0.9195, 0.0880 ),
117+
vec3( 0.0433, 0.0113, 0.8956 )
118+
);
119+
120+
// https://iolite-engine.com/blog_posts/minimal_agx_implementation
121+
// Mean error^2: 3.6705141e-06
122+
vec3 agxDefaultContrastApprox( vec3 x ) {
123+
vec3 x2 = x * x;
124+
vec3 x4 = x2 * x2;
125+
return + 15.5 * x4 * x2
126+
- 40.14 * x4 * x
127+
+ 31.96 * x4
128+
- 6.868 * x2 * x
129+
+ 0.4298 * x2
130+
+ 0.1191 * x
131+
- 0.00232;
132+
}
133+
134+
// AgX Tone Mapping implementation based on Filament, which in turn is based
135+
// on Blender's implementation using rec 2020 primaries
136+
// https://github.com/google/filament/pull/7236
137+
// Inputs and outputs are encoded as Linear-sRGB.
138+
139+
vec3 AgXToneMapping( vec3 color ) {
140+
141+
// AgX constants
142+
const mat3 AgXInsetMatrix = mat3(
143+
vec3( 0.856627153315983, 0.137318972929847, 0.11189821299995 ),
144+
vec3( 0.0951212405381588, 0.761241990602591, 0.0767994186031903 ),
145+
vec3( 0.0482516061458583, 0.101439036467562, 0.811302368396859 )
146+
);
147+
148+
// explicit AgXOutsetMatrix generated from Filaments AgXOutsetMatrixInv
149+
const mat3 AgXOutsetMatrix = mat3(
150+
vec3( 1.1271005818144368, - 0.1413297634984383, - 0.14132976349843826 ),
151+
vec3( - 0.11060664309660323, 1.157823702216272, - 0.11060664309660294 ),
152+
vec3( - 0.016493938717834573, - 0.016493938717834257, 1.2519364065950405 )
153+
);
154+
155+
// LOG2_MIN = -10.0
156+
// LOG2_MAX = +6.5
157+
// MIDDLE_GRAY = 0.18
158+
const float AgxMinEv = - 12.47393; // log2( pow( 2, LOG2_MIN ) * MIDDLE_GRAY )
159+
const float AgxMaxEv = 4.026069; // log2( pow( 2, LOG2_MAX ) * MIDDLE_GRAY )
160+
161+
color = LINEAR_SRGB_TO_LINEAR_REC2020 * color;
162+
163+
color = AgXInsetMatrix * color;
164+
165+
// Log2 encoding
166+
color = max( color, 1e-10 ); // avoid 0 or negative numbers for log2
167+
color = log2( color );
168+
color = ( color - AgxMinEv ) / ( AgxMaxEv - AgxMinEv );
169+
170+
color = clamp( color, 0.0, 1.0 );
171+
172+
// Apply sigmoid
173+
color = agxDefaultContrastApprox( color );
174+
175+
// Apply AgX look
176+
// v = agxLook(v, look);
177+
178+
color = AgXOutsetMatrix * color;
179+
180+
// Linearize
181+
color = pow( max( vec3( 0.0 ), color ), vec3( 2.2 ) );
182+
183+
color = LINEAR_REC2020_TO_LINEAR_SRGB * color;
184+
185+
// Gamut mapping. Simple clamp for now.
186+
color = clamp( color, 0.0, 1.0 );
187+
188+
return color;
189+
190+
}
191+
192+
// https://modelviewer.dev/examples/tone-mapping
193+
194+
vec3 NeutralToneMapping( vec3 color ) {
195+
196+
const float StartCompression = 0.8 - 0.04;
197+
const float Desaturation = 0.15;
198+
199+
float x = min( color.r, min( color.g, color.b ) );
200+
201+
float offset = x < 0.08 ? x - 6.25 * x * x : 0.04;
202+
203+
color -= offset;
204+
205+
float peak = max( color.r, max( color.g, color.b ) );
206+
207+
if ( peak < StartCompression ) return color;
208+
209+
float d = 1. - StartCompression;
210+
211+
float newPeak = 1. - d * d / ( peak + d - StartCompression );
212+
213+
color *= newPeak / peak;
214+
215+
float g = 1. - 1. / ( Desaturation * ( peak - newPeak ) + 1. );
216+
217+
return mix( color, vec3( newPeak ), g );
218+
219+
}
220+
221+
105222
106223
void main() {
107224
vec4 color = texture2D(map, vUv);
@@ -179,6 +296,14 @@ export class SDRMaterial extends ShaderMaterial {
179296
this.defines.TONEMAPPING_FUNCTION = 'LinearToneMapping'
180297
valid = true
181298
break
299+
case AgXToneMapping:
300+
this.defines.TONEMAPPING_FUNCTION = 'AgXToneMapping'
301+
valid = true
302+
break
303+
case NeutralToneMapping:
304+
this.defines.TONEMAPPING_FUNCTION = 'NeutralToneMapping'
305+
valid = true
306+
break
182307
default:
183308
console.error(`Unsupported toneMapping: ${value}. Using LinearToneMapping.`)
184309
this.defines.TONEMAPPING_FUNCTION = 'LinearToneMapping'

0 commit comments

Comments
 (0)