Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions doc/classes/Environment.xml
Original file line number Diff line number Diff line change
Expand Up @@ -425,11 +425,11 @@
Uses a film-like tonemapping curve to prevent clipping of bright values and provide better contrast than [constant TONE_MAPPER_REINHARDT]. Slightly slower than [constant TONE_MAPPER_REINHARDT].
</constant>
<constant name="TONE_MAPPER_ACES" value="3" enum="ToneMapper">
Uses a high-contrast film-like tonemapping curve and desaturates bright values for a more realistic appearance. Slightly slower than [constant TONE_MAPPER_FILMIC].
Uses a high-contrast film-like tonemapping curve and desaturates bright values for a more realistic appearance. Slightly slower than [constant TONE_MAPPER_FILMIC] and similar performance to [constant TONE_MAPPER_AGX].
[b]Note:[/b] This tonemapping operator is called "ACES Fitted" in Godot 3.x.
</constant>
<constant name="TONE_MAPPER_AGX" value="4" enum="ToneMapper">
Uses a film-like tonemapping curve and desaturates bright values for a more realistic appearance. Better than other tonemappers at maintaining the hue of colors as they become brighter. The slowest tonemapping option.
Uses a film-like tonemapping curve and desaturates bright values for a more realistic appearance. Better than other tonemappers at maintaining the hue of colors as they become brighter. Slightly slower than [constant TONE_MAPPER_FILMIC] and similar performance to [constant TONE_MAPPER_ACES].
[b]Note:[/b] [member tonemap_white] is fixed at a value of [code]16.29[/code], which makes [constant TONE_MAPPER_AGX] unsuitable for use with the Mobile rendering method.
</constant>
<constant name="GLOW_BLEND_MODE_ADDITIVE" value="0" enum="GlowBlendMode">
Expand Down
4 changes: 2 additions & 2 deletions doc/classes/RenderingServer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5403,11 +5403,11 @@
Uses a film-like tonemapping curve to prevent clipping of bright values and provide better contrast than [constant ENV_TONE_MAPPER_REINHARD]. Slightly slower than [constant ENV_TONE_MAPPER_REINHARD].
</constant>
<constant name="ENV_TONE_MAPPER_ACES" value="3" enum="EnvironmentToneMapper">
Uses a high-contrast film-like tonemapping curve and desaturates bright values for a more realistic appearance. Slightly slower than [constant ENV_TONE_MAPPER_FILMIC].
Uses a high-contrast film-like tonemapping curve and desaturates bright values for a more realistic appearance. Slightly slower than [constant ENV_TONE_MAPPER_FILMIC] and similar performance to [constant ENV_TONE_MAPPER_AGX].
[b]Note:[/b] This tonemapping operator is called "ACES Fitted" in Godot 3.x.
</constant>
<constant name="ENV_TONE_MAPPER_AGX" value="4" enum="EnvironmentToneMapper">
Uses a film-like tonemapping curve and desaturates bright values for a more realistic appearance. Better than other tonemappers at maintaining the hue of colors as they become brighter. The slowest tonemapping option.
Uses a film-like tonemapping curve and desaturates bright values for a more realistic appearance. Better than other tonemappers at maintaining the hue of colors as they become brighter. Slightly slower than [constant ENV_TONE_MAPPER_FILMIC] and similar performance to [constant ENV_TONE_MAPPER_ACES].
[b]Note:[/b] [member Environment.tonemap_white] is fixed at a value of [code]16.29[/code], which makes [constant ENV_TONE_MAPPER_AGX] unsuitable for use with the Mobile rendering method.
</constant>
<constant name="ENV_SSR_ROUGHNESS_QUALITY_DISABLED" value="0" enum="EnvironmentSSRRoughnessQuality">
Expand Down
56 changes: 24 additions & 32 deletions drivers/gles3/shaders/tonemap_inc.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -84,21 +84,14 @@ vec3 tonemap_aces(vec3 color, float p_white) {
return color_tonemapped / p_white_tonemapped;
}

// Polynomial approximation of EaryChow's AgX sigmoid curve.
// x must be within the range [0.0, 1.0]
vec3 agx_contrast_approx(vec3 x) {
// Generated with Excel trendline
// Input data: Generated using python sigmoid with EaryChow's configuration and 57 steps
// Additional padding values were added to give correct intersections at 0.0 and 1.0
// 6th order, intercept of 0.0 to remove an operation and ensure intersection at 0.0
vec3 x2 = x * x;
vec3 x4 = x2 * x2;
return 0.021 * x + 4.0111 * x2 - 25.682 * x2 * x + 70.359 * x4 - 74.778 * x4 * x + 27.069 * x4 * x2;
}

