diff --git a/src/engine/renderer/gl_shader.cpp b/src/engine/renderer/gl_shader.cpp index f23055feb4..94cf36cb70 100644 --- a/src/engine/renderer/gl_shader.cpp +++ b/src/engine/renderer/gl_shader.cpp @@ -799,6 +799,29 @@ static std::string GenEngineConstants() { AddDefine( str, "r_highPrecisionRendering", 1 ); } + if ( r_toneMappingLowLightRestorationSteps.Get() ) + { + AddDefine( str, "r_toneMappingLowLightRestorationSteps", r_toneMappingLowLightRestorationSteps.Get() ); + AddDefine( str, "r_toneMappingLowLightRestorationThreshold", r_toneMappingLowLightRestorationThreshold.Get() ); + + if ( r_showToneMappingLowLightRestoration.Get() ) + { + AddDefine( str, "r_showToneMappingLowLightRestoration", 1 ); + } + } + + if ( r_lowLightDithering.Get() ) + { + AddDefine( str, "r_lowLightDithering", 1 ); + AddDefine( str, "r_lowLightDitheringThreshold", r_lowLightDitheringThreshold.Get() ); + AddDefine( str, "r_lowLightDitheringAmplitudeFactor", r_lowLightDitheringAmplitudeFactor.Get() ); + + if ( r_showLowLightDithering.Get() ) + { + AddDefine( str, "r_showLowLightDithering", 1 ); + } + } + return str; } @@ -3066,4 +3089,4 @@ GlobalUBOProxy::GlobalUBOProxy() : u_Tonemap( this ), u_TonemapParms( this ), u_Exposure( this ) { -} \ No newline at end of file +} diff --git a/src/engine/renderer/glsl_source/cameraEffects_fp.glsl b/src/engine/renderer/glsl_source/cameraEffects_fp.glsl index 8b243fdb44..6fc6ea8318 100644 --- a/src/engine/renderer/glsl_source/cameraEffects_fp.glsl +++ b/src/engine/renderer/glsl_source/cameraEffects_fp.glsl @@ -88,10 +88,112 @@ void main() #if defined(r_highPrecisionRendering) && defined(HAVE_ARB_texture_float) if( u_Tonemap ) { - color.rgb = TonemapLottes( color.rgb ); + #if defined(r_toneMappingLowLightRestorationSteps) + vec3 mapped = TonemapLottes( color.rgb ); + + /* The threshold is the color channel value under which we blend + the tone mapped color with the original color to restore low + light in dark shadows clipped by the tone mapper. + + The threshold is in sRGB space for convenience. For example, + a threshold of 5 would make sure we start restoring low light + when the sRGB-converted tone mapped color goes below #050505, + a threshold of 10 would do the same for #0A0A0A and 16 would + target #101010. + + We need to convert the threshold back to linear because we're + still operating in linear space at this point. + + We don't exactly restore up to the threshold, we blend half + the tone-mapped color with half the color, to avoid producing + color artifacts that would happen by bumping the RGB channels + directly. Each step does the half blend again, so we can say + that the restoration _tends to the threshold_ with each step. + + Each step is done with a small bias to lower the threshold a bit + every step to not not always use the exact same frontier between + the area having light restored and the area being kept unmodified, + then smoothly blending the restoratiin with a gradient. + + Since we blend with half the colors, a threshold smaller than 2 + would do nothing. */ + float threshold; + + if ( u_SRGB ) + { + threshold = pow( float( r_toneMappingLowLightRestorationThreshold ) / 255.0f, 2.2f ); + } + else + { + threshold = float( r_toneMappingLowLightRestorationThreshold ) / 255.0f; + } + + #if defined(r_showToneMappingLowLightRestoration) + color.rgb = vec3(1.0f, 0.0f, 0.0f); + #endif + + bvec3 colorCutoff = lessThan( color.rgb, vec3( threshold ) ); + + for ( int i = 0; i < r_toneMappingLowLightRestorationSteps; i++ ) + { + float t = threshold - ( i * ( threshold / 10.0f ) ); + + bvec3 mappedCutoff = lessThan( mapped, vec3(t) ); + + bvec3 cutoff = bvec3(ivec3(mappedCutoff) * ivec3(colorCutoff)); + + #if __VERSION__ > 120 + vec3 interpolation = vec3(0.5f) * vec3(cutoff); + #else + vec3 interpolation = 0.5f * cutoff; + #endif + + mapped = mix( mapped, color.rgb, interpolation ); + } + + color.rgb = mapped; + #else + color.rgb = TonemapLottes( color.rgb ); + #endif } #endif + #if defined(r_lowLightDithering) + { + float threshold; + + if ( u_SRGB ) + { + threshold = pow( float( r_lowLightDitheringThreshold ) / 255.0f, 2.2f ); + } + else + { + threshold = float( r_lowLightDitheringThreshold ) / 255.0f; + } + + if ( ( color.r + color.g + color.b ) < ( 3.0f * threshold ) ) + { + const float bayer[ 16 ] = { + 0.0f, 8.0f, 2.0f, 10.0f, + 12.0f, 4.0f, 14.0f, 6.0f, + 3.0f, 11.0f, 1.0f, 9.0f, + 15.0f, 7.0f, 13.0f, 5.0f + }; + + float dither = bayer[ + int( mod( gl_FragCoord.x, 4.0f ) ) + + int( mod( gl_FragCoord.y, 4.0f ) ) * 4 + ] / 16.0f; + + color.rgb += vec3( ( ( dither - 0.5f ) / 255.0f ) * r_lowLightDitheringAmplitudeFactor ); + + #if defined(r_showLowLightDithering) + color.rgb = vec3(1.0f, 0.0f, 0.0f); + #endif + } + } + #endif + color.rgb = clamp( color.rgb, vec3( 0.0f ), vec3( 1.0f ) ); if ( u_SRGB ) diff --git a/src/engine/renderer/tr_init.cpp b/src/engine/renderer/tr_init.cpp index 366a08912b..2e6fdd2d83 100644 --- a/src/engine/renderer/tr_init.cpp +++ b/src/engine/renderer/tr_init.cpp @@ -192,7 +192,7 @@ Cvar::Cvar r_rendererAPI( "r_rendererAPI", "Renderer API: 0: OpenGL, 1: Vul "r_toneMappingExposure", "Exposure (brightness adjustment)", Cvar::NONE, 1.0f ); Cvar::Range> r_toneMappingContrast( "r_toneMappingContrast", "Makes dark areas light up faster", - Cvar::NONE, 1.6f, 1.0f, 10.0f ); + Cvar::NONE, 1.4f, 1.0f, 10.0f ); Cvar::Range> r_toneMappingHighlightsCompressionSpeed( "r_toneMappingHighlightsCompressionSpeed", "Highlights saturation", Cvar::NONE, 0.977f, 0.0f, 10.0f ); @@ -206,6 +206,27 @@ Cvar::Cvar r_rendererAPI( "r_rendererAPI", "Renderer API: 0: OpenGL, 1: Vul "r_toneMappingDarkAreaPointLDR", "Convert to this brightness at dark area cut-off", Cvar::NONE, 0.268f, 0.0f, 1.0f ); + Cvar::Range> r_toneMappingLowLightRestorationSteps( + "r_toneMappingLowLightRestorationSteps", "Amount of steps to restore the low lights", + Cvar::NONE, 1, 0, 5 ); + Cvar::Range> r_toneMappingLowLightRestorationThreshold( + "r_toneMappingLowLightRestorationThreshold", "Color channel sRGB value under which low light is restored", + Cvar::NONE, 10, 2, 20 ); + Cvar::Cvar r_showToneMappingLowLightRestoration( + "r_showToneMappingLowLightRestoration", "Show pixels affected by tone mapping low light restoration", + Cvar::CHEAT, false ); + + Cvar::Cvar r_lowLightDithering( + "r_lowLightDithering", "Use dithering in low light areas", Cvar::NONE, true ); + Cvar::Range> r_lowLightDitheringThreshold( + "r_lowLightDitheringThreshold", "Color channel sRGB value under which low light is dithered", + Cvar::NONE, 6, 2, 20 ); + Cvar::Range> r_lowLightDitheringAmplitudeFactor( + "r_lowLightDitheringAmplitudeFactor", "Amplitude factor for the dithering noise", + Cvar::NONE, 0.25, 0.05, 1.0 ); + Cvar::Cvar r_showLowLightDithering( + "r_showLowLightDithering", "Show dithering applied on low light areas", Cvar::CHEAT, false ); + cvar_t *r_lockpvs; cvar_t *r_noportals; @@ -1268,6 +1289,15 @@ ScreenshotCmd screenshotPNGRegistration("screenshotPNG", ssFormat_t::SSF_PNG, "p Cvar::Latch( r_highPrecisionRendering ); Cvar::Latch( r_accurateSRGB ); + Cvar::Latch( r_toneMappingLowLightRestorationSteps ); + Cvar::Latch( r_toneMappingLowLightRestorationThreshold ); + Cvar::Latch( r_showToneMappingLowLightRestoration ); + + Cvar::Latch( r_lowLightDithering ); + Cvar::Latch( r_lowLightDitheringThreshold ); + Cvar::Latch( r_lowLightDitheringAmplitudeFactor ); + Cvar::Latch( r_showLowLightDithering ); + r_drawBuffer = Cvar_Get( "r_drawBuffer", "GL_BACK", CVAR_CHEAT ); r_lockpvs = Cvar_Get( "r_lockpvs", "0", CVAR_CHEAT ); r_noportals = Cvar_Get( "r_noportals", "0", CVAR_CHEAT ); diff --git a/src/engine/renderer/tr_local.h b/src/engine/renderer/tr_local.h index 8463a4e28d..286ea35ecc 100644 --- a/src/engine/renderer/tr_local.h +++ b/src/engine/renderer/tr_local.h @@ -2717,6 +2717,15 @@ enum extern Cvar::Cvar r_highPrecisionRendering; extern Cvar::Cvar r_accurateSRGB; + extern Cvar::Range> r_toneMappingLowLightRestorationSteps; + extern Cvar::Range> r_toneMappingLowLightRestorationThreshold; + extern Cvar::Cvar r_showToneMappingLowLightRestoration; + + extern Cvar::Cvar r_lowLightDithering; + extern Cvar::Range> r_lowLightDitheringThreshold; + extern Cvar::Range> r_lowLightDitheringAmplitudeFactor; + extern Cvar::Cvar r_showLowLightDithering; + extern Cvar::Range> r_shadows; extern cvar_t *r_lockpvs;