Paranoia2/cl_dll/render/gl_shadows.new

1272 lines
36 KiB
Plaintext

/*
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 <mathlib.h>
#include <stringlib.h>
#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, model_t *model, Vector bounds[2] )
{
Vector worldBounds[2];
int numCasters = 0;
msurface_t *surf, **mark;
mextrasurf_t *esrf;
mleaf_t *leaf;
int i, j;
if( r_sunshadows->value < 2.0f )
model = NULL; // don't add the world
ClearBounds( bounds[0], bounds[1] );
if( model != NULL )
{
RI->currententity = GET_ENTITY( 0 );
RI->currentmodel = RI->currententity->model;
R_GrassPrepareFrame();
}
// orthogonal frustum for selected slice
// NOTE: combined PVS is already set
RI->view.frustum.InitProjectionFromMatrix( lightViewProjection );
memset( RI->view.visfaces, 0x00, (model->numsurfaces + 7) >> 3 );
if( model != NULL )
{
// always skip the leaf 0, because is outside leaf
// tehnically it's equal of job R_MarkLeaves
for( i = 1, leaf = &model->leafs[1]; i < model->numleafs + 1; i++, leaf++ )
{
mextraleaf_t *eleaf = LEAF_INFO( leaf, model ); // named like personal vaporisers manufacturer he-he
if( CHECKVISBIT( RI->view.pvsarray, leaf->cluster ) && ( leaf->efrags || leaf->nummarksurfaces ))
{
if( RI->view.frustum.CullBox( eleaf->mins, eleaf->maxs ))
continue;
// deal with model fragments in this leaf
if( leaf->efrags )
STORE_EFRAGS( &leaf->efrags, tr.realframecount );
r_stats.c_world_leafs++;
if( leaf->nummarksurfaces )
{
for( j = 0, mark = leaf->firstmarksurface; j < leaf->nummarksurfaces; j++, mark++ )
SETVISBIT( RI->view.visfaces, *mark - model->surfaces );
}
}
}
}
// brush faces not added here!
// only marks as visible in RI->view.visfaces array
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_brush:
R_MarkSubmodelVisibleFaces();
break;
case mod_studio:
R_AddStudioToDrawList( RI->currententity );
break;
}
}
// create drawlist for faces, do additional culling for world faces
for( i = 0; model != NULL && i < world->numsortedfaces; i++ )
{
ASSERT( world->sortedfaces != NULL );
j = world->sortedfaces[i];
ASSERT( j >= 0 && j < model->numsurfaces );
if( CHECKVISBIT( RI->view.visfaces, j ))
{
surf = model->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;
}
else
{
RI->currententity = GET_ENTITY( 0 );
RI->currentmodel = RI->currententity->model;
esrf->parent = RI->currententity; // setup dynamic upcast
bool force = R_AddGrassToChain( NULL, surf, RI->currententity, &RI->view.frustum, 0 );
if( !force && R_CullSurface( surf, GetVieworg(), &RI->view.frustum ))
{
CLEARVISBIT( RI->view.visfaces, j ); // not visible
continue;
}
}
if( R_OpaqueEntity( RI->currententity ))
R_AddSurfaceToDrawList( surf, DRAWLIST_SOLID );
}
}
for( i = 0; i < RI->frame.num_solid_faces; i++ )
{
gl_bmodelface_t *entry = &RI->frame.solid_faces[i];
mextrasurf_t *es = entry->surface->info;
RI->currentmodel = es->parent->model;
RI->currententity = es->parent;
msurface_t *s = entry->surface;
bool worldpos = ( RI->currententity->origin == g_vecZero && RI->currententity->angles == g_vecZero ) ? true : false;
if( !worldpos ) continue; // world polys only
if( es->grass && r_grass->value )
{
// already included surface minmax
worldBounds[0] = es->grass->mins;
worldBounds[1] = es->grass->maxs;
}
else
{
worldBounds[0] = es->mins;
worldBounds[1] = es->maxs;
}
// if( RI->view.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++;
}
// FIXME: nearplane culled studiomodels incorrectly. disabled for now
// RI->view.frustum.DisablePlane( FRUSTUM_NEAR );
// add studio models too
for( i = 0; i < RI->frame.num_solid_meshes; i++ )
{
if( !R_StudioGetBounds( &RI->frame.solid_meshes[i], worldBounds ))
continue;
// if( RI->view.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( DynamicLight *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, NULL, 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 );
RI->view.frustum.InitProjectionFromMatrix( viewProjectionMatrix );
}
/*
===============
R_ShadowPassSetupFrame
===============
*/
static void R_ShadowPassSetupFrame( DynamicLight *pl, int split = 0 )
{
matrix3x3 viewRot;
if( !FBitSet( RI->params, RP_OLDVIEWLEAF ))
{
r_oldviewleaf = r_viewleaf;
r_oldviewleaf2 = r_viewleaf2;
r_viewleaf = Mod_PointInLeaf( pl->viewMatrix.GetOrigin(), worldmodel->nodes ); // light pvs
r_viewleaf2 = Mod_PointInLeaf( RI->view.origin, worldmodel->nodes ); // client pvs
}
RI->view.farClip = pl->radius;
RI->view.origin = pl->origin;
R_GrassPrepareFrame();
// setup the screen FOV
RI->view.fov_x = pl->fov;
RI->view.fov_y = pl->fov;
tr.framecount++;
// setup frustum
if( pl->type == LIGHT_DIRECTIONAL )
{
pl->splitFrustum[split] = RI->view.splitFrustum[split];
RI->view.matrix = pl->viewMatrix;
}
else if( pl->type == LIGHT_OMNI )
{
RI->view.angles = light_sides[split];
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->currentlight = pl;
}
/*
=============
R_ShadowPassSetupGL
=============
*/
static void R_ShadowPassSetupGL( const DynamicLight *pl )
{
RI->view.worldProjectionMatrix = RI->view.projectionMatrix.Concat( RI->view.worldMatrix );
RI->view.worldMatrix.CopyToArray( RI->glstate.modelviewMatrix );
RI->view.worldProjectionMatrix.CopyToArray( RI->glstate.modelviewProjectionMatrix );
pglViewport( RI->view.port[0], RI->view.port[1], RI->view.port[2], RI->view.port[3] );
pglMatrixMode( GL_PROJECTION );
GL_LoadMatrix( RI->view.projectionMatrix );
pglMatrixMode( GL_MODELVIEW );
GL_LoadMatrix( RI->view.worldMatrix );
GL_Cull( GL_FRONT );
GL_DepthRange( 0.0001f, 1.0f ); // ignore paranoia opengl32.dll
pglEnable( GL_POLYGON_OFFSET_FILL );
GL_DepthMask( GL_TRUE );
pglPolygonOffset( 1.0f, 2.0f );
pglEnable( GL_DEPTH_TEST );
GL_AlphaTest( GL_FALSE );
GL_Blend( GL_FALSE );
pglClear( GL_DEPTH_BUFFER_BIT );
}
/*
=============
R_ShadowPassEndGL
=============
*/
static void R_ShadowPassEndGL( void )
{
pglDisable( GL_POLYGON_OFFSET_FILL );
pglPolygonOffset( -1, -2 );
GL_DepthRange( gldepthmin, gldepthmax );
r_stats.c_shadow_passes++;
GL_Cull( GL_FRONT );
}
/*
================
R_RecursiveShadowNode
================
*/
void R_RecursiveShadowNode( mnode_t *node, CFrustum *frustum, unsigned int clipflags )
{
msurface_t *surf, **mark;
mleaf_t *pleaf;
int c, side;
float dot;
if( node->contents == CONTENTS_SOLID )
return; // hit a solid leaf
if( node->visframe != tr.visframecount )
return;
if( clipflags )
{
for( int i = 0; i < 6; i++ )
{
const mplane_t *p = frustum->GetPlane( i );
if( !FBitSet( clipflags, BIT( i )))
continue;
int clipped = BoxOnPlaneSide( node->minmaxs, node->minmaxs + 3, p );
if( clipped == 2 ) return;
if( clipped == 1 ) ClearBits( clipflags, BIT( i ));
}
}
// if a leaf node, draw stuff
if( node->contents < 0 )
{
pleaf = (mleaf_t *)node;
mark = pleaf->firstmarksurface;
c = pleaf->nummarksurfaces;
if( c )
{
do
{
(*mark)->visframe = tr.framecount;
mark++;
} while( --c );
}
// deal with model fragments in this leaf
if( pleaf->efrags )
STORE_EFRAGS( &pleaf->efrags, tr.realframecount );
return;
}
// node is just a decision point, so go down the apropriate sides
// find which side of the node we are on
dot = PlaneDiff( tr.modelorg, node->plane );
side = (dot >= 0) ? 0 : 1;
// recurse down the children, front side first
R_RecursiveShadowNode( node->children[side], frustum, clipflags );
// draw stuff
for( c = node->numsurfaces, surf = worldmodel->surfaces + node->firstsurface; c; c--, surf++ )
{
if( RI->currentlight->type != LIGHT_DIRECTIONAL || r_sunshadows->value > 2.0f )
R_AddGrassToChain( NULL, surf, RI->currententity, frustum, clipflags );
if( R_CullSurface( surf, tr.modelorg, frustum, clipflags ))
continue;
R_AddSurfaceToDrawList( surf, true, true );
}
// recurse down the back side
R_RecursiveShadowNode( node->children[!side], frustum, clipflags );
}
/*
=============
R_ShadowPassDrawWorld
=============
*/
static void R_ShadowPassDrawWorld( DynamicLight *pl )
{
int i;
// restore worldmodel
RI->currententity = GET_ENTITY( 0 );
RI->currentmodel = RI->currententity->model;
tr.num_light_surfaces = 0;
tr.num_light_meshes = 0;
tr.modelorg = GetVieworg();
R_LoadIdentity();
// register worldviewProjectionMatrix at zero entry (~80% hits)
RI->currententity->hCachedMatrix = GL_CacheState( g_vecZero, g_vecZero );
if( pl->type != LIGHT_DIRECTIONAL || r_sunshadows->value > 1.0f )
R_RecursiveShadowNode( worldmodel->nodes, &RI->view.frustum, RI->view.frustum.GetClipFlags( ));
// add all studio models
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:
if( pl->type != LIGHT_DIRECTIONAL || r_sunshadows->value > 1.0f )
R_AddBmodelToDrawList( RI->currententity, R_OpaqueEntity( RI->currententity ));
break;
}
}
}
void R_ShadowPassDrawSolidEntities( void )
{
// draw solid entities only.
glState.drawTrans = false;
R_RenderShadowBrushList();
R_RenderShadowStudioList();
// g-cont. probably always empty
R_RenderShadowSpriteList();
// may be solid cables
R_DrawParticles( false );
}
/*
===============
R_SetupViewCache
simply version of setup view cache
especially for parallel shadow pass
===============
*/
static void R_SetupViewCacheForParallelLightShadow( const int viewport[4], DynamicLight *pl, int split = 0 )
{
model_t *model = worldmodel;
RI->view.changed = 0; // always clearing changes at start of frame
if( !model && FBitSet( RI->params, RP_DRAW_WORLD ))
HOST_ERROR( "R_SetupViewCacheShadow: NULL worldmodel\n" );
memcpy( RI->view.port, viewport, sizeof( RI->view.port ));
memcpy( RI->glstate.viewport, RI->view.port, sizeof( RI->glstate.viewport ));
RI->view.pvspoint = pl->viewMatrix.GetOrigin();
RI->view.origin = pl->origin;
glState.drawTrans = false;
RI->currentlight = pl;
RI->view.leaf = Mod_PointInLeaf( RI->view.pvspoint, model->nodes );
// merge PVS with previous pass
ENGINE_SET_PVS( RI->view.pvspoint, REFPVS_RADIUS, RI->view.pvsarray, true, false );
RI->view.farClip = pl->radius;
RI->view.fov_x = pl->fov;
RI->view.fov_y = pl->fov;
if( RI->view.fov_x <= 0.0f || RI->view.fov_y <= 0.0f )
HOST_ERROR( "R_SetupViewCacheForParallelLightShadow: bad fov!\n" );
if( IS_NAN( RI->view.fov_x ) || IS_NAN( RI->view.fov_y ))
HOST_ERROR( "R_SetupViewCacheForParallelLightShadow: NAN fov!\n" );
RI->view.frustum = RI->view.splitFrustum[split];
RI->view.worldMatrix = pl->modelviewMatrix;
RI->view.matrix = pl->viewMatrix;
matrix4x4 projectionMatrix, cropMatrix, s1;
Vector splitFrustumCorners[8];
Vector splitFrustumBounds[2];
Vector splitFrustumClipBounds[2];
Vector casterBounds[2];
Vector cropMins, cropMaxs;
int i;
s1.CreateTexture();
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( cropMins, cropMaxs );
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, cropMins, cropMaxs );
}
projectionMatrix.CreateOrthoRH( cropMins.x, cropMaxs.x, cropMins.y, cropMaxs.y, -cropMaxs.z, -cropMins.z );
matrix4x4 viewProjectionMatrix = projectionMatrix.Concat( pl->viewMatrix );
int numCasters = R_ComputeCropBounds( viewProjectionMatrix, model, 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
cropMins.x = Q_max( casterBounds[0].x, splitFrustumClipBounds[0].x );
cropMins.y = Q_max( casterBounds[0].y, splitFrustumClipBounds[0].y );
cropMins.z = Q_min( casterBounds[0].z, splitFrustumClipBounds[0].z );
cropMaxs.x = Q_min( casterBounds[1].x, splitFrustumClipBounds[1].x );
cropMaxs.y = Q_min( casterBounds[1].y, splitFrustumClipBounds[1].y );
cropMaxs.z = Q_max( casterBounds[1].z, splitFrustumClipBounds[1].z );
if( numCasters == 0 )
{
cropMins = splitFrustumClipBounds[0];
cropMaxs = splitFrustumClipBounds[1];
}
cropMatrix.Crop( cropMins, cropMaxs );
pl->projectionMatrix = cropMatrix.Concat( projectionMatrix );
viewProjectionMatrix = pl->projectionMatrix.Concat( pl->modelviewMatrix );
// NOTE: texture matrix is not used. Save it for pssm show split debug tool
pl->textureMatrix[split] = projectionMatrix;
// build shadow matrices for each split
pl->shadowMatrix[split] = s1.Concat( viewProjectionMatrix );
RI->view.frustum.InitProjectionFromMatrix( viewProjectionMatrix );
RI->view.projectionMatrix = pl->projectionMatrix;
}
/*
===============
R_SetupViewCache
simply version of setup view cache
especially for shadow pass
===============
*/
static void R_SetupViewCacheShadow( const int viewport[4], DynamicLight *pl, int split = 0 )
{
model_t *model = worldmodel;
RI->view.changed = 0; // always clearing changes at start of frame
if( !model && FBitSet( RI->params, RP_DRAW_WORLD ))
HOST_ERROR( "R_SetupViewCacheShadow: NULL worldmodel\n" );
memcpy( RI->view.port, viewport, sizeof( RI->view.port ));
memcpy( RI->glstate.viewport, RI->view.port, sizeof( RI->glstate.viewport ));
RI->view.pvspoint = pl->viewMatrix.GetOrigin();
RI->view.origin = pl->origin;
glState.drawTrans = false;
RI->currentlight = pl;
RI->view.leaf = Mod_PointInLeaf( RI->view.pvspoint, model->nodes );
ENGINE_SET_PVS( RI->view.pvspoint, REFPVS_RADIUS, RI->view.pvsarray, true, false );
RI->view.farClip = pl->radius;
RI->view.fov_x = pl->fov ? pl->fov : 90.0f;
RI->view.fov_y = pl->fov ? pl->fov : 90.0f;
if( RI->view.fov_x <= 0.0f || RI->view.fov_y <= 0.0f )
HOST_ERROR( "R_SetupViewCacheShadow: bad fov!\n" );
if( IS_NAN( RI->view.fov_x ) || IS_NAN( RI->view.fov_y ))
HOST_ERROR( "R_SetupViewCacheShadow: NAN fov!\n" );
// setup frustum
switch( pl->type )
{
case LIGHT_DIRECTIONAL:
RI->view.frustum = RI->view.splitFrustum[split];
RI->view.worldMatrix = pl->modelviewMatrix;
RI->view.matrix = pl->viewMatrix;
break;
case LIGHT_OMNI:
RI->view.angles = light_sides[split];
RI->view.matrix = matrix4x4( RI->view.origin, RI->view.angles );
RI->view.frustum.InitProjection( RI->view.matrix, Z_NEAR_LIGHT, pl->radius, pl->fov, pl->fov );
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 );
break;
case LIGHT_PROJECTION:
RI->view.worldMatrix = pl->modelviewMatrix;
RI->view.matrix = pl->viewMatrix;
RI->view.frustum = pl->frustum;
break;
default:
HOST_ERROR( "R_SetupViewCacheShadow: invalid light type\n" );
break;
}
CFrustum *frustum = &RI->view.frustum;
float maxdist = 0.0f;
msurface_t *surf, **mark;
mextrasurf_t *esrf;
mleaf_t *leaf;
int i, j;
if( model != NULL )
{
memset( RI->view.visfaces, 0x00, (model->numsurfaces + 7) >> 3 );
ClearBounds( RI->view.visMins, RI->view.visMaxs );
// always skip the leaf 0, because is outside leaf
for( i = 1, leaf = &model->leafs[1]; i < model->numleafs + 1; i++, leaf++ )
{
mextraleaf_t *eleaf = LEAF_INFO( leaf, model ); // named like personal vaporisers manufacturer he-he
if( CHECKVISBIT( RI->view.pvsarray, leaf->cluster ) && ( leaf->efrags || leaf->nummarksurfaces ))
{
if( RI->view.frustum.CullBox( eleaf->mins, eleaf->maxs ))
continue;
// deal with model fragments in this leaf
if( leaf->efrags )
STORE_EFRAGS( &leaf->efrags, tr.realframecount );
if( leaf->contents == CONTENTS_EMPTY )
{
// unrolled for speedup reasons
RI->view.visMins[0] = Q_min( RI->view.visMins[0], eleaf->mins[0] );
RI->view.visMaxs[0] = Q_max( RI->view.visMaxs[0], eleaf->maxs[0] );
RI->view.visMins[1] = Q_min( RI->view.visMins[1], eleaf->mins[1] );
RI->view.visMaxs[1] = Q_max( RI->view.visMaxs[1], eleaf->maxs[1] );
RI->view.visMins[2] = Q_min( RI->view.visMins[2], eleaf->mins[2] );
RI->view.visMaxs[2] = Q_max( RI->view.visMaxs[2], eleaf->maxs[2] );
}
r_stats.c_world_leafs++;
if( leaf->nummarksurfaces )
{
for( j = 0, mark = leaf->firstmarksurface; j < leaf->nummarksurfaces; j++, mark++ )
SETVISBIT( RI->view.visfaces, *mark - model->surfaces );
}
}
}
// now we have actual vismins\vismaxs and can calc farplane distance
for( i = 0; i < 8; i++ )
{
Vector v, dir;
float dist;
v[0] = ( i & 1 ) ? RI->view.visMins[0] : RI->view.visMaxs[0];
v[1] = ( i & 2 ) ? RI->view.visMins[1] : RI->view.visMaxs[1];
v[2] = ( i & 4 ) ? RI->view.visMins[2] : RI->view.visMaxs[2];
dir = v - RI->view.origin;
dist = DotProduct( dir, dir );
maxdist = Q_max( dist, maxdist );
}
RI->view.farClip = sqrt( maxdist ) + 64.0f; // add some bias
if( pl->type == LIGHT_DIRECTIONAL )
{
matrix4x4 projectionMatrix, cropMatrix, s1;
Vector splitFrustumCorners[8];
Vector splitFrustumBounds[2];
Vector splitFrustumClipBounds[2];
Vector casterBounds[2];
Vector cropMins, cropMaxs;
s1.CreateTexture();
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( cropMins, cropMaxs );
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, cropMins, cropMaxs );
}
projectionMatrix.CreateOrthoRH( cropMins.x, cropMaxs.x, cropMins.y, cropMaxs.y, -cropMaxs.z, -cropMins.z );
matrix4x4 viewProjectionMatrix = projectionMatrix.Concat( pl->viewMatrix );
int numCasters = R_ComputeCropBounds( viewProjectionMatrix, NULL, 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
cropMins.x = Q_max( casterBounds[0].x, splitFrustumClipBounds[0].x );
cropMins.y = Q_max( casterBounds[0].y, splitFrustumClipBounds[0].y );
cropMins.z = Q_min( casterBounds[0].z, splitFrustumClipBounds[0].z );
cropMaxs.x = Q_min( casterBounds[1].x, splitFrustumClipBounds[1].x );
cropMaxs.y = Q_min( casterBounds[1].y, splitFrustumClipBounds[1].y );
cropMaxs.z = Q_max( casterBounds[1].z, splitFrustumClipBounds[1].z );
if( numCasters == 0 )
{
cropMins = splitFrustumClipBounds[0];
cropMaxs = splitFrustumClipBounds[1];
}
cropMatrix.Crop( cropMins, cropMaxs );
pl->projectionMatrix = cropMatrix.Concat( projectionMatrix );
viewProjectionMatrix = pl->projectionMatrix.Concat( pl->modelviewMatrix );
// NOTE: texture matrix is not used. Save it for pssm show split debug tool
pl->textureMatrix[split] = projectionMatrix;
// build shadow matrices for each split
pl->shadowMatrix[split] = s1.Concat( viewProjectionMatrix );
RI->view.frustum.InitProjectionFromMatrix( viewProjectionMatrix );
RI->view.projectionMatrix = pl->projectionMatrix;
}
else
{
// farclip was changed so we need to recompute frustum and projection again
RI->view.frustum.InitProjection( RI->view.matrix, 0.0f, RI->view.farClip, RI->view.fov_x, RI->view.fov_y );
RI->view.projectionMatrix.CreateProjection( RI->view.fov_x, RI->view.fov_y, Z_NEAR, RI->view.farClip );
}
RI->frame.num_solid_faces = 0;
RI->frame.num_trans_faces = 0;
RI->frame.num_mpass_faces = 0;
RI->frame.num_solid_meshes = 0;
RI->frame.num_trans_meshes = 0;
RI->frame.num_solid_sprites = 0;
RI->frame.num_trans_sprites = 0;
if( model != NULL )
{
RI->currententity = GET_ENTITY( 0 );
RI->currentmodel = RI->currententity->model;
R_GrassPrepareFrame();
}
// brush faces not added here!
// only marks as visible in RI->view.visfaces array
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_brush:
R_MarkSubmodelVisibleFaces();
break;
case mod_studio:
R_AddStudioToDrawList( RI->currententity );
break;
}
}
// create drawlist for faces, do additional culling for world faces
for( i = 0; model != NULL && i < world->numsortedfaces; i++ )
{
ASSERT( world->sortedfaces != NULL );
j = world->sortedfaces[i];
ASSERT( j >= 0 && j < model->numsurfaces );
if( CHECKVISBIT( RI->view.visfaces, j ))
{
surf = model->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;
}
else
{
RI->currententity = GET_ENTITY( 0 );
RI->currentmodel = RI->currententity->model;
esrf->parent = RI->currententity; // setup dynamic upcast
bool force = R_AddGrassToChain( NULL, surf, RI->currententity, &RI->view.frustum, 0 );
if( !force && R_CullSurface( surf, GetVieworg(), frustum ))
{
CLEARVISBIT( RI->view.visfaces, j ); // not visible
continue;
}
// surface has passed all visibility checks
// and can be update some data (lightmaps, mirror matrix, etc)
R_UpdateSurfaceParams( surf );
}
if( R_OpaqueEntity( RI->currententity ))
R_AddSurfaceToDrawList( surf, DRAWLIST_SOLID );
}
}
}
}
/*
================
R_RenderShadowScene
fast version of R_RenderScene: no colors, no texcords etc
================
*/
void R_RenderShadowScene( DynamicLight *pl, int split = 0 )
{
int viewport[4];
RI->params = RP_SHADOWVIEW|RP_MERGEVISIBILITY|RP_DRAW_WORLD;
viewport[0] = viewport[1] = 0;
bool using_fbo = false;
if( pl->type == LIGHT_DIRECTIONAL )
{
if( tr.sunShadowFBO[split].Active( ))
{
pl->shadowTexture[split] = tr.sunShadowFBO[split].GetTexture();
viewport[2] = viewport[3] = sunShadowSize[split];
tr.sunShadowFBO[split].Bind();
using_fbo = true;
}
else viewport[2] = viewport[3] = 512; // simple size if FBO was missed
}
else
{
if( tr.fbo_shadow2D.Active( ))
{
pl->shadowTexture[0] = R_AllocateShadowTexture( false );
tr.fbo_shadow2D.Bind( pl->shadowTexture[0] );
viewport[2] = tr.fbo_shadow2D.GetWidth();
viewport[3] = tr.fbo_shadow2D.GetHeight();
using_fbo = true;
}
else viewport[2] = viewport[3] = 512; // simple size if FBO was missed
}
// set the worldmodel
worldmodel = GET_ENTITY( 0 )->model;
if( !worldmodel )
{
ALERT( at_error, "R_RenderShadowView: NULL worldmodel\n" );
return;
}
if( pl->type == LIGHT_DIRECTIONAL )
R_SetupViewCacheForParallelLightShadow( viewport, pl, split );
else R_SetupViewCacheShadow( viewport, pl, split );
R_ShadowPassSetupGL( pl );
R_ShadowPassDrawSolidEntities();
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( DynamicLight *pl, int side )
{
RI->params = RP_SHADOWVIEW|RP_MERGEVISIBILITY|RP_DRAW_WORLD;
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;
}
// set the worldmodel
worldmodel = GET_ENTITY( 0 )->model;
if( !worldmodel )
{
ALERT( at_error, "R_RenderShadowCubeSide: NULL worldmodel\n" );
return;
}
R_ShadowPassSetupFrame( pl, side );
R_ShadowPassSetupGL( pl );
R_MarkLeaves();
R_ShadowPassDrawWorld( pl );
R_ShadowPassDrawSolidEntities();
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++ )
{
DynamicLight *pl = &cl_dlights[i];
if( pl->die < GET_CLIENT_TIME() || !pl->radius || FBitSet( pl->flags, DLF_NOSHADOWS ))
continue;
RI->currentlight = pl;
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_PROJECTION )
{
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;
}