Skip to content

Commit e12cb5a

Browse files
authored
Merge pull request #1557 from slipher/ob-partial
Implement partial overbright clamping like Q3
2 parents b323b53 + 7a4b44c commit e12cb5a

11 files changed

+124
-120
lines changed

src/engine/renderer/Material.cpp

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -59,19 +59,14 @@ static void ComputeDynamics( shaderStage_t* pStage ) {
5959
// TODO: Move color and texMatrices stuff to a compute shader
6060
pStage->colorDynamic = false;
6161
switch ( pStage->rgbGen ) {
62+
case colorGen_t::CGEN_IDENTITY_LIGHTING:
6263
case colorGen_t::CGEN_IDENTITY:
6364
case colorGen_t::CGEN_ONE_MINUS_VERTEX:
64-
default:
65-
case colorGen_t::CGEN_IDENTITY_LIGHTING:
66-
/* Historically CGEN_IDENTITY_LIGHTING was done this way:
67-
68-
tess.svars.color = Color::White * tr.identityLight;
69-
70-
But tr.identityLight is always 1.0f in Dæmon engine
71-
as the as the overbright bit implementation is fully
72-
software. */
7365
case colorGen_t::CGEN_VERTEX:
7466
case colorGen_t::CGEN_CONST:
67+
default:
68+
break;
69+
7570
case colorGen_t::CGEN_ENTITY:
7671
case colorGen_t::CGEN_ONE_MINUS_ENTITY:
7772
{

src/engine/renderer/gl_shader.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2897,6 +2897,7 @@ GLShader_cameraEffects::GLShader_cameraEffects( GLShaderManager *manager ) :
28972897
GLShader( "cameraEffects", ATTR_POSITION | ATTR_TEXCOORD, manager ),
28982898
u_ColorMap3D( this ),
28992899
u_CurrentMap( this ),
2900+
u_GlobalLightFactor( this ),
29002901
u_ColorModulate( this ),
29012902
u_TextureMatrix( this ),
29022903
u_ModelViewProjectionMatrix( this ),

src/engine/renderer/gl_shader.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3536,6 +3536,21 @@ class u_Time :
35363536
}
35373537
};
35383538

3539+
class u_GlobalLightFactor :
3540+
GLUniform1f
3541+
{
3542+
public:
3543+
u_GlobalLightFactor( GLShader *shader ) :
3544+
GLUniform1f( shader, "u_GlobalLightFactor" )
3545+
{
3546+
}
3547+
3548+
void SetUniform_GlobalLightFactor( float value )
3549+
{
3550+
this->SetValue( value );
3551+
}
3552+
};
3553+
35393554
class GLDeformStage :
35403555
public u_Time
35413556
{
@@ -4459,6 +4474,7 @@ class GLShader_cameraEffects :
44594474
public GLShader,
44604475
public u_ColorMap3D,
44614476
public u_CurrentMap,
4477+
public u_GlobalLightFactor,
44624478
public u_ColorModulate,
44634479
public u_TextureMatrix,
44644480
public u_ModelViewProjectionMatrix,

src/engine/renderer/glsl_source/cameraEffects_fp.glsl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ uniform sampler3D u_ColorMap3D;
2929
#endif
3030

3131
uniform vec4 u_ColorModulate;
32+
uniform float u_GlobalLightFactor; // 1 / tr.identityLight
3233
uniform float u_InverseGamma;
3334

3435
IN(smooth) vec2 var_TexCoords;
@@ -55,6 +56,7 @@ void main()
5556
vec2 st = gl_FragCoord.st / r_FBufSize;
5657

5758
vec4 color = texture2D(u_CurrentMap, st);
59+
color *= u_GlobalLightFactor;
5860

5961
if( u_Tonemap ) {
6062
color.rgb = TonemapLottes( color.rgb * u_TonemapExposure );

src/engine/renderer/tr_backend.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3350,6 +3350,7 @@ void RB_CameraPostFX()
33503350
// enable shader, set arrays
33513351
gl_cameraEffectsShader->BindProgram( 0 );
33523352

3353+
gl_cameraEffectsShader->SetUniform_GlobalLightFactor( 1.0f / tr.identityLight );
33533354
gl_cameraEffectsShader->SetUniform_ColorModulate( backEnd.viewParms.gradingWeights );
33543355

33553356
gl_cameraEffectsShader->SetUniform_InverseGamma( 1.0 / r_gamma->value );

src/engine/renderer/tr_bsp.cpp

Lines changed: 34 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,7 @@ static void R_ColorShiftLightingBytes( byte bytes[ 4 ] )
7474
backward compatible with this bug for diagnostic purpose and fair comparison with
7575
other buggy engines. */
7676

77-
if ( tr.mapOverBrightBits == 0 )
78-
{
79-
return;
80-
}
77+
ASSERT_LT( tr.overbrightBits, tr.mapOverBrightBits );
8178

8279
/* Shift the color data based on overbright range.
8380
@@ -94,7 +91,7 @@ static void R_ColorShiftLightingBytes( byte bytes[ 4 ] )
9491
what hardware overbright bit feature was not doing, but
9592
this implementation is entirely software. */
9693

97-
int shift = tr.mapOverBrightBits;
94+
int shift = tr.mapOverBrightBits - tr.overbrightBits;
9895

9996
// shift the data based on overbright range
10097
int r = bytes[ 0 ] << shift;
@@ -120,10 +117,7 @@ static void R_ColorShiftLightingBytes( byte bytes[ 4 ] )
120117

121118
static void R_ColorShiftLightingBytesCompressed( byte bytes[ 8 ] )
122119
{
123-
if ( tr.mapOverBrightBits == 0 )
124-
{
125-
return;
126-
}
120+
ASSERT_LT( tr.overbrightBits, tr.mapOverBrightBits );
127121

128122
// color shift the endpoint colors in the dxt block
129123
unsigned short rgb565 = bytes[1] << 8 | bytes[0];
@@ -164,7 +158,7 @@ R_ProcessLightmap
164158
*/
165159
void R_ProcessLightmap( byte *bytes, int width, int height, int bits )
166160
{
167-
if ( tr.mapOverBrightBits == 0 )
161+
if ( tr.overbrightBits >= tr.mapOverBrightBits )
168162
{
169163
return;
170164
}
@@ -668,7 +662,7 @@ static void R_LoadLightmaps( lump_t *l, const char *bspName )
668662
lightMapBuffer[( index * 4 ) + 2 ] = buf_p[( ( x + ( y * internalLightMapSize ) ) * 3 ) + 2 ];
669663
lightMapBuffer[( index * 4 ) + 3 ] = 255;
670664

671-
if ( tr.legacyOverBrightClamping )
665+
if ( tr.overbrightBits < tr.mapOverBrightBits )
672666
{
673667
R_ColorShiftLightingBytes( &lightMapBuffer[( index * 4 ) + 0 ] );
674668
}
@@ -1029,7 +1023,7 @@ static void ParseFace( dsurface_t *ds, drawVert_t *verts, bspSurface_t *surf, in
10291023
cv->verts[ i ].lightColor = Color::Adapt( verts[ i ].color );
10301024

10311025

1032-
if ( tr.legacyOverBrightClamping )
1026+
if ( tr.overbrightBits < tr.mapOverBrightBits )
10331027
{
10341028
R_ColorShiftLightingBytes( cv->verts[ i ].lightColor.ToArray() );
10351029
}
@@ -1239,7 +1233,7 @@ static void ParseMesh( dsurface_t *ds, drawVert_t *verts, bspSurface_t *surf )
12391233

12401234
points[ i ].lightColor = Color::Adapt( verts[ i ].color );
12411235

1242-
if ( tr.legacyOverBrightClamping )
1236+
if ( tr.overbrightBits < tr.mapOverBrightBits )
12431237
{
12441238
R_ColorShiftLightingBytes( points[ i ].lightColor.ToArray() );
12451239
}
@@ -1366,7 +1360,7 @@ static void ParseTriSurf( dsurface_t *ds, drawVert_t *verts, bspSurface_t *surf,
13661360

13671361
cv->verts[ i ].lightColor = Color::Adapt( verts[ i ].color );
13681362

1369-
if ( tr.legacyOverBrightClamping )
1363+
if ( tr.overbrightBits < tr.mapOverBrightBits )
13701364
{
13711365
R_ColorShiftLightingBytes( cv->verts[ i ].lightColor.ToArray() );
13721366
}
@@ -3889,14 +3883,7 @@ static void R_LoadFogs( lump_t *l, lump_t *brushesLump, lump_t *sidesLump )
38893883
out->fogParms = shader->fogParms;
38903884

38913885
out->color = Color::Adapt( shader->fogParms.color );
3892-
3893-
/* Historically it was done:
3894-
3895-
out->color *= tr.identityLight;
3896-
3897-
But tr.identityLight is always 1.0f in Dæmon engine
3898-
as the as the overbright bit implementation is fully
3899-
software. */
3886+
out->color *= tr.identityLight;
39003887

39013888
out->color.SetAlpha( 1 );
39023889

@@ -4112,7 +4099,7 @@ void R_LoadLightGrid( lump_t *l )
41124099
tmpDirected[ 2 ] = in->directed[ 2 ];
41134100
tmpDirected[ 3 ] = 255;
41144101

4115-
if ( tr.legacyOverBrightClamping )
4102+
if ( tr.overbrightBits < tr.mapOverBrightBits )
41164103
{
41174104
R_ColorShiftLightingBytes( tmpAmbient );
41184105
R_ColorShiftLightingBytes( tmpDirected );
@@ -4372,24 +4359,6 @@ void R_LoadEntities( lump_t *l, std::string &externalEntities )
43724359
tr.mapOverBrightBits = Math::Clamp( atof( value ), 0.0, 3.0 );
43734360
continue;
43744361
}
4375-
4376-
if ( !Q_stricmp( keyname, "overbrightClamping" ) )
4377-
{
4378-
if ( !Q_stricmp( value, "0" ) )
4379-
{
4380-
tr.legacyOverBrightClamping = false;
4381-
}
4382-
else if ( !Q_stricmp( value, "1" ) )
4383-
{
4384-
tr.legacyOverBrightClamping = true;
4385-
}
4386-
else
4387-
{
4388-
Log::Warn( "invalid value for worldspawn key overbrightClamping" );
4389-
}
4390-
4391-
continue;
4392-
}
43934362
}
43944363

43954364
// check for deluxe mapping provided by NetRadiant's q3map2
@@ -5070,11 +5039,15 @@ void RE_LoadWorldMap( const char *name )
50705039
// try will not look at the partially loaded version
50715040
tr.world = nullptr;
50725041

5073-
// tr.worldDeluxeMapping will be set by R_LoadLightmaps()
5074-
tr.worldLightMapping = false;
5075-
// tr.worldDeluxeMapping will be set by R_LoadEntities()
5076-
tr.worldDeluxeMapping = false;
5077-
tr.worldHDR_RGBE = false;
5042+
// It's probably a mistake if any of these lighting parameters are actually
5043+
// used before a map is loaded.
5044+
tr.worldLightMapping = false; // set by R_LoadLightmaps
5045+
tr.worldDeluxeMapping = false; // set by R_LoadEntities
5046+
tr.worldHDR_RGBE = false; // set by R_LoadEntities
5047+
tr.mapOverBrightBits = r_overbrightDefaultExponent.Get(); // maybe set by R_LoadEntities
5048+
tr.overbrightBits = std::min( tr.mapOverBrightBits, r_overbrightBits.Get() ); // set by RE_LoadWorldMap
5049+
tr.mapLightFactor = 1.0f; // set by RE_LoadWorldMap
5050+
tr.identityLight = 1.0f; // set by RE_LoadWorldMap
50785051

50795052
s_worldData = {};
50805053
Q_strncpyz( s_worldData.name, name, sizeof( s_worldData.name ) );
@@ -5122,6 +5095,9 @@ void RE_LoadWorldMap( const char *name )
51225095
}
51235096
R_LoadEntities( &header->lumps[ LUMP_ENTITIES ], externalEntities );
51245097

5098+
// Now we can set this after checking a possible worldspawn value for mapOverbrightBits
5099+
tr.overbrightBits = std::min( tr.mapOverBrightBits, r_overbrightBits.Get() );
5100+
51255101
R_LoadShaders( &header->lumps[ LUMP_SHADERS ] );
51265102

51275103
R_LoadLightmaps( &header->lumps[ LUMP_LIGHTMAPS ], name );
@@ -5159,7 +5135,6 @@ void RE_LoadWorldMap( const char *name )
51595135
tr.worldLight = tr.lightMode;
51605136
tr.modelLight = lightMode_t::FULLBRIGHT;
51615137
tr.modelDeluxe = deluxeMode_t::NONE;
5162-
tr.mapLightFactor = 1.0f;
51635138

51645139
// Use fullbright lighting for everything if the world is fullbright.
51655140
if ( tr.worldLight != lightMode_t::FULLBRIGHT )
@@ -5231,11 +5206,19 @@ void RE_LoadWorldMap( const char *name )
52315206
}
52325207
}
52335208

5234-
/* Set GLSL overbright parameters if the legacy clamped overbright isn't used
5235-
and the lighting mode is not fullbright. */
5236-
if ( !tr.legacyOverBrightClamping && tr.lightMode != lightMode_t::FULLBRIGHT )
5209+
/* Set GLSL overbright parameters if the lighting mode is not fullbright. */
5210+
if ( tr.lightMode != lightMode_t::FULLBRIGHT )
52375211
{
5238-
tr.mapLightFactor = pow( 2, tr.mapOverBrightBits );
5212+
if ( r_overbrightQ3.Get() )
5213+
{
5214+
// light factor is applied to entire color buffer; identityLight can be used to cancel it
5215+
tr.identityLight = 1.0f / float( 1 << tr.overbrightBits );
5216+
}
5217+
else
5218+
{
5219+
// light factor is applied wherever a precomputed light is sampled
5220+
tr.mapLightFactor = float( 1 << tr.overbrightBits );
5221+
}
52395222
}
52405223

52415224
tr.worldLoaded = true;

src/engine/renderer/tr_image.cpp

Lines changed: 41 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1853,7 +1853,7 @@ image_t *R_FindImageFile( const char *imageName, imageParams_t &imageParams )
18531853
return nullptr;
18541854
}
18551855

1856-
if ( imageParams.bits & IF_LIGHTMAP && tr.legacyOverBrightClamping )
1856+
if ( imageParams.bits & IF_LIGHTMAP )
18571857
{
18581858
R_ProcessLightmap( *pic, width, height, imageParams.bits );
18591859
}
@@ -3054,16 +3054,11 @@ void R_InitImages()
30543054
tr.lightmaps.reserve( 128 );
30553055
tr.deluxemaps.reserve( 128 );
30563056

3057-
/* These are the values expected by the rest of the renderer
3058-
(esp. tr_bsp), used for "gamma correction of the map".
3059-
Both were set to 0 if we had neither COMPAT_ET nor COMPAT_Q3,
3060-
it may be interesting to remember.
3061-
3062-
Quake 3 and Tremulous values:
3063-
3064-
tr.overbrightBits = 0; // Software implementation.
3065-
tr.mapOverBrightBits = 2; // Quake 3 default.
3066-
tr.identityLight = 1.0f / ( 1 << tr.overbrightBits );
3057+
/*
3058+
**** Map overbright bits ****
3059+
Lightmaps and vertex light colors are notionally scaled up by a factor of
3060+
pow(2, tr.mapOverBrightBits). This is a good idea because we would like a bright light
3061+
to make a texture brighter than its original diffuse image.
30673062
30683063
Games like Quake 3 and Tremulous require tr.mapOverBrightBits
30693064
to be set to 2. Because this engine is primarily maintained for
@@ -3089,32 +3084,41 @@ void R_InitImages()
30893084
require to set a different default than what Unvanquished
30903085
requires.
30913086
3092-
Using a non-zero value for tr.mapOverBrightBits turns light
3093-
non-linear and makes deluxe mapping buggy though.
3094-
3095-
Mappers may port and fix maps by multiplying the lights by 2.5
3096-
and set the mapOverBrightBits key to 0 in map entities lump.
3097-
3098-
It will be possible to assume tr.mapOverBrightBits is 0 when
3099-
loading maps compiled with sRGB lightmaps as there is no
3100-
legacy map using sRGB lightmap yet, and then we will be
3101-
able to avoid the need to explicitly set mapOverBrightBits
3102-
to 0 in map entities. It will be required to assume that
3103-
tr.mapOverBrightBits is 0 when loading maps compiled with
3104-
sRGB lightmaps because otherwise the color shift computation
3105-
will break the light computation, not only the deluxe one.
3106-
3107-
In legacy engines, tr.overbrightBits was non-zero when
3108-
hardware overbright bits were enabled, zero when disabled.
3109-
This engine do not implement hardware overbright bit, so
3110-
this is always zero, and we can remove it and simplify all
3111-
the computations making use of it.
3112-
3113-
Because tr.overbrightBits is always 0, tr.identityLight is
3114-
always 1.0f. We can entirely remove it. */
3115-
3116-
tr.mapOverBrightBits = r_overbrightDefaultExponent.Get();
3117-
tr.legacyOverBrightClamping = r_overbrightDefaultClamp.Get();
3087+
**** r_overbrightBits ****
3088+
Although lightmaps are scaled up by pow(2, tr.overbrightBits), the actual ceiling for lightmap
3089+
values is pow(2, tr.overbrightBits). tr.overbrightBits may
3090+
be less than tr.mapOverbrightBits. This is a STUPID configuration because then you are
3091+
just throwing away 1 or bits of precision from the lightmap. But it was used for many games.
3092+
3093+
The excess (tr.mapOverbrightBits - tr.overbrightBits) bits of scaling are done to the lightmap
3094+
before uploading it. If some component exceeds 1, the color is proportionally downscaled until
3095+
the max component is 1.
3096+
3097+
Quake 3 and vanilla Tremulous used these default cvar values:
3098+
r_overbrightBits - 1
3099+
r_mapOverBrightBits - 2
3100+
3101+
So the same as Daemon. But if the game was not running in fullscreen mode or the system was
3102+
not detected as supporting hardware gamma control, tr.overbrightBit would be set to 0.
3103+
Tremfusion shipped with r_overbrightBits 0 and r_ignorehwgamma 1, either of which forces
3104+
tr.overbrightBits to 0, making it the same as the vanilla client's windowed mode.
3105+
3106+
**** How Quake 3 originally implemented overbright ****
3107+
When hardware overbright was on (tr.overbrightBits = 1), the color buffer notionally ranged
3108+
from 0 to 2, rather than 0 to 1. So a buffer with 8-bit colors only had 7 bits of
3109+
output precision (all values 128+ produced the same output), but the extra bit was useful
3110+
for intermediate values during blending. The rescaling was effected by using the hardware
3111+
gamma ramp, which affected the whole monitor (or whole system).
3112+
Shaders for materials that were not illuminated by any precomputed lighting could use
3113+
CGEN_IDENTITY_LIGHTING to multiply by tr.identityLight, which would cancel out the
3114+
rescaling so that the material looked the same regardless of tr.overbrightBits.
3115+
3116+
In Daemon tr.identityLight is usually 1, so any distincion between
3117+
CGEN_IDENTITY/CGEN_IDENTITY_LIGHTING is ignored. But if you set the cvar r_overbrightQ3,
3118+
which emulates Quake 3's technique of brightening the whole color buffer, it will be used.
3119+
3120+
For even more information, see https://github.com/DaemonEngine/Daemon/issues/1542.
3121+
*/
31183122

31193123
// create default texture and white texture
31203124
R_CreateBuiltinImages();

src/engine/renderer/tr_init.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,11 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
9292
cvar_t *r_realtimeLightingCastShadows;
9393
cvar_t *r_precomputedLighting;
9494
Cvar::Cvar<int> r_overbrightDefaultExponent("r_overbrightDefaultExponent", "default map light color shift (multiply by 2^x)", Cvar::NONE, 2);
95-
Cvar::Cvar<bool> r_overbrightDefaultClamp("r_overbrightDefaultClamp", "clamp lightmap colors to 1 (in absence of map worldspawn value)", Cvar::NONE, false);
95+
Cvar::Range<Cvar::Cvar<int>> r_overbrightBits("r_overbrightBits", "clamp lightmap colors to 2^x", Cvar::NONE, 1, 0, 3);
96+
97+
// also set r_highPrecisionRendering 0 for an even more authentic q3 experience
98+
Cvar::Cvar<bool> r_overbrightQ3("r_overbrightQ3", "brighten entire color buffer like Quake 3 (incompatible with newer assets)", Cvar::NONE, false);
99+
96100
Cvar::Cvar<bool> r_overbrightIgnoreMapSettings("r_overbrightIgnoreMapSettings", "force usage of r_overbrightDefaultClamp / r_overbrightDefaultExponent, ignoring worldspawn", Cvar::NONE, false);
97101
Cvar::Range<Cvar::Cvar<int>> 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));
98102
Cvar::Cvar<bool> r_colorGrading( "r_colorGrading", "Use color grading", Cvar::NONE, true );
@@ -1185,7 +1189,8 @@ ScreenshotCmd screenshotPNGRegistration("screenshotPNG", ssFormat_t::SSF_PNG, "p
11851189
r_realtimeLightingCastShadows = Cvar_Get( "r_realtimeLightingCastShadows", "1", 0 );
11861190
r_precomputedLighting = Cvar_Get( "r_precomputedLighting", "1", CVAR_CHEAT | CVAR_LATCH );
11871191
Cvar::Latch( r_overbrightDefaultExponent );
1188-
Cvar::Latch( r_overbrightDefaultClamp );
1192+
Cvar::Latch( r_overbrightBits );
1193+
Cvar::Latch( r_overbrightQ3 );
11891194
Cvar::Latch( r_overbrightIgnoreMapSettings );
11901195
Cvar::Latch( r_lightMode );
11911196
Cvar::Latch( r_colorGrading );

0 commit comments

Comments
 (0)