diff --git a/src.cmake b/src.cmake index 025649e22f..76ca097d33 100644 --- a/src.cmake +++ b/src.cmake @@ -160,6 +160,7 @@ set(GLSLSOURCELIST ${ENGINE_DIR}/renderer/glsl_source/blur_vp.glsl ${ENGINE_DIR}/renderer/glsl_source/cameraEffects_fp.glsl ${ENGINE_DIR}/renderer/glsl_source/cameraEffects_vp.glsl + ${ENGINE_DIR}/renderer/glsl_source/colorSpace.glsl ${ENGINE_DIR}/renderer/glsl_source/computeLight_fp.glsl ${ENGINE_DIR}/renderer/glsl_source/contrast_fp.glsl ${ENGINE_DIR}/renderer/glsl_source/contrast_vp.glsl diff --git a/src/common/Color.h b/src/common/Color.h index 8c98ebc74c..727b082d81 100644 --- a/src/common/Color.h +++ b/src/common/Color.h @@ -38,6 +38,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "Compiler.h" #include "Math.h" +#define convertFromSRGB( v ) (v <= 0.04045f ? v * (1.0f / 12.92f) : pow((v + 0.055f) * (1.0f / 1.055f), 2.4f)) + namespace Color { /* @@ -256,6 +258,20 @@ class BasicColor data_[ 3 ] = v; } + CONSTEXPR_FUNCTION_RELAXED component_type ConvertFromSRGB( component_type v ) NOEXCEPT + { + float f = float( v ) / 255.0f; + f = convertFromSRGB( f ); + return component_type( f * 255 ); + } + + CONSTEXPR_FUNCTION_RELAXED void ConvertFromSRGB() NOEXCEPT + { + SetRed( ConvertFromSRGB( Red() ) ); + SetGreen( ConvertFromSRGB( Green() ) ); + SetBlue( ConvertFromSRGB( Blue() ) ); + } + CONSTEXPR_FUNCTION_RELAXED BasicColor& operator*=( float factor ) NOEXCEPT { *this = *this * factor; diff --git a/src/engine/renderer/Material.cpp b/src/engine/renderer/Material.cpp index 9e4a6fc34b..25d388822b 100644 --- a/src/engine/renderer/Material.cpp +++ b/src/engine/renderer/Material.cpp @@ -158,6 +158,9 @@ void UpdateSurfaceDataGeneric3D( uint32_t* materials, shaderStage_t* pStage, boo Tess_ComputeColor( pStage ); gl_genericShaderMaterial->SetUniform_Color( tess.svars.color ); + // u_LinearizeTexture + gl_genericShaderMaterial->SetUniform_LinearizeTexture( pStage->linearizeTexture ); + bool hasDepthFade = pStage->hasDepthFade; if ( hasDepthFade ) { gl_genericShaderMaterial->SetUniform_DepthScale( pStage->depthFadeValue ); @@ -191,6 +194,9 @@ void UpdateSurfaceDataLightMapping( uint32_t* materials, shaderStage_t* pStage, // u_AlphaThreshold gl_lightMappingShaderMaterial->SetUniform_AlphaTest( pStage->stateBits ); + // u_LinearizeTexture + gl_lightMappingShaderMaterial->SetUniform_LinearizeTexture( pStage->linearizeTexture ); + // HeightMap if ( pStage->enableReliefMapping ) { float depthScale = RB_EvalExpression( &pStage->depthScaleExp, r_reliefDepthScale->value ); @@ -268,6 +274,9 @@ void UpdateSurfaceDataSkybox( uint32_t* materials, shaderStage_t* pStage, bool, gl_skyboxShaderMaterial->SetUniform_AlphaTest( GLS_ATEST_NONE ); gl_skyboxShaderMaterial->WriteUniformsToBuffer( materials ); + + // u_LinearizeTexture + gl_skyboxShaderMaterial->SetUniform_LinearizeTexture( pStage->linearizeTexture ); } void UpdateSurfaceDataScreen( uint32_t* materials, shaderStage_t* pStage, bool, bool, bool ) { @@ -360,6 +369,8 @@ void UpdateSurfaceDataFog( uint32_t* materials, shaderStage_t* pStage, bool, boo materials += pStage->bufferOffset; gl_fogQuake3ShaderMaterial->WriteUniformsToBuffer( materials ); + + gl_fogQuake3ShaderMaterial->SetUniform_LinearizeTexture( tr.worldLinearizeTexture ); } /* diff --git a/src/engine/renderer/gl_shader.cpp b/src/engine/renderer/gl_shader.cpp index 058ca81543..597acd1c9f 100644 --- a/src/engine/renderer/gl_shader.cpp +++ b/src/engine/renderer/gl_shader.cpp @@ -802,6 +802,11 @@ static std::string GenEngineConstants() { AddConst( str, "r_RimExponent", r_rimExponent->value ); } + if ( r_cheapSRGB.Get() ) + { + AddDefine( str, "r_cheapSRGB", 1 ); + } + if ( r_showLightTiles->integer ) { AddDefine( str, "r_showLightTiles", 1 ); @@ -2261,6 +2266,7 @@ GLShader_generic::GLShader_generic( GLShaderManager *manager ) : u_AlphaThreshold( this ), u_ModelMatrix( this ), u_ModelViewProjectionMatrix( this ), + u_LinearizeTexture( this ), u_ColorModulateColorGen( this ), u_Color( this ), u_Bones( this ), @@ -2293,6 +2299,7 @@ GLShader_genericMaterial::GLShader_genericMaterial( GLShaderManager* manager ) : u_AlphaThreshold( this ), u_ModelMatrix( this ), u_ModelViewProjectionMatrix( this ), + u_LinearizeTexture( this ), u_ColorModulateColorGen( this ), u_Color( this ), u_DepthScale( this ), @@ -2334,6 +2341,7 @@ GLShader_lightMapping::GLShader_lightMapping( GLShaderManager *manager ) : u_ViewOrigin( this ), u_ModelMatrix( this ), u_ModelViewProjectionMatrix( this ), + u_LinearizeTexture( this ), u_Bones( this ), u_VertexInterpolation( this ), u_ReliefDepthScale( this ), @@ -2402,6 +2410,7 @@ GLShader_lightMappingMaterial::GLShader_lightMappingMaterial( GLShaderManager* m u_ViewOrigin( this ), u_ModelMatrix( this ), u_ModelViewProjectionMatrix( this ), + u_LinearizeTexture( this ), u_ReliefDepthScale( this ), u_ReliefOffsetBias( this ), u_NormalScale( this ), @@ -2712,6 +2721,7 @@ GLShader_skybox::GLShader_skybox( GLShaderManager *manager ) : u_CloudHeight( this ), u_UseCloudMap( this ), u_AlphaThreshold( this ), + u_LinearizeTexture( this ), u_ModelViewProjectionMatrix( this ) { } @@ -2730,6 +2740,7 @@ GLShader_skyboxMaterial::GLShader_skyboxMaterial( GLShaderManager* manager ) : u_CloudHeight( this ), u_UseCloudMap( this ), u_AlphaThreshold( this ), + u_LinearizeTexture( this ), u_ModelViewProjectionMatrix( this ) {} @@ -2743,6 +2754,7 @@ GLShader_fogQuake3::GLShader_fogQuake3( GLShaderManager *manager ) : u_FogMap( this ), u_ModelMatrix( this ), u_ModelViewProjectionMatrix( this ), + u_LinearizeTexture( this ), u_ColorGlobal( this ), u_Bones( this ), u_VertexInterpolation( this ), @@ -2765,6 +2777,7 @@ GLShader_fogQuake3Material::GLShader_fogQuake3Material( GLShaderManager* manager u_FogMap( this ), u_ModelMatrix( this ), u_ModelViewProjectionMatrix( this ), + u_LinearizeTexture( this ), u_ColorGlobal( this ), u_FogDistanceVector( this ), u_FogDepthVector( this ), @@ -2782,6 +2795,7 @@ GLShader_fogGlobal::GLShader_fogGlobal( GLShaderManager *manager ) : u_DepthMap( this ), u_ModelViewProjectionMatrix( this ), u_UnprojectMatrix( this ), + u_LinearizeTexture( this ), u_Color( this ), u_FogDistanceVector( this ) { @@ -2896,7 +2910,8 @@ GLShader_cameraEffects::GLShader_cameraEffects( GLShaderManager *manager ) : u_ColorModulate( this ), u_TextureMatrix( this ), u_ModelViewProjectionMatrix( this ), - u_InverseGamma( this ) + u_InverseGamma( this ), + u_DelinearizeScreen( this ) { } diff --git a/src/engine/renderer/gl_shader.h b/src/engine/renderer/gl_shader.h index 2e357c651f..7ec6ae732b 100644 --- a/src/engine/renderer/gl_shader.h +++ b/src/engine/renderer/gl_shader.h @@ -2879,6 +2879,36 @@ class u_ShadowTexelSize : } }; +class u_LinearizeTexture : + GLUniform1i +{ +public: + u_LinearizeTexture( GLShader *shader ) : + GLUniform1i( shader, "u_LinearizeTexture" ) + { + } + + void SetUniform_LinearizeTexture( const int value ) + { + this->SetValue( value ); + } +}; + +class u_DelinearizeScreen : + GLUniform1i +{ +public: + u_DelinearizeScreen( GLShader *shader ) : + GLUniform1i( shader, "u_DelinearizeScreen" ) + { + } + + void SetUniform_DelinearizeScreen( const int value ) + { + this->SetValue( value ); + } +}; + class u_ShadowBlur : GLUniform1f { @@ -3907,6 +3937,7 @@ class GLShader_generic : public u_AlphaThreshold, public u_ModelMatrix, public u_ModelViewProjectionMatrix, + public u_LinearizeTexture, public u_ColorModulateColorGen, public u_Color, public u_Bones, @@ -3936,6 +3967,7 @@ class GLShader_genericMaterial : public u_AlphaThreshold, public u_ModelMatrix, public u_ModelViewProjectionMatrix, + public u_LinearizeTexture, public u_ColorModulateColorGen, public u_Color, public u_DepthScale, @@ -3974,6 +4006,7 @@ class GLShader_lightMapping : public u_ViewOrigin, public u_ModelMatrix, public u_ModelViewProjectionMatrix, + public u_LinearizeTexture, public u_Bones, public u_VertexInterpolation, public u_ReliefDepthScale, @@ -4025,6 +4058,7 @@ class GLShader_lightMappingMaterial : public u_ViewOrigin, public u_ModelMatrix, public u_ModelViewProjectionMatrix, + public u_LinearizeTexture, public u_ReliefDepthScale, public u_ReliefOffsetBias, public u_NormalScale, @@ -4268,6 +4302,7 @@ class GLShader_skybox : public u_CloudHeight, public u_UseCloudMap, public u_AlphaThreshold, + public u_LinearizeTexture, public u_ModelViewProjectionMatrix { public: @@ -4283,6 +4318,7 @@ class GLShader_skyboxMaterial : public u_CloudHeight, public u_UseCloudMap, public u_AlphaThreshold, + public u_LinearizeTexture, public u_ModelViewProjectionMatrix { public: GLShader_skyboxMaterial( GLShaderManager* manager ); @@ -4294,6 +4330,7 @@ class GLShader_fogQuake3 : public u_FogMap, public u_ModelMatrix, public u_ModelViewProjectionMatrix, + public u_LinearizeTexture, public u_ColorGlobal, public u_Bones, public u_VertexInterpolation, @@ -4314,6 +4351,7 @@ class GLShader_fogQuake3Material : public u_FogMap, public u_ModelMatrix, public u_ModelViewProjectionMatrix, + public u_LinearizeTexture, public u_ColorGlobal, public u_FogDistanceVector, public u_FogDepthVector, @@ -4330,6 +4368,7 @@ class GLShader_fogGlobal : public u_DepthMap, public u_ModelViewProjectionMatrix, public u_UnprojectMatrix, + public u_LinearizeTexture, public u_Color, public u_FogDistanceVector { @@ -4427,7 +4466,8 @@ class GLShader_cameraEffects : public u_ColorModulate, public u_TextureMatrix, public u_ModelViewProjectionMatrix, - public u_InverseGamma + public u_InverseGamma, + public u_DelinearizeScreen { public: GLShader_cameraEffects( GLShaderManager *manager ); diff --git a/src/engine/renderer/glsl_source/cameraEffects_fp.glsl b/src/engine/renderer/glsl_source/cameraEffects_fp.glsl index e58b37bbd9..d798a36b04 100644 --- a/src/engine/renderer/glsl_source/cameraEffects_fp.glsl +++ b/src/engine/renderer/glsl_source/cameraEffects_fp.glsl @@ -22,8 +22,11 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA /* cameraEffects_fp.glsl */ +#insert colorSpace + uniform sampler2D u_CurrentMap; +uniform bool u_DelinearizeScreen; #if defined(r_colorGrading) uniform sampler3D u_ColorMap3D; #endif @@ -42,6 +45,8 @@ void main() vec4 color = texture2D(u_CurrentMap, st); + convertToSRGB(color.rgb, u_DelinearizeScreen); + color = clamp(color, 0.0, 1.0); #if defined(r_colorGrading) diff --git a/src/engine/renderer/glsl_source/colorSpace.glsl b/src/engine/renderer/glsl_source/colorSpace.glsl new file mode 100644 index 0000000000..884b24e620 --- /dev/null +++ b/src/engine/renderer/glsl_source/colorSpace.glsl @@ -0,0 +1,107 @@ +#define SRGB_MIX + +uniform int u_LinearizeTexture; + +void convertFromSRGB(inout vec3 color, in bool condition) { + if (condition) + { + #if defined(r_cheapSRGB) + float gamma = 2.2; + color = pow(color, vec3(gamma)); + + #elif defined(SRGB_NAIVE) + // (((c) <= 0.04045f) ? (c) * (1.0f / 12.92f) : (float)pow(((c) + 0.055f)*(1.0f/1.055f), 2.4f)) + + float threshold = 0.0031308f; + + color.r = color.r <= threshold ? color.r * (1.0f / 12.92f) : pow((color.r + 0.055f) * (1.0f / 1.055f), 2.4f); + color.g = color.g <= threshold ? color.g * (1.0f / 12.92f) : pow((color.g + 0.055f) * (1.0f / 1.055f), 2.4f); + color.b = color.b <= threshold ? color.b * (1.0f / 12.92f) : pow((color.b + 0.055f) * (1.0f / 1.055f), 2.4f); + #elif defined(SRGB_FBOOL) + float threshold = 0.0031308f; + + vec3 yes = vec3(float(color.r <= threshold), float(color.g <= threshold), float(color.b <= threshold)); + vec3 no = yes * -1.0f + 1.0f; + + vec3 low = color * (1.0f / 12.92f); + vec3 high = pow((color + 0.055f) * (1.0f / 1.055f), vec3(2.4f)); + + color = (yes * low) + (no * high); + #elif defined(SRGB_BOOL) + float threshold = 0.0031308f; + + bvec3 yes = bvec3(color.r <= threshold, color.g <= threshold, color.b <= threshold); + bvec3 no = bvec3(!yes.x, !yes.y, !yes.z); + + vec3 low = color * (1.0f / 12.92f); + vec3 high = pow((color + 0.055f) * (1.0f / 1.055f), vec3(2.4f)); + + color = (float(yes) * low) + (float(no) * high); + #elif defined(SRGB_MIX) + float threshold = 0.0031308f; + + bvec3 cutoff = lessThan(color, vec3(threshold)); + vec3 low = color / vec3(12.92f); + vec3 high = pow((color + vec3(0.055f)) / vec3(1.055f), vec3(2.4f)); + + color = mix(high, low, cutoff); + #else + #error undefined SRGB computation + #endif + } +} + +void convertFromSRGB(inout vec3 color, in int condition) { + convertFromSRGB(color, condition != 0); +} + +void convertToSRGB(inout vec3 color, in bool condition) { + if (condition) + { + #if defined(r_cheapSRGB) + float gamma = 2.2; + color = pow(color, vec3(1/gamma)); + #elif defined(SRGB_NAIVE) + // (((c) < 0.0031308f) ? (c) * 12.92f : 1.055f * (float)pow((c), 1.0f/2.4f) - 0.055f) + float threshold = 0.0031308f; + + color.r = color.r < threshold ? color.r * 12.92f : 1.055f * pow(color.r, 1.0f / 2.4f) - 0.055f; + color.g = color.g < threshold ? color.g * 12.92f : 1.055f * pow(color.g, 1.0f / 2.4f) - 0.055f; + color.b = color.b < threshold ? color.b * 12.92f : 1.055f * pow(color.b, 1.0f / 2.4f) - 0.055f; + #elif defined(SRGB_FBOOL) + float threshold = 0.0031308f; + + vec3 yes = vec3(float(color.r < threshold), float(color.g < threshold), float(color.b < threshold)); + vec3 no = yes * -1.0f + 1.0f; + + vec3 low = color * 12.92f; + vec3 high = pow(color, vec3(1.0f / 2.4f)) - 0.055f; + + color = (yes * low) + (no * high); + #elif defined(SRGB_BOOL) + float threshold = 0.0031308f; + + bvec3 yes = bvec3(color.r < threshold, color.g < threshold, color.b < threshold); + bvec3 no = bvec3(!yes.x, !yes.y, !yes.z); + + vec3 low = color * 12.92f; + vec3 high = pow(color, vec3(1.0f / 2.4f)) - 0.055f; + + color = (float(yes) * low) + (float(no) * high); + #elif defined(SRGB_MIX) + float threshold = 0.0031308f; + + bvec3 cutoff = lessThan(color, vec3(threshold)); + vec3 low = vec3(12.92f) * color; + vec3 high = vec3(1.055f) * pow(color, vec3(1.0f / 2.4f)) - vec3(0.055f); + + color = mix(high, low, cutoff); + #else + #error undefined SRGB computation + #endif + } +} + +void convertToSRGB(inout vec3 color, in int condition) { + convertToSRGB(color, condition != 0); +} diff --git a/src/engine/renderer/glsl_source/computeLight_fp.glsl b/src/engine/renderer/glsl_source/computeLight_fp.glsl index 64d405ecb6..79ff4461f7 100644 --- a/src/engine/renderer/glsl_source/computeLight_fp.glsl +++ b/src/engine/renderer/glsl_source/computeLight_fp.glsl @@ -49,12 +49,27 @@ vec4 EnvironmentalSpecularFactor( vec3 viewDir, vec3 normal ) // lighting helper functions +#if defined(USE_LIGHT_MAPPING) + void TransformLightMap( inout vec3 lightColor, in float lightFactor, bool linearizeLightMap ) + { + convertFromSRGB(lightColor, linearizeLightMap); + + // When doing vertex lighting with full-range overbright, this reads out + // 1< shadermap({ { "cameraEffects_fp.glsl", std::string(reinterpret_cast(cameraEffects_fp_glsl), sizeof(cameraEffects_fp_glsl)) }, { "cameraEffects_vp.glsl", std::string(reinterpret_cast(cameraEffects_vp_glsl), sizeof(cameraEffects_vp_glsl)) }, { "computeLight_fp.glsl", std::string(reinterpret_cast(computeLight_fp_glsl), sizeof(computeLight_fp_glsl)) }, + { "colorSpace.glsl", std::string(reinterpret_cast(colorSpace_glsl), sizeof(colorSpace_glsl)) }, { "contrast_fp.glsl", std::string(reinterpret_cast(contrast_fp_glsl), sizeof(contrast_fp_glsl)) }, { "contrast_vp.glsl", std::string(reinterpret_cast(contrast_vp_glsl), sizeof(contrast_vp_glsl)) }, { "common.glsl", std::string( reinterpret_cast< const char* >( common_glsl ), sizeof( common_glsl ) ) }, diff --git a/src/engine/renderer/tr_backend.cpp b/src/engine/renderer/tr_backend.cpp index 3305c5208c..8211a05cf7 100644 --- a/src/engine/renderer/tr_backend.cpp +++ b/src/engine/renderer/tr_backend.cpp @@ -2980,6 +2980,9 @@ void RB_RenderGlobalFog() // go back to the world modelview matrix backEnd.orientation = backEnd.viewParms.world; + // u_LinearizeTexture + gl_fogGlobalShader->SetUniform_LinearizeTexture( tr.worldLinearizeTexture ); + { fog_t *fog; @@ -3045,7 +3048,9 @@ void RB_RenderBloom() GLimp_LogComment( "--- RB_RenderBloom ---\n" ); if ( ( backEnd.refdef.rdflags & ( RDF_NOWORLDMODEL | RDF_NOBLOOM ) ) - || !glConfig2.bloom || backEnd.viewParms.portalLevel > 0 ) { + || !glConfig2.bloom || backEnd.viewParms.portalLevel > 0 + || !tr.worldLinearizeTexture ) + { return; } @@ -3328,6 +3333,9 @@ void RB_CameraPostFX() // enable shader, set arrays gl_cameraEffectsShader->BindProgram( 0 ); + // u_DelinearizeScreen + gl_cameraEffectsShader->SetUniform_DelinearizeScreen( tr.worldLinearizeTexture ); + 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 c040d89579..ff994514ff 100644 --- a/src/engine/renderer/tr_bsp.cpp +++ b/src/engine/renderer/tr_bsp.cpp @@ -1028,6 +1028,10 @@ static void ParseFace( dsurface_t *ds, drawVert_t *verts, bspSurface_t *surf, in cv->verts[ i ].lightColor = Color::Adapt( verts[ i ].color ); + if ( tr.worldLinearizeLightMap ) + { + cv->verts[ i ].lightColor.ConvertFromSRGB(); + } if ( tr.legacyOverBrightClamping ) { @@ -4155,6 +4159,12 @@ void R_LoadLightGrid( lump_t *l ) { ambientColor[ j ] = tmpAmbient[ j ] * ( 1.0f / 255.0f ); directedColor[ j ] = tmpDirected[ j ] * ( 1.0f / 255.0f ); + + if ( tr.worldLinearizeLightMap ) + { + ambientColor[ j ] = convertFromSRGB( ambientColor[ j ] ); + directedColor[ j ] = convertFromSRGB( directedColor[ j ] ); + } } // standard spherical coordinates to cartesian coordinates conversion @@ -4425,6 +4435,73 @@ void R_LoadEntities( lump_t *l, std::string &externalEntities ) tr.worldDeluxeMapping = glConfig2.deluxeMapping; } + bool sRGBtex = false; + bool sRGBcolor = false; + bool sRGBlight = false; + + s = strstr( value, "-sRGB" ); + + if ( s && ( s[5] == ' ' || s[5] == '\0' ) ) + { + sRGBtex = true; + sRGBcolor = true; + sRGBlight = true; + } + + s = strstr( value, "-nosRGB" ); + + if ( s && ( s[5] == ' ' || s[5] == '\0' ) ) + { + sRGBtex = false; + sRGBcolor = false; + sRGBlight = true; + } + + if ( strstr( value, "-sRGBlight" ) ) + { + sRGBlight = true; + } + + if ( strstr( value, "-nosRGBlight" ) ) + { + sRGBlight = false; + } + + if ( strstr( value, "-sRGBcolor" ) ) + { + sRGBcolor = true; + } + + if ( strstr( value, "-nosRGBcolor" ) ) + { + sRGBcolor = false; + } + + if ( strstr( value, "-sRGBtex" ) ) + { + sRGBtex = true; + } + + if ( strstr( value, "-nosRGBtex" ) ) + { + sRGBtex = false; + } + + if ( sRGBlight ) + { + Log::Debug("map features lights in sRGB colorspace" ); + tr.worldLinearizeLightMap = true; + } + + if ( sRGBcolor && sRGBtex ) + { + Log::Debug("map features lights computed with linear colors and textures" ); + tr.worldLinearizeTexture = true; + /* The forceLegacyMapOverBrightClamping is only compatible and purposed + with legacy maps without color linearization. */ + tr. legacyOverBrightClamping= false; + } + continue; } @@ -5096,6 +5173,62 @@ void RE_LoadWorldMap( const char *name ) // tr.worldDeluxeMapping will be set by R_LoadEntities() tr.worldDeluxeMapping = false; tr.worldHDR_RGBE = false; + tr.worldLinearizeTexture = false; + tr.worldLinearizeLightMap = false; + + /* These are the values expected by the renderer, 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 = 1; + tr.mapOverBrightBits = 2; + tr.identityLight = 1.0f / ( 1 << tr.overbrightBits ); + + Wolfenstein: Enemy Territory values: + + tr.overbrightBits = 0; + tr.mapOverBrightBits = 2; + tr.identityLight = 1.0f / ( 1 << tr.overbrightBits ); + + Games like Quake 3 and Tremulous require tr.mapOverBrightBits + to be set to 2. Because this engine is primarily maintained for + Unvanquished and needs to keep compatibility with legacy Tremulous + maps, this value is set to 2. + + Games like True Combat: Elite (Wolf:ET mod) or Urban Terror 4 + (Quake 3 mod) require tr.mapOverBrightBits to be set to 0. + + The mapOverBrightBits value will be read as map entity key + by R_LoadEntities(), making possible to override the default + value and properly render a map with another value than the + default one. + + If this key is missing in map entity lump, there is no way + to know the required value for mapOverBrightBits when loading + a BSP, one may rely on arena files to do some guessing when + loading foreign maps and games ported to the Dæmon engine may + 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 maps by multiplying the lights by 2.5 and set + the mapOverBrightBits key to 0 in map entities lump. + + 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, so we entirely removed it. */ + + tr.mapOverBrightBits = r_overbrightDefaultExponent.Get(); + tr.legacyOverBrightClamping = r_overbrightDefaultClamp.Get(); s_worldData = {}; Q_strncpyz( s_worldData.name, name, sizeof( s_worldData.name ) ); diff --git a/src/engine/renderer/tr_image.cpp b/src/engine/renderer/tr_image.cpp index 381265293a..5a2b5952ce 100644 --- a/src/engine/renderer/tr_image.cpp +++ b/src/engine/renderer/tr_image.cpp @@ -3054,68 +3054,6 @@ void R_InitImages() tr.lightmaps.reserve( 128 ); tr.deluxemaps.reserve( 128 ); - /* 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 ); - - Games like Quake 3 and Tremulous require tr.mapOverBrightBits - to be set to 2. Because this engine is primarily maintained for - Unvanquished and needs to keep compatibility with legacy Tremulous - maps, this value is set to 2. - - Games like True Combat: Elite (Wolf:ET mod) or Urban Terror 4 - (Quake 3 mod) require tr.mapOverBrightBits to be set to 0. - - For some reasons the Wolf:ET engine sets this to 0 by default - but the game sets it to 2 (and requires it to be 2 for maps - looking as expected). - - The mapOverBrightBits value will be read as map entity key - by R_LoadEntities() in tr_bsp, making possible to override - the default value and properly render a map with another - value than the default one. - - If this key is missing in map entity lump, there is no way - to know the required value for mapOverBrightBits when loading - a BSP, one may rely on arena files to do some guessing when - loading foreign maps and games ported to the Dæmon engine may - 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. */ - - tr.mapOverBrightBits = r_overbrightDefaultExponent.Get(); - tr.legacyOverBrightClamping = r_overbrightDefaultClamp.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 2ece318382..c70e16a013 100644 --- a/src/engine/renderer/tr_init.cpp +++ b/src/engine/renderer/tr_init.cpp @@ -202,6 +202,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA cvar_t *r_rimExponent; Cvar::Cvar r_highPrecisionRendering("r_highPrecisionRendering", "use high precision frame buffers for rendering and blending", Cvar::NONE, true); + Cvar::Cvar r_cheapSRGB("r_cheapSRGB", "use cheap sRGB computation when converting images", Cvar::NONE, false); cvar_t *r_gamma; cvar_t *r_lockpvs; @@ -1288,6 +1289,7 @@ ScreenshotCmd screenshotPNGRegistration("screenshotPNG", ssFormat_t::SSF_PNG, "p AssertCvarRange( r_rimExponent, 0.5, 8.0, false ); Cvar::Latch( r_highPrecisionRendering ); + Cvar::Latch( r_cheapSRGB ); r_drawBuffer = Cvar_Get( "r_drawBuffer", "GL_BACK", CVAR_CHEAT ); r_lockpvs = Cvar_Get( "r_lockpvs", "0", CVAR_CHEAT ); diff --git a/src/engine/renderer/tr_local.h b/src/engine/renderer/tr_local.h index b581166672..1203fba16f 100644 --- a/src/engine/renderer/tr_local.h +++ b/src/engine/renderer/tr_local.h @@ -1123,6 +1123,7 @@ enum class shaderProfilerRenderSubGroupsMode { bool dpMaterial; + int linearizeTexture; // Core renderer (code path for when only OpenGL Core is available, or compatible OpenGL 2). stageRenderer_t colorRenderer; @@ -1206,6 +1207,7 @@ enum class shaderProfilerRenderSubGroupsMode { expression_t deformMagnitudeExp; + bool specularSRGB; bool noFog; // used only for shaders that have fog disabled, so we can enable it for individual stages bool useMaterialSystem = false; @@ -2730,6 +2732,8 @@ enum class shaderProfilerRenderSubGroupsMode { bool worldLightMapping; bool worldDeluxeMapping; bool worldHDR_RGBE; + bool worldLinearizeTexture; + bool worldLinearizeLightMap; lightMode_t lightMode; lightMode_t worldLight; @@ -3022,6 +3026,7 @@ enum class shaderProfilerRenderSubGroupsMode { extern cvar_t *r_rimExponent; extern Cvar::Cvar r_highPrecisionRendering; + extern Cvar::Cvar r_cheapSRGB; extern cvar_t *r_logFile; // number of frames to emit GL logs diff --git a/src/engine/renderer/tr_scene.cpp b/src/engine/renderer/tr_scene.cpp index aa02565b4a..d30f939221 100644 --- a/src/engine/renderer/tr_scene.cpp +++ b/src/engine/renderer/tr_scene.cpp @@ -334,6 +334,14 @@ void RE_AddDynamicLightToSceneET( const vec3_t org, float radius, float intensit light->l.color[ 1 ] = g; light->l.color[ 2 ] = b; + // Linearize dynamic lights. + if ( tr.worldLinearizeTexture ) + { + light->l.color[ 0 ] = convertFromSRGB( light->l.color[ 0 ] ); + light->l.color[ 1 ] = convertFromSRGB( light->l.color[ 1 ] ); + light->l.color[ 2 ] = convertFromSRGB( light->l.color[ 2 ] ); + } + light->l.inverseShadows = (flags & REF_INVERSE_DLIGHT) != 0; light->l.noShadows = !r_realtimeLightingCastShadows->integer && !light->l.inverseShadows; diff --git a/src/engine/renderer/tr_shade.cpp b/src/engine/renderer/tr_shade.cpp index 4f1ee75a40..56d8539985 100644 --- a/src/engine/renderer/tr_shade.cpp +++ b/src/engine/renderer/tr_shade.cpp @@ -861,6 +861,9 @@ void Render_generic3D( shaderStage_t *pStage ) gl_genericShader->SetUniform_ModelMatrix( backEnd.orientation.transformMatrix ); gl_genericShader->SetUniform_ModelViewProjectionMatrix( glState.modelViewProjectionMatrix[ glState.stackIndex ] ); + // u_LinearizeTexture + gl_genericShader->SetUniform_LinearizeTexture( pStage->linearizeTexture ); + // u_Bones if ( glConfig2.vboVertexSkinningAvailable && tess.vboVertexSkinning ) { @@ -1034,6 +1037,9 @@ void Render_lightMapping( shaderStage_t *pStage ) gl_lightMappingShader->SetUniform_ModelViewProjectionMatrix( glState.modelViewProjectionMatrix[ glState.stackIndex ] ); + // u_LinearizeTexture + gl_lightMappingShader->SetUniform_LinearizeTexture( pStage->linearizeTexture ); + if ( glConfig2.realtimeLighting && r_realtimeLightingRenderer.Get() == Util::ordinal( realtimeLightingRenderer_t::TILED ) ) { @@ -1961,6 +1967,9 @@ void Render_skybox( shaderStage_t *pStage ) GL_BindToTMU( 0, pStage->bundle[TB_COLORMAP].image[0] ) ); + // u_LinearizeTexture + gl_skyboxShader->SetUniform_LinearizeTexture( pStage->linearizeTexture ); + // u_AlphaThreshold gl_skyboxShader->SetUniform_AlphaTest( GLS_ATEST_NONE ); @@ -2269,6 +2278,9 @@ void Render_fog( shaderStage_t* pStage ) gl_fogQuake3Shader->BindProgram( 0 ); + // u_LinearizeTexture + gl_fogQuake3Shader->SetUniform_LinearizeTexture( tr.worldLinearizeTexture ); + gl_fogQuake3Shader->SetUniform_FogDistanceVector( fogDistanceVector ); gl_fogQuake3Shader->SetUniform_FogDepthVector( fogDepthVector ); gl_fogQuake3Shader->SetUniform_FogEyeT( eyeT ); @@ -2423,6 +2435,11 @@ void Tess_ComputeColor( shaderStage_t *pStage ) { rgb = Math::Clamp( RB_EvalExpression( &pStage->rgbExp, 1.0 ), 0.0f, 1.0f ); + if ( tr.worldLinearizeTexture ) + { + rgb = convertFromSRGB ( rgb ); + } + tess.svars.color = Color::White * rgb; break; } @@ -2451,6 +2468,13 @@ void Tess_ComputeColor( shaderStage_t *pStage ) blue = Math::Clamp( RB_EvalExpression( &pStage->blueExp, 1.0 ), 0.0f, 1.0f ); } + if ( tr.worldLinearizeTexture ) + { + red = convertFromSRGB ( red ); + green = convertFromSRGB ( green ); + blue = convertFromSRGB ( blue ); + } + tess.svars.color.SetRed( red ); tess.svars.color.SetGreen( green ); tess.svars.color.SetBlue( blue ); diff --git a/src/engine/renderer/tr_shader.cpp b/src/engine/renderer/tr_shader.cpp index 4f6ea8e370..ed27e35053 100644 --- a/src/engine/renderer/tr_shader.cpp +++ b/src/engine/renderer/tr_shader.cpp @@ -3233,6 +3233,10 @@ static bool ParseStage( shaderStage_t *stage, const char **text ) { ParseExpression( text, &stage->deformMagnitudeExp ); } + else if ( !Q_stricmp( token, "specularSRGB" ) ) + { + stage->specularSRGB = true; + } else { Log::Warn("unknown shader stage parameter '%s' in shader '%s'", token, shader.name ); @@ -4757,6 +4761,13 @@ static bool ParseShader( const char *_text ) return true; } +static int packLinearizeTexture( bool linearizeColorMap, bool linearizeMaterialMap, bool linearizeLightMap ) +{ + return linearizeColorMap << 0 + | linearizeLightMap << 1 + | linearizeMaterialMap << 2; +} + /* ======================================================================================== @@ -5299,6 +5310,33 @@ static void FinishStages() stage->hasHeightMapInNormalMap = stage->hasHeightMapInNormalMap && ( stage->enableNormalMapping || stage->enableReliefMapping ); + switch ( stage->type ) + { + case stageType_t::ST_COLORMAP: + case stageType_t::ST_COLLAPSE_COLORMAP: + case stageType_t::ST_SKYBOXMAP: + stage->linearizeTexture = tr.worldLinearizeTexture; + break; + case stageType_t::ST_STYLELIGHTMAP: + case stageType_t::ST_STYLECOLORMAP: + stage->linearizeTexture = tr.worldLinearizeLightMap; + break; + case stageType_t::ST_LIGHTMAP: + stage->linearizeTexture = packLinearizeTexture( false, false, tr.worldLinearizeLightMap ); + break; + case stageType_t::ST_DIFFUSEMAP: + case stageType_t::ST_COLLAPSE_DIFFUSEMAP: + stage->linearizeTexture = packLinearizeTexture( + tr.worldLinearizeTexture, + hasMaterialMap + && stage->collapseType == collapseType_t::COLLAPSE_PHONG + && stage->specularSRGB, + tr.worldLinearizeLightMap ); + break; + default: + break; + } + // Bind fallback textures if required. if ( !stage->enableNormalMapping && !( stage->enableReliefMapping && stage->hasHeightMapInNormalMap) ) { diff --git a/src/engine/renderer/tr_sky.cpp b/src/engine/renderer/tr_sky.cpp index de1c39eb4f..29960ee9c2 100644 --- a/src/engine/renderer/tr_sky.cpp +++ b/src/engine/renderer/tr_sky.cpp @@ -102,6 +102,9 @@ void Tess_StageIteratorSky() gl_skyboxShader->SetUniform_ModelViewProjectionMatrix( glState.modelViewProjectionMatrix[glState.stackIndex] ); + // u_LinearizeTexture + gl_skyboxShader->SetUniform_LinearizeTexture( tr.worldLinearizeTexture ); + gl_skyboxShader->SetRequiredVertexPointers(); // draw the outer skybox @@ -161,6 +164,9 @@ void Tess_StageIteratorSky() // u_AlphaThreshold gl_skyboxShader->SetUniform_AlphaTest( pStage->stateBits ); + // u_LinearizeTexture + gl_skyboxShader->SetUniform_LinearizeTexture( tr.worldLinearizeTexture ); + GL_State( pStage->stateBits ); switch ( pStage->type ) {