From 6d45c243f85942b20dab58753e735ec89a68f710 Mon Sep 17 00:00:00 2001 From: x2048 Date: Fri, 6 Jan 2023 22:33:25 +0100 Subject: Add dynamic exposure correction (#12959) * Add uniform for frame delta time * Adjust exposure in logarithmic (EV) space * Add network support and LUA API * Add testing mod --- client/shaders/extract_bloom/opengl_fragment.glsl | 13 ++-- client/shaders/extract_bloom/opengl_vertex.glsl | 8 +++ client/shaders/second_stage/opengl_fragment.glsl | 11 +++- client/shaders/second_stage/opengl_vertex.glsl | 13 ++++ .../shaders/update_exposure/opengl_fragment.glsl | 75 ++++++++++++++++++++++ client/shaders/update_exposure/opengl_vertex.glsl | 11 ++++ 6 files changed, 123 insertions(+), 8 deletions(-) create mode 100644 client/shaders/update_exposure/opengl_fragment.glsl create mode 100644 client/shaders/update_exposure/opengl_vertex.glsl (limited to 'client') diff --git a/client/shaders/extract_bloom/opengl_fragment.glsl b/client/shaders/extract_bloom/opengl_fragment.glsl index af320f9ab..45f5e9c6f 100644 --- a/client/shaders/extract_bloom/opengl_fragment.glsl +++ b/client/shaders/extract_bloom/opengl_fragment.glsl @@ -1,8 +1,12 @@ #define rendered texture0 +struct ExposureParams { + float compensationFactor; +}; + uniform sampler2D rendered; -uniform mediump float exposureFactor; uniform mediump float bloomStrength; +uniform ExposureParams exposureParams; #ifdef GL_ES varying mediump vec2 varTexCoord; @@ -10,6 +14,7 @@ varying mediump vec2 varTexCoord; centroid varying vec2 varTexCoord; #endif +varying float exposure; void main(void) { @@ -18,10 +23,6 @@ void main(void) // translate to linear colorspace (approximate) color = pow(color, vec3(2.2)); - // Scale colors by luminance to amplify bright colors - // in SDR textures. - float luminance = dot(color, vec3(0.213, 0.515, 0.072)); - luminance *= luminance; - color *= luminance * exposureFactor * bloomStrength; + color *= pow(2., exposure) * exposureParams.compensationFactor * bloomStrength; gl_FragColor = vec4(color, 1.0); // force full alpha to avoid holes in the image. } diff --git a/client/shaders/extract_bloom/opengl_vertex.glsl b/client/shaders/extract_bloom/opengl_vertex.glsl index 12692c296..479ae1079 100644 --- a/client/shaders/extract_bloom/opengl_vertex.glsl +++ b/client/shaders/extract_bloom/opengl_vertex.glsl @@ -1,11 +1,19 @@ +#define exposureMap texture1 + +uniform sampler2D exposureMap; + #ifdef GL_ES varying mediump vec2 varTexCoord; #else centroid varying vec2 varTexCoord; #endif +varying float exposure; + void main(void) { + exposure = texture2D(exposureMap, vec2(0.5)).r; + varTexCoord.st = inTexCoord0.st; gl_Position = inVertexPosition; } diff --git a/client/shaders/second_stage/opengl_fragment.glsl b/client/shaders/second_stage/opengl_fragment.glsl index 09b49ef0e..2ff58aa42 100644 --- a/client/shaders/second_stage/opengl_fragment.glsl +++ b/client/shaders/second_stage/opengl_fragment.glsl @@ -1,9 +1,14 @@ #define rendered texture0 #define bloom texture1 +struct ExposureParams { + float compensationFactor; +}; + uniform sampler2D rendered; uniform sampler2D bloom; -uniform mediump float exposureFactor; + +uniform ExposureParams exposureParams; uniform lowp float bloomIntensity; uniform lowp float saturation; @@ -13,6 +18,8 @@ varying mediump vec2 varTexCoord; centroid varying vec2 varTexCoord; #endif +varying float exposure; + #ifdef ENABLE_BLOOM vec4 applyBloom(vec4 color, vec2 uv) @@ -80,7 +87,7 @@ void main(void) if (uv.x > 0.5 || uv.y > 0.5) #endif { - color.rgb *= exposureFactor; + color.rgb *= exposure * exposureParams.compensationFactor; } diff --git a/client/shaders/second_stage/opengl_vertex.glsl b/client/shaders/second_stage/opengl_vertex.glsl index 12692c296..7c121f6d1 100644 --- a/client/shaders/second_stage/opengl_vertex.glsl +++ b/client/shaders/second_stage/opengl_vertex.glsl @@ -1,11 +1,24 @@ +#define exposureMap texture2 + +uniform sampler2D exposureMap; + #ifdef GL_ES varying mediump vec2 varTexCoord; #else centroid varying vec2 varTexCoord; #endif +varying float exposure; + void main(void) { +#ifdef ENABLE_AUTO_EXPOSURE + exposure = texture2D(exposureMap, vec2(0.5)).r; + exposure = pow(2., exposure); +#else + exposure = 1.0; +#endif + varTexCoord.st = inTexCoord0.st; gl_Position = inVertexPosition; } diff --git a/client/shaders/update_exposure/opengl_fragment.glsl b/client/shaders/update_exposure/opengl_fragment.glsl new file mode 100644 index 000000000..dfed8f0d8 --- /dev/null +++ b/client/shaders/update_exposure/opengl_fragment.glsl @@ -0,0 +1,75 @@ +#define exposure texture0 +#define screen texture1 + +struct ExposureParams { + float luminanceMin; + float luminanceMax; + float exposureCorrection; + float luminanceKey; + float speedDarkBright; + float speedBrightDark; + float centerWeightPower; + float compensationFactor; +}; + +uniform sampler2D exposure; +uniform sampler2D screen; + +#ifdef ENABLE_BLOOM +uniform float bloomStrength; +#else +const float bloomStrength = 1.0; +#endif +uniform ExposureParams exposureParams; +uniform float animationTimerDelta; + + +const vec3 luminanceFactors = vec3(0.213, 0.715, 0.072); + +float getLuminance(vec3 color) +{ + return dot(color, luminanceFactors); +} + +void main(void) +{ + float previousExposure = texture2D(exposure, vec2(0.5, 0.5)).r; + + vec3 averageColor = vec3(0.); + float n = 0.; + + // Scan the screen with center-weighting and sample average color + for (float _x = 0.1; _x < 0.9; _x += 0.17) { + float x = pow(_x, exposureParams.centerWeightPower); + for (float _y = 0.1; _y < 0.9; _y += 0.17) { + float y = pow(_y, exposureParams.centerWeightPower); + averageColor += texture2D(screen, vec2(0.5 + 0.5 * x, 0.5 + 0.5 * y)).rgb; + averageColor += texture2D(screen, vec2(0.5 + 0.5 * x, 0.5 - 0.5 * y)).rgb; + averageColor += texture2D(screen, vec2(0.5 - 0.5 * x, 0.5 + 0.5 * y)).rgb; + averageColor += texture2D(screen, vec2(0.5 - 0.5 * x, 0.5 - 0.5 * y)).rgb; + n += 4.; + } + } + + float luminance = getLuminance(averageColor); + luminance /= n; + + luminance /= pow(2., previousExposure) * bloomStrength * exposureParams.compensationFactor; // compensate for the configurable factors + + luminance = clamp(luminance, exposureParams.luminanceMin, exposureParams.luminanceMax); + + // From https://media.contentapi.ea.com/content/dam/eacom/frostbite/files/course-notes-moving-frostbite-to-pbr-v2.pdf + // 1. EV100 = log2(luminance * S / K) where S = 100, K = 0.125 = log2(luminance) + 3 + // 2. Lmax = 1.2 * 2 ^ (EV100 - EC) + // => Lmax = 1.2 * 2^3 * luminance / 2^EC = 9.6 * luminance / 2^EC + // 3. exposure = 1 / Lmax + // => exposure = 2^EC / (9.6 * luminance) + float wantedExposure = exposureParams.exposureCorrection - log(luminance)/0.693147180559945 - 3.263034405833794; + + if (wantedExposure < previousExposure) + wantedExposure = mix(wantedExposure, previousExposure, exp(-animationTimerDelta * exposureParams.speedDarkBright)); // dark -> bright + else + wantedExposure = mix(wantedExposure, previousExposure, exp(-animationTimerDelta * exposureParams.speedBrightDark)); // bright -> dark + + gl_FragColor = vec4(vec3(wantedExposure), 1.); +} diff --git a/client/shaders/update_exposure/opengl_vertex.glsl b/client/shaders/update_exposure/opengl_vertex.glsl new file mode 100644 index 000000000..12692c296 --- /dev/null +++ b/client/shaders/update_exposure/opengl_vertex.glsl @@ -0,0 +1,11 @@ +#ifdef GL_ES +varying mediump vec2 varTexCoord; +#else +centroid varying vec2 varTexCoord; +#endif + +void main(void) +{ + varTexCoord.st = inTexCoord0.st; + gl_Position = inVertexPosition; +} -- cgit v1.2.3