@@ -27,13 +27,6 @@ vec3 srgb_to_linear(vec3 color) {
27
27
28
28
#ifdef APPLY_TONEMAPPING
29
29
30
- // Based on Reinhard's extended formula, see equation 4 in https://doi.org/cjbgrt
31
- vec3 tonemap_reinhard(vec3 color, float p_white) {
32
- float white_squared = p_white * p_white;
33
- vec3 white_squared_color = white_squared * color;
34
- // Equivalent to color * (1 + color / white_squared) / (1 + color)
35
- return (white_squared_color + color * color) / (white_squared_color + white_squared);
36
- }
37
30
38
31
vec3 tonemap_filmic(vec3 color, float p_white) {
39
32
// exposure bias: input scale (color *= bias, white *= bias) to make the brightness consistent with other tonemappers
@@ -84,76 +77,82 @@ vec3 tonemap_aces(vec3 color, float p_white) {
84
77
return color_tonemapped / p_white_tonemapped;
85
78
}
86
79
87
- // Polynomial approximation of EaryChow's AgX sigmoid curve.
88
- // x must be within the range [0.0, 1.0]
89
- vec3 agx_contrast_approx(vec3 x) {
90
- // Generated with Excel trendline
91
- // Input data: Generated using python sigmoid with EaryChow's configuration and 57 steps
92
- // Additional padding values were added to give correct intersections at 0.0 and 1.0
93
- // 6th order, intercept of 0.0 to remove an operation and ensure intersection at 0.0
80
+ // Based on Reinhard's extended formula, see equation 4 in https://doi.org/cjbgrt
81
+ vec3 tonemap_reinhard(vec3 color, float p_white) {
82
+ float white_squared = p_white * p_white;
83
+ vec3 white_squared_color = white_squared * color;
84
+ // Equivalent to color * (1 + color / white_squared) / (1 + color)
85
+ return (white_squared_color + color * color) / (white_squared_color + white_squared);
86
+ }
87
+
88
+ // Mean error^2: 3.6705141e-06
89
+ vec3 agx_default_contrast_approx(vec3 x) {
94
90
vec3 x2 = x * x;
95
91
vec3 x4 = x2 * x2;
96
- return 0.021 * x + 4.0111 * x2 - 25.682 * x2 * x + 70.359 * x4 - 74.778 * x4 * x + 27.069 * x4 * x2;
92
+
93
+ return + 15.5 * x4 * x2 - 40.14 * x4 * x + 31.96 * x4 - 6.868 * x2 * x + 0.4298 * x2 + 0.1191 * x - 0.00232 ;
97
94
}
98
95
99
- // This is an approximation and simplification of EaryChow's AgX implementation that is used by Blender.
100
- // This code is based off of the script that generates the AgX_Base_sRGB.cube LUT that Blender uses.
101
- // Source: https://github.com/EaryChow/AgX_LUT_Gen/blob/main/AgXBasesRGB.py
102
- vec3 tonemap_agx(vec3 color) {
103
- // Combined linear sRGB to linear Rec 2020 and Blender AgX inset matrices:
104
- const mat3 srgb_to_rec2020_agx_inset_matrix = mat3 (
105
- 0.54490813676363087053 , 0.14044005884001287035 , 0.088827411851915368603 ,
106
- 0.37377945959812267119 , 0.75410959864013760045 , 0.17887712465043811023 ,
107
- 0.081384976686407536266 , 0.10543358536857773485 , 0.73224999956948382528 );
108
-
109
- // Combined inverse AgX outset matrix and linear Rec 2020 to linear sRGB matrices.
110
- const mat3 agx_outset_rec2020_to_srgb_matrix = mat3 (
111
- 1.9645509602733325934 , - 0.29932243390911083839 , - 0.16436833806080403409 ,
112
- - 0.85585845117807513559 , 1.3264510741502356555 , - 0.23822464068860595117 ,
113
- - 0.10886710826831608324 , - 0.027084020983874825605 , 1.402665347143271889 );
114
-
115
- // LOG2_MIN = -10.0
116
- // LOG2_MAX = +6.5
117
- // MIDDLE_GRAY = 0.18
118
- const float min_ev = - 12.4739311883324 ; // log2(pow(2, LOG2_MIN) * MIDDLE_GRAY)
119
- const float max_ev = 4.02606881166759 ; // log2(pow(2, LOG2_MAX) * MIDDLE_GRAY)
120
-
121
- // Large negative values in one channel and large positive values in other
122
- // channels can result in a colour that appears darker and more saturated than
123
- // desired after passing it through the inset matrix. For this reason, it is
124
- // best to prevent negative input values.
125
- // This is done before the Rec. 2020 transform to allow the Rec. 2020
126
- // transform to be combined with the AgX inset matrix. This results in a loss
127
- // of color information that could be correctly interpreted within the
128
- // Rec. 2020 color space as positive RGB values, but it is less common for Godot
129
- // to provide this function with negative sRGB values and therefore not worth
130
- // the performance cost of an additional matrix multiplication.
131
- // A value of 2e-10 intentionally introduces insignificant error to prevent
132
- // log2(0.0) after the inset matrix is applied; color will be >= 1e-10 after
133
- // the matrix transform.
134
- color = max (color, 2e-10 );
135
-
136
- // Do AGX in rec2020 to match Blender and then apply inset matrix.
137
- color = srgb_to_rec2020_agx_inset_matrix * color;
96
+ vec3 agx(vec3 val, float white) {
97
+ const mat3 agx_mat = mat3 (
98
+ 0.842479062253094 , 0.0423282422610123 , 0.0423756549057051 ,
99
+ 0.0784335999999992 , 0.878468636469772 , 0.0784336 ,
100
+ 0.0792237451477643 , 0.0791661274605434 , 0.879142973793104 );
101
+
102
+ const float min_ev = - 12 .47393f;
103
+ float max_ev = log2 (white);
104
+
105
+ // Input transform (inset).
106
+ val = agx_mat * val;
138
107
139
108
// Log2 space encoding.
140
- // Must be clamped because agx_contrast_approx may not work
141
- // well with values outside of the range [0.0, 1.0]
142
- color = clamp (log2 (color), min_ev, max_ev);
143
- color = (color - min_ev) / (max_ev - min_ev);
109
+ val = clamp (log2 (val), min_ev, max_ev);
110
+ val = (val - min_ev) / (max_ev - min_ev);
144
111
145
112
// Apply sigmoid function approximation.
146
- color = agx_contrast_approx(color);
113
+ val = agx_default_contrast_approx(val);
114
+
115
+ return val;
116
+ }
117
+
118
+ vec3 agx_eotf(vec3 val) {
119
+ const mat3 agx_mat_inv = mat3 (
120
+ 1.19687900512017 , - 0.0528968517574562 , - 0.0529716355144438 ,
121
+ - 0.0980208811401368 , 1.15190312990417 , - 0.0980434501171241 ,
122
+ - 0.0990297440797205 , - 0.0989611768448433 , 1.15107367264116 );
147
123
148
- // Convert back to linear before applying outset matrix .
149
- color = pow (color, vec3 ( 2.4 )) ;
124
+ // Inverse input transform ( outset) .
125
+ val = agx_mat_inv * val ;
150
126
151
- // Apply outset to make the result more chroma-laden and then go back to linear sRGB.
152
- color = agx_outset_rec2020_to_srgb_matrix * color;
127
+ // sRGB IEC 61966-2-1 2.2 Exponent Reference EOTF Display
128
+ // NOTE: We're linearizing the output here. Comment/adjust when
129
+ // *not* using a sRGB render target.
130
+ val = pow (val, vec3 (2.2 ));
153
131
154
- // Blender's lusRGB.compensate_low_side is too complex for this shader, so
155
- // simply return the color, even if it has negative components. These negative
156
- // components may be useful for subsequent color adjustments.
132
+ return val;
133
+ }
134
+
135
+ vec3 agx_look_punchy(vec3 val) {
136
+ const vec3 lw = vec3 (0.2126 , 0.7152 , 0.0722 );
137
+ float luma = dot (val, lw);
138
+
139
+ vec3 offset = vec3 (0.0 );
140
+ vec3 slope = vec3 (1.0 );
141
+ vec3 power = vec3 (1.35 , 1.35 , 1.35 );
142
+ float sat = 1.4 ;
143
+
144
+ // ASC CDL.
145
+ val = pow (val * slope + offset, power);
146
+ return luma + sat * (val - luma);
147
+ }
148
+
149
+ // Adapted from https://iolite-engine.com/blog_posts/minimal_agx_implementation
150
+ vec3 tonemap_agx(vec3 color, float white, bool punchy) {
151
+ color = agx(color, white);
152
+ if (punchy) {
153
+ color = agx_look_punchy(color);
154
+ }
155
+ color = agx_eotf(color);
157
156
return color;
158
157
}
159
158
@@ -162,6 +161,7 @@ vec3 tonemap_agx(vec3 color) {
162
161
#define TONEMAPPER_FILMIC 2
163
162
#define TONEMAPPER_ACES 3
164
163
#define TONEMAPPER_AGX 4
164
+ #define TONEMAPPER_AGX_PUNCHY 5
165
165
166
166
vec3 apply_tonemapping(vec3 color, float p_white) { // inputs are LINEAR
167
167
// Ensure color values passed to tonemappers are positive.
@@ -174,8 +174,10 @@ vec3 apply_tonemapping(vec3 color, float p_white) { // inputs are LINEAR
174
174
return tonemap_filmic(max (vec3 (0 .0f), color), p_white);
175
175
} else if (tonemapper == TONEMAPPER_ACES) {
176
176
return tonemap_aces(max (vec3 (0 .0f), color), p_white);
177
- } else { // TONEMAPPER_AGX
178
- return tonemap_agx(color);
177
+ } else if (tonemapper == TONEMAPPER_AGX) {
178
+ return tonemap_agx(max (vec3 (0 .0f), color), p_white, false);
179
+ } else { // TONEMAPPER_AGX_PUNCHY
180
+ return tonemap_agx(max (vec3 (0 .0f), color), p_white, true);
179
181
}
180
182
}
181
183
0 commit comments