From 8c147eb9dd6603aa59141c2f9a9a1e98947b84a7 Mon Sep 17 00:00:00 2001 From: Justin Marshall Date: Fri, 29 May 2026 20:10:26 -0700 Subject: [PATCH] Fixes for BC7 --- neo/engine/renderer/Image_load.cpp | 58 +++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/neo/engine/renderer/Image_load.cpp b/neo/engine/renderer/Image_load.cpp index cc7d9907..a1cafd12 100644 --- a/neo/engine/renderer/Image_load.cpp +++ b/neo/engine/renderer/Image_load.cpp @@ -1164,6 +1164,21 @@ static float R_NormalProjection( const byte *rgba, const idVec3 &axis ) { ( rgba[2] * ( 2.0f / 255.0f ) - 1.0f ) * axis.z; } +static byte R_NormalFloatToByte( float v ) { + return (byte)idMath::ClampInt( 0, 255, (int)( ( v * 0.5f + 0.5f ) * 255.0f + 0.5f ) ); +} + +static float R_NormalToSlopeHeight( const idVec3 &normal ) { + const float slope = idMath::ClampFloat( 0.0f, 1.0f, idMath::Sqrt( normal.x * normal.x + normal.y * normal.y ) ); + return idMath::ClampFloat( 0.0f, 1.0f, 0.5f + idMath::Pow( slope, 0.65f ) * 0.5f ); +} + +static byte R_NormalToBakedHeightByte( const idVec3 &normal, float blockAverageHeight ) { + const float localHeight = 0.5f + ( R_NormalToSlopeHeight( normal ) - blockAverageHeight ) * 0.55f; + const float height = idMath::ClampFloat( 0.35f, 0.65f, localHeight ); + return (byte)idMath::ClampInt( 1, 254, (int)( height * 255.0f + 0.5f ) ); +} + static void R_EncodeBC7Mode6Block( const byte *rgba, int width, int height, int x, int y, bool normalMap, byte *block ) { memset( block, 0, 16 ); @@ -1176,8 +1191,11 @@ static void R_EncodeBC7Mode6Block( const byte *rgba, int width, int height, int idVec3 averageNormal( 0.0f, 0.0f, 0.0f ); idVec3 variance( 0.0f, 0.0f, 0.0f ); idVec3 axis( 1.0f, 0.0f, 0.0f ); + float averageProjection = 0.0f; + float averageSlopeHeight = 0.5f; if ( normalMap ) { + averageSlopeHeight = 0.0f; for ( int by = 0; by < 4; by++ ) { const int sy = ( y + by < height ) ? y + by : height - 1; for ( int bx = 0; bx < 4; bx++ ) { @@ -1186,9 +1204,18 @@ static void R_EncodeBC7Mode6Block( const byte *rgba, int width, int height, int averageNormal.x += p[0] * ( 2.0f / 255.0f ) - 1.0f; averageNormal.y += p[1] * ( 2.0f / 255.0f ) - 1.0f; averageNormal.z += p[2] * ( 2.0f / 255.0f ) - 1.0f; + idVec3 sampleNormal( + p[0] * ( 2.0f / 255.0f ) - 1.0f, + p[1] * ( 2.0f / 255.0f ) - 1.0f, + p[2] * ( 2.0f / 255.0f ) - 1.0f ); + if ( !sampleNormal.Normalize() ) { + sampleNormal.Set( 0.0f, 0.0f, 1.0f ); + } + averageSlopeHeight += R_NormalToSlopeHeight( sampleNormal ); } } averageNormal *= 1.0f / 16.0f; + averageSlopeHeight *= 1.0f / 16.0f; for ( int by = 0; by < 4; by++ ) { const int sy = ( y + by < height ) ? y + by : height - 1; for ( int bx = 0; bx < 4; bx++ ) { @@ -1207,6 +1234,7 @@ static void R_EncodeBC7Mode6Block( const byte *rgba, int width, int height, int } else if ( variance.z > variance.x && variance.z > variance.y ) { axis.Set( 0.0f, 0.0f, 1.0f ); } + averageProjection = averageNormal * axis; } for ( int by = 0; by < 4; by++ ) { @@ -1218,11 +1246,9 @@ static void R_EncodeBC7Mode6Block( const byte *rgba, int width, int height, int const float projection = R_NormalProjection( p, axis ); if ( projection < minProjection ) { minProjection = projection; - memcpy( minColor, p, 4 ); } if ( projection > maxProjection ) { maxProjection = projection; - memcpy( maxColor, p, 4 ); } continue; } @@ -1238,6 +1264,30 @@ static void R_EncodeBC7Mode6Block( const byte *rgba, int width, int height, int } } + if ( normalMap ) { + idVec3 minNormal = averageNormal + axis * ( minProjection - averageProjection ); + idVec3 maxNormal = averageNormal + axis * ( maxProjection - averageProjection ); + if ( variance.x + variance.y + variance.z < 0.0002f || ( maxProjection - minProjection ) < 0.01f ) { + minNormal = averageNormal; + maxNormal = averageNormal; + minProjection = maxProjection = averageProjection; + } + if ( !minNormal.Normalize() ) { + minNormal.Set( 0.0f, 0.0f, 1.0f ); + } + if ( !maxNormal.Normalize() ) { + maxNormal.Set( 0.0f, 0.0f, 1.0f ); + } + minColor[0] = R_NormalFloatToByte( minNormal.x ); + minColor[1] = R_NormalFloatToByte( minNormal.y ); + minColor[2] = R_NormalFloatToByte( minNormal.z ); + minColor[3] = R_NormalToBakedHeightByte( minNormal, averageSlopeHeight ); + maxColor[0] = R_NormalFloatToByte( maxNormal.x ); + maxColor[1] = R_NormalFloatToByte( maxNormal.y ); + maxColor[2] = R_NormalFloatToByte( maxNormal.z ); + maxColor[3] = R_NormalToBakedHeightByte( maxNormal, averageSlopeHeight ); + } + int bit = 0; R_WriteBitsLSB( block, bit, 0x40, 7 ); // BC7 mode 6: six zero mode bits followed by one. @@ -1294,7 +1344,7 @@ static void R_AppendBytes( idList &dst, const void *src, int bytes ) { } static unsigned long R_GeneratedBC7CookVersion() { - return DDS_MAKEFOURCC( 'B', '7', 'C', '3' ); + return DDS_MAKEFOURCC( 'B', '7', 'C', '8' ); } static bool R_GetDDSAverageColor( const byte *data, int len, float averageColor[4] ) { @@ -1456,7 +1506,7 @@ bool idImage::CookGeneratedBC7Image( const byte *pic, int width, int height, ID_ header.dwFlags = DDSF_CAPS | DDSF_PIXELFORMAT | DDSF_WIDTH | DDSF_HEIGHT | DDSF_LINEARSIZE; header.dwHeight = scaled_height; header.dwWidth = scaled_width; - header.dwPitchOrLinearSize = ( ( scaled_width + 3 ) / 4 ) * 16; + header.dwPitchOrLinearSize = ( ( scaled_width + 3 ) / 4 ) * ( ( scaled_height + 3 ) / 4 ) * 16; header.dwMipMapCount = numLevels; header.dwFlags |= DDSF_MIPMAPCOUNT; header.dwCaps1 = DDSF_TEXTURE | DDSF_MIPMAP | DDSF_COMPLEX;