@@ -84,21 +84,14 @@ vec3 tonemap_aces(vec3 color, float p_white) {
84
84
return color_tonemapped / p_white_tonemapped;
85
85
}
86
86
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
94
- vec3 x2 = x * x;
95
- 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;
97
- }
98
-
99
- // This is an approximation and simplification of EaryChow's AgX implementation that is used by Blender.
87
+ // This is a simplified glsl implementation of EaryChow's AgX that is used by Blender.
88
+ // Input: unbounded linear Rec. 709
89
+ // Output: unbounded linear Rec. 709 (Most any value you care about will be within [0.0, 1.0], thus safe to clip.)
100
90
// This code is based off of the script that generates the AgX_Base_sRGB.cube LUT that Blender uses.
101
91
// Source: https://github.com/EaryChow/AgX_LUT_Gen/blob/main/AgXBasesRGB.py
92
+ // Changes: Negative clipping in input color space without "guard rails" and no chroma-angle mixing.
93
+ // Repository for this code: https://github.com/allenwp/AgX-GLSL-Shaders
94
+ // Refer to source repository for other matrices if input/output color space ever changes.
102
95
vec3 tonemap_agx(vec3 color) {
103
96
// Combined linear sRGB to linear Rec 2020 and Blender AgX inset matrices:
104
97
const mat3 srgb_to_rec2020_agx_inset_matrix = mat3 (
@@ -112,11 +105,20 @@ vec3 tonemap_agx(vec3 color) {
112
105
- 0.85585845117807513559 , 1.3264510741502356555 , - 0.23822464068860595117 ,
113
106
- 0.10886710826831608324 , - 0.027084020983874825605 , 1.402665347143271889 );
114
107
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)
108
+ const float min_ev = - 12.473931188332412333 ;
109
+ const float max_ev = 4.0260688116675876672 ;
110
+ const float dynamic_range = max_ev - min_ev;
111
+ const float x_pivot = 0.60606060606060606061 ; // = abs(normalized_log2_minimum / (normalized_log2_maximum - normalized_log2_minimum))
112
+ const float y_pivot = 0.48943708957387834110 ; // = midgrey ^ (1.0 / 2.4)
113
+ const float a_bottom = - 1.1441749659185295 ;
114
+ const float a_top = 0.904968426773028 ;
115
+ const float b_bottom = 35.355952713407210237 ;
116
+ const float b_top = - 27.96427282293113701 ;
117
+ const float c_bottom = - 58.33732197712189689 ;
118
+ const float c_top = 46.1410501578363761 ;
119
+ const float d = ((4.0 / 55.0 ) * - 20.0 );
120
+ const float e = ((4.0 / 55.0 ) * 33.0 );
121
+ const vec3 inverse_power = vec3 (1.0 / 1.5 );
120
122
121
123
// Large negative values in one channel and large positive values in other
122
124
// channels can result in a colour that appears darker and more saturated than
@@ -125,27 +127,25 @@ vec3 tonemap_agx(vec3 color) {
125
127
// This is done before the Rec. 2020 transform to allow the Rec. 2020
126
128
// transform to be combined with the AgX inset matrix. This results in a loss
127
129
// 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
+ // Rec. 2020 color space as positive RGB values, but is often not worth
130
131
// the performance cost of an additional matrix multiplication.
131
132
// A value of 2e-10 intentionally introduces insignificant error to prevent
132
133
// log2(0.0) after the inset matrix is applied; color will be >= 1e-10 after
133
134
// the matrix transform.
134
135
color = max (color, 2e-10 );
135
136
136
- // Do AGX in rec2020 to match Blender and then apply inset matrix.
137
+ // Apply inset matrix.
137
138
color = srgb_to_rec2020_agx_inset_matrix * color;
138
139
139
- // 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);
140
+ color = (log2 (color) / dynamic_range) - (min_ev / dynamic_range);
141
+ color = max (color, 0 );
144
142
145
- // Apply sigmoid function approximation.
146
- color = agx_contrast_approx(color);
143
+ vec3 mask = step (vec3 (x_pivot), color);
144
+ vec3 a = a_bottom + (a_top - a_bottom) * mask;
145
+ vec3 b = b_bottom + (b_top - b_bottom) * mask;
146
+ vec3 c = c_bottom + (c_top - c_bottom) * mask;
147
+ color = y_pivot + (d + (e * color)) / pow (abs (1.0 + a * (color - x_pivot) * sqrt (abs (b + (c * color)))), inverse_power);
147
148
148
- // Convert back to linear before applying outset matrix.
149
149
color = pow (color, vec3 (2.4 ));
150
150
151
151
// Apply outset to make the result more chroma-laden and then go back to linear sRGB.
0 commit comments