/* =========================================================================== Doom 3 GPL Source Code Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code"). Doom 3 Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Doom 3 Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Doom 3 Source Code. If not, see . In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ #include "sys/platform.h" #include "renderer/VertexCache.h" #include "renderer/tr_local.h" extern idCVar r_useCarmacksReverse; extern idCVar r_useStencilOpSeparate; /* ===================== RB_BakeTextureMatrixIntoTexgen ===================== */ void RB_BakeTextureMatrixIntoTexgen( idPlane lightProject[3], const float *textureMatrix ) { float genMatrix[16]; float final[16]; genMatrix[0] = lightProject[0][0]; genMatrix[4] = lightProject[0][1]; genMatrix[8] = lightProject[0][2]; genMatrix[12] = lightProject[0][3]; genMatrix[1] = lightProject[1][0]; genMatrix[5] = lightProject[1][1]; genMatrix[9] = lightProject[1][2]; genMatrix[13] = lightProject[1][3]; genMatrix[2] = 0; genMatrix[6] = 0; genMatrix[10] = 0; genMatrix[14] = 0; genMatrix[3] = lightProject[2][0]; genMatrix[7] = lightProject[2][1]; genMatrix[11] = lightProject[2][2]; genMatrix[15] = lightProject[2][3]; myGlMultMatrix( genMatrix, backEnd.lightTextureMatrix, final ); lightProject[0][0] = final[0]; lightProject[0][1] = final[4]; lightProject[0][2] = final[8]; lightProject[0][3] = final[12]; lightProject[1][0] = final[1]; lightProject[1][1] = final[5]; lightProject[1][2] = final[9]; lightProject[1][3] = final[13]; } /* ================ RB_PrepareStageTexturing ================ */ void RB_PrepareStageTexturing( const shaderStage_t *pStage, const drawSurf_t *surf, idDrawVert *ac ) { // set privatePolygonOffset if necessary if ( pStage->privatePolygonOffset ) { qglEnable( GL_POLYGON_OFFSET_FILL ); qglPolygonOffset( r_offsetFactor.GetFloat(), r_offsetUnits.GetFloat() * pStage->privatePolygonOffset ); } // set the texture matrix if needed if ( pStage->texture.hasMatrix ) { RB_LoadShaderTextureMatrix( surf->shaderRegisters, &pStage->texture ); } // texgens if ( pStage->texture.texgen == TG_DIFFUSE_CUBE ) { qglTexCoordPointer( 3, GL_FLOAT, sizeof( idDrawVert ), ac->normal.ToFloatPtr() ); } if ( pStage->texture.texgen == TG_SKYBOX_CUBE || pStage->texture.texgen == TG_WOBBLESKY_CUBE ) { qglTexCoordPointer( 3, GL_FLOAT, 0, vertexCache.Position( surf->dynamicTexCoords ) ); } if ( pStage->texture.texgen == TG_SCREEN ) { qglEnable( GL_TEXTURE_GEN_S ); qglEnable( GL_TEXTURE_GEN_T ); qglEnable( GL_TEXTURE_GEN_Q ); float mat[16], plane[4]; myGlMultMatrix( surf->space->modelViewMatrix, backEnd.viewDef->projectionMatrix, mat ); plane[0] = mat[0]; plane[1] = mat[4]; plane[2] = mat[8]; plane[3] = mat[12]; qglTexGenfv( GL_S, GL_OBJECT_PLANE, plane ); plane[0] = mat[1]; plane[1] = mat[5]; plane[2] = mat[9]; plane[3] = mat[13]; qglTexGenfv( GL_T, GL_OBJECT_PLANE, plane ); plane[0] = mat[3]; plane[1] = mat[7]; plane[2] = mat[11]; plane[3] = mat[15]; qglTexGenfv( GL_Q, GL_OBJECT_PLANE, plane ); } if ( pStage->texture.texgen == TG_SCREEN2 ) { qglEnable( GL_TEXTURE_GEN_S ); qglEnable( GL_TEXTURE_GEN_T ); qglEnable( GL_TEXTURE_GEN_Q ); float mat[16], plane[4]; myGlMultMatrix( surf->space->modelViewMatrix, backEnd.viewDef->projectionMatrix, mat ); plane[0] = mat[0]; plane[1] = mat[4]; plane[2] = mat[8]; plane[3] = mat[12]; qglTexGenfv( GL_S, GL_OBJECT_PLANE, plane ); plane[0] = mat[1]; plane[1] = mat[5]; plane[2] = mat[9]; plane[3] = mat[13]; qglTexGenfv( GL_T, GL_OBJECT_PLANE, plane ); plane[0] = mat[3]; plane[1] = mat[7]; plane[2] = mat[11]; plane[3] = mat[15]; qglTexGenfv( GL_Q, GL_OBJECT_PLANE, plane ); } if ( pStage->texture.texgen == TG_GLASSWARP ) { if ( tr.backEndRenderer == BE_ARB2 /*|| tr.backEndRenderer == BE_NV30*/ ) { qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, FPROG_GLASSWARP ); qglEnable( GL_FRAGMENT_PROGRAM_ARB ); GL_SelectTexture( 2 ); globalImages->scratchImage->Bind(); GL_SelectTexture( 1 ); globalImages->scratchImage2->Bind(); qglEnable( GL_TEXTURE_GEN_S ); qglEnable( GL_TEXTURE_GEN_T ); qglEnable( GL_TEXTURE_GEN_Q ); float mat[16], plane[4]; myGlMultMatrix( surf->space->modelViewMatrix, backEnd.viewDef->projectionMatrix, mat ); plane[0] = mat[0]; plane[1] = mat[4]; plane[2] = mat[8]; plane[3] = mat[12]; qglTexGenfv( GL_S, GL_OBJECT_PLANE, plane ); plane[0] = mat[1]; plane[1] = mat[5]; plane[2] = mat[9]; plane[3] = mat[13]; qglTexGenfv( GL_T, GL_OBJECT_PLANE, plane ); plane[0] = mat[3]; plane[1] = mat[7]; plane[2] = mat[11]; plane[3] = mat[15]; qglTexGenfv( GL_Q, GL_OBJECT_PLANE, plane ); GL_SelectTexture( 0 ); } } if ( pStage->texture.texgen == TG_REFLECT_CUBE ) { if ( tr.backEndRenderer == BE_ARB2 ) { // see if there is also a bump map specified const shaderStage_t *bumpStage = surf->material->GetBumpStage(); if ( bumpStage ) { // per-pixel reflection mapping with bump mapping GL_SelectTexture( 1 ); bumpStage->texture.image->Bind(); GL_SelectTexture( 0 ); qglNormalPointer( GL_FLOAT, sizeof( idDrawVert ), ac->normal.ToFloatPtr() ); qglVertexAttribPointerARB( 10, 3, GL_FLOAT, false, sizeof( idDrawVert ), ac->tangents[1].ToFloatPtr() ); qglVertexAttribPointerARB( 9, 3, GL_FLOAT, false, sizeof( idDrawVert ), ac->tangents[0].ToFloatPtr() ); qglEnableVertexAttribArrayARB( 9 ); qglEnableVertexAttribArrayARB( 10 ); qglEnableClientState( GL_NORMAL_ARRAY ); // Program env 5, 6, 7, 8 have been set in RB_SetProgramEnvironmentSpace qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, FPROG_BUMPY_ENVIRONMENT ); qglEnable( GL_FRAGMENT_PROGRAM_ARB ); qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, VPROG_BUMPY_ENVIRONMENT ); qglEnable( GL_VERTEX_PROGRAM_ARB ); } else { // per-pixel reflection mapping without a normal map qglNormalPointer( GL_FLOAT, sizeof( idDrawVert ), ac->normal.ToFloatPtr() ); qglEnableClientState( GL_NORMAL_ARRAY ); qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, FPROG_ENVIRONMENT ); qglEnable( GL_FRAGMENT_PROGRAM_ARB ); qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, VPROG_ENVIRONMENT ); qglEnable( GL_VERTEX_PROGRAM_ARB ); } } else { qglEnable( GL_TEXTURE_GEN_S ); qglEnable( GL_TEXTURE_GEN_T ); qglEnable( GL_TEXTURE_GEN_R ); qglTexGenf( GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP_EXT ); qglTexGenf( GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP_EXT ); qglTexGenf( GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP_EXT ); qglEnableClientState( GL_NORMAL_ARRAY ); qglNormalPointer( GL_FLOAT, sizeof( idDrawVert ), ac->normal.ToFloatPtr() ); qglMatrixMode( GL_TEXTURE ); float mat[16]; R_TransposeGLMatrix( backEnd.viewDef->worldSpace.modelViewMatrix, mat ); qglLoadMatrixf( mat ); qglMatrixMode( GL_MODELVIEW ); } } } /* ================ RB_FinishStageTexturing ================ */ void RB_FinishStageTexturing( const shaderStage_t *pStage, const drawSurf_t *surf, idDrawVert *ac ) { // unset privatePolygonOffset if necessary if ( pStage->privatePolygonOffset && !surf->material->TestMaterialFlag(MF_POLYGONOFFSET) ) { qglDisable( GL_POLYGON_OFFSET_FILL ); } if ( pStage->texture.texgen == TG_DIFFUSE_CUBE || pStage->texture.texgen == TG_SKYBOX_CUBE || pStage->texture.texgen == TG_WOBBLESKY_CUBE ) { qglTexCoordPointer( 2, GL_FLOAT, sizeof( idDrawVert ), (void *)&ac->st ); } if ( pStage->texture.texgen == TG_SCREEN ) { qglDisable( GL_TEXTURE_GEN_S ); qglDisable( GL_TEXTURE_GEN_T ); qglDisable( GL_TEXTURE_GEN_Q ); } if ( pStage->texture.texgen == TG_SCREEN2 ) { qglDisable( GL_TEXTURE_GEN_S ); qglDisable( GL_TEXTURE_GEN_T ); qglDisable( GL_TEXTURE_GEN_Q ); } if ( pStage->texture.texgen == TG_GLASSWARP ) { if ( tr.backEndRenderer == BE_ARB2 /*|| tr.backEndRenderer == BE_NV30*/ ) { GL_SelectTexture( 2 ); globalImages->BindNull(); GL_SelectTexture( 1 ); if ( pStage->texture.hasMatrix ) { RB_LoadShaderTextureMatrix( surf->shaderRegisters, &pStage->texture ); } qglDisable( GL_TEXTURE_GEN_S ); qglDisable( GL_TEXTURE_GEN_T ); qglDisable( GL_TEXTURE_GEN_Q ); qglDisable( GL_FRAGMENT_PROGRAM_ARB ); globalImages->BindNull(); GL_SelectTexture( 0 ); } } if ( pStage->texture.texgen == TG_REFLECT_CUBE ) { if ( tr.backEndRenderer == BE_ARB2 ) { // see if there is also a bump map specified const shaderStage_t *bumpStage = surf->material->GetBumpStage(); if ( bumpStage ) { // per-pixel reflection mapping with bump mapping GL_SelectTexture( 1 ); globalImages->BindNull(); GL_SelectTexture( 0 ); qglDisableVertexAttribArrayARB( 9 ); qglDisableVertexAttribArrayARB( 10 ); } else { // per-pixel reflection mapping without bump mapping } qglDisableClientState( GL_NORMAL_ARRAY ); qglDisable( GL_FRAGMENT_PROGRAM_ARB ); qglDisable( GL_VERTEX_PROGRAM_ARB ); // Fixme: Hack to get around an apparent bug in ATI drivers. Should remove as soon as it gets fixed. qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, 0 ); // FIXME ... } else { qglDisable( GL_TEXTURE_GEN_S ); qglDisable( GL_TEXTURE_GEN_T ); qglDisable( GL_TEXTURE_GEN_R ); qglTexGenf( GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR ); qglTexGenf( GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR ); qglTexGenf( GL_R, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR ); qglDisableClientState( GL_NORMAL_ARRAY ); qglMatrixMode( GL_TEXTURE ); qglLoadIdentity(); qglMatrixMode( GL_MODELVIEW ); } } if ( pStage->texture.hasMatrix ) { qglMatrixMode( GL_TEXTURE ); qglLoadIdentity(); qglMatrixMode( GL_MODELVIEW ); } } /* ============================================================================================= FILL DEPTH BUFFER ============================================================================================= */ /* ================== RB_T_FillDepthBuffer ================== */ void RB_T_FillDepthBuffer( const drawSurf_t *surf ) { int stage; const idMaterial *shader; const shaderStage_t *pStage; const float *regs; float color[4]; const srfTriangles_t *tri; tri = surf->geo; shader = surf->material; // update the clip plane if needed if ( backEnd.viewDef->numClipPlanes && surf->space != backEnd.currentSpace ) { GL_SelectTexture( 1 ); idPlane plane; R_GlobalPlaneToLocal( surf->space->modelMatrix, backEnd.viewDef->clipPlanes[0], plane ); plane[3] += 0.5; // the notch is in the middle qglTexGenfv( GL_S, GL_OBJECT_PLANE, plane.ToFloatPtr() ); GL_SelectTexture( 0 ); } if ( !shader->IsDrawn() ) { return; } // some deforms may disable themselves by setting numIndexes = 0 if ( !tri->numIndexes ) { return; } // translucent surfaces don't put anything in the depth buffer and don't // test against it, which makes them fail the mirror clip plane operation if ( shader->Coverage() == MC_TRANSLUCENT ) { return; } if ( !tri->ambientCache ) { common->Printf( "RB_T_FillDepthBuffer: !tri->ambientCache\n" ); return; } // get the expressions for conditionals / color / texcoords regs = surf->shaderRegisters; // if all stages of a material have been conditioned off, don't do anything for ( stage = 0; stage < shader->GetNumStages() ; stage++ ) { pStage = shader->GetStage(stage); // check the stage enable condition if ( regs[ pStage->conditionRegister ] != 0 ) { break; } } if ( stage == shader->GetNumStages() ) { return; } // set polygon offset if necessary if ( shader->TestMaterialFlag(MF_POLYGONOFFSET) ) { qglEnable( GL_POLYGON_OFFSET_FILL ); qglPolygonOffset( r_offsetFactor.GetFloat(), r_offsetUnits.GetFloat() * shader->GetPolygonOffset() ); } // subviews will just down-modulate the color buffer by overbright if ( shader->GetSort() == SS_SUBVIEW ) { GL_State( GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO | GLS_DEPTHFUNC_LESS ); color[0] = color[1] = color[2] = ( 1.0 / backEnd.overBright ); color[3] = 1; } else { // others just draw black color[0] = 0; color[1] = 0; color[2] = 0; color[3] = 1; } idDrawVert *ac = (idDrawVert *)vertexCache.Position( tri->ambientCache ); qglVertexPointer( 3, GL_FLOAT, sizeof( idDrawVert ), ac->xyz.ToFloatPtr() ); qglTexCoordPointer( 2, GL_FLOAT, sizeof( idDrawVert ), reinterpret_cast(&ac->st) ); bool drawSolid = false; if ( shader->Coverage() == MC_OPAQUE ) { drawSolid = true; } // we may have multiple alpha tested stages if ( shader->Coverage() == MC_PERFORATED ) { // if the only alpha tested stages are condition register omitted, // draw a normal opaque surface bool didDraw = false; qglEnable( GL_ALPHA_TEST ); // perforated surfaces may have multiple alpha tested stages for ( stage = 0; stage < shader->GetNumStages() ; stage++ ) { pStage = shader->GetStage(stage); if ( !pStage->hasAlphaTest ) { continue; } // check the stage enable condition if ( regs[ pStage->conditionRegister ] == 0 ) { continue; } // if we at least tried to draw an alpha tested stage, // we won't draw the opaque surface didDraw = true; // set the alpha modulate color[3] = regs[ pStage->color.registers[3] ]; // skip the entire stage if alpha would be black if ( color[3] <= 0 ) { continue; } qglColor4fv( color ); qglAlphaFunc( GL_GREATER, regs[ pStage->alphaTestRegister ] ); // bind the texture pStage->texture.image->Bind(); // set texture matrix and texGens RB_PrepareStageTexturing( pStage, surf, ac ); // draw it RB_DrawElementsWithCounters( tri ); RB_FinishStageTexturing( pStage, surf, ac ); } qglDisable( GL_ALPHA_TEST ); if ( !didDraw ) { drawSolid = true; } } // draw the entire surface solid if ( drawSolid ) { qglColor4fv( color ); globalImages->whiteImage->Bind(); // draw it RB_DrawElementsWithCounters( tri ); } // reset polygon offset if ( shader->TestMaterialFlag(MF_POLYGONOFFSET) ) { qglDisable( GL_POLYGON_OFFSET_FILL ); } // reset blending if ( shader->GetSort() == SS_SUBVIEW ) { GL_State( GLS_DEPTHFUNC_LESS ); } } void RB_SetProgramEnvironment( bool isPostProcess ); // so RB_STD_FillDepthBuffer() can use it /* ===================== RB_STD_FillDepthBuffer If we are rendering a subview with a near clip plane, use a second texture to force the alpha test to fail when behind that clip plane ===================== */ void RB_STD_FillDepthBuffer( drawSurf_t **drawSurfs, int numDrawSurfs ) { // if we are just doing 2D rendering, no need to fill the depth buffer if ( !backEnd.viewDef->viewEntitys ) { return; } // enable the second texture for mirror plane clipping if needed if ( backEnd.viewDef->numClipPlanes ) { GL_SelectTexture( 1 ); globalImages->alphaNotchImage->Bind(); qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); qglEnable( GL_TEXTURE_GEN_S ); qglTexCoord2f( 1, 0.5 ); } // the first texture will be used for alpha tested surfaces GL_SelectTexture( 0 ); qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); // decal surfaces may enable polygon offset qglPolygonOffset( r_offsetFactor.GetFloat(), r_offsetUnits.GetFloat() ); GL_State( GLS_DEPTHFUNC_LESS ); // Enable stencil test if we are going to be using it for shadows. // If we didn't do this, it would be legal behavior to get z fighting // from the ambient pass and the light passes. qglEnable( GL_STENCIL_TEST ); qglStencilFunc( GL_ALWAYS, 1, 255 ); RB_RenderDrawSurfListWithFunction( drawSurfs, numDrawSurfs, RB_T_FillDepthBuffer ); // Make the early depth pass available to shaders. #3877 bool getDepthCapture = r_enableDepthCapture.GetInteger() == 1 || (r_enableDepthCapture.GetInteger() == -1 && r_useSoftParticles.GetBool()); if ( getDepthCapture && backEnd.viewDef->renderView.viewID >= 0 ) // Suppress for lightgem rendering passes { globalImages->currentDepthImage->CopyDepthbuffer( backEnd.viewDef->viewport.x1, backEnd.viewDef->viewport.y1, backEnd.viewDef->viewport.x2 - backEnd.viewDef->viewport.x1 + 1, backEnd.viewDef->viewport.y2 - backEnd.viewDef->viewport.y1 + 1, true ); bool isPostProcess = false; RB_SetProgramEnvironment( isPostProcess ); } if ( backEnd.viewDef->numClipPlanes ) { GL_SelectTexture( 1 ); globalImages->BindNull(); qglDisable( GL_TEXTURE_GEN_S ); GL_SelectTexture( 0 ); } } /* ============================================================================================= SHADER PASSES ============================================================================================= */ /* ================== RB_SetProgramEnvironment Sets variables that can be used by all vertex programs [SteveL #3877] Note on the use of fragment program environmental variables. Parameters 0 and 1 are set here to allow conversion of screen coordinates to texture coordinates, for use when sampling _currentRender. Those same parameters 0 and 1, plus 2 and 3, are given entirely different meanings in draw_arb2.cpp while light interactions are being drawn. This function is called again before currentRender size is needed by post processing effects are done, so there's no clash. Only parameters 0..3 were in use before #3877 - and in dhewm3 also 4, for gamma in shader. Now I've used a new parameter 22 for the size of _currentDepth. It's needed throughout, including by light interactions, and its size might in theory differ from _currentRender. Parameters 23 and 24 are used by soft particles #3878. Note these can be freely reused by different draw calls. ================== */ void RB_SetProgramEnvironment( bool isPostProcess ) { float parm[4]; int pot; if ( !glConfig.ARBVertexProgramAvailable ) { return; } #if 0 // screen power of two correction factor, one pixel in so we don't get a bilerp // of an uncopied pixel int w = backEnd.viewDef->viewport.x2 - backEnd.viewDef->viewport.x1 + 1; pot = globalImages->currentRenderImage->uploadWidth; if ( w == pot ) { parm[0] = 1.0; } else { parm[0] = (float)(w-1) / pot; } int h = backEnd.viewDef->viewport.y2 - backEnd.viewDef->viewport.y1 + 1; pot = globalImages->currentRenderImage->uploadHeight; if ( h == pot ) { parm[1] = 1.0; } else { parm[1] = (float)(h-1) / pot; } parm[2] = 0; parm[3] = 1; qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 0, parm ); #else // screen power of two correction factor, assuming the copy to _currentRender // also copied an extra row and column for the bilerp int w = backEnd.viewDef->viewport.x2 - backEnd.viewDef->viewport.x1 + 1; pot = globalImages->currentRenderImage->uploadWidth; parm[0] = (float)w / pot; int h = backEnd.viewDef->viewport.y2 - backEnd.viewDef->viewport.y1 + 1; pot = globalImages->currentRenderImage->uploadHeight; parm[1] = (float)h / pot; parm[2] = 0; parm[3] = 1; qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 0, parm ); #endif qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 0, parm ); // window coord to 0.0 to 1.0 conversion parm[0] = 1.0 / w; parm[1] = 1.0 / h; parm[2] = 0; parm[3] = 1; qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 1, parm ); // DG: brightness and gamma in shader as program.env[4] if ( r_gammaInShader.GetBool() ) { // program.env[4].xyz are all r_brightness, program.env[4].w is 1.0/r_gamma if ( !isPostProcess ) { parm[0] = parm[1] = parm[2] = r_brightness.GetFloat(); parm[3] = 1.0/r_gamma.GetFloat(); // 1.0/gamma so the shader doesn't have to do this calculation } else { // don't apply gamma/brightness in postprocess passes to avoid applying them twice // (setting them to 1.0 makes them no-ops) parm[0] = parm[1] = parm[2] = parm[3] = 1.0f; } qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, PP_GAMMA_BRIGHTNESS, parm ); } // #3877: Allow shaders to access depth buffer. // Two useful ratios are packed into this parm: [0] and [1] hold the x,y multipliers you need to map a screen // coordinate (fragment position) to the depth image: those are simply the reciprocal of the depth // image size, which has been rounded up to a power of two. Slots [3] and [4] hold the ratio of the depth image // size to the current render image size. These sizes can differ if the game crops the render viewport temporarily // during post-processing effects. The depth render is smaller during the effect too, but the depth image doesn't // need to be downsized, whereas the current render image does get downsized when it's captured by the game after // the skybox render pass. The ratio is needed to map between the two render images. parm[0] = 1.0f / globalImages->currentDepthImage->uploadWidth; parm[1] = 1.0f / globalImages->currentDepthImage->uploadHeight; parm[2] = static_cast(globalImages->currentRenderImage->uploadWidth) / globalImages->currentDepthImage->uploadWidth; parm[3] = static_cast(globalImages->currentRenderImage->uploadHeight) / globalImages->currentDepthImage->uploadHeight; qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, PP_CURDEPTH_RECIPR, parm ); // // set eye position in global space // parm[0] = backEnd.viewDef->renderView.vieworg[0]; parm[1] = backEnd.viewDef->renderView.vieworg[1]; parm[2] = backEnd.viewDef->renderView.vieworg[2]; parm[3] = 1.0; qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 1, parm ); } /* ================== RB_SetProgramEnvironmentSpace Sets variables related to the current space that can be used by all vertex programs ================== */ void RB_SetProgramEnvironmentSpace( void ) { if ( !glConfig.ARBVertexProgramAvailable ) { return; } const struct viewEntity_s *space = backEnd.currentSpace; float parm[4]; // set eye position in local space R_GlobalPointToLocal( space->modelMatrix, backEnd.viewDef->renderView.vieworg, *(idVec3 *)parm ); parm[3] = 1.0; qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 5, parm ); // we need the model matrix without it being combined with the view matrix // so we can transform local vectors to global coordinates parm[0] = space->modelMatrix[0]; parm[1] = space->modelMatrix[4]; parm[2] = space->modelMatrix[8]; parm[3] = space->modelMatrix[12]; qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 6, parm ); parm[0] = space->modelMatrix[1]; parm[1] = space->modelMatrix[5]; parm[2] = space->modelMatrix[9]; parm[3] = space->modelMatrix[13]; qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 7, parm ); parm[0] = space->modelMatrix[2]; parm[1] = space->modelMatrix[6]; parm[2] = space->modelMatrix[10]; parm[3] = space->modelMatrix[14]; qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 8, parm ); } /* ================== RB_STD_T_RenderShaderPasses This is also called for the generated 2D rendering ================== */ void RB_STD_T_RenderShaderPasses( const drawSurf_t *surf ) { int stage; const idMaterial *shader; const shaderStage_t *pStage; const float *regs; float color[4]; const srfTriangles_t *tri; tri = surf->geo; shader = surf->material; if ( !shader->HasAmbient() ) { return; } if ( shader->IsPortalSky() ) { return; } // change the matrix if needed if ( surf->space != backEnd.currentSpace ) { qglLoadMatrixf( surf->space->modelViewMatrix ); backEnd.currentSpace = surf->space; RB_SetProgramEnvironmentSpace(); } // change the scissor if needed if ( r_useScissor.GetBool() && !backEnd.currentScissor.Equals( surf->scissorRect ) ) { backEnd.currentScissor = surf->scissorRect; qglScissor( backEnd.viewDef->viewport.x1 + backEnd.currentScissor.x1, backEnd.viewDef->viewport.y1 + backEnd.currentScissor.y1, backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1, backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1 ); } // some deforms may disable themselves by setting numIndexes = 0 if ( !tri->numIndexes ) { return; } if ( !tri->ambientCache ) { common->Printf( "RB_T_RenderShaderPasses: !tri->ambientCache\n" ); return; } // check whether we're drawing a soft particle surface #3878 const bool soft_particle = ( surf->dsFlags & DSF_SOFT_PARTICLE ) != 0; // get the expressions for conditionals / color / texcoords regs = surf->shaderRegisters; // set face culling appropriately GL_Cull( shader->GetCullType() ); // set polygon offset if necessary if ( shader->TestMaterialFlag(MF_POLYGONOFFSET) ) { qglEnable( GL_POLYGON_OFFSET_FILL ); qglPolygonOffset( r_offsetFactor.GetFloat(), r_offsetUnits.GetFloat() * shader->GetPolygonOffset() ); } if ( surf->space->weaponDepthHack ) { RB_EnterWeaponDepthHack(); } if ( surf->space->modelDepthHack != 0.0f && !soft_particle ) // #3878 soft particles don't want modelDepthHack, which is { // an older way to slightly "soften" particles RB_EnterModelDepthHack( surf->space->modelDepthHack ); } idDrawVert *ac = (idDrawVert *)vertexCache.Position( tri->ambientCache ); qglVertexPointer( 3, GL_FLOAT, sizeof( idDrawVert ), ac->xyz.ToFloatPtr() ); qglTexCoordPointer( 2, GL_FLOAT, sizeof( idDrawVert ), reinterpret_cast(&ac->st) ); for ( stage = 0; stage < shader->GetNumStages() ; stage++ ) { pStage = shader->GetStage(stage); // check the enable condition if ( regs[ pStage->conditionRegister ] == 0 ) { continue; } // skip the stages involved in lighting if ( pStage->lighting != SL_AMBIENT ) { continue; } // skip if the stage is ( GL_ZERO, GL_ONE ), which is used for some alpha masks if ( ( pStage->drawStateBits & (GLS_SRCBLEND_BITS|GLS_DSTBLEND_BITS) ) == ( GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE ) ) { continue; } // determine the blend mode (used by soft particles #3878) const int src_blend = pStage->drawStateBits & GLS_SRCBLEND_BITS; // see if we are a new-style stage newShaderStage_t *newStage = pStage->newStage; if ( newStage ) { //-------------------------- // // new style stages // //-------------------------- // completely skip the stage if we don't have the capability if ( tr.backEndRenderer != BE_ARB2 ) { continue; } if ( r_skipNewAmbient.GetBool() ) { continue; } qglColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( idDrawVert ), (void *)&ac->color ); qglVertexAttribPointerARB( 9, 3, GL_FLOAT, false, sizeof( idDrawVert ), ac->tangents[0].ToFloatPtr() ); qglVertexAttribPointerARB( 10, 3, GL_FLOAT, false, sizeof( idDrawVert ), ac->tangents[1].ToFloatPtr() ); qglNormalPointer( GL_FLOAT, sizeof( idDrawVert ), ac->normal.ToFloatPtr() ); qglEnableClientState( GL_COLOR_ARRAY ); qglEnableVertexAttribArrayARB( 9 ); qglEnableVertexAttribArrayARB( 10 ); qglEnableClientState( GL_NORMAL_ARRAY ); GL_State( pStage->drawStateBits ); qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, newStage->vertexProgram ); qglEnable( GL_VERTEX_PROGRAM_ARB ); // megaTextures bind a lot of images and set a lot of parameters if ( newStage->megaTexture ) { newStage->megaTexture->SetMappingForSurface( tri ); idVec3 localViewer; R_GlobalPointToLocal( surf->space->modelMatrix, backEnd.viewDef->renderView.vieworg, localViewer ); newStage->megaTexture->BindForViewOrigin( localViewer ); } for ( int i = 0 ; i < newStage->numVertexParms ; i++ ) { float parm[4]; parm[0] = regs[ newStage->vertexParms[i][0] ]; parm[1] = regs[ newStage->vertexParms[i][1] ]; parm[2] = regs[ newStage->vertexParms[i][2] ]; parm[3] = regs[ newStage->vertexParms[i][3] ]; qglProgramLocalParameter4fvARB( GL_VERTEX_PROGRAM_ARB, i, parm ); } for ( int i = 0 ; i < newStage->numFragmentProgramImages ; i++ ) { if ( newStage->fragmentProgramImages[i] ) { GL_SelectTexture( i ); newStage->fragmentProgramImages[i]->Bind(); } } qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, newStage->fragmentProgram ); qglEnable( GL_FRAGMENT_PROGRAM_ARB ); // draw it RB_DrawElementsWithCounters( tri ); for ( int i = 1 ; i < newStage->numFragmentProgramImages ; i++ ) { if ( newStage->fragmentProgramImages[i] ) { GL_SelectTexture( i ); globalImages->BindNull(); } } if ( newStage->megaTexture ) { newStage->megaTexture->Unbind(); } GL_SelectTexture( 0 ); qglDisable( GL_VERTEX_PROGRAM_ARB ); qglDisable( GL_FRAGMENT_PROGRAM_ARB ); // Fixme: Hack to get around an apparent bug in ATI drivers. Should remove as soon as it gets fixed. qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, 0 ); // FIXME: ... qglDisableClientState( GL_COLOR_ARRAY ); qglDisableVertexAttribArrayARB( 9 ); qglDisableVertexAttribArrayARB( 10 ); qglDisableClientState( GL_NORMAL_ARRAY ); continue; } else if ( soft_particle && surf->particle_radius > 0.0f && ( src_blend == GLS_SRCBLEND_ONE || src_blend == GLS_SRCBLEND_SRC_ALPHA ) && tr.backEndRenderer == BE_ARB2 && !r_skipNewAmbient.GetBool() ) { // SteveL #3878. Particles are automatically softened by the engine, unless they have shader programs of // their own (i.e. are "newstages" handled above). This section comes after the newstage part so that if a // designer has specified their own shader programs, those will be used instead of the soft particle program. if ( pStage->vertexColor == SVC_IGNORE ) { // Ignoring vertexColor is not recommended for particles. The particle system uses vertexColor for fading. // However, there are existing particle effects that don't use it, in which case we default to using the // rgb color modulation specified in the material like the "old stages" do below. color[0] = regs[pStage->color.registers[0]]; color[1] = regs[pStage->color.registers[1]]; color[2] = regs[pStage->color.registers[2]]; color[3] = regs[pStage->color.registers[3]]; qglColor4fv( color ); } else { // A properly set-up particle shader qglColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( idDrawVert ), (void *)&ac->color ); qglEnableClientState( GL_COLOR_ARRAY ); } #if 0 // debug stuff: render particles opaque so debug colors written in the shader are properly visible int dsbits = pStage->drawStateBits | GLS_DEPTHFUNC_ALWAYS; dsbits &= ~(GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS); //dsbits |= GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO; both values are 0, so this would be a noop GL_State( dsbits ); #endif GL_State( pStage->drawStateBits | GLS_DEPTHFUNC_ALWAYS ); // Disable depth clipping. The fragment program will // handle it to allow overdraw. qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, VPROG_SOFT_PARTICLE ); qglEnable( GL_VERTEX_PROGRAM_ARB ); // Bind image and _currentDepth GL_SelectTexture( 0 ); pStage->texture.image->Bind(); GL_SelectTexture( 1 ); globalImages->currentDepthImage->Bind(); qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, FPROG_SOFT_PARTICLE ); qglEnable( GL_FRAGMENT_PROGRAM_ARB ); #if 0 // debug stuff // Set up parameters for fragment program const char* srcblendstr = "???"; if ( src_blend >= 0 && src_blend <= 9 ) { const char* blendModes[] = { "ONE", "ZERO", "!! INVALID !!", "DST_COLOR", "ONE_MINUS_DST_COLOR", "SRC_ALPHA", "ONE_MINUS_SRC_ALPHA", "DST_ALPHA", "ONE_MINUS_DST_ALPHA", "ALPHA_SATURATE" }; srcblendstr = blendModes[src_blend]; } int dst_blend = pStage->drawStateBits & GLS_DSTBLEND_BITS; const char* dstblend = "???"; switch ( dst_blend ) { #define MY_CASE(X) case GLS_DSTBLEND_ ##X : dstblend = #X; break; MY_CASE(ZERO) MY_CASE(ONE) MY_CASE(SRC_COLOR) MY_CASE(ONE_MINUS_SRC_COLOR) MY_CASE(SRC_ALPHA) MY_CASE(ONE_MINUS_SRC_ALPHA) MY_CASE(DST_ALPHA) MY_CASE(ONE_MINUS_DST_ALPHA) #undef MY_CASE } printf("XX mat: %s, src_blend = %s dest_blend = %s radius = %g\n", shader->GetName(), srcblendstr, dstblend, surf->particle_radius); #endif // DG: some particle materials (at least the muzzle flash in dentonmod) set the // texture matrix (with scroll, translate, scale, centerScale, shear or rotate). // Support that like in R_SetDrawInteraction() (the soft particle shader only // uses one texture, so I set the diffuse matrix for it) idVec4 texMatrix[2]; if ( pStage->texture.hasMatrix ) { texMatrix[0][0] = regs[pStage->texture.matrix[0][0]]; texMatrix[0][1] = regs[pStage->texture.matrix[0][1]]; texMatrix[0][2] = 0; texMatrix[0][3] = regs[pStage->texture.matrix[0][2]]; texMatrix[1][0] = regs[pStage->texture.matrix[1][0]]; texMatrix[1][1] = regs[pStage->texture.matrix[1][1]]; texMatrix[1][2] = 0; texMatrix[1][3] = regs[pStage->texture.matrix[1][2]]; // we attempt to keep scrolls from generating incredibly large texture values, but // center rotations and center scales can still generate offsets that need to be > 1 if ( texMatrix[0][3] < -40 || texMatrix[0][3] > 40 ) { texMatrix[0][3] -= (int)texMatrix[0][3]; } if ( texMatrix[1][3] < -40 || texMatrix[1][3] > 40 ) { texMatrix[1][3] -= (int)texMatrix[1][3]; } } else { texMatrix[0].Set( 1, 0, 0, 0 ); texMatrix[1].Set( 0, 1, 0, 0 ); } qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_DIFFUSE_MATRIX_S, texMatrix[0].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_DIFFUSE_MATRIX_T, texMatrix[1].ToFloatPtr() ); // program.env[23] is the particle radius, given as { radius, 1/(faderange), 1/radius } float fadeRange = 1.0f; // fadeRange is the particle diameter for alpha blends (like smoke), but the particle radius for additive // blends (light glares), because additive effects work differently. Fog is half as apparent when a wall // is in the middle of it. Light glares lose no visibility when they have something to reflect off. See // The Dark Mod issue #3878 for diagram if ( src_blend == GLS_SRCBLEND_SRC_ALPHA ) // an alpha blend material { fadeRange = surf->particle_radius * 2.0f; } else if ( src_blend == GLS_SRCBLEND_ONE ) // an additive (blend add) material { fadeRange = surf->particle_radius; } float parm[4] = { surf->particle_radius, 1.0f / ( fadeRange ), 1.0f / surf->particle_radius, 0.0f }; qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, PP_PARTICLE_RADIUS, parm ); // program.env[24] is the color channel mask. It gets added to the fade multiplier, so adding 1 // to a channel will make sure it doesn't get faded at all. Particles with additive blend // need their RGB channels modifying to blend them out. Particles with an alpha blend need // their alpha channel modifying. if ( src_blend == GLS_SRCBLEND_SRC_ALPHA ) // an alpha blend material { parm[0] = parm[1] = parm[2] = 1.0f; // Leave the rgb channels at full strength when fading parm[3] = 0.0f; // but fade the alpha channel } else if ( src_blend == GLS_SRCBLEND_ONE ) // an additive (blend add) material { parm[0] = parm[1] = parm[2] = 0.0f; // Fade the rgb channels but parm[3] = 1.0f; // leave the alpha channel at full strength } qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, PP_PARTICLE_COLCHAN_MASK, parm ); // draw it RB_DrawElementsWithCounters( tri ); // Clean up GL state GL_SelectTexture( 1 ); globalImages->BindNull(); GL_SelectTexture( 0 ); globalImages->BindNull(); qglDisable( GL_VERTEX_PROGRAM_ARB ); qglDisable( GL_FRAGMENT_PROGRAM_ARB ); if ( pStage->vertexColor != SVC_IGNORE ) { qglDisableClientState( GL_COLOR_ARRAY ); } continue; } //-------------------------- // // old style stages // //-------------------------- // set the color color[0] = regs[ pStage->color.registers[0] ]; color[1] = regs[ pStage->color.registers[1] ]; color[2] = regs[ pStage->color.registers[2] ]; color[3] = regs[ pStage->color.registers[3] ]; // skip the entire stage if an add would be black if ( ( pStage->drawStateBits & (GLS_SRCBLEND_BITS|GLS_DSTBLEND_BITS) ) == ( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ) && color[0] <= 0 && color[1] <= 0 && color[2] <= 0 ) { continue; } // skip the entire stage if a blend would be completely transparent if ( ( pStage->drawStateBits & (GLS_SRCBLEND_BITS|GLS_DSTBLEND_BITS) ) == ( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ) && color[3] <= 0 ) { continue; } // select the vertex color source if ( pStage->vertexColor == SVC_IGNORE ) { qglColor4fv( color ); } else { qglColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( idDrawVert ), (void *)&ac->color ); qglEnableClientState( GL_COLOR_ARRAY ); if ( pStage->vertexColor == SVC_INVERSE_MODULATE ) { GL_TexEnv( GL_COMBINE_ARB ); qglTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE ); qglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE ); qglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PRIMARY_COLOR_ARB ); qglTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR ); qglTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_ONE_MINUS_SRC_COLOR ); qglTexEnvi( GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 1 ); } // for vertex color and modulated color, we need to enable a second // texture stage if ( color[0] != 1 || color[1] != 1 || color[2] != 1 || color[3] != 1 ) { GL_SelectTexture( 1 ); globalImages->whiteImage->Bind(); GL_TexEnv( GL_COMBINE_ARB ); qglTexEnvfv( GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, color ); qglTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE ); qglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB ); qglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_CONSTANT_ARB ); qglTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR ); qglTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR ); qglTexEnvi( GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 1 ); qglTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_MODULATE ); qglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS_ARB ); qglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_ARB, GL_CONSTANT_ARB ); qglTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA ); qglTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_ALPHA_ARB, GL_SRC_ALPHA ); qglTexEnvi( GL_TEXTURE_ENV, GL_ALPHA_SCALE, 1 ); GL_SelectTexture( 0 ); } } // bind the texture RB_BindVariableStageImage( &pStage->texture, regs ); // set the state GL_State( pStage->drawStateBits ); RB_PrepareStageTexturing( pStage, surf, ac ); // draw it RB_DrawElementsWithCounters( tri ); RB_FinishStageTexturing( pStage, surf, ac ); if ( pStage->vertexColor != SVC_IGNORE ) { qglDisableClientState( GL_COLOR_ARRAY ); GL_SelectTexture( 1 ); GL_TexEnv( GL_MODULATE ); globalImages->BindNull(); GL_SelectTexture( 0 ); GL_TexEnv( GL_MODULATE ); } } // reset polygon offset if ( shader->TestMaterialFlag(MF_POLYGONOFFSET) ) { qglDisable( GL_POLYGON_OFFSET_FILL ); } if ( surf->space->weaponDepthHack || ( !soft_particle && surf->space->modelDepthHack != 0.0f ) ) // #3878 soft particles { RB_LeaveDepthHack(); } } /* ===================== RB_STD_DrawShaderPasses Draw non-light dependent passes ===================== */ int RB_STD_DrawShaderPasses( drawSurf_t **drawSurfs, int numDrawSurfs ) { int i; // only obey skipAmbient if we are rendering a view if ( backEnd.viewDef->viewEntitys && r_skipAmbient.GetBool() ) { return numDrawSurfs; } bool isPostProcess = false; // if we are about to draw the first surface that needs // the rendering in a texture, copy it over if ( drawSurfs[0]->material->GetSort() >= SS_POST_PROCESS ) { if ( r_skipPostProcess.GetBool() ) { return 0; } isPostProcess = true; // only dump if in a 3d view if ( backEnd.viewDef->viewEntitys && tr.backEndRenderer == BE_ARB2 ) { globalImages->currentRenderImage->CopyFramebuffer( backEnd.viewDef->viewport.x1, backEnd.viewDef->viewport.y1, backEnd.viewDef->viewport.x2 - backEnd.viewDef->viewport.x1 + 1, backEnd.viewDef->viewport.y2 - backEnd.viewDef->viewport.y1 + 1, true ); } backEnd.currentRenderCopied = true; } GL_SelectTexture( 1 ); globalImages->BindNull(); GL_SelectTexture( 0 ); qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); RB_SetProgramEnvironment( isPostProcess ); // we don't use RB_RenderDrawSurfListWithFunction() // because we want to defer the matrix load because many // surfaces won't draw any ambient passes backEnd.currentSpace = NULL; for (i = 0 ; i < numDrawSurfs ; i++ ) { if ( drawSurfs[i]->material->SuppressInSubview() ) { continue; } if ( backEnd.viewDef->isXraySubview && drawSurfs[i]->space->entityDef ) { if ( drawSurfs[i]->space->entityDef->parms.xrayIndex != 2 ) { continue; } } // we need to draw the post process shaders after we have drawn the fog lights if ( drawSurfs[i]->material->GetSort() >= SS_POST_PROCESS && !backEnd.currentRenderCopied ) { break; } RB_STD_T_RenderShaderPasses( drawSurfs[i] ); } GL_Cull( CT_FRONT_SIDED ); qglColor3f( 1, 1, 1 ); return i; } /* ============================================================================== BACK END RENDERING OF STENCIL SHADOWS ============================================================================== */ /* ===================== RB_T_Shadow the shadow volumes face INSIDE ===================== */ static void RB_T_Shadow( const drawSurf_t *surf ) { const srfTriangles_t *tri; // set the light position if we are using a vertex program to project the rear surfaces if ( tr.backEndRendererHasVertexPrograms && r_useShadowVertexProgram.GetBool() && surf->space != backEnd.currentSpace ) { idVec4 localLight; R_GlobalPointToLocal( surf->space->modelMatrix, backEnd.vLight->globalLightOrigin, localLight.ToVec3() ); localLight.w = 0.0f; qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_ORIGIN, localLight.ToFloatPtr() ); } tri = surf->geo; if ( !tri->shadowCache ) { return; } qglVertexPointer( 4, GL_FLOAT, sizeof( shadowCache_t ), vertexCache.Position(tri->shadowCache) ); // we always draw the sil planes, but we may not need to draw the front or rear caps int numIndexes; bool external = false; if ( !r_useExternalShadows.GetInteger() ) { numIndexes = tri->numIndexes; } else if ( r_useExternalShadows.GetInteger() == 2 ) { // force to no caps for testing numIndexes = tri->numShadowIndexesNoCaps; } else if ( !(surf->dsFlags & DSF_VIEW_INSIDE_SHADOW) ) { // if we aren't inside the shadow projection, no caps are ever needed needed numIndexes = tri->numShadowIndexesNoCaps; external = true; } else if ( !backEnd.vLight->viewInsideLight && !(surf->geo->shadowCapPlaneBits & SHADOW_CAP_INFINITE) ) { // if we are inside the shadow projection, but outside the light, and drawing // a non-infinite shadow, we can skip some caps if ( backEnd.vLight->viewSeesShadowPlaneBits & surf->geo->shadowCapPlaneBits ) { // we can see through a rear cap, so we need to draw it, but we can skip the // caps on the actual surface numIndexes = tri->numShadowIndexesNoFrontCaps; } else { // we don't need to draw any caps numIndexes = tri->numShadowIndexesNoCaps; } external = true; } else { // must draw everything numIndexes = tri->numIndexes; } // set depth bounds if( glConfig.depthBoundsTestAvailable && r_useDepthBoundsTest.GetBool() ) { qglDepthBoundsEXT( surf->scissorRect.zmin, surf->scissorRect.zmax ); } // debug visualization if ( r_showShadows.GetInteger() ) { if ( r_showShadows.GetInteger() == 3 ) { if ( external ) { qglColor3f( 0.1/backEnd.overBright, 1/backEnd.overBright, 0.1/backEnd.overBright ); } else { // these are the surfaces that require the reverse qglColor3f( 1/backEnd.overBright, 0.1/backEnd.overBright, 0.1/backEnd.overBright ); } } else { // draw different color for turboshadows if ( surf->geo->shadowCapPlaneBits & SHADOW_CAP_INFINITE ) { if ( numIndexes == tri->numIndexes ) { qglColor3f( 1/backEnd.overBright, 0.1/backEnd.overBright, 0.1/backEnd.overBright ); } else { qglColor3f( 1/backEnd.overBright, 0.4/backEnd.overBright, 0.1/backEnd.overBright ); } } else { if ( numIndexes == tri->numIndexes ) { qglColor3f( 0.1/backEnd.overBright, 1/backEnd.overBright, 0.1/backEnd.overBright ); } else if ( numIndexes == tri->numShadowIndexesNoFrontCaps ) { qglColor3f( 0.1/backEnd.overBright, 1/backEnd.overBright, 0.6/backEnd.overBright ); } else { qglColor3f( 0.6/backEnd.overBright, 1/backEnd.overBright, 0.1/backEnd.overBright ); } } } qglStencilOp( GL_KEEP, GL_KEEP, GL_KEEP ); qglDisable( GL_STENCIL_TEST ); GL_Cull( CT_TWO_SIDED ); RB_DrawShadowElementsWithCounters( tri, numIndexes ); GL_Cull( CT_FRONT_SIDED ); qglEnable( GL_STENCIL_TEST ); return; } // DG: that bloody patent on depth-fail stencil shadows has finally expired on 2019-10-13, // so use them (see https://patents.google.com/patent/US6384822B1/en for expiration status) bool useStencilOpSeperate = r_useStencilOpSeparate.GetBool() && qglStencilOpSeparate != NULL; if( !r_useCarmacksReverse.GetBool() ) { if( useStencilOpSeperate ) { // not using z-fail, but using qglStencilOpSeparate() GLenum firstFace = backEnd.viewDef->isMirror ? GL_FRONT : GL_BACK; GLenum secondFace = backEnd.viewDef->isMirror ? GL_BACK : GL_FRONT; GL_Cull( CT_TWO_SIDED ); if ( !external ) { qglStencilOpSeparate( firstFace, GL_KEEP, tr.stencilDecr, tr.stencilDecr ); qglStencilOpSeparate( secondFace, GL_KEEP, tr.stencilIncr, tr.stencilIncr ); RB_DrawShadowElementsWithCounters( tri, numIndexes ); } qglStencilOpSeparate( firstFace, GL_KEEP, GL_KEEP, tr.stencilIncr ); qglStencilOpSeparate( secondFace, GL_KEEP, GL_KEEP, tr.stencilDecr ); RB_DrawShadowElementsWithCounters( tri, numIndexes ); } else { // DG: this is the original code: // patent-free work around if ( !external ) { // "preload" the stencil buffer with the number of volumes // that get clipped by the near or far clip plane qglStencilOp( GL_KEEP, tr.stencilDecr, tr.stencilDecr ); GL_Cull( CT_FRONT_SIDED ); RB_DrawShadowElementsWithCounters( tri, numIndexes ); qglStencilOp( GL_KEEP, tr.stencilIncr, tr.stencilIncr ); GL_Cull( CT_BACK_SIDED ); RB_DrawShadowElementsWithCounters( tri, numIndexes ); } // traditional depth-pass stencil shadows qglStencilOp( GL_KEEP, GL_KEEP, tr.stencilIncr ); GL_Cull( CT_FRONT_SIDED ); RB_DrawShadowElementsWithCounters( tri, numIndexes ); qglStencilOp( GL_KEEP, GL_KEEP, tr.stencilDecr ); GL_Cull( CT_BACK_SIDED ); RB_DrawShadowElementsWithCounters( tri, numIndexes ); } } else { // use the formerly patented "Carmack's Reverse" Z-Fail code if( useStencilOpSeperate ) { // Z-Fail with glStencilOpSeparate() which will reduce draw calls GLenum firstFace = backEnd.viewDef->isMirror ? GL_FRONT : GL_BACK; GLenum secondFace = backEnd.viewDef->isMirror ? GL_BACK : GL_FRONT; if ( !external ) { // z-fail qglStencilOpSeparate( firstFace, GL_KEEP, tr.stencilDecr, GL_KEEP ); qglStencilOpSeparate( secondFace, GL_KEEP, tr.stencilIncr, GL_KEEP ); } else { // depth-pass qglStencilOpSeparate( firstFace, GL_KEEP, GL_KEEP, tr.stencilIncr ); qglStencilOpSeparate( secondFace, GL_KEEP, GL_KEEP, tr.stencilDecr ); } GL_Cull( CT_TWO_SIDED ); RB_DrawShadowElementsWithCounters( tri, numIndexes ); } else { // Z-Fail without glStencilOpSeparate() // LEITH: the (formerly patented) "Carmack's Reverse" code // depth-fail/Z-Fail stencil shadows if ( !external ) { qglStencilOp( GL_KEEP, tr.stencilDecr, GL_KEEP ); GL_Cull( CT_FRONT_SIDED ); RB_DrawShadowElementsWithCounters( tri, numIndexes ); qglStencilOp( GL_KEEP, tr.stencilIncr, GL_KEEP ); GL_Cull( CT_BACK_SIDED ); RB_DrawShadowElementsWithCounters( tri, numIndexes ); } // traditional depth-pass stencil shadows else { qglStencilOp( GL_KEEP, GL_KEEP, tr.stencilIncr ); GL_Cull( CT_FRONT_SIDED ); RB_DrawShadowElementsWithCounters( tri, numIndexes ); qglStencilOp( GL_KEEP, GL_KEEP, tr.stencilDecr ); GL_Cull( CT_BACK_SIDED ); RB_DrawShadowElementsWithCounters( tri, numIndexes ); } } } } /* ===================== RB_StencilShadowPass Stencil test should already be enabled, and the stencil buffer should have been set to 128 on any surfaces that might receive shadows ===================== */ void RB_StencilShadowPass( const drawSurf_t *drawSurfs ) { if ( !r_shadows.GetBool() ) { return; } if ( !drawSurfs ) { return; } globalImages->BindNull(); qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); // for visualizing the shadows if ( r_showShadows.GetInteger() ) { if ( r_showShadows.GetInteger() == 2 ) { // draw filled in GL_State( GLS_DEPTHMASK | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_LESS ); } else { // draw as lines, filling the depth buffer GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO | GLS_POLYMODE_LINE | GLS_DEPTHFUNC_ALWAYS ); } } else { // don't write to the color buffer, just the stencil buffer GL_State( GLS_DEPTHMASK | GLS_COLORMASK | GLS_ALPHAMASK | GLS_DEPTHFUNC_LESS ); } if ( r_shadowPolygonFactor.GetFloat() || r_shadowPolygonOffset.GetFloat() ) { qglPolygonOffset( r_shadowPolygonFactor.GetFloat(), -r_shadowPolygonOffset.GetFloat() ); qglEnable( GL_POLYGON_OFFSET_FILL ); } qglStencilFunc( GL_ALWAYS, 1, 255 ); if ( glConfig.depthBoundsTestAvailable && r_useDepthBoundsTest.GetBool() ) { qglEnable( GL_DEPTH_BOUNDS_TEST_EXT ); } RB_RenderDrawSurfChainWithFunction( drawSurfs, RB_T_Shadow ); GL_Cull( CT_FRONT_SIDED ); if ( r_shadowPolygonFactor.GetFloat() || r_shadowPolygonOffset.GetFloat() ) { qglDisable( GL_POLYGON_OFFSET_FILL ); } if ( glConfig.depthBoundsTestAvailable && r_useDepthBoundsTest.GetBool() ) { qglDisable( GL_DEPTH_BOUNDS_TEST_EXT ); } qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); qglStencilFunc( GL_GEQUAL, 128, 255 ); qglStencilOp( GL_KEEP, GL_KEEP, GL_KEEP ); } /* ============================================================================================= BLEND LIGHT PROJECTION ============================================================================================= */ /* ===================== RB_T_BlendLight ===================== */ static void RB_T_BlendLight( const drawSurf_t *surf ) { const srfTriangles_t *tri; tri = surf->geo; if ( backEnd.currentSpace != surf->space ) { idPlane lightProject[4]; int i; for ( i = 0 ; i < 4 ; i++ ) { R_GlobalPlaneToLocal( surf->space->modelMatrix, backEnd.vLight->lightProject[i], lightProject[i] ); } GL_SelectTexture( 0 ); qglTexGenfv( GL_S, GL_OBJECT_PLANE, lightProject[0].ToFloatPtr() ); qglTexGenfv( GL_T, GL_OBJECT_PLANE, lightProject[1].ToFloatPtr() ); qglTexGenfv( GL_Q, GL_OBJECT_PLANE, lightProject[2].ToFloatPtr() ); GL_SelectTexture( 1 ); qglTexGenfv( GL_S, GL_OBJECT_PLANE, lightProject[3].ToFloatPtr() ); } // this gets used for both blend lights and shadow draws if ( tri->ambientCache ) { idDrawVert *ac = (idDrawVert *)vertexCache.Position( tri->ambientCache ); qglVertexPointer( 3, GL_FLOAT, sizeof( idDrawVert ), ac->xyz.ToFloatPtr() ); } else if ( tri->shadowCache ) { shadowCache_t *sc = (shadowCache_t *)vertexCache.Position( tri->shadowCache ); qglVertexPointer( 3, GL_FLOAT, sizeof( shadowCache_t ), sc->xyz.ToFloatPtr() ); } RB_DrawElementsWithCounters( tri ); } /* ===================== RB_BlendLight Dual texture together the falloff and projection texture with a blend mode to the framebuffer, instead of interacting with the surface texture ===================== */ static void RB_BlendLight( const drawSurf_t *drawSurfs, const drawSurf_t *drawSurfs2 ) { const idMaterial *lightShader; const shaderStage_t *stage; int i; const float *regs; if ( !drawSurfs ) { return; } if ( r_skipBlendLights.GetBool() ) { return; } lightShader = backEnd.vLight->lightShader; regs = backEnd.vLight->shaderRegisters; // texture 1 will get the falloff texture GL_SelectTexture( 1 ); qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); qglEnable( GL_TEXTURE_GEN_S ); qglTexCoord2f( 0, 0.5 ); backEnd.vLight->falloffImage->Bind(); // texture 0 will get the projected texture GL_SelectTexture( 0 ); qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); qglEnable( GL_TEXTURE_GEN_S ); qglEnable( GL_TEXTURE_GEN_T ); qglEnable( GL_TEXTURE_GEN_Q ); for ( i = 0 ; i < lightShader->GetNumStages() ; i++ ) { stage = lightShader->GetStage(i); if ( !regs[ stage->conditionRegister ] ) { continue; } GL_State( GLS_DEPTHMASK | stage->drawStateBits | GLS_DEPTHFUNC_EQUAL ); GL_SelectTexture( 0 ); stage->texture.image->Bind(); if ( stage->texture.hasMatrix ) { RB_LoadShaderTextureMatrix( regs, &stage->texture ); } // get the modulate values from the light, including alpha, unlike normal lights backEnd.lightColor[0] = regs[ stage->color.registers[0] ]; backEnd.lightColor[1] = regs[ stage->color.registers[1] ]; backEnd.lightColor[2] = regs[ stage->color.registers[2] ]; backEnd.lightColor[3] = regs[ stage->color.registers[3] ]; qglColor4fv( backEnd.lightColor ); RB_RenderDrawSurfChainWithFunction( drawSurfs, RB_T_BlendLight ); RB_RenderDrawSurfChainWithFunction( drawSurfs2, RB_T_BlendLight ); if ( stage->texture.hasMatrix ) { GL_SelectTexture( 0 ); qglMatrixMode( GL_TEXTURE ); qglLoadIdentity(); qglMatrixMode( GL_MODELVIEW ); } } GL_SelectTexture( 1 ); qglDisable( GL_TEXTURE_GEN_S ); globalImages->BindNull(); GL_SelectTexture( 0 ); qglDisable( GL_TEXTURE_GEN_S ); qglDisable( GL_TEXTURE_GEN_T ); qglDisable( GL_TEXTURE_GEN_Q ); } //======================================================================== static idPlane fogPlanes[4]; /* ===================== RB_T_BasicFog ===================== */ static void RB_T_BasicFog( const drawSurf_t *surf ) { if ( backEnd.currentSpace != surf->space ) { idPlane local; GL_SelectTexture( 0 ); R_GlobalPlaneToLocal( surf->space->modelMatrix, fogPlanes[0], local ); local[3] += 0.5; qglTexGenfv( GL_S, GL_OBJECT_PLANE, local.ToFloatPtr() ); // R_GlobalPlaneToLocal( surf->space->modelMatrix, fogPlanes[1], local ); // local[3] += 0.5; local[0] = local[1] = local[2] = 0; local[3] = 0.5; qglTexGenfv( GL_T, GL_OBJECT_PLANE, local.ToFloatPtr() ); GL_SelectTexture( 1 ); // GL_S is constant per viewer R_GlobalPlaneToLocal( surf->space->modelMatrix, fogPlanes[2], local ); local[3] += FOG_ENTER; qglTexGenfv( GL_T, GL_OBJECT_PLANE, local.ToFloatPtr() ); R_GlobalPlaneToLocal( surf->space->modelMatrix, fogPlanes[3], local ); qglTexGenfv( GL_S, GL_OBJECT_PLANE, local.ToFloatPtr() ); } RB_T_RenderTriangleSurface( surf ); } /* ================== RB_FogPass ================== */ static void RB_FogPass( const drawSurf_t *drawSurfs, const drawSurf_t *drawSurfs2 ) { const srfTriangles_t*frustumTris; drawSurf_t ds; const idMaterial *lightShader; const shaderStage_t *stage; const float *regs; // create a surface for the light frustom triangles, which are oriented drawn side out frustumTris = backEnd.vLight->frustumTris; // if we ran out of vertex cache memory, skip it if ( !frustumTris->ambientCache ) { return; } memset( &ds, 0, sizeof( ds ) ); ds.space = &backEnd.viewDef->worldSpace; ds.geo = frustumTris; ds.scissorRect = backEnd.viewDef->scissor; // find the current color and density of the fog lightShader = backEnd.vLight->lightShader; regs = backEnd.vLight->shaderRegisters; // assume fog shaders have only a single stage stage = lightShader->GetStage(0); backEnd.lightColor[0] = regs[ stage->color.registers[0] ]; backEnd.lightColor[1] = regs[ stage->color.registers[1] ]; backEnd.lightColor[2] = regs[ stage->color.registers[2] ]; backEnd.lightColor[3] = regs[ stage->color.registers[3] ]; qglColor3fv( backEnd.lightColor ); // calculate the falloff planes float a; // if they left the default value on, set a fog distance of 500 if ( backEnd.lightColor[3] <= 1.0 ) { a = -0.5f / DEFAULT_FOG_DISTANCE; } else { // otherwise, distance = alpha color a = -0.5f / backEnd.lightColor[3]; } GL_State( GLS_DEPTHMASK | GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_DEPTHFUNC_EQUAL ); // texture 0 is the falloff image GL_SelectTexture( 0 ); globalImages->fogImage->Bind(); //GL_Bind( tr.whiteImage ); qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); qglEnable( GL_TEXTURE_GEN_S ); qglEnable( GL_TEXTURE_GEN_T ); qglTexCoord2f( 0.5f, 0.5f ); // make sure Q is set fogPlanes[0][0] = a * backEnd.viewDef->worldSpace.modelViewMatrix[2]; fogPlanes[0][1] = a * backEnd.viewDef->worldSpace.modelViewMatrix[6]; fogPlanes[0][2] = a * backEnd.viewDef->worldSpace.modelViewMatrix[10]; fogPlanes[0][3] = a * backEnd.viewDef->worldSpace.modelViewMatrix[14]; fogPlanes[1][0] = a * backEnd.viewDef->worldSpace.modelViewMatrix[0]; fogPlanes[1][1] = a * backEnd.viewDef->worldSpace.modelViewMatrix[4]; fogPlanes[1][2] = a * backEnd.viewDef->worldSpace.modelViewMatrix[8]; fogPlanes[1][3] = a * backEnd.viewDef->worldSpace.modelViewMatrix[12]; // texture 1 is the entering plane fade correction GL_SelectTexture( 1 ); globalImages->fogEnterImage->Bind(); qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); qglEnable( GL_TEXTURE_GEN_S ); qglEnable( GL_TEXTURE_GEN_T ); // T will get a texgen for the fade plane, which is always the "top" plane on unrotated lights fogPlanes[2][0] = 0.001f * backEnd.vLight->fogPlane[0]; fogPlanes[2][1] = 0.001f * backEnd.vLight->fogPlane[1]; fogPlanes[2][2] = 0.001f * backEnd.vLight->fogPlane[2]; fogPlanes[2][3] = 0.001f * backEnd.vLight->fogPlane[3]; // S is based on the view origin float s = backEnd.viewDef->renderView.vieworg * fogPlanes[2].Normal() + fogPlanes[2][3]; fogPlanes[3][0] = 0; fogPlanes[3][1] = 0; fogPlanes[3][2] = 0; fogPlanes[3][3] = FOG_ENTER + s; qglTexCoord2f( FOG_ENTER + s, FOG_ENTER ); // draw it RB_RenderDrawSurfChainWithFunction( drawSurfs, RB_T_BasicFog ); RB_RenderDrawSurfChainWithFunction( drawSurfs2, RB_T_BasicFog ); // the light frustum bounding planes aren't in the depth buffer, so use depthfunc_less instead // of depthfunc_equal GL_State( GLS_DEPTHMASK | GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_DEPTHFUNC_LESS ); GL_Cull( CT_BACK_SIDED ); RB_RenderDrawSurfChainWithFunction( &ds, RB_T_BasicFog ); GL_Cull( CT_FRONT_SIDED ); GL_SelectTexture( 1 ); qglDisable( GL_TEXTURE_GEN_S ); qglDisable( GL_TEXTURE_GEN_T ); globalImages->BindNull(); GL_SelectTexture( 0 ); qglDisable( GL_TEXTURE_GEN_S ); qglDisable( GL_TEXTURE_GEN_T ); } /* ================== RB_STD_FogAllLights ================== */ void RB_STD_FogAllLights( void ) { viewLight_t *vLight; if ( r_skipFogLights.GetBool() || r_showOverDraw.GetInteger() != 0 || backEnd.viewDef->isXraySubview /* dont fog in xray mode*/ ) { return; } qglDisable( GL_STENCIL_TEST ); for ( vLight = backEnd.viewDef->viewLights ; vLight ; vLight = vLight->next ) { backEnd.vLight = vLight; if ( !vLight->lightShader->IsFogLight() && !vLight->lightShader->IsBlendLight() ) { continue; } #if 0 // _D3XP disabled that if ( r_ignore.GetInteger() ) { // we use the stencil buffer to guarantee that no pixels will be // double fogged, which happens in some areas that are thousands of // units from the origin backEnd.currentScissor = vLight->scissorRect; if ( r_useScissor.GetBool() ) { qglScissor( backEnd.viewDef->viewport.x1 + backEnd.currentScissor.x1, backEnd.viewDef->viewport.y1 + backEnd.currentScissor.y1, backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1, backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1 ); } qglClear( GL_STENCIL_BUFFER_BIT ); qglEnable( GL_STENCIL_TEST ); // only pass on the cleared stencil values qglStencilFunc( GL_EQUAL, 128, 255 ); // when we pass the stencil test and depth test and are going to draw, // increment the stencil buffer so we don't ever draw on that pixel again qglStencilOp( GL_KEEP, GL_KEEP, GL_INCR ); } #endif if ( vLight->lightShader->IsFogLight() ) { RB_FogPass( vLight->globalInteractions, vLight->localInteractions ); } else if ( vLight->lightShader->IsBlendLight() ) { RB_BlendLight( vLight->globalInteractions, vLight->localInteractions ); } qglDisable( GL_STENCIL_TEST ); } qglEnable( GL_STENCIL_TEST ); } //========================================================================================= /* ================== RB_STD_LightScale Perform extra blending passes to multiply the entire buffer by a floating point value ================== */ void RB_STD_LightScale( void ) { float v, f; if ( backEnd.overBright == 1.0f ) { return; } if ( r_skipLightScale.GetBool() ) { return; } // the scissor may be smaller than the viewport for subviews if ( r_useScissor.GetBool() ) { qglScissor( backEnd.viewDef->viewport.x1 + backEnd.viewDef->scissor.x1, backEnd.viewDef->viewport.y1 + backEnd.viewDef->scissor.y1, backEnd.viewDef->scissor.x2 - backEnd.viewDef->scissor.x1 + 1, backEnd.viewDef->scissor.y2 - backEnd.viewDef->scissor.y1 + 1 ); backEnd.currentScissor = backEnd.viewDef->scissor; } // full screen blends qglLoadIdentity(); qglMatrixMode( GL_PROJECTION ); qglPushMatrix(); qglLoadIdentity(); qglOrtho( 0, 1, 0, 1, -1, 1 ); GL_State( GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_SRC_COLOR ); GL_Cull( CT_TWO_SIDED ); // so mirror views also get it globalImages->BindNull(); qglDisable( GL_DEPTH_TEST ); qglDisable( GL_STENCIL_TEST ); v = 1; while ( idMath::Fabs( v - backEnd.overBright ) > 0.01 ) { // a little extra slop f = backEnd.overBright / v; f /= 2; if ( f > 1 ) { f = 1; } qglColor3f( f, f, f ); v = v * f * 2; qglBegin( GL_QUADS ); qglVertex2f( 0,0 ); qglVertex2f( 0,1 ); qglVertex2f( 1,1 ); qglVertex2f( 1,0 ); qglEnd(); } qglPopMatrix(); qglEnable( GL_DEPTH_TEST ); qglMatrixMode( GL_MODELVIEW ); GL_Cull( CT_FRONT_SIDED ); } //========================================================================================= /* ============= RB_STD_DrawView ============= */ void RB_STD_DrawView( void ) { drawSurf_t **drawSurfs; int numDrawSurfs; backEnd.depthFunc = GLS_DEPTHFUNC_EQUAL; drawSurfs = (drawSurf_t **)&backEnd.viewDef->drawSurfs[0]; numDrawSurfs = backEnd.viewDef->numDrawSurfs; // clear the z buffer, set the projection matrix, etc RB_BeginDrawingView(); // decide how much overbrighting we are going to do RB_DetermineLightScale(); // fill the depth buffer and clear color buffer to black except on // subviews RB_STD_FillDepthBuffer( drawSurfs, numDrawSurfs ); // main light renderer switch( tr.backEndRenderer ) { case BE_ARB2: RB_ARB2_DrawInteractions(); break; } // disable stencil shadow test qglStencilFunc( GL_ALWAYS, 128, 255 ); // uplight the entire screen to crutch up not having better blending range RB_STD_LightScale(); // now draw any non-light dependent shading passes int processed = RB_STD_DrawShaderPasses( drawSurfs, numDrawSurfs ); // fob and blend lights RB_STD_FogAllLights(); // now draw any post-processing effects using _currentRender if ( processed < numDrawSurfs ) { RB_STD_DrawShaderPasses( drawSurfs+processed, numDrawSurfs-processed ); } RB_RenderDebugTools( drawSurfs, numDrawSurfs ); }