mirror of
https://github.com/godotengine/godot.git
synced 2025-12-08 06:09:55 +00:00
Add agx_white, agx_contrast and HDR support to the AgX tonemapper.
Also optimize all tonemappers to perform less calculations per-pixel. Note: unlike `white`, `agx_white` is limited to a minimum of `2.0` and defaults to `16.29`. When using a RGB10A2 render buffer, `agx_white` will be ignored and a value of `2.0` will be used instead to ensure good behavior on the Mobile renderer.
This commit is contained in:
parent
7a228b4b91
commit
628df323e2
24 changed files with 546 additions and 241 deletions
|
|
@ -208,13 +208,7 @@ void RendererEnvironmentStorage::environment_set_tonemap(RID p_env, RS::Environm
|
|||
ERR_FAIL_NULL(env);
|
||||
env->exposure = p_exposure;
|
||||
env->tone_mapper = p_tone_mapper;
|
||||
if (p_tone_mapper == RS::ENV_TONE_MAPPER_LINEAR) {
|
||||
env->white = 1.0; // With HDR output, this should be the output max value instead.
|
||||
} else if (p_tone_mapper == RS::ENV_TONE_MAPPER_AGX) {
|
||||
env->white = 16.29;
|
||||
} else {
|
||||
env->white = MAX(1.0, p_white); // Glow with screen blend mode does not work when white < 1.0.
|
||||
}
|
||||
env->white = p_white;
|
||||
}
|
||||
|
||||
RS::EnvironmentToneMapper RendererEnvironmentStorage::environment_get_tone_mapper(RID p_env) const {
|
||||
|
|
@ -229,10 +223,124 @@ float RendererEnvironmentStorage::environment_get_exposure(RID p_env) const {
|
|||
return env->exposure;
|
||||
}
|
||||
|
||||
float RendererEnvironmentStorage::environment_get_white(RID p_env) const {
|
||||
float RendererEnvironmentStorage::environment_get_white(RID p_env, bool p_limit_agx_white) const {
|
||||
Environment *env = environment_owner.get_or_null(p_env);
|
||||
ERR_FAIL_NULL_V(env, 1.0);
|
||||
return env->white;
|
||||
|
||||
const float output_max_value = 1.0; // SDR always has an output_max_value of 1.0.
|
||||
|
||||
// Glow with screen blend mode does not work when white < 1.0, so make sure
|
||||
// it is at least 1.0 for all tonemappers:
|
||||
if (env->tone_mapper == RS::ENV_TONE_MAPPER_LINEAR) {
|
||||
return output_max_value;
|
||||
} else if (env->tone_mapper == RS::ENV_TONE_MAPPER_FILMIC || env->tone_mapper == RS::ENV_TONE_MAPPER_ACES) {
|
||||
// Filmic and ACES only support SDR; their white is stable regardless
|
||||
// of output_max_value.
|
||||
return MAX(1.0, env->white);
|
||||
} else if (env->tone_mapper == RS::ENV_TONE_MAPPER_AGX) {
|
||||
// AgX works best with a high white. 2.0 is the minimum required for
|
||||
// good behavior with Mobile rendering method.
|
||||
if (p_limit_agx_white) {
|
||||
return 2.0;
|
||||
} else {
|
||||
float agx_white = MAX(2.0, env->white);
|
||||
// Instead of constraining by matching the output_max_value, constrain
|
||||
// by multiplying to ensure the desired non-uniform scaling behavior
|
||||
// is maintained in the shoulder.
|
||||
return agx_white * output_max_value;
|
||||
}
|
||||
} else { // Reinhard
|
||||
// The Reinhard tonemapper is not designed to have a white parameter
|
||||
// that is less than the output max value. This is especially important
|
||||
// in the variable Extended Dynamic Range (EDR) paradigm where the
|
||||
// output max value may change to be greater or less than the white
|
||||
// parameter, depending on the available dynamic range.
|
||||
return MAX(output_max_value, env->white);
|
||||
}
|
||||
}
|
||||
|
||||
void RendererEnvironmentStorage::environment_set_tonemap_agx_contrast(RID p_env, float p_agx_contrast) {
|
||||
Environment *env = environment_owner.get_or_null(p_env);
|
||||
ERR_FAIL_NULL(env);
|
||||
env->tonemap_agx_contrast = p_agx_contrast;
|
||||
}
|
||||
|
||||
float RendererEnvironmentStorage::environment_get_tonemap_agx_contrast(RID p_env) const {
|
||||
Environment *env = environment_owner.get_or_null(p_env);
|
||||
ERR_FAIL_NULL_V(env, 1.0);
|
||||
return env->tonemap_agx_contrast;
|
||||
}
|
||||
|
||||
RendererEnvironmentStorage::TonemapParameters RendererEnvironmentStorage::environment_get_tonemap_parameters(RID p_env, bool p_limit_agx_white) const {
|
||||
Environment *env = environment_owner.get_or_null(p_env);
|
||||
ERR_FAIL_NULL_V(env, TonemapParameters());
|
||||
|
||||
const float output_max_value = 1.0; // SDR always has an output_max_value of 1.0.
|
||||
|
||||
float white = environment_get_white(p_env, p_limit_agx_white);
|
||||
TonemapParameters tonemap_parameters = TonemapParameters();
|
||||
|
||||
if (env->tone_mapper == RS::ENV_TONE_MAPPER_LINEAR) {
|
||||
// Linear has no tonemapping parameters
|
||||
} else if (env->tone_mapper == RS::ENV_TONE_MAPPER_REINHARD) {
|
||||
tonemap_parameters.white_squared = white * white;
|
||||
} else if (env->tone_mapper == RS::ENV_TONE_MAPPER_FILMIC) {
|
||||
// These constants must match those in the shader code.
|
||||
// exposure_bias: Input scale (color *= bias, white *= bias) to make the brightness consistent with other tonemappers
|
||||
// also useful to scale the input to the range that the tonemapper is designed for (some require very high input values).
|
||||
// Has no effect on the curve's general shape or visual properties.
|
||||
const float exposure_bias = 2.0f;
|
||||
const float A = 0.22f * exposure_bias * exposure_bias; // bias baked into constants for performance
|
||||
const float B = 0.30f * exposure_bias;
|
||||
const float C = 0.10f;
|
||||
const float D = 0.20f;
|
||||
const float E = 0.01f;
|
||||
const float F = 0.30f;
|
||||
|
||||
tonemap_parameters.white_tonemapped = ((white * (A * white + C * B) + D * E) / (white * (A * white + B) + D * F)) - E / F;
|
||||
} else if (env->tone_mapper == RS::ENV_TONE_MAPPER_ACES) {
|
||||
// These constants must match those in the shader code.
|
||||
const float exposure_bias = 1.8f;
|
||||
const float A = 0.0245786f;
|
||||
const float B = 0.000090537f;
|
||||
const float C = 0.983729f;
|
||||
const float D = 0.432951f;
|
||||
const float E = 0.238081f;
|
||||
|
||||
white *= exposure_bias;
|
||||
float white_tonemapped = (white * (white + A) - B) / (white * (C * white + D) + E);
|
||||
tonemap_parameters.white_tonemapped = white_tonemapped;
|
||||
} else if (env->tone_mapper == RS::ENV_TONE_MAPPER_AGX) {
|
||||
// Calculate allenwp tonemapping curve parameters on the CPU to improve shader performance.
|
||||
// Source and details: https://allenwp.com/blog/2025/05/29/allenwp-tonemapping-curve/
|
||||
|
||||
// These constants must match the those in the shader code.
|
||||
// 18% "middle gray" is perceptually 50% of the brightness of reference white.
|
||||
const float awp_crossover_point = 0.18;
|
||||
// When output_max_value and/or awp_crossover_point are no longer constant, awp_shoulder_max can
|
||||
// be calculated on the CPU and passed in as tonemap_parameters.tonemap_e.
|
||||
const float awp_shoulder_max = output_max_value - awp_crossover_point;
|
||||
|
||||
float awp_high_clip = white;
|
||||
|
||||
// awp_toe_a is a solution generated by Mathematica that ensures intersection at awp_crossover_point.
|
||||
float awp_toe_a = ((1.0 / awp_crossover_point) - 1.0) * pow(awp_crossover_point, env->tonemap_agx_contrast);
|
||||
// Slope formula is simply the derivative of the toe function with an input of awp_crossover_point.
|
||||
float awp_slope_denom = pow(awp_crossover_point, env->tonemap_agx_contrast) + awp_toe_a;
|
||||
float awp_slope = (env->tonemap_agx_contrast * pow(awp_crossover_point, env->tonemap_agx_contrast - 1.0) * awp_toe_a) / (awp_slope_denom * awp_slope_denom);
|
||||
|
||||
float awp_w = awp_high_clip - awp_crossover_point;
|
||||
awp_w = awp_w * awp_w;
|
||||
awp_w = awp_w / awp_shoulder_max;
|
||||
awp_w = awp_w * awp_slope;
|
||||
|
||||
tonemap_parameters.awp_contrast = env->tonemap_agx_contrast;
|
||||
tonemap_parameters.awp_toe_a = awp_toe_a;
|
||||
tonemap_parameters.awp_slope = awp_slope;
|
||||
tonemap_parameters.awp_w = awp_w;
|
||||
}
|
||||
|
||||
return tonemap_parameters;
|
||||
}
|
||||
|
||||
// Fog
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue