From 52932417cf2e4bec71c8d2ea99fea2e0756d808d Mon Sep 17 00:00:00 2001 From: slipher Date: Mon, 16 Feb 2026 00:08:31 -0600 Subject: [PATCH 1/8] R_LoadLightGrid: comment suspicious-looking things Also fix reversed naming of lat/long and fix wrong comments. --- src/engine/renderer/tr_bsp.cpp | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/engine/renderer/tr_bsp.cpp b/src/engine/renderer/tr_bsp.cpp index d03844d401..959cae6b63 100644 --- a/src/engine/renderer/tr_bsp.cpp +++ b/src/engine/renderer/tr_bsp.cpp @@ -3569,22 +3569,17 @@ void R_LoadLightGrid( lump_t *l ) } // standard spherical coordinates to cartesian coordinates conversion - - // decode X as cos( lat ) * sin( long ) - // decode Y as sin( lat ) * sin( long ) - // decode Z as cos( long ) - // RB: having a look in NormalToLatLong used by q3map2 shows the order of latLong + // Lng = 0 at (1,0,0), 90 at (0,1,0), etc., encoded in 8-bit sine table format + // Lat = 0 at (0,0,1) to 180 (0,0,-1), encoded in 8-bit sine table format + // (so the upper bit of lat is wasted) - // Lat = 0 at (1,0,0) to 360 (-1,0,0), encoded in 8-bit sine table format - // Lng = 0 at (0,0,1) to 180 (0,0,-1), encoded in 8-bit sine table format - - lat = DEG2RAD( in->latLong[ 1 ] * ( 360.0f / 255.0f ) ); - lng = DEG2RAD( in->latLong[ 0 ] * ( 360.0f / 255.0f ) ); + lat = DEG2RAD( in->latLong[ 0 ] * ( 360.0f / 255.0f ) ); + lng = DEG2RAD( in->latLong[ 1 ] * ( 360.0f / 255.0f ) ); - direction[ 0 ] = cosf( lat ) * sinf( lng ); - direction[ 1 ] = sinf( lat ) * sinf( lng ); - direction[ 2 ] = cosf( lng ); + direction[ 0 ] = cosf( lng ) * sinf( lat ); + direction[ 1 ] = sinf( lng ) * sinf( lat ); + direction[ 2 ] = cosf( lat ); // Pack data into an bspGridPoint gridPoint1->color[ 0 ] = floatToUnorm8( 0.5f * (ambientColor[ 0 ] + directedColor[ 0 ]) ); @@ -3605,6 +3600,8 @@ void R_LoadLightGrid( lump_t *l ) // fill in gridpoints with zero light (samples in walls) to avoid // darkening of objects near walls + // FIXME: the interpolation includes other interpolated data points so the + // result depends on iteration order gridPoint1 = w->lightGridData1; gridPoint2 = w->lightGridData2; From 45463befdf2ecbf27c11f538c83b5a5507fba6a1 Mon Sep 17 00:00:00 2001 From: slipher Date: Fri, 13 Feb 2026 00:48:19 -0600 Subject: [PATCH 2/8] Don't lose precision while linearizing light grid In one step of light grid processing, values were converted from sRGB to linear and stored in bytes. This unnecessarily loses precision during the calculation. --- src/common/Color.h | 1 + src/engine/renderer/tr_bsp.cpp | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/common/Color.h b/src/common/Color.h index fba2482fb4..2ecb4d3988 100644 --- a/src/common/Color.h +++ b/src/common/Color.h @@ -55,6 +55,7 @@ inline void convertFromSRGB( float* v, bool accurate = true ) v[ 2 ] = convertFromSRGB( v[ 2 ], accurate ); } +// Beware: this instantly loses precision, there are less than 256 possible outputs! inline void convertFromSRGB( byte* bytes, bool accurate = true ) { vec3_t v; diff --git a/src/engine/renderer/tr_bsp.cpp b/src/engine/renderer/tr_bsp.cpp index 959cae6b63..313a14f240 100644 --- a/src/engine/renderer/tr_bsp.cpp +++ b/src/engine/renderer/tr_bsp.cpp @@ -3544,9 +3544,6 @@ void R_LoadLightGrid( lump_t *l ) tmpDirected[ 2 ] = in->directed[ 2 ]; tmpDirected[ 3 ] = 255; - R_LinearizeLightingColorBytes( tmpAmbient ); - R_LinearizeLightingColorBytes( tmpDirected ); - R_ColorShiftLightingBytes( tmpAmbient ); R_ColorShiftLightingBytes( tmpDirected ); @@ -3556,6 +3553,12 @@ void R_LoadLightGrid( lump_t *l ) directedColor[ j ] = tmpDirected[ j ] * ( 1.0f / 255.0f ); } + if ( tr.worldLinearizeTexture ) + { + convertFromSRGB( ambientColor ); + convertFromSRGB( directedColor ); + } + const float forceAmbient = r_forceAmbient.Get(); if ( ambientColor[0] < forceAmbient && ambientColor[1] < forceAmbient && From aede980a752845788118d914631257161fd72da4 Mon Sep 17 00:00:00 2001 From: slipher Date: Sat, 14 Feb 2026 22:02:14 -0600 Subject: [PATCH 3/8] Don't store alpha channel for lightGrid2 image Set the alpha byte to 255 so the image uploading code will choose an RGB format. --- src/engine/renderer/tr_bsp.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/engine/renderer/tr_bsp.cpp b/src/engine/renderer/tr_bsp.cpp index 313a14f240..0fe3db1d8b 100644 --- a/src/engine/renderer/tr_bsp.cpp +++ b/src/engine/renderer/tr_bsp.cpp @@ -3400,7 +3400,7 @@ static void R_SetConstantColorLightGrid( const byte color[3] ) gridPoint2->direction[ 0 ] = floatToSnorm8(0.0f); gridPoint2->direction[ 1 ] = floatToSnorm8(0.0f); gridPoint2->direction[ 2 ] = floatToSnorm8(1.0f); - gridPoint2->unused = 0; + gridPoint2->unused = 255; w->lightGridData1 = gridPoint1; w->lightGridData2 = gridPoint2; @@ -3598,7 +3598,7 @@ void R_LoadLightGrid( lump_t *l ) gridPoint2->direction[0] = 128 + floatToSnorm8( direction[ 0 ] ); gridPoint2->direction[1] = 128 + floatToSnorm8( direction[ 1 ] ); gridPoint2->direction[2] = 128 + floatToSnorm8( direction[ 2 ] ); - gridPoint2->unused = 0; + gridPoint2->unused = 255; } // fill in gridpoints with zero light (samples in walls) to avoid @@ -3645,7 +3645,6 @@ void R_LoadLightGrid( lump_t *l ) gridPoint2->direction[0] = 128 + floatToSnorm8(direction[0]); gridPoint2->direction[1] = 128 + floatToSnorm8(direction[1]); gridPoint2->direction[2] = 128 + floatToSnorm8(direction[2]); - gridPoint2->unused = 0; } } } From 2b6c82b2d05573060567a38eb2a614f1b66922c7 Mon Sep 17 00:00:00 2001 From: slipher Date: Sat, 14 Feb 2026 22:29:21 -0600 Subject: [PATCH 4/8] Don't create lightGrid2 image if deluxe disabled Don't create the light grid direction image if deluxe mapping is disabled. --- src/engine/renderer/tr_bsp.cpp | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/engine/renderer/tr_bsp.cpp b/src/engine/renderer/tr_bsp.cpp index 0fe3db1d8b..1ba80ce58a 100644 --- a/src/engine/renderer/tr_bsp.cpp +++ b/src/engine/renderer/tr_bsp.cpp @@ -3411,7 +3411,11 @@ static void R_SetConstantColorLightGrid( const byte color[3] ) imageParams.wrapType = wrapTypeEnum_t::WT_EDGE_CLAMP; tr.lightGrid1Image = R_Create3DImage("", (const byte *)w->lightGridData1, w->lightGridBounds[ 0 ], w->lightGridBounds[ 1 ], w->lightGridBounds[ 2 ], imageParams ); - tr.lightGrid2Image = R_Create3DImage("", (const byte *)w->lightGridData2, w->lightGridBounds[ 0 ], w->lightGridBounds[ 1 ], w->lightGridBounds[ 2 ], imageParams ); + + if ( glConfig.deluxeMapping ) + { + tr.lightGrid2Image = R_Create3DImage("", (const byte *)w->lightGridData2, w->lightGridBounds[ 0 ], w->lightGridBounds[ 1 ], w->lightGridBounds[ 2 ], imageParams ); + } } /* @@ -3656,7 +3660,11 @@ void R_LoadLightGrid( lump_t *l ) imageParams.wrapType = wrapTypeEnum_t::WT_EDGE_CLAMP; tr.lightGrid1Image = R_Create3DImage("", (const byte *)w->lightGridData1, w->lightGridBounds[ 0 ], w->lightGridBounds[ 1 ], w->lightGridBounds[ 2 ], imageParams ); - tr.lightGrid2Image = R_Create3DImage("", (const byte *)w->lightGridData2, w->lightGridBounds[ 0 ], w->lightGridBounds[ 1 ], w->lightGridBounds[ 2 ], imageParams ); + + if ( glConfig.deluxeMapping ) + { + tr.lightGrid2Image = R_Create3DImage("", (const byte *)w->lightGridData2, w->lightGridBounds[ 0 ], w->lightGridBounds[ 1 ], w->lightGridBounds[ 2 ], imageParams ); + } Log::Debug("%i light grid points created", w->numLightGridPoints ); } @@ -4617,14 +4625,13 @@ static void SetWorldLight() { tr.modelLight = lightMode_t::GRID; } - if ( glConfig.deluxeMapping ) { - // Enable deluxe mapping emulation if light direction grid is there. - if ( tr.lightGrid2Image ) { - // Game model surfaces use grid lighting, they don't have vertex light colors. - tr.modelDeluxe = deluxeMode_t::GRID; + // Enable deluxe mapping emulation if light direction grid is there. + if ( tr.lightGrid2Image ) { + ASSERT( glConfig.deluxeMapping ); + // Game model surfaces use grid lighting, they don't have vertex light colors. + tr.modelDeluxe = deluxeMode_t::GRID; - // Only game models use emulated deluxe map from light direction grid. - } + // Only game models use emulated deluxe map from light direction grid. } } @@ -4677,7 +4684,11 @@ static void SetConstUniforms() { } globalUBOProxy->SetUniform_LightGrid1Bindless( GL_BindToTMU( BIND_LIGHTGRID1, tr.lightGrid1Image ) ); - globalUBOProxy->SetUniform_LightGrid2Bindless( GL_BindToTMU( BIND_LIGHTGRID2, tr.lightGrid2Image ) ); + + if ( tr.lightGrid2Image ) + { + globalUBOProxy->SetUniform_LightGrid2Bindless( GL_BindToTMU( BIND_LIGHTGRID2, tr.lightGrid2Image ) ); + } } if ( glConfig.usingMaterialSystem ) { From c969391043acdbf6d2f86fae11bc1e758ca8522f Mon Sep 17 00:00:00 2001 From: slipher Date: Mon, 16 Feb 2026 23:39:49 -0600 Subject: [PATCH 5/8] R_LoadLightGrid: simplify code for skipping 0 points --- src/engine/renderer/tr_bsp.cpp | 29 ++++++++++++++++------------- src/engine/renderer/tr_light.cpp | 2 +- src/engine/renderer/tr_local.h | 3 ++- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/engine/renderer/tr_bsp.cpp b/src/engine/renderer/tr_bsp.cpp index 1ba80ce58a..865c5a2ec4 100644 --- a/src/engine/renderer/tr_bsp.cpp +++ b/src/engine/renderer/tr_bsp.cpp @@ -3400,7 +3400,7 @@ static void R_SetConstantColorLightGrid( const byte color[3] ) gridPoint2->direction[ 0 ] = floatToSnorm8(0.0f); gridPoint2->direction[ 1 ] = floatToSnorm8(0.0f); gridPoint2->direction[ 2 ] = floatToSnorm8(1.0f); - gridPoint2->unused = 255; + gridPoint2->isSet = 255; w->lightGridData1 = gridPoint1; w->lightGridData2 = gridPoint2; @@ -3543,6 +3543,13 @@ void R_LoadLightGrid( lump_t *l ) tmpAmbient[ 2 ] = in->ambient[ 2 ]; tmpAmbient[ 3 ] = 255; + /* Make sure we don't change the (0, 0, 0) points because those are points in walls, + which we'll fill up by interpolating nearby points later */ + if ( tmpAmbient[ 0 ] == 0 && tmpAmbient[ 1 ] == 0 && tmpAmbient[ 2 ] == 0 ) + { + continue; + } + tmpDirected[ 0 ] = in->directed[ 0 ]; tmpDirected[ 1 ] = in->directed[ 1 ]; tmpDirected[ 2 ] = in->directed[ 2 ]; @@ -3566,12 +3573,8 @@ void R_LoadLightGrid( lump_t *l ) const float forceAmbient = r_forceAmbient.Get(); if ( ambientColor[0] < forceAmbient && ambientColor[1] < forceAmbient && - ambientColor[2] < forceAmbient && - /* Make sure we don't change the (0, 0, 0) points because those are points in walls, - which we'll fill up by interpolating nearby points later */ - ( ambientColor[0] != 0 || - ambientColor[1] != 0 || - ambientColor[2] != 0 ) ) { + ambientColor[2] < forceAmbient ) + { VectorSet( ambientColor, forceAmbient, forceAmbient, forceAmbient ); } @@ -3593,16 +3596,15 @@ void R_LoadLightGrid( lump_t *l ) gridPoint1->color[ 1 ] = floatToUnorm8( 0.5f * (ambientColor[ 1 ] + directedColor[ 1 ]) ); gridPoint1->color[ 2 ] = floatToUnorm8( 0.5f * (ambientColor[ 2 ] + directedColor[ 2 ]) ); - // Avoid division-by-zero. float ambientLength = VectorLength(ambientColor); float directedLength = VectorLength(directedColor); float length = ambientLength + directedLength; - gridPoint1->ambientPart = length ? floatToUnorm8( ambientLength / length ) : 0; + gridPoint1->ambientPart = floatToUnorm8( ambientLength / length ); gridPoint2->direction[0] = 128 + floatToSnorm8( direction[ 0 ] ); gridPoint2->direction[1] = 128 + floatToSnorm8( direction[ 1 ] ); gridPoint2->direction[2] = 128 + floatToSnorm8( direction[ 2 ] ); - gridPoint2->unused = 255; + gridPoint2->isSet = 255; } // fill in gridpoints with zero light (samples in walls) to avoid @@ -3625,10 +3627,10 @@ void R_LoadLightGrid( lump_t *l ) from[ 0 ] = i - 1; to[ 0 ] = i + 1; - if( gridPoint1->color[ 0 ] || - gridPoint1->color[ 1 ] || - gridPoint1->color[ 2 ] ) + if ( gridPoint2->isSet ) + { continue; + } scale = R_InterpolateLightGrid( w, from, to, factors, ambientColor, directedColor, @@ -3649,6 +3651,7 @@ void R_LoadLightGrid( lump_t *l ) gridPoint2->direction[0] = 128 + floatToSnorm8(direction[0]); gridPoint2->direction[1] = 128 + floatToSnorm8(direction[1]); gridPoint2->direction[2] = 128 + floatToSnorm8(direction[2]); + gridPoint2->isSet = 255; } } } diff --git a/src/engine/renderer/tr_light.cpp b/src/engine/renderer/tr_light.cpp index b7e0740060..37130f697f 100644 --- a/src/engine/renderer/tr_light.cpp +++ b/src/engine/renderer/tr_light.cpp @@ -67,7 +67,7 @@ float R_InterpolateLightGrid( world_t *w, int from[3], int to[3], gp1 = w->lightGridData1 + x * gridStep[ 0 ] + y * gridStep[ 1 ] + z * gridStep[ 2 ]; gp2 = w->lightGridData2 + x * gridStep[ 0 ] + y * gridStep[ 1 ] + z * gridStep[ 2 ]; - if ( !( gp1->color[ 0 ] || gp1->color[ 1 ] || gp1->color[ 2 ]) ) + if ( !gp2->isSet ) { continue; // ignore samples in walls } diff --git a/src/engine/renderer/tr_local.h b/src/engine/renderer/tr_local.h index d67b4ba3c5..46cc2fd604 100644 --- a/src/engine/renderer/tr_local.h +++ b/src/engine/renderer/tr_local.h @@ -1704,7 +1704,8 @@ enum struct bspGridPoint2_t { byte direction[3]; - byte unused; + // use 0 and 255 as the boolean values so that the image uploader skips the alpha component + byte isSet; }; struct AABB { From 3d192f35e674a947eba19d67b3bd8f5b37db9c79 Mon Sep 17 00:00:00 2001 From: slipher Date: Wed, 18 Feb 2026 22:30:45 -0600 Subject: [PATCH 6/8] Fix wrong default light grid (when BSP lacks one) The direction vector encoding was wrong. --- src/engine/renderer/tr_bsp.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/engine/renderer/tr_bsp.cpp b/src/engine/renderer/tr_bsp.cpp index 865c5a2ec4..e7f221c368 100644 --- a/src/engine/renderer/tr_bsp.cpp +++ b/src/engine/renderer/tr_bsp.cpp @@ -3397,9 +3397,9 @@ static void R_SetConstantColorLightGrid( const byte color[3] ) gridPoint1->color[ 1 ] = color[1]; gridPoint1->color[ 2 ] = color[2]; gridPoint1->ambientPart = 128; - gridPoint2->direction[ 0 ] = floatToSnorm8(0.0f); - gridPoint2->direction[ 1 ] = floatToSnorm8(0.0f); - gridPoint2->direction[ 2 ] = floatToSnorm8(1.0f); + gridPoint2->direction[ 0 ] = 128 + floatToSnorm8( 0.0f ); + gridPoint2->direction[ 1 ] = 128 + floatToSnorm8( 0.0f ); + gridPoint2->direction[ 2 ] = 128 + floatToSnorm8( 1.0f ); gridPoint2->isSet = 255; w->lightGridData1 = gridPoint1; From 393ff9830171cc33fe3312d7a42b439eaa03be2e Mon Sep 17 00:00:00 2001 From: slipher Date: Tue, 17 Feb 2026 03:03:28 -0600 Subject: [PATCH 7/8] r_showLightGrid: fix wrong colors displayed Sometimes color values were slightly greater than 1; this led to an incorrect float color -> byte color conversion. Fix it by clamping the colors in Tess_AddTetrahedron. --- src/engine/renderer/tr_local.h | 2 +- src/engine/renderer/tr_surface.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/engine/renderer/tr_local.h b/src/engine/renderer/tr_local.h index 46cc2fd604..7aa78d50b6 100644 --- a/src/engine/renderer/tr_local.h +++ b/src/engine/renderer/tr_local.h @@ -3230,7 +3230,7 @@ void GLimp_LogComment_( std::string comment ); @param tetraVerts[0..2] are the ground vertices, tetraVerts[3] is the pyramid offset */ - void Tess_AddTetrahedron( vec4_t tetraVerts[ 4 ], const Color::Color& color ); + void Tess_AddTetrahedron( vec4_t tetraVerts[ 4 ], Color::Color color ); void Tess_AddCube( const vec3_t position, const vec3_t minSize, const vec3_t maxSize, const Color::Color& color ); void Tess_AddCubeWithNormals( const vec3_t position, const vec3_t minSize, const vec3_t maxSize, const Color::Color& color ); diff --git a/src/engine/renderer/tr_surface.cpp b/src/engine/renderer/tr_surface.cpp index 6874af2267..660bc5e0a6 100644 --- a/src/engine/renderer/tr_surface.cpp +++ b/src/engine/renderer/tr_surface.cpp @@ -399,12 +399,13 @@ void Tess_AddQuadStamp2WithNormals( vec4_t quadVerts[ 4 ], const Color::Color& c } // Defines ATTR_POSITION, ATTR_COLOR -void Tess_AddTetrahedron( vec4_t tetraVerts[ 4 ], const Color::Color& colorf ) +void Tess_AddTetrahedron( vec4_t tetraVerts[ 4 ], Color::Color colorf ) { int k; Tess_CheckOverflow( 12, 12 ); + colorf.Clamp(); Color::Color32Bit color = colorf; // ground triangle From 038043c9bd8d6575ccb7fea380e18303641c7b6b Mon Sep 17 00:00:00 2001 From: slipher Date: Tue, 17 Feb 2026 01:14:06 -0600 Subject: [PATCH 8/8] r_showLightGrid: access grid values directly Don't use R_LightForPoint which has some levels of tweaking and interpolation. --- src/engine/renderer/tr_backend.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/engine/renderer/tr_backend.cpp b/src/engine/renderer/tr_backend.cpp index 41f26a2de6..b4290b1932 100644 --- a/src/engine/renderer/tr_backend.cpp +++ b/src/engine/renderer/tr_backend.cpp @@ -2237,7 +2237,7 @@ static void RB_RenderDebugUtils() // set uniforms gl_genericShader->SetUniform_AlphaTest( GLS_ATEST_NONE ); - SetUniform_ColorModulateColorGen( gl_genericShader, colorGen_t::CGEN_VERTEX, alphaGen_t::AGEN_VERTEX ); + SetUniform_ColorModulateColorGen( gl_genericShader, colorGen_t::CGEN_VERTEX, alphaGen_t::AGEN_IDENTITY ); SetUniform_Color( gl_genericShader, Color::Black ); GL_State( GLS_DEFAULT ); @@ -2263,8 +2263,6 @@ static void RB_RenderDebugUtils() for ( y = 0; y < tr.world->lightGridBounds[ 1 ]; y++ ) { for ( x = 0; x < tr.world->lightGridBounds[ 0 ]; x++ ) { vec3_t origin; - Color::Color ambientColor; - Color::Color directedColor; vec3_t lightDir; VectorCopy( tr.world->lightGridOrigin, origin ); @@ -2277,8 +2275,19 @@ static void RB_RenderDebugUtils() continue; } - R_LightForPoint( origin, ambientColor.ToArray(), - directedColor.ToArray(), lightDir ); + // read out grid... + int gridIndex = x + tr.world->lightGridBounds[ 0 ] * ( y + tr.world->lightGridBounds[ 1 ] * z ); + const bspGridPoint1_t *gp1 = tr.world->lightGridData1 + gridIndex; + const bspGridPoint2_t *gp2 = tr.world->lightGridData2 + gridIndex; + Color::Color generalColor = Color::Adapt( gp1->color ); + float ambientScale = 2.0f * unorm8ToFloat( gp1->ambientPart ); + float directedScale = 2.0f - ambientScale; + Color::Color ambientColor = generalColor * ambientScale; + Color::Color directedColor = generalColor * directedScale; + lightDir[ 0 ] = snorm8ToFloat( gp2->direction[ 0 ] - 128 ); + lightDir[ 1 ] = snorm8ToFloat( gp2->direction[ 1 ] - 128 ); + lightDir[ 2 ] = snorm8ToFloat( gp2->direction[ 2 ] - 128 ); + VectorNegate( lightDir, lightDir ); length = 8;