From 18d36f2eb327c644d999bbb0ba4ae6e62271a130 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 5edea219ccc9e9ffdc473f84a6c0214a66c67eb0 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 4357174a5cfc01dcac2dc2ce1e6725e1cc456c79 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 5b3dd5d9559b9f76863c67e4e4667b4d4f441a8a 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 3c11bfd9915d73f8f3fa7c84b88fdc31953adba5 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 | 2 +- 3 files changed, 18 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..bbfbc7ffa6 100644 --- a/src/engine/renderer/tr_local.h +++ b/src/engine/renderer/tr_local.h @@ -1704,7 +1704,7 @@ enum struct bspGridPoint2_t { byte direction[3]; - byte unused; + byte isSet; }; struct AABB { From fa6d96773586c8f3cc0605550a54350315147ea5 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 fc023da52dac63cf872ca25367bceac655be1545 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 bbfbc7ffa6..0e23c56843 100644 --- a/src/engine/renderer/tr_local.h +++ b/src/engine/renderer/tr_local.h @@ -3229,7 +3229,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 3f78683e3b9d812390837c99c8562088405083f1 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;