|
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' |
2 | 2 |
|
3 | 3 | const vertexShader = /* glsl */` |
4 | 4 | varying vec2 vUv; |
@@ -102,6 +102,123 @@ vec3 LinearToneMapping ( vec3 color ) { |
102 | 102 | return color; |
103 | 103 | } |
104 | 104 |
|
| 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 | +
|
105 | 222 |
|
106 | 223 | void main() { |
107 | 224 | vec4 color = texture2D(map, vUv); |
@@ -179,6 +296,14 @@ export class SDRMaterial extends ShaderMaterial { |
179 | 296 | this.defines.TONEMAPPING_FUNCTION = 'LinearToneMapping' |
180 | 297 | valid = true |
181 | 298 | 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 |
182 | 307 | default: |
183 | 308 | console.error(`Unsupported toneMapping: ${value}. Using LinearToneMapping.`) |
184 | 309 | this.defines.TONEMAPPING_FUNCTION = 'LinearToneMapping' |
|
0 commit comments