From adbd337038d43d1caf25d32519c91681d57de899 Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Tue, 23 Jan 2024 00:52:56 +0100 Subject: [PATCH 1/2] renderer: implement complete overBright in GLSL We cannot overBright separate stage as stage are clamped within [0.0, 1.0] when blending multiple stages together, meaning the result of the multiplication of a separate light stage with a light factor will be clamped before blending it with the color map. This implementation implements overBright in the camera shader, but to only apply this overBright to surfaces having received lights, surfaces that do not receive lights gets divided by the light factor in a way re-multiplying them cancels the division. Stages and surfaces to be blended over other stages or surfaces are also divided to avoid multiplying multiple time the same surface, in order to have the final result being (m * a) + b instead of m * (a + b) wich would be (m * a) + (m * b) and then sump up the factor, something we don't want to do. The legacy code for overbrighting (but clamping) the light at BSP load is kept for when the feature is disabled, but rewritten to make the code better. This feature can be enabled by disabling the r_mapClampOverBright cvar. --- src/engine/renderer/gl_shader.cpp | 9 +- src/engine/renderer/gl_shader.h | 39 ++++- .../glsl_source/cameraEffects_fp.glsl | 8 +- .../renderer/glsl_source/contrast_fp.glsl | 4 + .../renderer/glsl_source/fogGlobal_fp.glsl | 8 +- .../renderer/glsl_source/fogQuake3_fp.glsl | 4 + .../renderer/glsl_source/generic_fp.glsl | 9 ++ .../renderer/glsl_source/lightMapping_fp.glsl | 19 ++- .../renderer/glsl_source/skybox_fp.glsl | 4 + src/engine/renderer/tr_backend.cpp | 9 ++ src/engine/renderer/tr_bsp.cpp | 150 +++++++++++++----- src/engine/renderer/tr_image.cpp | 7 +- src/engine/renderer/tr_init.cpp | 2 + src/engine/renderer/tr_local.h | 14 +- src/engine/renderer/tr_scene.cpp | 3 + src/engine/renderer/tr_shade.cpp | 21 +++ src/engine/renderer/tr_shader.cpp | 23 +++ src/engine/renderer/tr_sky.cpp | 3 + 18 files changed, 283 insertions(+), 53 deletions(-) diff --git a/src/engine/renderer/gl_shader.cpp b/src/engine/renderer/gl_shader.cpp index 43bd43bb51..d195a70487 100644 --- a/src/engine/renderer/gl_shader.cpp +++ b/src/engine/renderer/gl_shader.cpp @@ -1615,6 +1615,7 @@ GLShader_generic::GLShader_generic( GLShaderManager *manager ) : u_ModelMatrix( this ), u_ProjectionMatrixTranspose( this ), u_ModelViewProjectionMatrix( this ), + u_InverseLightFactor( this ), u_ColorModulate( this ), u_Color( this ), u_Bones( this ), @@ -1653,6 +1654,7 @@ GLShader_lightMapping::GLShader_lightMapping( GLShaderManager *manager ) : u_ViewOrigin( this ), u_ModelMatrix( this ), u_ModelViewProjectionMatrix( this ), + u_InverseLightFactor( this ), u_Bones( this ), u_VertexInterpolation( this ), u_ReliefDepthScale( this ), @@ -1973,6 +1975,7 @@ GLShader_skybox::GLShader_skybox( GLShaderManager *manager ) : u_AlphaThreshold( this ), u_ModelMatrix( this ), u_ModelViewProjectionMatrix( this ), + u_InverseLightFactor( this ), u_VertexInterpolation( this ), GLDeformStage( this ), GLCompileMacro_USE_ALPHA_TESTING( this ) @@ -1989,6 +1992,7 @@ GLShader_fogQuake3::GLShader_fogQuake3( GLShaderManager *manager ) : GLShader( "fogQuake3", ATTR_POSITION | ATTR_QTANGENT, manager ), u_ModelMatrix( this ), u_ModelViewProjectionMatrix( this ), + u_InverseLightFactor( this ), u_Color( this ), u_Bones( this ), u_VertexInterpolation( this ), @@ -2017,6 +2021,7 @@ GLShader_fogGlobal::GLShader_fogGlobal( GLShaderManager *manager ) : u_ViewMatrix( this ), u_ModelViewProjectionMatrix( this ), u_UnprojectMatrix( this ), + u_InverseLightFactor( this ), u_Color( this ), u_FogDistanceVector( this ), u_FogDepthVector( this ) @@ -2094,7 +2099,8 @@ void GLShader_portal::SetShaderProgramUniforms( shaderProgram_t *shaderProgram ) GLShader_contrast::GLShader_contrast( GLShaderManager *manager ) : GLShader( "contrast", ATTR_POSITION, manager ), - u_ModelViewProjectionMatrix( this ) + u_ModelViewProjectionMatrix( this ), + u_InverseLightFactor( this ) { } @@ -2108,6 +2114,7 @@ GLShader_cameraEffects::GLShader_cameraEffects( GLShaderManager *manager ) : u_ColorModulate( this ), u_TextureMatrix( this ), u_ModelViewProjectionMatrix( this ), + u_LightFactor( this ), u_DeformMagnitude( this ), u_InverseGamma( this ) { diff --git a/src/engine/renderer/gl_shader.h b/src/engine/renderer/gl_shader.h index d20e77cf00..ee4a5a731f 100644 --- a/src/engine/renderer/gl_shader.h +++ b/src/engine/renderer/gl_shader.h @@ -1304,6 +1304,36 @@ class GLCompileMacro_USE_ALPHA_TESTING : } }; +class u_LightFactor : + GLUniform1f +{ +public: + u_LightFactor( GLShader *shader ) : + GLUniform1f( shader, "u_LightFactor" ) + { + } + + void SetUniform_LightFactor( const float lightFactor ) + { + this->SetValue( lightFactor ); + } +}; + +class u_InverseLightFactor : + GLUniform1f +{ +public: + u_InverseLightFactor( GLShader *shader ) : + GLUniform1f( shader, "u_InverseLightFactor" ) + { + } + + void SetUniform_InverseLightFactor( const float inverseLightFactor ) + { + this->SetValue( inverseLightFactor ); + } +}; + class u_TextureMatrix : GLUniformMatrix4f { @@ -2270,6 +2300,7 @@ class GLShader_generic : public u_ModelMatrix, public u_ProjectionMatrixTranspose, public u_ModelViewProjectionMatrix, + public u_InverseLightFactor, public u_ColorModulate, public u_Color, public u_Bones, @@ -2300,6 +2331,7 @@ class GLShader_lightMapping : public u_ViewOrigin, public u_ModelMatrix, public u_ModelViewProjectionMatrix, + public u_InverseLightFactor, public u_Bones, public u_VertexInterpolation, public u_ReliefDepthScale, @@ -2502,6 +2534,7 @@ class GLShader_skybox : public u_AlphaThreshold, public u_ModelMatrix, public u_ModelViewProjectionMatrix, + public u_InverseLightFactor, public u_VertexInterpolation, public GLDeformStage, public GLCompileMacro_USE_ALPHA_TESTING @@ -2515,6 +2548,7 @@ class GLShader_fogQuake3 : public GLShader, public u_ModelMatrix, public u_ModelViewProjectionMatrix, + public u_InverseLightFactor, public u_Color, public u_Bones, public u_VertexInterpolation, @@ -2537,6 +2571,7 @@ class GLShader_fogGlobal : public u_ViewMatrix, public u_ModelViewProjectionMatrix, public u_UnprojectMatrix, + public u_InverseLightFactor, public u_Color, public u_FogDistanceVector, public u_FogDepthVector @@ -2595,7 +2630,8 @@ class GLShader_portal : class GLShader_contrast : public GLShader, - public u_ModelViewProjectionMatrix + public u_ModelViewProjectionMatrix, + public u_InverseLightFactor { public: GLShader_contrast( GLShaderManager *manager ); @@ -2607,6 +2643,7 @@ class GLShader_cameraEffects : public u_ColorModulate, public u_TextureMatrix, public u_ModelViewProjectionMatrix, + public u_LightFactor, public u_DeformMagnitude, public u_InverseGamma { diff --git a/src/engine/renderer/glsl_source/cameraEffects_fp.glsl b/src/engine/renderer/glsl_source/cameraEffects_fp.glsl index b9289b93e0..1659bc2b1b 100644 --- a/src/engine/renderer/glsl_source/cameraEffects_fp.glsl +++ b/src/engine/renderer/glsl_source/cameraEffects_fp.glsl @@ -24,6 +24,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA uniform sampler2D u_CurrentMap; uniform sampler3D u_ColorMap; + +uniform float u_LightFactor; uniform vec4 u_ColorModulate; uniform float u_InverseGamma; @@ -36,9 +38,11 @@ void main() // calculate the screen texcoord in the 0.0 to 1.0 range vec2 st = gl_FragCoord.st / r_FBufSize; - vec4 original = clamp(texture2D(u_CurrentMap, st), 0.0, 1.0); + vec4 color = texture2D(u_CurrentMap, st); + + color.rgb *= u_LightFactor; - vec4 color = original; + color = clamp(color, 0.0, 1.0); // apply color grading vec3 colCoord = color.rgb * 15.0 / 16.0 + 0.5 / 16.0; diff --git a/src/engine/renderer/glsl_source/contrast_fp.glsl b/src/engine/renderer/glsl_source/contrast_fp.glsl index ef74cf594b..7c06cd7a8f 100644 --- a/src/engine/renderer/glsl_source/contrast_fp.glsl +++ b/src/engine/renderer/glsl_source/contrast_fp.glsl @@ -24,6 +24,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA uniform sampler2D u_ColorMap; +uniform float u_InverseLightFactor; + const vec4 LUMINANCE_VECTOR = vec4(0.2125, 0.7154, 0.0721, 0.0); #if __VERSION__ > 120 @@ -57,5 +59,7 @@ void main() color += f(texture2D(u_ColorMap, st + vec2(1.0, 1.0) * scale)); color *= 0.25; + color.rgb *= u_InverseLightFactor; + outputColor = color; } diff --git a/src/engine/renderer/glsl_source/fogGlobal_fp.glsl b/src/engine/renderer/glsl_source/fogGlobal_fp.glsl index 1299bd1bee..2151f5b1b9 100644 --- a/src/engine/renderer/glsl_source/fogGlobal_fp.glsl +++ b/src/engine/renderer/glsl_source/fogGlobal_fp.glsl @@ -24,6 +24,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA uniform sampler2D u_ColorMap; // fog texture uniform sampler2D u_DepthMap; + +uniform float u_InverseLightFactor; uniform vec3 u_ViewOrigin; uniform vec4 u_FogDistanceVector; uniform vec4 u_FogDepthVector; @@ -52,5 +54,9 @@ void main() // st.s = vertexDistanceToCamera; st.t = 1.0; - outputColor = u_Color * texture2D(u_ColorMap, st); + vec4 color = texture2D(u_ColorMap, st); + + color.rgb *= u_InverseLightFactor; + + outputColor = u_Color * color; } diff --git a/src/engine/renderer/glsl_source/fogQuake3_fp.glsl b/src/engine/renderer/glsl_source/fogQuake3_fp.glsl index f618ea294c..7b8751a645 100644 --- a/src/engine/renderer/glsl_source/fogQuake3_fp.glsl +++ b/src/engine/renderer/glsl_source/fogQuake3_fp.glsl @@ -24,6 +24,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA uniform sampler2D u_ColorMap; +uniform float u_InverseLightFactor; IN(smooth) vec3 var_Position; IN(smooth) vec2 var_TexCoords; IN(smooth) vec4 var_Color; @@ -35,6 +36,9 @@ void main() vec4 color = texture2D(u_ColorMap, var_TexCoords); color *= var_Color; + + color.rgb *= u_InverseLightFactor; + outputColor = color; #if 0 diff --git a/src/engine/renderer/glsl_source/generic_fp.glsl b/src/engine/renderer/glsl_source/generic_fp.glsl index f422a16f9a..6b55915d1b 100644 --- a/src/engine/renderer/glsl_source/generic_fp.glsl +++ b/src/engine/renderer/glsl_source/generic_fp.glsl @@ -25,6 +25,10 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA uniform sampler2D u_ColorMap; uniform float u_AlphaThreshold; +#if !defined(GENERIC_2D) +uniform float u_InverseLightFactor; +#endif + IN(smooth) vec2 var_TexCoords; IN(smooth) vec4 var_Color; @@ -54,6 +58,11 @@ void main() #endif color *= var_Color; + +#if !defined(GENERIC_2D) && !defined(USE_DEPTH_FADE) + color.rgb *= u_InverseLightFactor; +#endif + outputColor = color; #if defined(GENERIC_2D) diff --git a/src/engine/renderer/glsl_source/lightMapping_fp.glsl b/src/engine/renderer/glsl_source/lightMapping_fp.glsl index ef1d1b7bb9..63ec85c6c6 100644 --- a/src/engine/renderer/glsl_source/lightMapping_fp.glsl +++ b/src/engine/renderer/glsl_source/lightMapping_fp.glsl @@ -27,6 +27,7 @@ uniform sampler2D u_MaterialMap; uniform sampler2D u_GlowMap; uniform float u_AlphaThreshold; +uniform float u_InverseLightFactor; uniform vec3 u_ViewOrigin; IN(smooth) vec3 var_Position; @@ -172,9 +173,25 @@ void main() color.rgb += 0.7 * emission; #endif + /* HACK: use sign to know if there is a light or not, and + then if it will receive overbright multiplication or not. */ + if ( u_InverseLightFactor > 0 ) + { + color.rgb *= u_InverseLightFactor; + } + #if defined(r_glowMapping) // Blend glow map. - color.rgb += texture2D(u_GlowMap, texCoords).rgb; + vec3 glow = texture2D(u_GlowMap, texCoords).rgb; + + /* HACK: use sign to know if there is a light or not, and + then if it will receive overbright multiplication or not. */ + if ( u_InverseLightFactor < 0 ) + { + glow *= - u_InverseLightFactor; + } + + color.rgb += glow; #endif outputColor = color; diff --git a/src/engine/renderer/glsl_source/skybox_fp.glsl b/src/engine/renderer/glsl_source/skybox_fp.glsl index 2073f1e034..abc98a2e82 100644 --- a/src/engine/renderer/glsl_source/skybox_fp.glsl +++ b/src/engine/renderer/glsl_source/skybox_fp.glsl @@ -25,6 +25,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA const float radiusWorld = 4096.0; // Value used by quake 3 skybox code uniform samplerCube u_ColorMap; + +uniform float u_InverseLightFactor; uniform sampler2D u_CloudMap; uniform bool u_UseCloudMap; @@ -73,5 +75,7 @@ void main() } #endif + color.rgb *= u_InverseLightFactor; + outputColor = color; } diff --git a/src/engine/renderer/tr_backend.cpp b/src/engine/renderer/tr_backend.cpp index fce4d115ea..7c7fdc3ccc 100644 --- a/src/engine/renderer/tr_backend.cpp +++ b/src/engine/renderer/tr_backend.cpp @@ -2971,6 +2971,9 @@ void RB_RenderGlobalFog() gl_fogGlobalShader->SetUniform_ViewOrigin( backEnd.viewParms.orientation.origin ); // world space + // u_InverseLightFactor + gl_fogGlobalShader->SetUniform_InverseLightFactor( tr.mapInverseLightFactor ); + { fog_t *fog; @@ -3067,6 +3070,9 @@ void RB_RenderBloom() gl_contrastShader->SetUniform_ModelViewProjectionMatrix( glState.modelViewProjectionMatrix[ glState.stackIndex ] ); + // u_InverseLightFactor + gl_contrastShader->SetUniform_InverseLightFactor( tr.mapInverseLightFactor ); + GL_BindToTMU( 0, tr.currentRenderImage[ backEnd.currentMainFBO ] ); GL_PopMatrix(); // special 1/4th of the screen contrastRenderFBO ortho @@ -3292,6 +3298,9 @@ void RB_CameraPostFX() // enable shader, set arrays gl_cameraEffectsShader->BindProgram( 0 ); + // u_LightFactor + gl_cameraEffectsShader->SetUniform_LightFactor( tr.mapLightFactor ); + gl_cameraEffectsShader->SetUniform_ColorModulate( backEnd.viewParms.gradingWeights ); gl_cameraEffectsShader->SetUniform_ModelViewProjectionMatrix( glState.modelViewProjectionMatrix[ glState.stackIndex ] ); diff --git a/src/engine/renderer/tr_bsp.cpp b/src/engine/renderer/tr_bsp.cpp index 7e88f34d8a..5b79602982 100644 --- a/src/engine/renderer/tr_bsp.cpp +++ b/src/engine/renderer/tr_bsp.cpp @@ -51,9 +51,39 @@ static int c_vboShadowSurfaces; R_ColorShiftLightingBytes =============== */ -static void R_ColorShiftLightingBytes( byte in[ 4 ], byte out[ 4 ] ) +static void R_ColorShiftLightingBytes( byte bytes[ 4 ] ) { - int shift, r, g, b; + /* This implementation is strongly buggy as for every shift bit, the max light + is clamped by one bit and then divided by two, the stronger the light factor is, + the more the light is clamped. + + The Q3Radiant Shader Manual said: + > Colors will be (1.0,1.0,1.0) if running without overbright bits + > (NT, linux, windowed modes), or (0.5, 0.5, 0.5) if running with + > overbright. + > -- https://icculus.org/gtkradiant/documentation/Q3AShader_Manual/ch05/pg5_1.htm + + In this sentence, “running with overbright” is about using hardware + overbright, and “running without overbright” is about using this function. + + This means Quake III Arena was only supporting hardware overbright + on pre-NT Windows 9x systems when fullscreen, and running this buggy + code on every other platforms and when windowed. + + Debugging regressions from Tremulous and other Quake 3 or Wolf:ET derivated games + in legacy features unrelated to lighting overbright may require to temporarily + re-enable such buggy clamping to keep a fair comparison and avoid reimplementing + some clamping in an attempt to get a 1:1 comparison while not running a code not + backward compatible with legacy bugs. + + This function is then kept to provide the ability to load map with a renderer + backward compatible with this bug for diagnostic purpose and fair comparison with + other buggy engines. */ + + if ( tr.mapOverBrightBits == 0 ) + { + return; + } /* Shift the color data based on overbright range. @@ -69,12 +99,13 @@ static void R_ColorShiftLightingBytes( byte in[ 4 ], byte out[ 4 ] ) The original code was there to only shift in software what hardware overbright bit feature was not doing, but this implementation is entirely software. */ - shift = tr.mapOverBrightBits; + + int shift = tr.mapOverBrightBits; // shift the data based on overbright range - r = in[ 0 ] << shift; - g = in[ 1 ] << shift; - b = in[ 2 ] << shift; + int r = bytes[ 0 ] << shift; + int g = bytes[ 1 ] << shift; + int b = bytes[ 2 ] << shift; // normalize by color instead of saturating to white if ( ( r | g | b ) > 255 ) @@ -88,74 +119,78 @@ static void R_ColorShiftLightingBytes( byte in[ 4 ], byte out[ 4 ] ) b = b * 255 / max; } - out[ 0 ] = r; - out[ 1 ] = g; - out[ 2 ] = b; - out[ 3 ] = in[ 3 ]; + bytes[ 0 ] = r; + bytes[ 1 ] = g; + bytes[ 2 ] = b; } -static void R_ColorShiftLightingBytesCompressed( byte in[ 8 ], byte out[ 8 ] ) +static void R_ColorShiftLightingBytesCompressed( byte bytes[ 8 ] ) { - unsigned short rgb565; - byte rgba[4]; + if ( tr.mapOverBrightBits == 0 ) + { + return; + } // color shift the endpoint colors in the dxt block - rgb565 = in[1] << 8 | in[0]; + unsigned short rgb565 = bytes[1] << 8 | bytes[0]; + byte rgba[4]; + rgba[0] = (rgb565 >> 8) & 0xf8; rgba[1] = (rgb565 >> 3) & 0xfc; rgba[2] = (rgb565 << 3) & 0xf8; rgba[3] = 0xff; - R_ColorShiftLightingBytes( rgba, rgba ); + + R_ColorShiftLightingBytes( rgba ); + rgb565 = ((rgba[0] >> 3) << 11) | ((rgba[1] >> 2) << 5) | ((rgba[2] >> 3) << 0); - out[0] = rgb565 & 0xff; - out[1] = rgb565 >> 8; + bytes[0] = rgb565 & 0xff; + bytes[1] = rgb565 >> 8; - rgb565 = in[3] << 8 | in[2]; + rgb565 = bytes[3] << 8 | bytes[2]; rgba[0] = (rgb565 >> 8) & 0xf8; rgba[1] = (rgb565 >> 3) & 0xfc; rgba[2] = (rgb565 << 3) & 0xf8; rgba[3] = 0xff; - R_ColorShiftLightingBytes( rgba, rgba ); + + R_ColorShiftLightingBytes( rgba ); + rgb565 = ((rgba[0] >> 3) << 11) | ((rgba[1] >> 2) << 5) | ((rgba[2] >> 3) << 0); - out[2] = rgb565 & 0xff; - out[3] = rgb565 >> 8; + bytes[2] = rgb565 & 0xff; + bytes[3] = rgb565 >> 8; } /* =============== R_ProcessLightmap - - returns maxIntensity =============== */ -float R_ProcessLightmap( byte *pic, int in_padding, int width, int height, int bits, byte *pic_out ) +void R_ProcessLightmap( byte *bytes, int width, int height, int bits ) { - int j; - float maxIntensity = 0; + if ( tr.mapOverBrightBits == 0 ) + { + return; + } - if( bits & IF_BC1 ) { - for ( j = 0; j < ((width + 3) >> 2) * ((height + 3) >> 2); j++ ) + if ( bits & IF_BC1 ) { + for ( int i = 0; i < ((width + 3) >> 2) * ((height + 3) >> 2); i++ ) { - R_ColorShiftLightingBytesCompressed( &pic[ j * 8 ], &pic_out[ j * 8 ] ); + R_ColorShiftLightingBytesCompressed( &bytes[ i * 8 ] ); } } else if( bits & (IF_BC2 | IF_BC3) ) { - for ( j = 0; j < ((width + 3) >> 2) * ((height + 3) >> 2); j++ ) + for ( int i = 0; i < ((width + 3) >> 2) * ((height + 3) >> 2); i++ ) { - R_ColorShiftLightingBytesCompressed( &pic[ j * 16 ], &pic_out[ j * 16 ] ); + R_ColorShiftLightingBytesCompressed( &bytes[ i * 16 ] ); } } else { - for ( j = 0; j < width * height; j++ ) + for ( int i = 0; i < width * height; i++ ) { - R_ColorShiftLightingBytes( &pic[ j * in_padding ], &pic_out[ j * 4 ] ); - pic_out[ j * 4 + 3 ] = 255; + R_ColorShiftLightingBytes( &bytes[ i * 4 ] ); } } - - return maxIntensity; } static int LightmapNameCompare( const char *s1, const char *s2 ) @@ -640,7 +675,10 @@ static void R_LoadLightmaps( lump_t *l, const char *bspName ) lightMapBuffer[( index * 4 ) + 2 ] = buf_p[( ( x + ( y * internalLightMapSize ) ) * 3 ) + 2 ]; lightMapBuffer[( index * 4 ) + 3 ] = 255; - R_ColorShiftLightingBytes( &lightMapBuffer[( index * 4 ) + 0 ], &lightMapBuffer[( index * 4 ) + 0 ] ); + if ( tr.forceLegacyMapOverBrightClamping ) + { + R_ColorShiftLightingBytes( &lightMapBuffer[( index * 4 ) + 0 ] ); + } } } @@ -1004,7 +1042,10 @@ static void ParseFace( dsurface_t *ds, drawVert_t *verts, bspSurface_t *surf, in cv->verts[ i ].lightColor = Color::Adapt( verts[ i ].color ); - R_ColorShiftLightingBytes( cv->verts[ i ].lightColor.ToArray(), cv->verts[ i ].lightColor.ToArray() ); + if ( tr.forceLegacyMapOverBrightClamping ) + { + R_ColorShiftLightingBytes( cv->verts[ i ].lightColor.ToArray() ); + } } // copy triangles @@ -1211,7 +1252,10 @@ static void ParseMesh( dsurface_t *ds, drawVert_t *verts, bspSurface_t *surf ) points[ i ].lightColor = Color::Adapt( verts[ i ].color ); - R_ColorShiftLightingBytes( points[ i ].lightColor.ToArray(), points[ i ].lightColor.ToArray() ); + if ( tr.forceLegacyMapOverBrightClamping ) + { + R_ColorShiftLightingBytes( points[ i ].lightColor.ToArray() ); + } } // center texture coords @@ -1335,7 +1379,10 @@ static void ParseTriSurf( dsurface_t *ds, drawVert_t *verts, bspSurface_t *surf, cv->verts[ i ].lightColor = Color::Adapt( verts[ i ].color ); - R_ColorShiftLightingBytes( cv->verts[ i ].lightColor.ToArray(), cv->verts[ i ].lightColor.ToArray() ); + if ( tr.forceLegacyMapOverBrightClamping ) + { + R_ColorShiftLightingBytes( cv->verts[ i ].lightColor.ToArray() ); + } } // copy triangles @@ -4123,8 +4170,11 @@ void R_LoadLightGrid( lump_t *l ) tmpDirected[ 2 ] = in->directed[ 2 ]; tmpDirected[ 3 ] = 255; - R_ColorShiftLightingBytes( tmpAmbient, tmpAmbient ); - R_ColorShiftLightingBytes( tmpDirected, tmpDirected ); + if ( tr.forceLegacyMapOverBrightClamping ) + { + R_ColorShiftLightingBytes( tmpAmbient ); + R_ColorShiftLightingBytes( tmpDirected ); + } for ( j = 0; j < 3; j++ ) { @@ -4366,6 +4416,12 @@ void R_LoadEntities( lump_t *l ) tr.mapOverBrightBits = Math::Clamp( atof( value ), 0.0, 3.0 ); } + // Force forceLegacyMapOverBrightClamping even if r_forceLegacyMapOverBrightClamping is false. + else if ( !Q_stricmp( keyname, "forceLegacyMapOverBrightClamping" ) && !Q_stricmp( value, "1" ) ) + { + tr.forceLegacyMapOverBrightClamping = true; + } + // check for deluxe mapping provided by NetRadiant's q3map2 if ( !Q_stricmp( keyname, "_q3map2_cmdline" ) ) { @@ -6931,6 +6987,8 @@ void RE_LoadWorldMap( const char *name ) tr.worldLight = tr.lightMode; tr.modelDeluxe = deluxeMode_t::NONE; + tr.mapLightFactor = 1.0f; + tr.mapInverseLightFactor = 1.0f; if ( tr.worldLight == lightMode_t::FULLBRIGHT ) { @@ -7005,4 +7063,12 @@ void RE_LoadWorldMap( const char *name ) } } } + + /* Used in GLSL code for the GLSL implementation + without color clamping and normalization. */ + if ( !tr.forceLegacyMapOverBrightClamping ) + { + tr.mapLightFactor = pow( 2, tr.mapOverBrightBits ); + tr.mapInverseLightFactor = 1.0f / tr.mapLightFactor; + } } diff --git a/src/engine/renderer/tr_image.cpp b/src/engine/renderer/tr_image.cpp index a27dbdf15a..918a96d88e 100644 --- a/src/engine/renderer/tr_image.cpp +++ b/src/engine/renderer/tr_image.cpp @@ -1739,9 +1739,9 @@ image_t *R_FindImageFile( const char *imageName, imageParams_t &imageParams ) return nullptr; } - if ( imageParams.bits & IF_LIGHTMAP ) + if ( imageParams.bits & IF_LIGHTMAP && tr.forceLegacyMapOverBrightClamping ) { - R_ProcessLightmap( pic[ 0 ], 4, width, height, imageParams.bits, pic[ 0 ] ); + R_ProcessLightmap( pic[ 0 ], width, height, imageParams.bits ); } image = R_CreateImage( ( char * ) buffer, (const byte **)pic, width, height, numMips, imageParams ); @@ -2960,7 +2960,8 @@ void R_InitImages() Because tr.overbrightBits is always 0, tr.identityLight is always 1.0f. We can entirely remove it. */ - tr.mapOverBrightBits = r_mapOverBrightBits.Get(); + tr.mapOverBrightBits = r_mapOverBrightBits.Get(); + tr.forceLegacyMapOverBrightClamping = r_forceLegacyMapOverBrightClamping.Get(); // create default texture and white texture R_CreateBuiltinImages(); diff --git a/src/engine/renderer/tr_init.cpp b/src/engine/renderer/tr_init.cpp index 7fdbb47fda..a8f4c7c4dd 100644 --- a/src/engine/renderer/tr_init.cpp +++ b/src/engine/renderer/tr_init.cpp @@ -82,6 +82,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA cvar_t *r_dynamicLightCastShadows; cvar_t *r_precomputedLighting; Cvar::Cvar r_mapOverBrightBits("r_mapOverBrightBits", "default map light color shift", Cvar::NONE, 2); + Cvar::Cvar r_forceLegacyMapOverBrightClamping("r_forceLegacyMapOverBrightClamping", "clamp over bright of legacy maps (enable multiplied color clamping and normalization)", Cvar::NONE, false); Cvar::Range> r_lightMode("r_lightMode", "lighting mode: 0: fullbright (cheat), 1: vertex light, 2: grid light (cheat), 3: light map", Cvar::NONE, Util::ordinal(lightMode_t::MAP), Util::ordinal(lightMode_t::FULLBRIGHT), Util::ordinal(lightMode_t::MAP)); cvar_t *r_lightStyles; cvar_t *r_exportTextures; @@ -1115,6 +1116,7 @@ ScreenshotCmd screenshotPNGRegistration("screenshotPNG", ssFormat_t::SSF_PNG, "p r_dynamicLightCastShadows = Cvar_Get( "r_dynamicLightCastShadows", "1", 0 ); r_precomputedLighting = Cvar_Get( "r_precomputedLighting", "1", CVAR_CHEAT | CVAR_LATCH ); Cvar::Latch( r_mapOverBrightBits ); + Cvar::Latch( r_forceLegacyMapOverBrightClamping ); Cvar::Latch( r_lightMode ); r_lightStyles = Cvar_Get( "r_lightStyles", "1", CVAR_LATCH | CVAR_ARCHIVE ); r_exportTextures = Cvar_Get( "r_exportTextures", "0", 0 ); diff --git a/src/engine/renderer/tr_local.h b/src/engine/renderer/tr_local.h index 0439d40b67..f8cb70a126 100644 --- a/src/engine/renderer/tr_local.h +++ b/src/engine/renderer/tr_local.h @@ -1209,6 +1209,8 @@ enum class dynamicLightRenderer_t { LEGACY, TILED }; bool dpMaterial; + bool shaderHasNoLight; + textureBundle_t bundle[ MAX_TEXTURE_BUNDLES ]; expression_t ifExp; @@ -2823,7 +2825,14 @@ enum class dynamicLightRenderer_t { LEGACY, TILED }; viewParms_t viewParms; - int mapOverBrightBits; // r_mapOverbrightBits->integer, but can be overridden by mapper using the worldspawn + // r_mapOverbrightBits->integer, but can be overridden by mapper using the worldspawn + int mapOverBrightBits; + // pow(2, mapOverbrightBits) + float mapLightFactor; + // 1 / mapLightFactor + float mapInverseLightFactor; + // May have to be true on some legacy maps: clamp and normalize multiplied colors. + bool forceLegacyMapOverBrightClamping; orientationr_t orientation; // for current entity @@ -2950,6 +2959,7 @@ enum class dynamicLightRenderer_t { LEGACY, TILED }; extern cvar_t *r_dynamicLightCastShadows; extern cvar_t *r_precomputedLighting; extern Cvar::Cvar r_mapOverBrightBits; + extern Cvar::Cvar r_forceLegacyMapOverBrightClamping; extern Cvar::Range> r_lightMode; extern cvar_t *r_lightStyles; extern cvar_t *r_exportTextures; @@ -3307,7 +3317,7 @@ inline bool checkGLErrors() void RE_Shutdown( bool destroyWindow ); bool R_GetEntityToken( char *buffer, int size ); - float R_ProcessLightmap( byte *pic, int in_padding, int width, int height, int bits, byte *pic_out ); // Arnout + void R_ProcessLightmap( byte *bytes, int width, int height, int bits ); // Arnout model_t *R_AllocModel(); diff --git a/src/engine/renderer/tr_scene.cpp b/src/engine/renderer/tr_scene.cpp index b9b00d700b..2e798de658 100644 --- a/src/engine/renderer/tr_scene.cpp +++ b/src/engine/renderer/tr_scene.cpp @@ -429,6 +429,9 @@ void RE_AddDynamicLightToSceneET( const vec3_t org, float radius, float intensit light->l.color[ 1 ] = g; light->l.color[ 2 ] = b; + // Cancel overBright on dynamic lights. + VectorScale( light->l.color, tr.mapInverseLightFactor, light->l.color ); + light->l.inverseShadows = (flags & REF_INVERSE_DLIGHT) != 0; light->l.noShadows = !r_dynamicLightCastShadows->integer && !light->l.inverseShadows; diff --git a/src/engine/renderer/tr_shade.cpp b/src/engine/renderer/tr_shade.cpp index d1185bfa7f..bd584bd01e 100644 --- a/src/engine/renderer/tr_shade.cpp +++ b/src/engine/renderer/tr_shade.cpp @@ -764,6 +764,13 @@ static void Render_generic( shaderStage_t *pStage ) break; } + // u_InverseLightFactor + // We should cancel overbrightBits if there is no light, + // and it's not using blendFunc dst_color. + bool blendFunc_dstColor = ( pStage->stateBits & GLS_SRCBLEND_BITS ) == GLS_SRCBLEND_DST_COLOR; + float inverseLightFactor = ( pStage->shaderHasNoLight && !blendFunc_dstColor ) ? tr.mapInverseLightFactor : 1.0f; + gl_genericShader->SetUniform_InverseLightFactor( inverseLightFactor ); + // u_ColorModulate gl_genericShader->SetUniform_ColorModulate( rgbGen, alphaGen ); @@ -1083,6 +1090,14 @@ static void Render_lightMapping( shaderStage_t *pStage ) // u_DeformGen gl_lightMappingShader->SetUniform_Time( backEnd.refdef.floatTime - backEnd.currentEntity->e.shaderTime ); + // u_InverseLightFactor + /* HACK: use sign to know if there is a light or not, and + then if it will receive overbright multiplication or not. */ + bool blendFunc_dstColor = ( pStage->stateBits & GLS_SRCBLEND_BITS ) == GLS_SRCBLEND_DST_COLOR; + bool noLight = pStage->shaderHasNoLight || lightMode == lightMode_t::FULLBRIGHT; + float inverseLightFactor = ( noLight && !blendFunc_dstColor ) ? tr.mapInverseLightFactor : - tr.mapInverseLightFactor; + gl_lightMappingShader->SetUniform_InverseLightFactor( inverseLightFactor ); + // u_ColorModulate gl_lightMappingShader->SetUniform_ColorModulate( colorGen, alphaGen ); @@ -2085,6 +2100,9 @@ static void Render_skybox( shaderStage_t *pStage ) // bind u_ColorMap GL_BindToTMU( 0, pStage->bundle[ TB_COLORMAP ].image[ 0 ] ); + // u_InverseLightFactor + gl_skyboxShader->SetUniform_InverseLightFactor( tr.mapInverseLightFactor ); + gl_skyboxShader->SetRequiredVertexPointers(); Tess_DrawElements(); @@ -2422,6 +2440,9 @@ static void Render_fog() gl_fogQuake3Shader->BindProgram( 0 ); + // u_InverseLightFactor + gl_fogQuake3Shader->SetUniform_InverseLightFactor( tr.mapInverseLightFactor ); + gl_fogQuake3Shader->SetUniform_FogDistanceVector( fogDistanceVector ); gl_fogQuake3Shader->SetUniform_FogDepthVector( fogDepthVector ); gl_fogQuake3Shader->SetUniform_FogEyeT( eyeT ); diff --git a/src/engine/renderer/tr_shader.cpp b/src/engine/renderer/tr_shader.cpp index 658405e80a..d7770841f5 100644 --- a/src/engine/renderer/tr_shader.cpp +++ b/src/engine/renderer/tr_shader.cpp @@ -5149,10 +5149,33 @@ static void CollapseStages() shader.numStages = numActiveStages; // Do some precomputation. + + bool shaderHasNoLight = true; + for ( int s = 0; s < shader.numStages; s++ ) + { + shaderStage_t *stage = &stages[ s ]; + + switch ( stage->type ) + { + case stageType_t::ST_LIGHTMAP: + case stageType_t::ST_STYLELIGHTMAP: + case stageType_t::ST_STYLECOLORMAP: + case stageType_t::ST_DIFFUSEMAP: + case stageType_t::ST_COLLAPSE_DIFFUSEMAP: + shaderHasNoLight = false; + break; + default: + break; + } + } + for ( int s = 0; s < shader.numStages; s++ ) { shaderStage_t *stage = &stages[ s ]; + // We should cancel overBrightBits if there is no light stage. + stage->shaderHasNoLight = shaderHasNoLight; + // Available textures. stage->hasNormalMap = stage->bundle[ TB_NORMALMAP ].image[ 0 ] != nullptr; stage->hasHeightMap = stage->bundle[ TB_HEIGHTMAP ].image[ 0 ] != nullptr; diff --git a/src/engine/renderer/tr_sky.cpp b/src/engine/renderer/tr_sky.cpp index 4f029ad1fd..d8f35097e2 100644 --- a/src/engine/renderer/tr_sky.cpp +++ b/src/engine/renderer/tr_sky.cpp @@ -115,6 +115,9 @@ void Tess_StageIteratorSky() gl_skyboxShader->SetUniform_ModelViewProjectionMatrix( glState.modelViewProjectionMatrix[glState.stackIndex] ); + // u_InverseLightFactor + gl_skyboxShader->SetUniform_InverseLightFactor( tr.mapInverseLightFactor ); + gl_skyboxShader->SetRequiredVertexPointers(); // draw the outer skybox From 42bc94f09ccac882bd8054a94576ae9946678f5f Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Mon, 5 Feb 2024 11:17:47 +0100 Subject: [PATCH 2/2] renderer: implement high precision rendering framebuffers This is needed to avoid color-banding with the various division/multiplication round trips of the complete overBright implementation. This will also be needed for sRGB colors in the future too. It is enabled by default if a feature requires it. This is optional and can be disabled with r_highPrecisionRendering, or automatically disabled if the hardware doesn't support the feature. --- src/engine/renderer/tr_image.cpp | 11 ++++++++++- src/engine/renderer/tr_init.cpp | 5 +++++ src/engine/renderer/tr_local.h | 2 ++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/engine/renderer/tr_image.cpp b/src/engine/renderer/tr_image.cpp index 918a96d88e..a76d7406dc 100644 --- a/src/engine/renderer/tr_image.cpp +++ b/src/engine/renderer/tr_image.cpp @@ -2408,13 +2408,22 @@ static void R_CreateCurrentRenderImage() imageParams_t imageParams = {}; imageParams.bits = IF_NOPICMIP; + + if ( glConfig2.textureFloatAvailable && r_highPrecisionRendering.Get() ) + { + imageParams.bits |= IF_RGBA16; + } + imageParams.filterType = filterType_t::FT_NEAREST; imageParams.wrapType = wrapTypeEnum_t::WT_CLAMP; tr.currentRenderImage[0] = R_CreateImage( "_currentRender[0]", nullptr, width, height, 1, imageParams ); tr.currentRenderImage[1] = R_CreateImage( "_currentRender[1]", nullptr, width, height, 1, imageParams ); - imageParams.bits |= IF_PACKED_DEPTH24_STENCIL8; + imageParams = {}; + imageParams.bits = IF_NOPICMIP | IF_PACKED_DEPTH24_STENCIL8; + imageParams.filterType = filterType_t::FT_NEAREST; + imageParams.wrapType = wrapTypeEnum_t::WT_CLAMP; tr.currentDepthImage = R_CreateImage( "_currentDepth", nullptr, width, height, 1, imageParams ); } diff --git a/src/engine/renderer/tr_init.cpp b/src/engine/renderer/tr_init.cpp index a8f4c7c4dd..be5e8afba2 100644 --- a/src/engine/renderer/tr_init.cpp +++ b/src/engine/renderer/tr_init.cpp @@ -186,6 +186,9 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA cvar_t *r_halfLambertLighting; cvar_t *r_rimLighting; cvar_t *r_rimExponent; + + Cvar::Cvar r_highPrecisionRendering("r_highPrecisionRendering", "use high precision frame buffers for rendering and blending", Cvar::NONE, true); + cvar_t *r_gamma; cvar_t *r_lockpvs; cvar_t *r_noportals; @@ -1254,6 +1257,8 @@ ScreenshotCmd screenshotPNGRegistration("screenshotPNG", ssFormat_t::SSF_PNG, "p r_rimExponent = Cvar_Get( "r_rimExponent", "3", CVAR_CHEAT | CVAR_LATCH ); AssertCvarRange( r_rimExponent, 0.5, 8.0, false ); + Cvar::Latch( r_highPrecisionRendering ); + 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 f8cb70a126..3058dd26de 100644 --- a/src/engine/renderer/tr_local.h +++ b/src/engine/renderer/tr_local.h @@ -3032,6 +3032,8 @@ enum class dynamicLightRenderer_t { LEGACY, TILED }; extern cvar_t *r_rimLighting; extern cvar_t *r_rimExponent; + extern Cvar::Cvar r_highPrecisionRendering; + extern cvar_t *r_logFile; // number of frames to emit GL logs extern cvar_t *r_clear; // force screen clear every frame