/* gl_shadows.cpp - render shadowmaps for directional lights Copyright (C) 2012 Uncle Mike This program 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. This program 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. */ #include "hud.h" #include "cl_util.h" #include "const.h" #include "gl_local.h" #include #include #include "gl_studio.h" #include "gl_sprite.h" #include "gl_world.h" #include "gl_grass.h" #include "pm_defs.h" static Vector light_sides[] = { Vector( 0.0f, 0.0f, 90.0f ), // GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB Vector( 0.0f, 180.0f, -90.0f ), // GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB Vector( 0.0f, 90.0f, 0.0f ), // GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB Vector( 0.0f, 270.0f, 180.0f ), // GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB Vector(-90.0f, 180.0f, -90.0f ), // GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB Vector( 90.0f, 0.0f, 90.0f ), // GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB }; /* ============================================================= SHADOW RENDERING ============================================================= */ int R_AllocateShadowTexture( bool copyToImage = true ) { int i = tr.num_2D_shadows_used; if( i >= MAX_SHADOWS ) { ALERT( at_error, "R_AllocateShadowTexture: shadow textures limit exceeded!\n" ); return 0; // disable } int texture = tr.shadowTextures[i]; tr.num_2D_shadows_used++; if( !tr.shadowTextures[i] ) { char txName[16]; Q_snprintf( txName, sizeof( txName ), "*shadow2D%i", i ); tr.shadowTextures[i] = CREATE_TEXTURE( txName, RI->view.port[2], RI->view.port[3], NULL, TF_SHADOW ); texture = tr.shadowTextures[i]; } if( copyToImage ) { GL_BindTexture( GL_TEXTURE0, texture ); pglCopyTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, RI->view.port[0], RI->view.port[1], RI->view.port[2], RI->view.port[3], 0 ); } return texture; } int R_AllocateShadowCubemap( int side, bool copyToImage = true ) { int texture = 0; if( side != 0 ) { int i = (tr.num_CM_shadows_used - 1); if( i >= MAX_SHADOWS ) { ALERT( at_error, "R_AllocateShadowCubemap: shadow cubemaps limit exceeded!\n" ); return 0; // disable } texture = tr.shadowCubemaps[i]; if( !tr.shadowCubemaps[i] ) { ALERT( at_error, "R_AllocateShadowCubemap: cubemap not initialized!\n" ); return 0; // disable } } else { int i = tr.num_CM_shadows_used; if( i >= MAX_SHADOWS ) { ALERT( at_error, "R_AllocateShadowCubemap: shadow cubemaps limit exceeded!\n" ); return 0; // disable } texture = tr.shadowCubemaps[i]; tr.num_CM_shadows_used++; if( !tr.shadowCubemaps[i] ) { char txName[16]; Q_snprintf( txName, sizeof( txName ), "*shadowCM%i", i ); tr.shadowCubemaps[i] = CREATE_TEXTURE( txName, RI->view.port[2], RI->view.port[3], NULL, TF_SHADOW_CUBEMAP ); texture = tr.shadowCubemaps[i]; } } if( copyToImage ) { GL_BindTexture( GL_TEXTURE0, texture ); pglCopyTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB + side, 0, GL_DEPTH_COMPONENT, RI->view.port[0], RI->view.port[1], RI->view.port[2], RI->view.port[3], 0 ); } return texture; } static int R_ComputeCropBounds( const matrix4x4 &lightViewProjection, Vector bounds[2] ) { Vector worldBounds[2]; int numCasters = 0; ref_instance_t *prevRI = R_GetPrevInstance(); CFrustum frustum; ClearBounds( bounds[0], bounds[1] ); frustum.InitProjectionFromMatrix( lightViewProjection ); // FIXME: nearplane culled studiomodels incorrectly. disabled for now frustum.DisablePlane( FRUSTUM_NEAR ); frustum.DisablePlane( FRUSTUM_FAR ); int i; for( i = 0; i < prevRI->frame.solid_faces.Count(); i++ ) { CSolidEntry *entry = &prevRI->frame.solid_faces[i]; if( entry->m_bDrawType != DRAWTYPE_SURFACE ) continue; mextrasurf_t *es = entry->m_pSurf->info; RI->currentmodel = es->parent->model; RI->currententity = es->parent; msurface_t *s = entry->m_pSurf; bool worldpos = R_StaticEntity( RI->currententity ); if( !worldpos ) continue; // world polys only if( es->grass && CVAR_TO_BOOL( r_grass )) { // already included surface minmax worldBounds[0] = es->grass->mins; worldBounds[1] = es->grass->maxs; } else { worldBounds[0] = es->mins; worldBounds[1] = es->maxs; } if( frustum.CullBox( worldBounds[0], worldBounds[1] )) continue; for( int j = 0; j < 8; j++ ) { Vector4D point; point.x = worldBounds[(j >> 0) & 1].x; point.y = worldBounds[(j >> 1) & 1].y; point.z = worldBounds[(j >> 2) & 1].z; point.w = 1.0f; Vector4D transf = lightViewProjection.VectorTransform( point ); transf.x /= transf.w; transf.y /= transf.w; transf.z /= transf.w; AddPointToBounds( transf, bounds[0], bounds[1] ); } numCasters++; } // add studio models too for( int i = 0; i < prevRI->frame.solid_meshes.Count(); i++ ) { if( !R_StudioGetBounds( &prevRI->frame.solid_meshes[i], worldBounds )) continue; if( frustum.CullBox( worldBounds[0], worldBounds[1] )) continue; for( int j = 0; j < 8; j++ ) { Vector4D point; point.x = worldBounds[(j >> 0) & 1].x; point.y = worldBounds[(j >> 1) & 1].y; point.z = worldBounds[(j >> 2) & 1].z; point.w = 1.0f; Vector4D transf = lightViewProjection.VectorTransform( point ); transf.x /= transf.w; transf.y /= transf.w; transf.z /= transf.w; AddPointToBounds( transf, bounds[0], bounds[1] ); } numCasters++; } return numCasters; } /* =============== R_SetupLightDirectional =============== */ void R_SetupLightDirectional( CDynLight *pl, int split ) { matrix4x4 projectionMatrix, cropMatrix, s1; Vector splitFrustumCorners[8]; Vector splitFrustumBounds[2]; Vector splitFrustumClipBounds[2]; Vector casterBounds[2]; Vector cropBounds[2]; int i; RI->view.splitFrustum[split].ComputeFrustumCorners( splitFrustumCorners ); ClearBounds( splitFrustumBounds[0], splitFrustumBounds[1] ); for( i = 0; i < 8; i++ ) AddPointToBounds( splitFrustumCorners[i], splitFrustumBounds[0], splitFrustumBounds[1] ); // find the bounding box of the current split in the light's view space ClearBounds( cropBounds[0], cropBounds[1] ); for( i = 0; i < 8; i++ ) { Vector4D point( splitFrustumCorners[i] ); Vector4D transf = pl->viewMatrix.VectorTransform( point ); transf.x /= transf.w; transf.y /= transf.w; transf.z /= transf.w; AddPointToBounds( transf, cropBounds[0], cropBounds[1] ); } projectionMatrix.CreateOrthoRH( cropBounds[0].x, cropBounds[1].x, cropBounds[0].y, cropBounds[1].y, -cropBounds[1].z, -cropBounds[0].z ); matrix4x4 viewProjectionMatrix = projectionMatrix.Concat( pl->viewMatrix ); int numCasters = R_ComputeCropBounds( viewProjectionMatrix, casterBounds ); // find the bounding box of the current split in the light's clip space ClearBounds( splitFrustumClipBounds[0], splitFrustumClipBounds[1] ); for( i = 0; i < 8; i++ ) { Vector4D point( splitFrustumCorners[i] ); Vector4D transf = viewProjectionMatrix.VectorTransform( point ); transf.x /= transf.w; transf.y /= transf.w; transf.z /= transf.w; AddPointToBounds( transf, splitFrustumClipBounds[0], splitFrustumClipBounds[1] ); } // scene-dependent bounding volume cropBounds[0].x = Q_max( casterBounds[0].x, splitFrustumClipBounds[0].x ); cropBounds[0].y = Q_max( casterBounds[0].y, splitFrustumClipBounds[0].y ); cropBounds[0].z = Q_min( casterBounds[0].z, splitFrustumClipBounds[0].z ); cropBounds[1].x = Q_min( casterBounds[1].x, splitFrustumClipBounds[1].x ); cropBounds[1].y = Q_min( casterBounds[1].y, splitFrustumClipBounds[1].y ); cropBounds[1].z = Q_max( casterBounds[1].z, splitFrustumClipBounds[1].z ); if( numCasters == 0 ) { cropBounds[0] = splitFrustumClipBounds[0]; cropBounds[1] = splitFrustumClipBounds[1]; } cropMatrix.Crop( cropBounds[0], cropBounds[1] ); pl->projectionMatrix = cropMatrix.Concat( projectionMatrix ); s1.CreateTranslate( 0.5f, 0.5f, 0.5f ); s1.ConcatScale( 0.5f, 0.5f, 0.5f ); viewProjectionMatrix = pl->projectionMatrix.Concat( pl->modelviewMatrix ); // NOTE: texture matrix is not used. Save it for pssm show split debug tool pl->textureMatrix[split] = pl->projectionMatrix; // build shadow matrices for each split pl->shadowMatrix[split] = s1.Concat( viewProjectionMatrix ); pl->shadowMatrix[split].CopyToArray( pl->gl_shadowMatrix[split] ); RI->view.frustum.InitProjectionFromMatrix( viewProjectionMatrix ); } /* =============== R_ShadowPassSetupViewCache =============== */ static void R_ShadowPassSetupViewCache( CDynLight *pl, int split = 0 ) { memcpy( RI->glstate.viewport, RI->view.port, sizeof( RI->glstate.viewport )); RI->view.farClip = pl->radius; RI->view.origin = pl->origin; // setup the screen FOV RI->view.fov_x = pl->fov; RI->view.fov_y = pl->fov; // setup frustum if( pl->type == LIGHT_DIRECTIONAL ) { pl->splitFrustum[split] = RI->view.splitFrustum[split]; RI->view.matrix = pl->viewMatrix; R_SetupLightDirectional( pl, split ); } else if( pl->type == LIGHT_OMNI ) { RI->view.angles = light_sides[split]; // this is cube side of course RI->view.matrix = matrix4x4( RI->view.origin, RI->view.angles ); RI->view.frustum.InitProjection( RI->view.matrix, 0.1f, pl->radius, 90.0f, 90.0f ); } else { RI->view.matrix = pl->viewMatrix; RI->view.frustum = pl->frustum; } if( pl->type == LIGHT_OMNI ) { RI->view.worldMatrix.CreateModelview(); RI->view.worldMatrix.ConcatRotate( -light_sides[split].z, 1, 0, 0 ); RI->view.worldMatrix.ConcatRotate( -light_sides[split].x, 0, 1, 0 ); RI->view.worldMatrix.ConcatRotate( -light_sides[split].y, 0, 0, 1 ); RI->view.worldMatrix.ConcatTranslate( -pl->origin.x, -pl->origin.y, -pl->origin.z ); RI->view.projectionMatrix = pl->projectionMatrix; } else { // matrices already computed RI->view.worldMatrix = pl->modelviewMatrix; RI->view.projectionMatrix = pl->projectionMatrix; } RI->view.worldProjectionMatrix = RI->view.projectionMatrix.Concat( RI->view.worldMatrix ); RI->view.worldProjectionMatrix.CopyToArray( RI->glstate.modelviewProjectionMatrix ); RI->view.projectionMatrix.CopyToArray( RI->glstate.projectionMatrix ); RI->view.worldMatrix.CopyToArray( RI->glstate.modelviewMatrix ); RI->currentlight = pl; R_MarkWorldVisibleFaces( worldmodel ); msurface_t *surf; mextrasurf_t *esrf; int i, j; // add all studio models, mark visible bmodels for( i = 0; i < tr.num_draw_entities; i++ ) { RI->currententity = tr.draw_entities[i]; RI->currentmodel = RI->currententity->model; switch( RI->currentmodel->type ) { case mod_studio: R_AddStudioToDrawList( RI->currententity ); break; case mod_brush: R_MarkSubmodelVisibleFaces(); break; } } // create drawlist for faces, do additional culling for world faces for( i = 0; i < world->numsortedfaces; i++ ) { ASSERT( world->sortedfaces != NULL ); j = world->sortedfaces[i]; ASSERT( j >= 0 && j < worldmodel->numsurfaces ); if( CHECKVISBIT( RI->view.visfaces, j )) { surf = worldmodel->surfaces + j; esrf = surf->info; // submodel faces already passed through this // operation but world is not if( FBitSet( surf->flags, SURF_OF_SUBMODEL )) { RI->currententity = esrf->parent; RI->currentmodel = RI->currententity->model; R_AddGrassToDrawList( surf, DRAWLIST_SHADOW ); } else { RI->currententity = GET_ENTITY( 0 ); RI->currentmodel = RI->currententity->model; esrf->parent = RI->currententity; // setup dynamic upcast R_AddGrassToDrawList( surf, DRAWLIST_SHADOW ); if( R_CullSurface( surf, GetVieworg(), &RI->view.frustum )) { CLEARVISBIT( RI->view.visfaces, j ); // not visible continue; } } // only opaque faces interesting us if( R_OpaqueEntity( RI->currententity )) { R_AddSurfaceToDrawList( surf, DRAWLIST_SHADOW ); } } } } /* ============= R_ShadowPassSetupGL ============= */ static void R_ShadowPassSetupGL( const CDynLight *pl ) { R_SetupGLstate(); pglEnable( GL_POLYGON_OFFSET_FILL ); if( RI->currentlight->type == LIGHT_DIRECTIONAL ) { if( r_shadows->value > 2.0f ) pglPolygonOffset( 3.0f, 0.0f ); else pglPolygonOffset( 2.0f, 0.0f ); GL_Cull( GL_NONE ); } else { if( r_shadows->value > 2.0f ) pglPolygonOffset( 2.0f, 0.0f ); else pglPolygonOffset( 1.0f, 0.0f ); GL_Cull( GL_FRONT ); } // HACKHACK to ignore paranoia opengl32.dll GL_DepthRange( 0.0001f, 1.0f ); pglEnable( GL_DEPTH_TEST ); GL_AlphaTest( GL_FALSE ); GL_DepthMask( GL_TRUE ); GL_Blend( GL_FALSE ); } /* ============= R_ShadowPassEndGL ============= */ static void R_ShadowPassEndGL( void ) { pglDisable( GL_POLYGON_OFFSET_FILL ); GL_DepthRange( gldepthmin, gldepthmax ); pglPolygonOffset( -1, -2 ); r_stats.c_shadow_passes++; GL_Cull( GL_FRONT ); } /* ================ R_RenderShadowScene fast version of R_RenderScene: no colors, no texcords etc ================ */ void R_RenderShadowScene( CDynLight *pl, int split = 0 ) { RI->params = RP_SHADOWVIEW; bool using_fbo = false; if( pl->type == LIGHT_DIRECTIONAL ) { if( tr.sunShadowFBO[split].Active( )) { RI->view.port[2] = RI->view.port[3] = sunSize[split]; pl->shadowTexture[split] = tr.sunShadowFBO[split].GetTexture(); tr.sunShadowFBO[split].Bind(); using_fbo = true; } else RI->view.port[2] = RI->view.port[3] = 512; // simple size if FBO was missed } else { if( tr.fbo_shadow2D.Active( )) { RI->view.port[2] = tr.fbo_shadow2D.GetWidth(); RI->view.port[3] = tr.fbo_shadow2D.GetHeight(); pl->shadowTexture[0] = R_AllocateShadowTexture( false ); tr.fbo_shadow2D.Bind( pl->shadowTexture[0] ); using_fbo = true; } else RI->view.port[2] = RI->view.port[3] = 512; } R_ShadowPassSetupViewCache( pl, split ); R_ShadowPassSetupGL( pl ); pglClear( GL_DEPTH_BUFFER_BIT ); R_RenderShadowBrushList(); R_RenderShadowStudioList(); R_DrawParticles( false ); R_ShadowPassEndGL(); if( !using_fbo ) pl->shadowTexture[split] = R_AllocateShadowTexture(); } /* ================ R_RenderShadowCubeSide fast version of R_RenderScene: no colors, no texcords etc ================ */ void R_RenderShadowCubeSide( CDynLight *pl, int side ) { RI->params = RP_SHADOWVIEW; bool using_fbo = false; if( tr.fbo_shadowCM.Active( )) { RI->view.port[2] = tr.fbo_shadowCM.GetWidth(); RI->view.port[3] = tr.fbo_shadowCM.GetHeight(); pl->shadowTexture[0] = R_AllocateShadowCubemap( side, false ); tr.fbo_shadowCM.Bind( pl->shadowTexture[0], side ); using_fbo = true; } else { // same size if FBO was missed RI->view.port[2] = RI->view.port[3] = 512; using_fbo = false; } R_ShadowPassSetupViewCache( pl, side ); R_ShadowPassSetupGL( pl ); pglClear( GL_DEPTH_BUFFER_BIT ); R_RenderShadowBrushList(); R_RenderShadowStudioList(); R_DrawParticles( false ); R_ShadowPassEndGL(); if( !using_fbo ) pl->shadowTexture[0] = R_AllocateShadowCubemap( side ); } void R_RenderShadowmaps( void ) { unsigned int oldFBO; if( R_FullBright() || !CVAR_TO_BOOL( r_shadows ) || tr.fGamePaused ) return; if( FBitSet( RI->params, ( RP_NOSHADOWS|RP_ENVVIEW|RP_SKYVIEW ))) return; // check for dynamic lights if( !HasDynamicLights( )) return; R_PushRefState(); // make refinst backup oldFBO = glState.frameBuffer; for( int i = 0; i < MAX_DLIGHTS; i++ ) { CDynLight *pl = &tr.dlights[i]; if( !pl->Active() || FBitSet( pl->flags, DLF_NOSHADOWS )) continue; if( pl->type == LIGHT_OMNI ) { // need GL_EXT_gpu_shader4 for cubemap shadows if( !GL_Support( R_TEXTURECUBEMAP_EXT ) || !GL_Support( R_EXT_GPU_SHADER4 )) continue; if( !Mod_CheckBoxVisible( pl->absmin, pl->absmax )) continue; if( R_CullBox( pl->absmin, pl->absmax )) continue; for( int j = 0; j < 6; j++ ) { R_RenderShadowCubeSide( pl, j ); R_ResetRefState(); // restore ref instance } } else if( pl->type == LIGHT_SPOT ) { if( !Mod_CheckBoxVisible( pl->absmin, pl->absmax )) continue; if( R_CullBox( pl->absmin, pl->absmax )) continue; R_RenderShadowScene( pl ); R_ResetRefState(); // restore ref instance } else if( pl->type == LIGHT_DIRECTIONAL ) { if( !CVAR_TO_BOOL( r_sunshadows ) || tr.sky_normal.z >= 0.0f ) continue; // shadows are invisible for( int j = 0; j <= NUM_SHADOW_SPLITS; j++ ) { // PSSM: draw all the splits R_RenderShadowScene( pl, j ); R_ResetRefState(); // restore ref instance } } } R_PopRefState(); // restore ref instance // restore FBO state GL_BindFBO( oldFBO ); GL_BindShader( NULL ); RI->currentlight = NULL; }