From 2f31cf315c1b29525be12699c3cf0cd5a9d791cc Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 9 Apr 2026 11:58:51 +0500 Subject: [PATCH] ref: gl: add litwater support --- ref/gl/gl_local.h | 3 + ref/gl/gl_opengl.c | 7 ++ ref/gl/gl_rmain.c | 8 ++ ref/gl/gl_rsurf.c | 208 +++++++++++++++++++++++++++++++++++++++------ 4 files changed, 198 insertions(+), 28 deletions(-) diff --git a/ref/gl/gl_local.h b/ref/gl/gl_local.h index f99a105b..95138b6d 100644 --- a/ref/gl/gl_local.h +++ b/ref/gl/gl_local.h @@ -720,6 +720,9 @@ extern convar_t gl_msaa; extern convar_t gl_stencilbits; extern convar_t gl_overbright; extern convar_t gl_fog; +extern convar_t gl_litwater_force; +extern convar_t gl_litwater_minlight; +extern convar_t gl_litwater_scale; extern convar_t r_lighting_ambient; extern convar_t r_studio_lambert; diff --git a/ref/gl/gl_opengl.c b/ref/gl/gl_opengl.c index 0df07725..0588f825 100644 --- a/ref/gl/gl_opengl.c +++ b/ref/gl/gl_opengl.c @@ -22,6 +22,10 @@ CVAR_DEFINE_AUTO( gl_msaa, "1", FCVAR_GLCONFIG, "enable or disable multisample a CVAR_DEFINE_AUTO( gl_stencilbits, "8", FCVAR_GLCONFIG|FCVAR_READ_ONLY, "pixelformat stencil bits (0 - auto)" ); CVAR_DEFINE_AUTO( gl_overbright, "1", FCVAR_GLCONFIG, "overbrights" ); CVAR_DEFINE_AUTO( gl_fog, "1", FCVAR_GLCONFIG, "allow for rendering fog using built-in OpenGL fog implementation" ); +CVAR_DEFINE_AUTO( gl_litwater_force, "0", FCVAR_GLCONFIG, "force enable lightmapped water, even if support not declared in the map" ); +CVAR_DEFINE_AUTO( gl_litwater_minlight, "0", FCVAR_GLCONFIG, "minimal light water receives, helps avoid too dark lightmapped water" ); +CVAR_DEFINE_AUTO( gl_litwater_scale, "3", FCVAR_GLCONFIG, "lightmapped water scale factor" ); +CVAR_DEFINE_AUTO( r_lighting_extended, "1", FCVAR_GLCONFIG, "allow to get lighting from world and bmodels" ); CVAR_DEFINE_AUTO( r_lighting_ambient, "0.3", FCVAR_GLCONFIG, "map ambient lighting scale" ); CVAR_DEFINE_AUTO( r_detailtextures, "1", FCVAR_GLCONFIG, "enable detail textures support" ); CVAR_DEFINE_AUTO( r_novis, "0", 0, "ignore vis information (perfomance test)" ); @@ -1183,6 +1187,9 @@ static void GL_InitCommands( void ) gEngfuncs.Cvar_RegisterVariable( &gl_round_down ); gEngfuncs.Cvar_RegisterVariable( &gl_overbright ); gEngfuncs.Cvar_RegisterVariable( &gl_fog ); + gEngfuncs.Cvar_RegisterVariable( &gl_litwater_force ); + gEngfuncs.Cvar_RegisterVariable( &gl_litwater_minlight ); + gEngfuncs.Cvar_RegisterVariable( &gl_litwater_scale ); gEngfuncs.Cvar_RegisterVariable( &gl_polyoffset ); gEngfuncs.Cvar_RegisterVariable( &gl_polyoffset_bmodels ); diff --git a/ref/gl/gl_rmain.c b/ref/gl/gl_rmain.c index 05684861..b5108c3a 100644 --- a/ref/gl/gl_rmain.c +++ b/ref/gl/gl_rmain.c @@ -1035,6 +1035,14 @@ static void R_CheckCvars( void ) rebuild = true; } + // we only recalculate lightmap on the fly if map hasn't declared support for lightmapped water + if( !FBitSet( tr.world->flags, FWORLD_HAS_LITWATER ) && FBitSet( gl_litwater_scale.flags|gl_litwater_minlight.flags, FCVAR_CHANGED )) + { + rebuild = true; + ClearBits( gl_litwater_scale.flags, FCVAR_CHANGED ); + ClearBits( gl_litwater_minlight.flags, FCVAR_CHANGED ); + } + if( rebuild ) R_GammaChanged( false ); } diff --git a/ref/gl/gl_rsurf.c b/ref/gl/gl_rsurf.c index 38c4a053..8750c466 100644 --- a/ref/gl/gl_rsurf.c +++ b/ref/gl/gl_rsurf.c @@ -76,6 +76,42 @@ static inline qboolean R_SeparatePassActive( const separate_pass_t *sp ) return sp->last >= 0 ? true : false; } +static qboolean Mod_HaveLightmappedWater( void ) +{ + // if this flag set, it's level designer's job to ensure water has been properly lit + if( FBitSet( tr.world->flags, FWORLD_HAS_LITWATER )) + return true; + + // otherwise, check user preference, as some maps have lightmapped water + return gl_litwater_force.value ? true : false; +} + +static int Mod_LightmappedWaterMinlight( void ) +{ + if( FBitSet( tr.world->flags, FWORLD_HAS_LITWATER )) + { + if( tr.world->litwater_minlight >= 0 ) + return tr.world->litwater_minlight; + + return 0; + } + + return Q_max( (int)gl_litwater_minlight.value, 0 ); +} + +static float Mod_LightmappedWaterScale( void ) +{ + if( FBitSet( tr.world->flags, FWORLD_HAS_LITWATER )) + { + if( tr.world->litwater_scale >= 0.0f ) + return tr.world->litwater_scale; + + return 1.0f; + } + + return gl_litwater_scale.value; +} + byte *Mod_GetCurrentVis( void ) { if( gEngfuncs.drawFuncs->Mod_GetCurrentVis && tr.fCustomRendering ) @@ -252,12 +288,29 @@ static void SubdividePolygon_r( model_t *loadmodel, msurface_t *warpface, int nu { VectorCopy( verts, poly->verts[i] ); R_TextureCoord( verts, warpface, &poly->verts[i][3] ); + // lightmap texcoords for subdivided surfaces will be calculated later + } +} - // for speed reasons - if( !FBitSet( warpface->flags, SURF_DRAWTURB )) +static void GL_BuildLightmapWater( model_t *mod, msurface_t *fa ) +{ + float sample_size; + glpoly2_t *poly; + + if( !mod || !fa->texinfo || !fa->texinfo->texture ) + return; // bad polygon? + + sample_size = gEngfuncs.Mod_SampleSizeForFace( fa ); + + for( poly = fa->polys; poly; poly = poly->next ) + { + int i; + + for( i = 0; i < poly->numverts; i++ ) { - // lightmap texture coordinates - R_LightmapCoord( verts, warpface, sample_size, &poly->verts[i][5] ); + vec3_t vec; + VectorCopy( poly->verts[i], vec ); + R_LightmapCoord( vec, fa, sample_size, &poly->verts[i][5] ); } } } @@ -703,6 +756,8 @@ static void R_BuildLightMap( const msurface_t *surf, byte *dest, int stride, qbo const mextrasurf_t *info = surf->info; int lightscale; + const int litwater_minlight = Mod_LightmappedWaterMinlight(); + const float litwater_scale = Mod_LightmappedWaterScale(); const int sample_size = gEngfuncs.Mod_SampleSizeForFace( surf ); const int smax = ( info->lightextents[0] / sample_size ) + 1; const int tmax = ( info->lightextents[1] / sample_size ) + 1; @@ -752,6 +807,14 @@ static void R_BuildLightMap( const msurface_t *surf, byte *dest, int stride, qbo { int t = bl[i] * lightscale >> 14; + // amp up water lightmap to avoid too dark water + // when the it wasn't properly lit by the level designer + if( FBitSet( surf->flags, SURF_DRAWTURB )) + { + float ft = t * litwater_scale; + t = Q_max( Q_rint( ft ), litwater_minlight ); + } + if( t > 1023 ) t = 1023; @@ -977,6 +1040,58 @@ static void EmitWaterPolys( msurface_t *warp, qboolean reverse, qboolean ripples GL_SetupFogColorForSurfaces(); } +/* +================ +EmitWaterLightPolys + +Render water lightmaps +================ +*/ +static void EmitWaterLightPolys( msurface_t *warp, float soffset, float toffset, qboolean dynamic ) +{ + const qboolean useQuads = FBitSet( warp->flags, SURF_DRAWTURB_QUADS ); + glpoly2_t *p; + float waveHeight = RI.currententity->curstate.scale; + + // set the current waveheight + if( warp->polys->verts[0][2] >= RI.vieworg[2] ) + waveHeight = -waveHeight; + + if( useQuads ) + pglBegin( GL_QUADS ); + + for( p = warp->polys; p; p = p->next ) + { + float *v = p->verts[0]; + int i; + + if( !useQuads ) + pglBegin( GL_POLYGON ); + + for( i = 0; i < p->numverts; i++, v += VERTEXSIZE ) + { + if( !dynamic ) pglTexCoord2f( v[5], v[6] ); + else pglTexCoord2f( v[5] - soffset, v[6] - toffset ); + + if( waveHeight ) + { + float nv = r_turbsin[(int)(gp_cl->time * 160.0f + v[1] + v[0]) & 255] + 8.0f; + nv = (r_turbsin[(int)(v[0] * 5.0f + gp_cl->time * 171.0f - v[1]) & 255] + 8.0f ) * 0.8f + nv; + nv = nv * waveHeight + v[2]; + + pglVertex3f( v[0], v[1], nv ); + } + else pglVertex3fv( v ); + } + + if( !useQuads ) + pglEnd (); + } + + if( useQuads ) + pglEnd(); +} + /* ================ DrawGLPolyChain @@ -984,10 +1099,16 @@ DrawGLPolyChain Render lightmaps ================ */ -static void DrawGLPolyChain( glpoly2_t *p, float soffset, float toffset ) +static void DrawGLPolyChain( glpoly2_t *p, float soffset, float toffset, msurface_t *surf ) { const qboolean dynamic = soffset != 0.0f || toffset != 0.0f; + if( FBitSet( surf->flags, SURF_DRAWTURB )) + { + EmitWaterLightPolys( surf, soffset, toffset, dynamic ); + return; + } + for( ; p != NULL; p = p->chain ) { float *v; @@ -1020,6 +1141,8 @@ static qboolean R_HasLightmap( void ) switch( RI.currententity->curstate.rendermode ) { case kRenderTransTexture: + return FBitSet( RI.currentmodel->flags, MODEL_LIQUID ) ? true : false; + case kRenderTransColor: case kRenderTransAdd: case kRenderGlow: @@ -1049,21 +1172,29 @@ static void R_BlendLightmaps( void ) pglEnable( GL_BLEND ); else pglDisable( GL_BLEND ); - // lightmapped solid surfaces - pglDepthMask( GL_FALSE ); - pglDepthFunc( GL_EQUAL ); - pglDisable( GL_ALPHA_TEST ); - if( gl_overbright.value ) + + if( RI.currententity->curstate.rendermode == kRenderTransTexture ) { - pglBlendFunc( GL_DST_COLOR, GL_SRC_COLOR ); - if(!( R_HasEnabledVBO() && !r_vbo_overbrightmode.value )) - pglColor4f( 128.0f / 192.0f, 128.0f / 192.0f, 128.0f / 192.0f, 1.0f ); + pglBlendFunc( GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA ); } else { - pglBlendFunc( GL_ZERO, GL_SRC_COLOR ); + // lightmapped solid surfaces + pglDepthMask( GL_FALSE ); + pglDepthFunc( GL_EQUAL ); + if( gl_overbright.value ) + { + pglBlendFunc( GL_DST_COLOR, GL_SRC_COLOR ); + if(!( R_HasEnabledVBO() && !r_vbo_overbrightmode.value )) + pglColor4f( 128.0f / 192.0f, 128.0f / 192.0f, 128.0f / 192.0f, 1.0f ); + } + else + { + pglBlendFunc( GL_ZERO, GL_SRC_COLOR ); + } } + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); // render static lightmaps first @@ -1075,7 +1206,7 @@ static void R_BlendLightmaps( void ) for( surf = gl_lms.lightmap_surfaces[i]; surf != NULL; surf = surf->info->lightmapchain ) { - if( surf->polys ) DrawGLPolyChain( surf->polys, 0.0f, 0.0f ); + DrawGLPolyChain( surf->polys, 0.0f, 0.0f, surf ); } } } @@ -1116,12 +1247,10 @@ static void R_BlendLightmaps( void ) // draw all surfaces that use this lightmap for( drawsurf = newsurf; drawsurf != surf; drawsurf = drawsurf->info->lightmapchain ) { - if( drawsurf->polys ) - { - DrawGLPolyChain( drawsurf->polys, + DrawGLPolyChain( drawsurf->polys, ( drawsurf->light_s - drawsurf->info->dlight_s ) * ( 1.0f / (float)BLOCK_SIZE ), - ( drawsurf->light_t - drawsurf->info->dlight_t ) * ( 1.0f / (float)BLOCK_SIZE )); - } + ( drawsurf->light_t - drawsurf->info->dlight_t ) * ( 1.0f / (float)BLOCK_SIZE ), + drawsurf ); } newsurf = drawsurf; @@ -1145,12 +1274,10 @@ static void R_BlendLightmaps( void ) for( surf = newsurf; surf != NULL; surf = surf->info->lightmapchain ) { - if( surf->polys ) - { - DrawGLPolyChain( surf->polys, + DrawGLPolyChain( surf->polys, ( surf->light_s - surf->info->dlight_s ) * ( 1.0f / (float)BLOCK_SIZE ), - ( surf->light_t - surf->info->dlight_t ) * ( 1.0f / (float)BLOCK_SIZE )); - } + ( surf->light_t - surf->info->dlight_t ) * ( 1.0f / (float)BLOCK_SIZE ), + surf ); } } @@ -1418,8 +1545,13 @@ static void R_RenderBrushPoly( msurface_t *fa, int cull_type ) if( FBitSet( fa->flags, SURF_DRAWTURB )) { - // warp texture, no lightmaps + // warp texture EmitWaterPolys( fa, cull_type == CULL_BACKSIDE, R_UploadRipples( t )); + + // add lightmaps if requested + if( Mod_HaveLightmappedWater( )) + R_RenderLightmapForSurface( fa ); + return; } else GL_Bind( XASH_TEXTURE0, t->gl_texturenum ); @@ -1600,11 +1732,20 @@ void R_DrawWaterSurfaces( void ) continue; for( ; s; s = s->texturechain ) + { EmitWaterPolys( s, false, R_UploadRipples( t )); + if( Mod_HaveLightmappedWater( )) + R_RenderLightmapForSurface( s ); + } + t->texturechain = NULL; } +// litwater: not sure if needed + GL_ResetFogColor(); + R_BlendLightmaps(); + R_ResetSeparatePass( &draw_wateralpha ); pglDisable( GL_BLEND ); @@ -1717,8 +1858,16 @@ static int R_SortBrushModelSurfaces( cl_entity_t *e, model_t *clmodel, vec3_t mi if( cull_type == CULL_BACKSIDE ) { - if( !FBitSet( psurf->flags, SURF_DRAWTURB ) && !( psurf->pdecals && e->curstate.rendermode == kRenderTransTexture )) - continue; + if( FBitSet( psurf->flags, SURF_DRAWTURB )) + { + if( Mod_HaveLightmappedWater( )) + continue; // we don't want back faces when drawing lightmaps to avoid Z fighting + } + else + { + if( !( psurf->pdecals && e->curstate.rendermode == kRenderTransTexture )) + continue; + } } if( num_sorted < gpGlobals->max_surfaces ) @@ -3917,7 +4066,10 @@ void GL_BuildLightmaps( void ) GL_CreateSurfaceLightmap( m->surfaces + j, m ); if( m->surfaces[j].flags & SURF_DRAWTURB ) + { + GL_BuildLightmapWater( m, m->surfaces + j ); continue; + } nColinElim += GL_BuildPolygonFromSurface( m, m->surfaces + j ); }