// This is an approximation and simplification of EaryChow's AgX implementation that is used by Blender.
// This is a simplified glsl implementation of EaryChow's AgX that is used by Blender.
// Input: unbounded linear Rec. 709
// Output: unbounded linear Rec. 709 (Most any value you care about will be within [0.0, 1.0], thus safe to clip.)
// This code is based off of the script that generates the AgX_Base_sRGB.cube LUT that Blender uses.
// Source: https://github.com/EaryChow/AgX_LUT_Gen/blob/main/AgXBasesRGB.py
// Changes: Negative clipping in input color space without "guard rails" and no chroma-angle mixing.
// Repository for this code: https://github.com/allenwp/AgX-GLSL-Shaders
// Refer to source repository for other matrices if input/output color space ever changes.
vec3 tonemap_agx(vec3 color) {
// Combined linear sRGB to linear Rec 2020 and Blender AgX inset matrices:
const mat3 srgb_to_rec2020_agx_inset_matrix = mat3(
Expand All @@ -112,11 +105,13 @@ vec3 tonemap_agx(vec3 color) {
-0.85585845117807513559, 1.3264510741502356555, -0.23822464068860595117,
-0.10886710826831608324, -0.027084020983874825605, 1.402665347143271889);

// LOG2_MIN = -10.0
// LOG2_MAX = +6.5
// MIDDLE_GRAY = 0.18
const float min_ev = -12.4739311883324; // log2(pow(2, LOG2_MIN) * MIDDLE_GRAY)
const float max_ev = 4.02606881166759; // log2(pow(2, LOG2_MAX) * MIDDLE_GRAY)
// Terms of Timothy Lottes' tonemapping curve equation:
// c and b are calculated based on a and d with AgX mid and max parameters
// using the Mathematica notebook in the source AgX-GLSL-Shaders repository.
const float a = 1.36989969378897;
const float c = 0.3589386656982;
const float b = 1.4325264680543;
const float e = a * 0.903916850555009; // = a * d

// Large negative values in one channel and large positive values in other
// channels can result in a colour that appears darker and more saturated than
Expand All @@ -125,28 +120,25 @@ vec3 tonemap_agx(vec3 color) {
// This is done before the Rec. 2020 transform to allow the Rec. 2020
// transform to be combined with the AgX inset matrix. This results in a loss
// of color information that could be correctly interpreted within the
// Rec. 2020 color space as positive RGB values, but it is less common for Godot
// to provide this function with negative sRGB values and therefore not worth
// Rec. 2020 color space as positive RGB values, but is often not worth
// the performance cost of an additional matrix multiplication.
// A value of 2e-10 intentionally introduces insignificant error to prevent
// log2(0.0) after the inset matrix is applied; color will be >= 1e-10 after
// the matrix transform.
color = max(color, 2e-10);

// Do AGX in rec2020 to match Blender and then apply inset matrix.
// Apply inset matrix.
color = srgb_to_rec2020_agx_inset_matrix * color;

// Log2 space encoding.
// Must be clamped because agx_contrast_approx may not work
// well with values outside of the range [0.0, 1.0]
color = clamp(log2(color), min_ev, max_ev);
color = (color - min_ev) / (max_ev - min_ev);

// Apply sigmoid function approximation.
color = agx_contrast_approx(color);

// Convert back to linear before applying outset matrix.
color = pow(color, vec3(2.4));
// Use Timothy Lottes' tonemapping equation to approximate AgX's curve.
// Slide 44 of "Advanced Techniques and Optimization of HDR Color Pipelines"
// https://gpuopen.com/wp-content/uploads/2016/03/GdcVdrLottes.pdf
// color = pow(color, a);
// color = color / (pow(color, d) * b + c);
// Simplified using hardware-implemented shader operations.
// Thanks to Stephen Hill for this optimization tip!
color = log2(color);
color = exp2(color * a) / (exp2(color * e) * b + c);

// Apply outset to make the result more chroma-laden and then go back to linear sRGB.
color = agx_outset_rec2020_to_srgb_matrix * color;
Expand Down
2 changes: 1 addition & 1 deletion scene/resources/environment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1278,7 +1278,7 @@ void Environment::_bind_methods() {
ADD_GROUP("Tonemap", "tonemap_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "tonemap_mode", PROPERTY_HINT_ENUM, "Linear,Reinhard,Filmic,ACES,AgX"), "set_tonemapper", "get_tonemapper");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "tonemap_exposure", PROPERTY_HINT_RANGE, "0,16,0.01"), "set_tonemap_exposure", "get_tonemap_exposure");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "tonemap_white", PROPERTY_HINT_RANGE, "0,16,0.01"), "set_tonemap_white", "get_tonemap_white");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "tonemap_white", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_tonemap_white", "get_tonemap_white");
Comment thread
Calinou marked this conversation as resolved.
Outdated

// SSR

Expand Down
56 changes: 24 additions & 32 deletions servers/rendering/renderer_rd/shaders/effects/tonemap.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -264,21 +264,14 @@ vec3 tonemap_aces(vec3 color, float white) {
return color_tonemapped / white_tonemapped;
}

// Polynomial approximation of EaryChow's AgX sigmoid curve.
// x must be within the range [0.0, 1.0]
vec3 agx_contrast_approx(vec3 x) {
// Generated with Excel trendline
// Input data: Generated using python sigmoid with EaryChow's configuration and 57 steps
// Additional padding values were added to give correct intersections at 0.0 and 1.0
// 6th order, intercept of 0.0 to remove an operation and ensure intersection at 0.0
vec3 x2 = x * x;
vec3 x4 = x2 * x2;
return 0.021 * x + 4.0111 * x2 - 25.682 * x2 * x + 70.359 * x4 - 74.778 * x4 * x + 27.069 * x4 * x2;
}

// This is an approximation and simplification of EaryChow's AgX implementation that is used by Blender.
// This is a simplified glsl implementation of EaryChow's AgX that is used by Blender.
// Input: unbounded linear Rec. 709
// Output: unbounded linear Rec. 709 (Most any value you care about will be within [0.0, 1.0], thus safe to clip.)
// This code is based off of the script that generates the AgX_Base_sRGB.cube LUT that Blender uses.
// Source: https://github.com/EaryChow/AgX_LUT_Gen/blob/main/AgXBasesRGB.py
// Changes: Negative clipping in input color space without "guard rails" and no chroma-angle mixing.
// Repository for this code: https://github.com/allenwp/AgX-GLSL-Shaders
// Refer to source repository for other matrices if input/output color space ever changes.
vec3 tonemap_agx(vec3 color) {
// Combined linear sRGB to linear Rec 2020 and Blender AgX inset matrices:
const mat3 srgb_to_rec2020_agx_inset_matrix = mat3(
Expand All @@ -292,11 +285,13 @@ vec3 tonemap_agx(vec3 color) {
-0.85585845117807513559, 1.3264510741502356555, -0.23822464068860595117,
-0.10886710826831608324, -0.027084020983874825605, 1.402665347143271889);

// LOG2_MIN = -10.0
// LOG2_MAX = +6.5
// MIDDLE_GRAY = 0.18
const float min_ev = -12.4739311883324; // log2(pow(2, LOG2_MIN) * MIDDLE_GRAY)
const float max_ev = 4.02606881166759; // log2(pow(2, LOG2_MAX) * MIDDLE_GRAY)
// Terms of Timothy Lottes' tonemapping curve equation:
Comment thread
allenwp marked this conversation as resolved.
Outdated
// c and b are calculated based on a and d with AgX mid and max parameters
// using the Mathematica notebook in the source AgX-GLSL-Shaders repository.
const float a = 1.36989969378897;
const float c = 0.3589386656982;
const float b = 1.4325264680543;
const float e = a * 0.903916850555009; // = a * d

// Large negative values in one channel and large positive values in other
// channels can result in a colour that appears darker and more saturated than
Expand All @@ -305,28 +300,25 @@ vec3 tonemap_agx(vec3 color) {
// This is done before the Rec. 2020 transform to allow the Rec. 2020
// transform to be combined with the AgX inset matrix. This results in a loss
// of color information that could be correctly interpreted within the
// Rec. 2020 color space as positive RGB values, but it is less common for Godot
// to provide this function with negative sRGB values and therefore not worth
// Rec. 2020 color space as positive RGB values, but is often not worth
// the performance cost of an additional matrix multiplication.
// A value of 2e-10 intentionally introduces insignificant error to prevent
// log2(0.0) after the inset matrix is applied; color will be >= 1e-10 after
// the matrix transform.
color = max(color, 2e-10);

// Do AGX in rec2020 to match Blender and then apply inset matrix.
// Apply inset matrix.
color = srgb_to_rec2020_agx_inset_matrix * color;

// Log2 space encoding.
// Must be clamped because agx_contrast_approx may not work
// well with values outside of the range [0.0, 1.0]
color = clamp(log2(color), min_ev, max_ev);
color = (color - min_ev) / (max_ev - min_ev);

// Apply sigmoid function approximation.
color = agx_contrast_approx(color);

// Convert back to linear before applying outset matrix.
color = pow(color, vec3(2.4));
// Use Timothy Lottes' tonemapping equation to approximate AgX's curve.
// Slide 44 of "Advanced Techniques and Optimization of HDR Color Pipelines"
// https://gpuopen.com/wp-content/uploads/2016/03/GdcVdrLottes.pdf
// color = pow(color, a);
// color = color / (pow(color, d) * b + c);
// Simplified using hardware-implemented shader operations.
// Thanks to Stephen Hill for this optimization tip!
color = log2(color);
color = exp2(color * a) / (exp2(color * e) * b + c);

// Apply outset to make the result more chroma-laden and then go back to linear sRGB.
color = agx_outset_rec2020_to_srgb_matrix * color;
Expand Down