From f27fdce5fac6dd31330ba80a974222b4b06f480a Mon Sep 17 00:00:00 2001 From: slipher Date: Sat, 15 Feb 2025 16:59:41 -0600 Subject: [PATCH 1/4] Implement partial overbright clamping like q3 The lighting values continue to be multiplied by pow(2, tr.mapOverbrightBits) but the resulting values are capped at pow(2, tr.overbrightBits) which defaults to 1. This matches the original behavior of Quake 3 (in full screen mode) and Tremulous 1.1.0. The cvar r_overbrightBits has come back and has the same behavior as ioq3. Setting r_overbrightBits to 0 gives you the behavior of pre-0.55 Unvanquished. Setting r_overbrightBits to 2 gives you 0.55.x behavior. See https://github.com/DaemonEngine/Daemon/issues/1542 for more information. --- src/engine/renderer/tr_bsp.cpp | 50 +++++++++----------------------- src/engine/renderer/tr_image.cpp | 6 ++-- src/engine/renderer/tr_init.cpp | 4 +-- src/engine/renderer/tr_local.h | 12 ++++---- 4 files changed, 27 insertions(+), 45 deletions(-) diff --git a/src/engine/renderer/tr_bsp.cpp b/src/engine/renderer/tr_bsp.cpp index ad2cf25bfa..0412b20a2f 100644 --- a/src/engine/renderer/tr_bsp.cpp +++ b/src/engine/renderer/tr_bsp.cpp @@ -74,10 +74,7 @@ static void R_ColorShiftLightingBytes( byte bytes[ 4 ] ) backward compatible with this bug for diagnostic purpose and fair comparison with other buggy engines. */ - if ( tr.mapOverBrightBits == 0 ) - { - return; - } + ASSERT_LT( tr.overbrightBits, tr.mapOverBrightBits ); /* Shift the color data based on overbright range. @@ -94,7 +91,7 @@ static void R_ColorShiftLightingBytes( byte bytes[ 4 ] ) what hardware overbright bit feature was not doing, but this implementation is entirely software. */ - int shift = tr.mapOverBrightBits; + int shift = tr.mapOverBrightBits - tr.overbrightBits; // shift the data based on overbright range int r = bytes[ 0 ] << shift; @@ -120,10 +117,7 @@ static void R_ColorShiftLightingBytes( byte bytes[ 4 ] ) static void R_ColorShiftLightingBytesCompressed( byte bytes[ 8 ] ) { - if ( tr.mapOverBrightBits == 0 ) - { - return; - } + ASSERT_LT( tr.overbrightBits, tr.mapOverBrightBits ); // color shift the endpoint colors in the dxt block unsigned short rgb565 = bytes[1] << 8 | bytes[0]; @@ -164,7 +158,7 @@ R_ProcessLightmap */ void R_ProcessLightmap( byte *bytes, int width, int height, int bits ) { - if ( tr.mapOverBrightBits == 0 ) + if ( tr.overbrightBits >= tr.mapOverBrightBits ) { return; } @@ -668,7 +662,7 @@ 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; - if ( tr.legacyOverBrightClamping ) + if ( tr.overbrightBits < tr.mapOverBrightBits ) { R_ColorShiftLightingBytes( &lightMapBuffer[( index * 4 ) + 0 ] ); } @@ -1029,7 +1023,7 @@ static void ParseFace( dsurface_t *ds, drawVert_t *verts, bspSurface_t *surf, in cv->verts[ i ].lightColor = Color::Adapt( verts[ i ].color ); - if ( tr.legacyOverBrightClamping ) + if ( tr.overbrightBits < tr.mapOverBrightBits ) { R_ColorShiftLightingBytes( cv->verts[ i ].lightColor.ToArray() ); } @@ -1239,7 +1233,7 @@ static void ParseMesh( dsurface_t *ds, drawVert_t *verts, bspSurface_t *surf ) points[ i ].lightColor = Color::Adapt( verts[ i ].color ); - if ( tr.legacyOverBrightClamping ) + if ( tr.overbrightBits < tr.mapOverBrightBits ) { R_ColorShiftLightingBytes( points[ i ].lightColor.ToArray() ); } @@ -1366,7 +1360,7 @@ static void ParseTriSurf( dsurface_t *ds, drawVert_t *verts, bspSurface_t *surf, cv->verts[ i ].lightColor = Color::Adapt( verts[ i ].color ); - if ( tr.legacyOverBrightClamping ) + if ( tr.overbrightBits < tr.mapOverBrightBits ) { R_ColorShiftLightingBytes( cv->verts[ i ].lightColor.ToArray() ); } @@ -4112,7 +4106,7 @@ void R_LoadLightGrid( lump_t *l ) tmpDirected[ 2 ] = in->directed[ 2 ]; tmpDirected[ 3 ] = 255; - if ( tr.legacyOverBrightClamping ) + if ( tr.overbrightBits < tr.mapOverBrightBits ) { R_ColorShiftLightingBytes( tmpAmbient ); R_ColorShiftLightingBytes( tmpDirected ); @@ -4372,24 +4366,6 @@ void R_LoadEntities( lump_t *l, std::string &externalEntities ) tr.mapOverBrightBits = Math::Clamp( atof( value ), 0.0, 3.0 ); continue; } - - if ( !Q_stricmp( keyname, "overbrightClamping" ) ) - { - if ( !Q_stricmp( value, "0" ) ) - { - tr.legacyOverBrightClamping = false; - } - else if ( !Q_stricmp( value, "1" ) ) - { - tr.legacyOverBrightClamping = true; - } - else - { - Log::Warn( "invalid value for worldspawn key overbrightClamping" ); - } - - continue; - } } // check for deluxe mapping provided by NetRadiant's q3map2 @@ -5122,6 +5098,9 @@ void RE_LoadWorldMap( const char *name ) } R_LoadEntities( &header->lumps[ LUMP_ENTITIES ], externalEntities ); + // Now we can set this after checking a possible worldspawn value for mapOverbrightBits + tr.overbrightBits = std::min( tr.mapOverBrightBits, r_overbrightBits.Get() ); + R_LoadShaders( &header->lumps[ LUMP_SHADERS ] ); R_LoadLightmaps( &header->lumps[ LUMP_LIGHTMAPS ], name ); @@ -5159,7 +5138,6 @@ void RE_LoadWorldMap( const char *name ) tr.worldLight = tr.lightMode; tr.modelLight = lightMode_t::FULLBRIGHT; tr.modelDeluxe = deluxeMode_t::NONE; - tr.mapLightFactor = 1.0f; // Use fullbright lighting for everything if the world is fullbright. if ( tr.worldLight != lightMode_t::FULLBRIGHT ) @@ -5233,9 +5211,9 @@ void RE_LoadWorldMap( const char *name ) /* Set GLSL overbright parameters if the legacy clamped overbright isn't used and the lighting mode is not fullbright. */ - if ( !tr.legacyOverBrightClamping && tr.lightMode != lightMode_t::FULLBRIGHT ) + if ( tr.lightMode != lightMode_t::FULLBRIGHT ) { - tr.mapLightFactor = pow( 2, tr.mapOverBrightBits ); + tr.mapLightFactor = float( 1 << tr.overbrightBits ); } tr.worldLoaded = true; diff --git a/src/engine/renderer/tr_image.cpp b/src/engine/renderer/tr_image.cpp index 381265293a..a39728e34a 100644 --- a/src/engine/renderer/tr_image.cpp +++ b/src/engine/renderer/tr_image.cpp @@ -1853,7 +1853,7 @@ image_t *R_FindImageFile( const char *imageName, imageParams_t &imageParams ) return nullptr; } - if ( imageParams.bits & IF_LIGHTMAP && tr.legacyOverBrightClamping ) + if ( imageParams.bits & IF_LIGHTMAP ) { R_ProcessLightmap( *pic, width, height, imageParams.bits ); } @@ -3054,6 +3054,7 @@ void R_InitImages() tr.lightmaps.reserve( 128 ); tr.deluxemaps.reserve( 128 ); + //TODO rewrite WoT with new info :) /* These are the values expected by the rest of the renderer (esp. tr_bsp), used for "gamma correction of the map". Both were set to 0 if we had neither COMPAT_ET nor COMPAT_Q3, @@ -3113,8 +3114,9 @@ void R_InitImages() Because tr.overbrightBits is always 0, tr.identityLight is always 1.0f. We can entirely remove it. */ + // TODO is there any reason to set these before a map is loaded? tr.mapOverBrightBits = r_overbrightDefaultExponent.Get(); - tr.legacyOverBrightClamping = r_overbrightDefaultClamp.Get(); + tr.overbrightBits = std::min(tr.mapOverBrightBits, r_overbrightBits.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 7156dcf52d..6458ccbd6a 100644 --- a/src/engine/renderer/tr_init.cpp +++ b/src/engine/renderer/tr_init.cpp @@ -92,7 +92,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA cvar_t *r_realtimeLightingCastShadows; cvar_t *r_precomputedLighting; Cvar::Cvar r_overbrightDefaultExponent("r_overbrightDefaultExponent", "default map light color shift (multiply by 2^x)", Cvar::NONE, 2); - Cvar::Cvar r_overbrightDefaultClamp("r_overbrightDefaultClamp", "clamp lightmap colors to 1 (in absence of map worldspawn value)", Cvar::NONE, false); + Cvar::Range> r_overbrightBits("r_overbrightBits", "clamp lightmap colors to 2^x", Cvar::NONE, 1, 0, 3); Cvar::Cvar r_overbrightIgnoreMapSettings("r_overbrightIgnoreMapSettings", "force usage of r_overbrightDefaultClamp / r_overbrightDefaultExponent, ignoring worldspawn", 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::Cvar r_colorGrading( "r_colorGrading", "Use color grading", Cvar::NONE, true ); @@ -1185,7 +1185,7 @@ ScreenshotCmd screenshotPNGRegistration("screenshotPNG", ssFormat_t::SSF_PNG, "p r_realtimeLightingCastShadows = Cvar_Get( "r_realtimeLightingCastShadows", "1", 0 ); r_precomputedLighting = Cvar_Get( "r_precomputedLighting", "1", CVAR_CHEAT | CVAR_LATCH ); Cvar::Latch( r_overbrightDefaultExponent ); - Cvar::Latch( r_overbrightDefaultClamp ); + Cvar::Latch( r_overbrightBits ); Cvar::Latch( r_overbrightIgnoreMapSettings ); Cvar::Latch( r_lightMode ); Cvar::Latch( r_colorGrading ); diff --git a/src/engine/renderer/tr_local.h b/src/engine/renderer/tr_local.h index ed59f14404..74ba66692a 100644 --- a/src/engine/renderer/tr_local.h +++ b/src/engine/renderer/tr_local.h @@ -2809,12 +2809,14 @@ enum class shaderProfilerRenderSubGroupsMode { viewParms_t viewParms; - // r_overbrightDefaultExponent, but can be overridden by mapper using the worldspawn + // Brightness scaling: roughly speaking, a lightmap value of x will be interpreted as + // min(x * pow(2, mapOverBrightBits), pow(2, overbrightBits)) + // (but when a component hits the max allowed value, others are scaled down to keep the "same color") int mapOverBrightBits; - // pow(2, mapOverbrightBits) + // min(r_overbrightBits.Get(), mapOverBrightBits) + int overbrightBits; + // pow(2, overbrightBits) float mapLightFactor; - // May have to be true on some legacy maps: clamp and normalize multiplied colors. - bool legacyOverBrightClamping; orientationr_t orientation; // for current entity @@ -2938,7 +2940,7 @@ enum class shaderProfilerRenderSubGroupsMode { extern cvar_t *r_realtimeLightingCastShadows; extern cvar_t *r_precomputedLighting; extern Cvar::Cvar r_overbrightDefaultExponent; - extern Cvar::Cvar r_overbrightDefaultClamp; + extern Cvar::Range> r_overbrightBits; extern Cvar::Cvar r_overbrightIgnoreMapSettings; extern Cvar::Range> r_lightMode; extern Cvar::Cvar r_colorGrading; From 2f4202dd3fd50ceb1221ba6d5b87442fba06e467 Mon Sep 17 00:00:00 2001 From: slipher Date: Tue, 25 Feb 2025 19:52:44 -0600 Subject: [PATCH 2/4] Add cvar r_overbrightQ3 to simulate its overbright The Quake 3 overbright implementation worked by scaling up the entire color buffer. For surfaces that were not lit by precomputed lighting you had to use cgen identityLighting to cancel out. This breaks a lot of stuff so this cvar is only for testing. --- src/engine/renderer/gl_shader.cpp | 1 + src/engine/renderer/gl_shader.h | 16 +++++++++++++ .../glsl_source/cameraEffects_fp.glsl | 2 ++ src/engine/renderer/tr_backend.cpp | 1 + src/engine/renderer/tr_bsp.cpp | 24 ++++++++++--------- src/engine/renderer/tr_init.cpp | 2 ++ src/engine/renderer/tr_local.h | 7 ++++-- src/engine/renderer/tr_shade.cpp | 14 ++++------- src/engine/renderer/tr_shader.cpp | 2 +- 9 files changed, 45 insertions(+), 24 deletions(-) diff --git a/src/engine/renderer/gl_shader.cpp b/src/engine/renderer/gl_shader.cpp index 6ef7a0a48b..1508b6bb6d 100644 --- a/src/engine/renderer/gl_shader.cpp +++ b/src/engine/renderer/gl_shader.cpp @@ -2897,6 +2897,7 @@ GLShader_cameraEffects::GLShader_cameraEffects( GLShaderManager *manager ) : GLShader( "cameraEffects", ATTR_POSITION | ATTR_TEXCOORD, manager ), u_ColorMap3D( this ), u_CurrentMap( this ), + u_GlobalLightFactor( this ), u_ColorModulate( this ), u_TextureMatrix( this ), u_ModelViewProjectionMatrix( this ), diff --git a/src/engine/renderer/gl_shader.h b/src/engine/renderer/gl_shader.h index e53b8119a2..80c5054a67 100644 --- a/src/engine/renderer/gl_shader.h +++ b/src/engine/renderer/gl_shader.h @@ -3536,6 +3536,21 @@ class u_Time : } }; +class u_GlobalLightFactor : + GLUniform1f +{ +public: + u_GlobalLightFactor( GLShader *shader ) : + GLUniform1f( shader, "u_GlobalLightFactor" ) + { + } + + void SetUniform_GlobalLightFactor( float value ) + { + this->SetValue( value ); + } +}; + class GLDeformStage : public u_Time { @@ -4459,6 +4474,7 @@ class GLShader_cameraEffects : public GLShader, public u_ColorMap3D, public u_CurrentMap, + public u_GlobalLightFactor, public u_ColorModulate, public u_TextureMatrix, public u_ModelViewProjectionMatrix, diff --git a/src/engine/renderer/glsl_source/cameraEffects_fp.glsl b/src/engine/renderer/glsl_source/cameraEffects_fp.glsl index f4b4fbcfd6..56874c6644 100644 --- a/src/engine/renderer/glsl_source/cameraEffects_fp.glsl +++ b/src/engine/renderer/glsl_source/cameraEffects_fp.glsl @@ -29,6 +29,7 @@ uniform sampler3D u_ColorMap3D; #endif uniform vec4 u_ColorModulate; +uniform float u_GlobalLightFactor; // 1 / tr.identityLight uniform float u_InverseGamma; IN(smooth) vec2 var_TexCoords; @@ -55,6 +56,7 @@ void main() vec2 st = gl_FragCoord.st / r_FBufSize; vec4 color = texture2D(u_CurrentMap, st); + color *= u_GlobalLightFactor; if( u_Tonemap ) { color.rgb = TonemapLottes( color.rgb * u_TonemapExposure ); diff --git a/src/engine/renderer/tr_backend.cpp b/src/engine/renderer/tr_backend.cpp index 133f2dfba6..0e3c749abe 100644 --- a/src/engine/renderer/tr_backend.cpp +++ b/src/engine/renderer/tr_backend.cpp @@ -3350,6 +3350,7 @@ void RB_CameraPostFX() // enable shader, set arrays gl_cameraEffectsShader->BindProgram( 0 ); + gl_cameraEffectsShader->SetUniform_GlobalLightFactor( 1.0f / tr.identityLight ); gl_cameraEffectsShader->SetUniform_ColorModulate( backEnd.viewParms.gradingWeights ); gl_cameraEffectsShader->SetUniform_InverseGamma( 1.0 / r_gamma->value ); diff --git a/src/engine/renderer/tr_bsp.cpp b/src/engine/renderer/tr_bsp.cpp index 0412b20a2f..8d93d8853a 100644 --- a/src/engine/renderer/tr_bsp.cpp +++ b/src/engine/renderer/tr_bsp.cpp @@ -3883,14 +3883,7 @@ static void R_LoadFogs( lump_t *l, lump_t *brushesLump, lump_t *sidesLump ) out->fogParms = shader->fogParms; out->color = Color::Adapt( shader->fogParms.color ); - - /* Historically it was done: - - out->color *= tr.identityLight; - - But tr.identityLight is always 1.0f in Dæmon engine - as the as the overbright bit implementation is fully - software. */ + out->color *= tr.identityLight; out->color.SetAlpha( 1 ); @@ -5138,6 +5131,7 @@ void RE_LoadWorldMap( const char *name ) tr.worldLight = tr.lightMode; tr.modelLight = lightMode_t::FULLBRIGHT; tr.modelDeluxe = deluxeMode_t::NONE; + tr.mapLightFactor = tr.identityLight = 1.0f; // Use fullbright lighting for everything if the world is fullbright. if ( tr.worldLight != lightMode_t::FULLBRIGHT ) @@ -5209,11 +5203,19 @@ void RE_LoadWorldMap( const char *name ) } } - /* Set GLSL overbright parameters if the legacy clamped overbright isn't used - and the lighting mode is not fullbright. */ + /* Set GLSL overbright parameters if the lighting mode is not fullbright. */ if ( tr.lightMode != lightMode_t::FULLBRIGHT ) { - tr.mapLightFactor = float( 1 << tr.overbrightBits ); + if ( r_overbrightQ3.Get() ) + { + // light factor is applied to entire color buffer; identityLight can be used to cancel it + tr.identityLight = 1.0f / float( 1 << tr.overbrightBits ); + } + else + { + // light factor is applied wherever a precomputed light is sampled + tr.mapLightFactor = float( 1 << tr.overbrightBits ); + } } tr.worldLoaded = true; diff --git a/src/engine/renderer/tr_init.cpp b/src/engine/renderer/tr_init.cpp index 6458ccbd6a..e05ab06835 100644 --- a/src/engine/renderer/tr_init.cpp +++ b/src/engine/renderer/tr_init.cpp @@ -93,6 +93,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA cvar_t *r_precomputedLighting; Cvar::Cvar r_overbrightDefaultExponent("r_overbrightDefaultExponent", "default map light color shift (multiply by 2^x)", Cvar::NONE, 2); Cvar::Range> r_overbrightBits("r_overbrightBits", "clamp lightmap colors to 2^x", Cvar::NONE, 1, 0, 3); + Cvar::Cvar r_overbrightQ3("r_overbrightQ3", "brighten entire color buffer like Quake 3 (incompatible with newer assets)", Cvar::NONE, false); Cvar::Cvar r_overbrightIgnoreMapSettings("r_overbrightIgnoreMapSettings", "force usage of r_overbrightDefaultClamp / r_overbrightDefaultExponent, ignoring worldspawn", 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::Cvar r_colorGrading( "r_colorGrading", "Use color grading", Cvar::NONE, true ); @@ -1186,6 +1187,7 @@ ScreenshotCmd screenshotPNGRegistration("screenshotPNG", ssFormat_t::SSF_PNG, "p r_precomputedLighting = Cvar_Get( "r_precomputedLighting", "1", CVAR_CHEAT | CVAR_LATCH ); Cvar::Latch( r_overbrightDefaultExponent ); Cvar::Latch( r_overbrightBits ); + Cvar::Latch( r_overbrightQ3 ); Cvar::Latch( r_overbrightIgnoreMapSettings ); Cvar::Latch( r_lightMode ); Cvar::Latch( r_colorGrading ); diff --git a/src/engine/renderer/tr_local.h b/src/engine/renderer/tr_local.h index 74ba66692a..4bee0e9c9b 100644 --- a/src/engine/renderer/tr_local.h +++ b/src/engine/renderer/tr_local.h @@ -825,7 +825,7 @@ enum class shaderProfilerRenderSubGroupsMode { enum class colorGen_t { CGEN_BAD, - CGEN_IDENTITY_LIGHTING, // Always (1,1,1,1) in Dæmon engine as the overbright bit implementation is fully software. + CGEN_IDENTITY_LIGHTING, // Always (1,1,1,1) in Dæmon engine, unless you set r_overbrightQ3. CGEN_IDENTITY, // always (1,1,1,1) CGEN_ENTITY, // grabbed from entity's modulate field CGEN_ONE_MINUS_ENTITY, // grabbed from 1 - entity.modulate @@ -2815,8 +2815,10 @@ enum class shaderProfilerRenderSubGroupsMode { int mapOverBrightBits; // min(r_overbrightBits.Get(), mapOverBrightBits) int overbrightBits; - // pow(2, overbrightBits) + // pow(2, overbrightBits), unless r_overbrightQ3 is on float mapLightFactor; + // 1/pow(2, overbrightBits) if r_overbrightQ3 is on + float identityLight; orientationr_t orientation; // for current entity @@ -2941,6 +2943,7 @@ enum class shaderProfilerRenderSubGroupsMode { extern cvar_t *r_precomputedLighting; extern Cvar::Cvar r_overbrightDefaultExponent; extern Cvar::Range> r_overbrightBits; + extern Cvar::Cvar r_overbrightQ3; extern Cvar::Cvar r_overbrightIgnoreMapSettings; extern Cvar::Range> r_lightMode; extern Cvar::Cvar r_colorGrading; diff --git a/src/engine/renderer/tr_shade.cpp b/src/engine/renderer/tr_shade.cpp index 19dd45a2fc..6f48634b01 100644 --- a/src/engine/renderer/tr_shade.cpp +++ b/src/engine/renderer/tr_shade.cpp @@ -2321,21 +2321,15 @@ void Tess_ComputeColor( shaderStage_t *pStage ) // rgbGen switch ( pStage->rgbGen ) { + case colorGen_t::CGEN_IDENTITY_LIGHTING: + tess.svars.color = Color::Color(tr.identityLight, tr.identityLight, tr.identityLight); + break; + case colorGen_t::CGEN_IDENTITY: case colorGen_t::CGEN_ONE_MINUS_VERTEX: default: - case colorGen_t::CGEN_IDENTITY_LIGHTING: - /* Historically CGEN_IDENTITY_LIGHTING was done this way: - - tess.svars.color = Color::White * tr.identityLight; - - But tr.identityLight is always 1.0f in Dæmon engine - as the as the overbright bit implementation is fully - software. */ - { tess.svars.color = Color::White; break; - } case colorGen_t::CGEN_VERTEX: { diff --git a/src/engine/renderer/tr_shader.cpp b/src/engine/renderer/tr_shader.cpp index eeac8658df..d24cb13b61 100644 --- a/src/engine/renderer/tr_shader.cpp +++ b/src/engine/renderer/tr_shader.cpp @@ -4836,7 +4836,7 @@ static void CollapseStages() bool rgbGen_identity = stages[ i ].rgbGen == colorGen_t::CGEN_IDENTITY - || stages[ i ].rgbGen == colorGen_t::CGEN_IDENTITY_LIGHTING; + || ( stages[ i ].rgbGen == colorGen_t::CGEN_IDENTITY_LIGHTING && !r_overbrightQ3.Get() ); bool alphaGen_identity = stages[ i ].alphaGen == alphaGen_t::AGEN_IDENTITY; From 399ba8fa5a1ad23fa1b958d2eecfb36873e228a3 Mon Sep 17 00:00:00 2001 From: slipher Date: Tue, 18 Feb 2025 15:45:13 -0600 Subject: [PATCH 3/4] Update overbright-related comments --- src/engine/renderer/Material.cpp | 13 ++---- src/engine/renderer/tr_image.cpp | 74 +++++++++++++++++--------------- src/engine/renderer/tr_init.cpp | 3 ++ src/engine/renderer/tr_shade.cpp | 6 +-- 4 files changed, 49 insertions(+), 47 deletions(-) diff --git a/src/engine/renderer/Material.cpp b/src/engine/renderer/Material.cpp index f9beb9a05f..a858389947 100644 --- a/src/engine/renderer/Material.cpp +++ b/src/engine/renderer/Material.cpp @@ -59,19 +59,14 @@ static void ComputeDynamics( shaderStage_t* pStage ) { // TODO: Move color and texMatrices stuff to a compute shader pStage->colorDynamic = false; switch ( pStage->rgbGen ) { + case colorGen_t::CGEN_IDENTITY_LIGHTING: case colorGen_t::CGEN_IDENTITY: case colorGen_t::CGEN_ONE_MINUS_VERTEX: - default: - case colorGen_t::CGEN_IDENTITY_LIGHTING: - /* Historically CGEN_IDENTITY_LIGHTING was done this way: - - tess.svars.color = Color::White * tr.identityLight; - - But tr.identityLight is always 1.0f in Dæmon engine - as the as the overbright bit implementation is fully - software. */ case colorGen_t::CGEN_VERTEX: case colorGen_t::CGEN_CONST: + default: + break; + case colorGen_t::CGEN_ENTITY: case colorGen_t::CGEN_ONE_MINUS_ENTITY: { diff --git a/src/engine/renderer/tr_image.cpp b/src/engine/renderer/tr_image.cpp index a39728e34a..a4812f73b0 100644 --- a/src/engine/renderer/tr_image.cpp +++ b/src/engine/renderer/tr_image.cpp @@ -3054,17 +3054,11 @@ void R_InitImages() tr.lightmaps.reserve( 128 ); tr.deluxemaps.reserve( 128 ); - //TODO rewrite WoT with new info :) - /* These are the values expected by the rest of the renderer - (esp. tr_bsp), used for "gamma correction of the map". - Both were set to 0 if we had neither COMPAT_ET nor COMPAT_Q3, - it may be interesting to remember. - - Quake 3 and Tremulous values: - - tr.overbrightBits = 0; // Software implementation. - tr.mapOverBrightBits = 2; // Quake 3 default. - tr.identityLight = 1.0f / ( 1 << tr.overbrightBits ); + /* + **** Map overbright bits **** + Lightmaps and vertex light colors are notionally scaled up by a factor of + pow(2, tr.mapOverBrightBits). This is a good idea because we would like a bright light + to make a texture brighter than its original diffuse image. Games like Quake 3 and Tremulous require tr.mapOverBrightBits to be set to 2. Because this engine is primarily maintained for @@ -3090,29 +3084,41 @@ void R_InitImages() require to set a different default than what Unvanquished requires. - Using a non-zero value for tr.mapOverBrightBits turns light - non-linear and makes deluxe mapping buggy though. - - Mappers may port and fix maps by multiplying the lights by 2.5 - and set the mapOverBrightBits key to 0 in map entities lump. - - It will be possible to assume tr.mapOverBrightBits is 0 when - loading maps compiled with sRGB lightmaps as there is no - legacy map using sRGB lightmap yet, and then we will be - able to avoid the need to explicitly set mapOverBrightBits - to 0 in map entities. It will be required to assume that - tr.mapOverBrightBits is 0 when loading maps compiled with - sRGB lightmaps because otherwise the color shift computation - will break the light computation, not only the deluxe one. - - In legacy engines, tr.overbrightBits was non-zero when - hardware overbright bits were enabled, zero when disabled. - This engine do not implement hardware overbright bit, so - this is always zero, and we can remove it and simplify all - the computations making use of it. - - Because tr.overbrightBits is always 0, tr.identityLight is - always 1.0f. We can entirely remove it. */ + **** r_overbrightBits **** + Although lightmaps are scaled up by pow(2, tr.overbrightBits), the actual ceiling for lightmap + values is pow(2, tr.overbrightBits). tr.overbrightBits may + be less than tr.mapOverbrightBits. This is a STUPID configuration because then you are + just throwing away 1 or bits of precision from the lightmap. But it was used for many games. + + The excess (tr.mapOverbrightBits - tr.overbrightBits) bits of scaling are done to the lightmap + before uploading it. If some component exceeds 1, the color is proportionally downscaled until + the max component is 1. + + Quake 3 and vanilla Tremulous used these default cvar values: + r_overbrightBits - 1 + r_mapOverBrightBits - 2 + + So the same as Daemon. But if the game was not running in fullscreen mode or the system was + not detected as supporting hardware gamma control, tr.overbrightBit would be set to 0. + Tremfusion shipped with r_overbrightBits 0 and r_ignorehwgamma 1, either of which forces + tr.overbrightBits to 0, making it the same as the vanilla client's windowed mode. + + **** How Quake 3 originally implemented overbright **** + When hardware overbright was on (tr.overbrightBits = 1), the color buffer notionally ranged + from 0 to 2, rather than 0 to 1. So a buffer with 8-bit colors only had 7 bits of + output precision (all values 128+ produced the same output), but the extra bit was useful + for intermediate values during blending. The rescaling was effected by using the hardware + gamma ramp, which affected the whole monitor (or whole system). + Shaders for materials that were not illuminated by any precomputed lighting could use + CGEN_IDENTITY_LIGHTING to multiply by tr.identityLight, which would cancel out the + rescaling so that the material looked the same regardless of tr.overbrightBits. + + In Daemon tr.identityLight is usually 1, so any distincion between + CGEN_IDENTITY/CGEN_IDENTITY_LIGHTING is ignored. But if you set the cvar r_overbrightQ3, + which emulates Quake 3's technique of brightening the whole color buffer, it will be used. + + For even more information, see https://github.com/DaemonEngine/Daemon/issues/1542. + */ // TODO is there any reason to set these before a map is loaded? tr.mapOverBrightBits = r_overbrightDefaultExponent.Get(); diff --git a/src/engine/renderer/tr_init.cpp b/src/engine/renderer/tr_init.cpp index e05ab06835..d734da5641 100644 --- a/src/engine/renderer/tr_init.cpp +++ b/src/engine/renderer/tr_init.cpp @@ -93,7 +93,10 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA cvar_t *r_precomputedLighting; Cvar::Cvar r_overbrightDefaultExponent("r_overbrightDefaultExponent", "default map light color shift (multiply by 2^x)", Cvar::NONE, 2); Cvar::Range> r_overbrightBits("r_overbrightBits", "clamp lightmap colors to 2^x", Cvar::NONE, 1, 0, 3); + + // also set r_highPrecisionRendering 0 for an even more authentic q3 experience Cvar::Cvar r_overbrightQ3("r_overbrightQ3", "brighten entire color buffer like Quake 3 (incompatible with newer assets)", Cvar::NONE, false); + Cvar::Cvar r_overbrightIgnoreMapSettings("r_overbrightIgnoreMapSettings", "force usage of r_overbrightDefaultClamp / r_overbrightDefaultExponent, ignoring worldspawn", 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::Cvar r_colorGrading( "r_colorGrading", "Use color grading", Cvar::NONE, true ); diff --git a/src/engine/renderer/tr_shade.cpp b/src/engine/renderer/tr_shade.cpp index 6f48634b01..faff8eaff6 100644 --- a/src/engine/renderer/tr_shade.cpp +++ b/src/engine/renderer/tr_shade.cpp @@ -846,12 +846,10 @@ void Render_generic3D( shaderStage_t *pStage ) colorGen_t rgbGen = SetRgbGen( pStage ); alphaGen_t alphaGen = SetAlphaGen( pStage ); - // Here, it's safe to multiply the overbright factor for vertex lighting into the color gen` - // since the `generic` fragment shader only takes a single input color. `lightMapping` on the - // hand needs to know the real diffuse color, hence the separate u_LightFactor. bool mayUseVertexOverbright = pStage->type == stageType_t::ST_COLORMAP && tess.bspSurface; const bool styleLightMap = pStage->type == stageType_t::ST_STYLELIGHTMAP || pStage->type == stageType_t::ST_STYLECOLORMAP; - gl_genericShader->SetUniform_ColorModulateColorGen( rgbGen, alphaGen, mayUseVertexOverbright, styleLightMap ); + gl_genericShader->SetUniform_ColorModulateColorGen( + rgbGen, alphaGen, mayUseVertexOverbright, /*useMapLightFactor=*/ styleLightMap); // u_Color gl_genericShader->SetUniform_Color( tess.svars.color ); From 7a4b44cf98f0801ae4ac539db35464a2d17b8c09 Mon Sep 17 00:00:00 2001 From: slipher Date: Fri, 21 Feb 2025 21:37:55 -0600 Subject: [PATCH 4/4] Clean up w/ initial values of world lighting params --- src/engine/renderer/tr_bsp.cpp | 15 +++++++++------ src/engine/renderer/tr_image.cpp | 4 ---- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/engine/renderer/tr_bsp.cpp b/src/engine/renderer/tr_bsp.cpp index 8d93d8853a..14e9d3dd75 100644 --- a/src/engine/renderer/tr_bsp.cpp +++ b/src/engine/renderer/tr_bsp.cpp @@ -5039,11 +5039,15 @@ void RE_LoadWorldMap( const char *name ) // try will not look at the partially loaded version tr.world = nullptr; - // tr.worldDeluxeMapping will be set by R_LoadLightmaps() - tr.worldLightMapping = false; - // tr.worldDeluxeMapping will be set by R_LoadEntities() - tr.worldDeluxeMapping = false; - tr.worldHDR_RGBE = false; + // It's probably a mistake if any of these lighting parameters are actually + // used before a map is loaded. + tr.worldLightMapping = false; // set by R_LoadLightmaps + tr.worldDeluxeMapping = false; // set by R_LoadEntities + tr.worldHDR_RGBE = false; // set by R_LoadEntities + tr.mapOverBrightBits = r_overbrightDefaultExponent.Get(); // maybe set by R_LoadEntities + tr.overbrightBits = std::min( tr.mapOverBrightBits, r_overbrightBits.Get() ); // set by RE_LoadWorldMap + tr.mapLightFactor = 1.0f; // set by RE_LoadWorldMap + tr.identityLight = 1.0f; // set by RE_LoadWorldMap s_worldData = {}; Q_strncpyz( s_worldData.name, name, sizeof( s_worldData.name ) ); @@ -5131,7 +5135,6 @@ void RE_LoadWorldMap( const char *name ) tr.worldLight = tr.lightMode; tr.modelLight = lightMode_t::FULLBRIGHT; tr.modelDeluxe = deluxeMode_t::NONE; - tr.mapLightFactor = tr.identityLight = 1.0f; // Use fullbright lighting for everything if the world is fullbright. if ( tr.worldLight != lightMode_t::FULLBRIGHT ) diff --git a/src/engine/renderer/tr_image.cpp b/src/engine/renderer/tr_image.cpp index a4812f73b0..4b608e7487 100644 --- a/src/engine/renderer/tr_image.cpp +++ b/src/engine/renderer/tr_image.cpp @@ -3120,10 +3120,6 @@ void R_InitImages() For even more information, see https://github.com/DaemonEngine/Daemon/issues/1542. */ - // TODO is there any reason to set these before a map is loaded? - tr.mapOverBrightBits = r_overbrightDefaultExponent.Get(); - tr.overbrightBits = std::min(tr.mapOverBrightBits, r_overbrightBits.Get()); - // create default texture and white texture R_CreateBuiltinImages(); }