Paranoia2/cl_dll/render/gl_shadows.cpp

660 lines
16 KiB
C++

/*
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, 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( 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;
}