Skip to content

Commit f78975e

Browse files
committed
Preserving original AGX Punchy from old PR
Add AGX and AGX Punchy tonemapper options to Environment removed duplicated tonemap_agx method in tonemap_incl.glsl and readded alternative agx_punchy methods tonemap agx punchy
1 parent 82537f7 commit f78975e

File tree

6 files changed

+156
-70
lines changed

6 files changed

+156
-70
lines changed

drivers/gles3/shaders/tonemap_inc.glsl

Lines changed: 70 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,6 @@ vec3 srgb_to_linear(vec3 color) {
2727

2828
#ifdef APPLY_TONEMAPPING
2929

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-
}
3730

3831
vec3 tonemap_filmic(vec3 color, float p_white) {
3932
// 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) {
8477
return color_tonemapped / p_white_tonemapped;
8578
}
8679

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) {
9490
vec3 x2 = x * x;
9591
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;
9794
}
9895

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;
138107

139108
// 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);
144111

145112
// 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);
147123

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;
150126

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));
153131

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);
157156
return color;
158157
}
159158

@@ -162,6 +161,7 @@ vec3 tonemap_agx(vec3 color) {
162161
#define TONEMAPPER_FILMIC 2
163162
#define TONEMAPPER_ACES 3
164163
#define TONEMAPPER_AGX 4
164+
#define TONEMAPPER_AGX_PUNCHY 5
165165

166166
vec3 apply_tonemapping(vec3 color, float p_white) { // inputs are LINEAR
167167
// Ensure color values passed to tonemappers are positive.
@@ -174,8 +174,10 @@ vec3 apply_tonemapping(vec3 color, float p_white) { // inputs are LINEAR
174174
return tonemap_filmic(max(vec3(0.0f), color), p_white);
175175
} else if (tonemapper == TONEMAPPER_ACES) {
176176
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);
179181
}
180182
}
181183

scene/resources/environment.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1276,7 +1276,7 @@ void Environment::_bind_methods() {
12761276
ClassDB::bind_method(D_METHOD("get_tonemap_white"), &Environment::get_tonemap_white);
12771277

12781278
ADD_GROUP("Tonemap", "tonemap_");
1279-
ADD_PROPERTY(PropertyInfo(Variant::INT, "tonemap_mode", PROPERTY_HINT_ENUM, "Linear,Reinhard,Filmic,ACES,AgX"), "set_tonemapper", "get_tonemapper");
1279+
ADD_PROPERTY(PropertyInfo(Variant::INT, "tonemap_mode", PROPERTY_HINT_ENUM, "Linear,Reinhard,Filmic,ACES,AgX,AgX Punchy"), "set_tonemapper", "get_tonemapper");
12801280
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "tonemap_exposure", PROPERTY_HINT_RANGE, "0,16,0.01"), "set_tonemap_exposure", "get_tonemap_exposure");
12811281
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "tonemap_white", PROPERTY_HINT_RANGE, "0,16,0.01"), "set_tonemap_white", "get_tonemap_white");
12821282

