Merge pull request #93324 from tracefree/reinhard-fix

Fix incorrect Reinhard tonemap operator
This commit is contained in:
Rémi Verschelde 2024-09-25 12:39:06 +02:00
commit 70fede82c5
No known key found for this signature in database
GPG Key ID: C3336907360768E1
4 changed files with 14 additions and 6 deletions

View File

@ -414,7 +414,7 @@
Linear tonemapper operator. Reads the linear data and passes it on unmodified. This can cause bright lighting to look blown out, with noticeable clipping in the output colors. Linear tonemapper operator. Reads the linear data and passes it on unmodified. This can cause bright lighting to look blown out, with noticeable clipping in the output colors.
</constant> </constant>
<constant name="TONE_MAPPER_REINHARDT" value="1" enum="ToneMapper"> <constant name="TONE_MAPPER_REINHARDT" value="1" enum="ToneMapper">
Reinhardt tonemapper operator. Performs a variation on rendered pixels' colors by this formula: [code]color = color / (1 + color)[/code]. This avoids clipping bright highlights, but the resulting image can look a bit dull. Reinhard tonemapper operator. Performs a variation on rendered pixels' colors by this formula: [code]color = color * (1 + color / (white * white)) / (1 + color)[/code]. This avoids clipping bright highlights, but the resulting image can look a bit dull. When [member tonemap_white] is left at the default value of [code]1.0[/code] this is identical to [constant TONE_MAPPER_LINEAR] while also being slightly less performant.
</constant> </constant>
<constant name="TONE_MAPPER_FILMIC" value="2" enum="ToneMapper"> <constant name="TONE_MAPPER_FILMIC" value="2" enum="ToneMapper">
Filmic tonemapper operator. This avoids clipping bright highlights, with a resulting image that usually looks more vivid than [constant TONE_MAPPER_REINHARDT]. Filmic tonemapper operator. This avoids clipping bright highlights, with a resulting image that usually looks more vivid than [constant TONE_MAPPER_REINHARDT].

View File

@ -5219,7 +5219,7 @@
Output color as they came in. This can cause bright lighting to look blown out, with noticeable clipping in the output colors. Output color as they came in. This can cause bright lighting to look blown out, with noticeable clipping in the output colors.
</constant> </constant>
<constant name="ENV_TONE_MAPPER_REINHARD" value="1" enum="EnvironmentToneMapper"> <constant name="ENV_TONE_MAPPER_REINHARD" value="1" enum="EnvironmentToneMapper">
Use the Reinhard tonemapper. Performs a variation on rendered pixels' colors by this formula: [code]color = color / (1 + color)[/code]. This avoids clipping bright highlights, but the resulting image can look a bit dull. Use the Reinhard tonemapper. Performs a variation on rendered pixels' colors by this formula: [code]color = color * (1 + color / (white * white)) / (1 + color)[/code]. This avoids clipping bright highlights, but the resulting image can look a bit dull. When [member Environment.tonemap_white] is left at the default value of [code]1.0[/code] this is identical to [constant ENV_TONE_MAPPER_LINEAR] while also being slightly less performant.
</constant> </constant>
<constant name="ENV_TONE_MAPPER_FILMIC" value="2" enum="EnvironmentToneMapper"> <constant name="ENV_TONE_MAPPER_FILMIC" value="2" enum="EnvironmentToneMapper">
Use the filmic tonemapper. This avoids clipping bright highlights, with a resulting image that usually looks more vivid than [constant ENV_TONE_MAPPER_REINHARD]. Use the filmic tonemapper. This avoids clipping bright highlights, with a resulting image that usually looks more vivid than [constant ENV_TONE_MAPPER_REINHARD].

View File

@ -76,8 +76,12 @@ vec3 tonemap_aces(vec3 color, float p_white) {
return color_tonemapped / p_white_tonemapped; return color_tonemapped / p_white_tonemapped;
} }
// Based on Reinhard's extended formula, see equation 4 in https://doi.org/cjbgrt
vec3 tonemap_reinhard(vec3 color, float p_white) { vec3 tonemap_reinhard(vec3 color, float p_white) {
return (p_white * color + color) / (color * p_white + p_white); float white_squared = p_white * p_white;
vec3 white_squared_color = white_squared * color;
// Equivalent to color * (1 + color / white_squared) / (1 + color)
return (white_squared_color + color * color) / (white_squared_color + white_squared);
} }
#define TONEMAPPER_LINEAR 0 #define TONEMAPPER_LINEAR 0
@ -85,7 +89,7 @@ vec3 tonemap_reinhard(vec3 color, float p_white) {
#define TONEMAPPER_FILMIC 2 #define TONEMAPPER_FILMIC 2
#define TONEMAPPER_ACES 3 #define TONEMAPPER_ACES 3
vec3 apply_tonemapping(vec3 color, float p_white) { // inputs are LINEAR, always outputs clamped [0;1] color vec3 apply_tonemapping(vec3 color, float p_white) { // inputs are LINEAR
// Ensure color values passed to tonemappers are positive. // Ensure color values passed to tonemappers are positive.
// They can be negative in the case of negative lights, which leads to undesired behavior. // They can be negative in the case of negative lights, which leads to undesired behavior.
if (tonemapper == TONEMAPPER_LINEAR) { if (tonemapper == TONEMAPPER_LINEAR) {

View File

@ -256,8 +256,12 @@ vec3 tonemap_aces(vec3 color, float white) {
return color_tonemapped / white_tonemapped; return color_tonemapped / white_tonemapped;
} }
// Based on Reinhard's extended formula, see equation 4 in https://doi.org/cjbgrt
vec3 tonemap_reinhard(vec3 color, float white) { vec3 tonemap_reinhard(vec3 color, float white) {
return (white * color + color) / (color * white + white); float white_squared = white * white;
vec3 white_squared_color = white_squared * color;
// Equivalent to color * (1 + color / white_squared) / (1 + color)
return (white_squared_color + color * color) / (white_squared_color + white_squared);
} }
vec3 linear_to_srgb(vec3 color) { vec3 linear_to_srgb(vec3 color) {
@ -272,7 +276,7 @@ vec3 linear_to_srgb(vec3 color) {
#define TONEMAPPER_FILMIC 2 #define TONEMAPPER_FILMIC 2
#define TONEMAPPER_ACES 3 #define TONEMAPPER_ACES 3
vec3 apply_tonemapping(vec3 color, float white) { // inputs are LINEAR, always outputs clamped [0;1] color vec3 apply_tonemapping(vec3 color, float white) { // inputs are LINEAR
// Ensure color values passed to tonemappers are positive. // Ensure color values passed to tonemappers are positive.
// They can be negative in the case of negative lights, which leads to undesired behavior. // They can be negative in the case of negative lights, which leads to undesired behavior.
if (params.tonemapper == TONEMAPPER_LINEAR) { if (params.tonemapper == TONEMAPPER_LINEAR) {