@@ -1582,6 +1582,7 @@ void Environment::_bind_methods() {
15821582
BIND_ENUM_CONSTANT(TONE_MAPPER_FILMIC);
15831583
BIND_ENUM_CONSTANT(TONE_MAPPER_ACES);
15841584
BIND_ENUM_CONSTANT(TONE_MAPPER_AGX);
1585+
BIND_ENUM_CONSTANT(TONE_MAPPER_AGX_PUNCHY);
15851586

15861587
BIND_ENUM_CONSTANT(GLOW_BLEND_MODE_ADDITIVE);
15871588
BIND_ENUM_CONSTANT(GLOW_BLEND_MODE_SCREEN);

scene/resources/environment.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ class Environment : public Resource {
6868
TONE_MAPPER_FILMIC,
6969
TONE_MAPPER_ACES,
7070
TONE_MAPPER_AGX,
71+
TONE_MAPPER_AGX_PUNCHY,
7172
};
7273

7374
enum SDFGIYScale {

servers/rendering/renderer_rd/shaders/effects/tonemap.glsl

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,82 @@ vec3 agx_contrast_approx(vec3 x) {
276276
return 0.021 * x + 4.0111 * x2 - 25.682 * x2 * x + 70.359 * x4 - 74.778 * x4 * x + 27.069 * x4 * x2;
277277
}
278278

279+
// Mean error^2: 3.6705141e-06
280+
vec3 agx_punchy_default_contrast_approx(vec3 x) {
281+
vec3 x2 = x * x;
282+
vec3 x4 = x2 * x2;
283+
284+
return +15.5 * x4 * x2 - 40.14 * x4 * x + 31.96 * x4 - 6.868 * x2 * x + 0.4298 * x2 + 0.1191 * x - 0.00232;
285+
}
286+
287+
vec3 agx(vec3 val, float white) {
288+
const mat3 agx_mat = mat3(
289+
0.842479062253094, 0.0423282422610123, 0.0423756549057051,
290+
0.0784335999999992, 0.878468636469772, 0.0784336,
291+
0.0792237451477643, 0.0791661274605434, 0.879142973793104);
292+
293+
const float min_ev = -12.47393f;
294+
float max_ev = log2(white);
295+
296+
// Input transform (inset).
297+
val = agx_mat * val;
298+
299+
// Log2 space encoding.
300+
val = clamp(log2(val), min_ev, max_ev);
301+
val = (val - min_ev) / (max_ev - min_ev);
302+
303+
// Apply sigmoid function approximation.
304+
val = agx_punchy_default_contrast_approx(val);
305+
306+
return val;
307+
}
308+
309+
vec3 agx_eotf(vec3 val) {
310+
const mat3 agx_mat_inv = mat3(
311+
1.19687900512017, -0.0528968517574562, -0.0529716355144438,
312+
-0.0980208811401368, 1.15190312990417, -0.0980434501171241,
313+
-0.0990297440797205, -0.0989611768448433, 1.15107367264116);
314+
315+
// Inverse input transform (outset).
316+
val = agx_mat_inv * val;
317+
318+
// sRGB IEC 61966-2-1 2.2 Exponent Reference EOTF Display
319+
// NOTE: We're linearizing the output here. Comment/adjust when
320+
// *not* using a sRGB render target.
321+
val = pow(val, vec3(2.2));
322+
323+
return val;
324+
}
325+
326+
vec3 agx_look_punchy(vec3 val) {
327+
const vec3 lw = vec3(0.2126, 0.7152, 0.0722);
328+
float luma = dot(val, lw);
329+
330+
vec3 offset = vec3(0.0);
331+
vec3 slope = vec3(1.0);
332+
vec3 power = vec3(1.35, 1.35, 1.35);
333+
float sat = 1.4;
334+
335+
// ASC CDL.
336+
val = pow(val * slope + offset, power);
337+
return luma + sat * (val - luma);
338+
}
339+
340+
const mat3 LINEAR_SRGB_TO_LINEAR_REC2020 = mat3(
341+
vec3(0.6274, 0.0691, 0.0164),
342+
vec3(0.3293, 0.9195, 0.0880),
343+
vec3(0.0433, 0.0113, 0.8956));
344+
345+
// Adapted from https://iolite-engine.com/blog_posts/minimal_agx_implementation
346+
vec3 tonemap_agx_punchy(vec3 color, float white, bool punchy) {
347+
color = agx(color, white);
348+
if (punchy) {
349+
color = agx_look_punchy(color);
350+
}
351+
color = agx_eotf(color);
352+
return color;
353+
}
354+
279355
// This is an approximation and simplification of EaryChow's AgX implementation that is used by Blender.
280356
// This code is based off of the script that generates the AgX_Base_sRGB.cube LUT that Blender uses.
281357
// Source: https://github.com/EaryChow/AgX_LUT_Gen/blob/main/AgXBasesRGB.py
@@ -337,6 +413,7 @@ vec3 tonemap_agx(vec3 color) {
337413
return color;
338414
}
339415

416+
340417
vec3 linear_to_srgb(vec3 color) {
341418
//if going to srgb, clamp from 0 to 1.
342419
color = clamp(color, vec3(0.0), vec3(1.0));
@@ -349,6 +426,7 @@ vec3 linear_to_srgb(vec3 color) {
349426
#define TONEMAPPER_FILMIC 2
350427
#define TONEMAPPER_ACES 3
351428
#define TONEMAPPER_AGX 4
429+
#define TONEMAPPER_AGX_PUNCHY 5
352430

353431
vec3 apply_tonemapping(vec3 color, float white) { // inputs are LINEAR
354432
// Ensure color values passed to tonemappers are positive.
@@ -361,8 +439,10 @@ vec3 apply_tonemapping(vec3 color, float white) { // inputs are LINEAR
361439
return tonemap_filmic(max(vec3(0.0f), color), white);
362440
} else if (params.tonemapper == TONEMAPPER_ACES) {
363441
return tonemap_aces(max(vec3(0.0f), color), white);
364-
} else { // TONEMAPPER_AGX
442+
} else if (params.tonemapper == TONEMAPPER_AGX) {
365443
return tonemap_agx(color);
444+
} else { // TONEMAPPER_AGX_PUNCHY
445+
return tonemap_agx_punchy(max(vec3(0.0f), color), white, true);
366446
}
367447
}
368448

servers/rendering_server.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3076,6 +3076,7 @@ void RenderingServer::_bind_methods() {
30763076
BIND_ENUM_CONSTANT(ENV_TONE_MAPPER_FILMIC);
30773077
BIND_ENUM_CONSTANT(ENV_TONE_MAPPER_ACES);
30783078
BIND_ENUM_CONSTANT(ENV_TONE_MAPPER_AGX);
3079+
BIND_ENUM_CONSTANT(ENV_TONE_MAPPER_AGX_PUNCHY);
30793080

30803081
BIND_ENUM_CONSTANT(ENV_SSR_ROUGHNESS_QUALITY_DISABLED);
30813082
BIND_ENUM_CONSTANT(ENV_SSR_ROUGHNESS_QUALITY_LOW);

servers/rendering_server.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1254,6 +1254,7 @@ class RenderingServer : public Object {
12541254
ENV_TONE_MAPPER_FILMIC,
12551255
ENV_TONE_MAPPER_ACES,
12561256
ENV_TONE_MAPPER_AGX,
1257+
ENV_TONE_MAPPER_AGX_PUNCHY,
12571258
};
12581259

12591260
virtual void environment_set_tonemap(RID p_env, EnvironmentToneMapper p_tone_mapper, float p_exposure, float p_white) = 0;

0 commit comments

Comments
 (0)