Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

6908 lines
237 KiB

//===== Copyright � 1996-2005, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
// $NoKeywords: $
//
// Interface to the client system responsible for dealing with shadows
//
// Boy is this complicated. OK, lets talk about how this works at the moment
//
// The ClientShadowMgr contains all of the highest-level state for rendering
// shadows, and it controls the ShadowMgr in the engine which is the central
// clearing house for rendering shadows.
//
// There are two important types of objects with respect to shadows:
// the shadow receiver, and the shadow caster. How is the association made
// between casters + the receivers? Turns out it's done slightly differently
// depending on whether the receiver is the world, or if it's an entity.
//
// In the case of the world, every time the engine's ProjectShadow() is called,
// any previous receiver state stored (namely, which world surfaces are
// receiving shadows) are cleared. Then, when ProjectShadow is called,
// the engine iterates over all nodes + leaves within the shadow volume and
// marks front-facing surfaces in them as potentially being affected by the
// shadow. Later on, if those surfaces are actually rendered, the surfaces
// are clipped by the shadow volume + rendered.
//
// In the case of entities, there are slightly different methods depending
// on whether the receiver is a brush model or a studio model. However, there
// are a couple central things that occur with both.
//
// Every time a shadow caster is moved, the ClientLeafSystem's ProjectShadow
// method is called to tell it to remove the shadow from all leaves + all
// renderables it's currently associated with. Then it marks each leaf in the
// shadow volume as being affected by that shadow, and it marks every renderable
// in that volume as being potentially affected by the shadow (the function
// AddShadowToRenderable is called for each renderable in leaves affected
// by the shadow volume).
//
// Every time a shadow receiver is moved, the ClientLeafSystem first calls
// RemoveAllShadowsFromRenderable to have it clear out its state, and then
// the ClientLeafSystem calls AddShadowToRenderable() for all shadows in all
// leaves the renderable has moved into.
//
// Now comes the difference between brush models + studio models. In the case
// of brush models, when a shadow is added to the studio model, it's done in
// the exact same way as for the world. Surfaces on the brush model are marked
// as potentially being affected by the shadow, and if those surfaces are
// rendered, the surfaces are clipped to the shadow volume. When ProjectShadow()
// is called, turns out the same operation that removes the shadow that moved
// from the world surfaces also works to remove the shadow from brush surfaces.
//
// In the case of studio models, we need a separate operation to remove
// the shadow from all studio models
//
// DEFERRED SHADOW RENDERING
//
// When deferred shadow rendering (currently 360 only) is enabled. The
// ClientShadowMgr bypasses most calls to the engine shadow mgr to avoid the
// CPU overhead of clipping world geometry against shadow frustums. Instead,
// We render each shadow frustum and use the depth buffer to back-project each
// covered screen pixel into shadow space and apply the shadow. This causes
// everything that rendered into the depth buffer during the opaque renderables
// pass to be a shadow receiver (shadows on static props are free). Because this
// approach requires a lot of fill-rate, we impose the limitation that shadow
// casters can't receive shadows. Shadow casters are marked in the stencil buffer
// (using stencil mask 0x4) AND in the 360's heirarchical stencil buffer, which
// is most important for controlling fill rate. Initializing the stencil mask
// for shadow casters currently happens in several places: the staticpropmgr,
// c_baseanimating rendering code, and L4D-specific entity classes.
//
//===========================================================================//
#include "cbase.h"
#include "engine/ishadowmgr.h"
#include "model_types.h"
#include "bitmap/imageformat.h"
#include "materialsystem/imaterialproxy.h"
#include "materialsystem/imaterialvar.h"
#include "materialsystem/imaterial.h"
#include "materialsystem/imesh.h"
#include "materialsystem/itexture.h"
#include "bsptreedata.h"
#include "utlmultilist.h"
#include "collisionutils.h"
#include "iviewrender.h"
#include "ivrenderview.h"
#include "tier0/vprof.h"
#include "engine/ivmodelinfo.h"
#include "view_shared.h"
#include "engine/ivdebugoverlay.h"
#include "engine/IStaticPropMgr.h"
#include "datacache/imdlcache.h"
#include "viewrender.h"
#include "tier0/icommandline.h"
#include "vstdlib/jobthread.h"
#include "bonetoworldarray.h"
#include "debugoverlay_shared.h"
#include "shaderapi/ishaderapi.h"
#include "renderparm.h"
#include "rendertexture.h"
#include "clientalphaproperty.h"
#include "flashlighteffect.h"
#include "c_env_projectedtexture.h"
#include "imaterialproxydict.h"
#include "c_env_cascade_light.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#ifdef CSTRIKE15
// Slam blobby shadows to disabled in CS:GO (RTT shadows are also disabled too).
static ConVar r_disable_update_shadow( "r_disable_update_shadow", "1", FCVAR_CHEAT );
#else
static ConVar r_disable_update_shadow( "r_disable_update_shadow", "0", FCVAR_CHEAT );
#endif
static ConVar r_flashlightdrawfrustum( "r_flashlightdrawfrustum", "0" );
static ConVar r_flashlightdrawfrustumbbox( "r_flashlightdrawfrustumbbox", "0" );
static ConVar r_flashlightmodels( "r_flashlightmodels", "1" );
static ConVar r_shadowrendertotexture( "r_shadowrendertotexture", "0" );
static ConVar r_shadow_lightpos_lerptime( "r_shadow_lightpos_lerptime", "0.5" );
static ConVar r_shadowfromworldlights_debug( "r_shadowfromworldlights_debug", "0", FCVAR_CHEAT );
static ConVar r_shadowfromanyworldlight( "r_shadowfromanyworldlight", "0", FCVAR_CHEAT );
static ConVar r_shadow_shortenfactor( "r_shadow_shortenfactor", "2" , 0, "Makes shadows cast from local lights shorter" );
// Flashlight culling code isn't Portal-aware, so they pop on/off when viewed through portals.
#ifdef PORTAL2
ConVar r_flashlightenableculling( "r_flashlightenableculling", "0", 0, "Enable frustum culling of flashlights");
#else
ConVar r_flashlightenableculling( "r_flashlightenableculling", "1", 0, "Enable frustum culling of flashlights");
#endif
static void HalfUpdateRateCallback( IConVar *var, const char *pOldValue, float flOldValue );
static ConVar r_shadow_half_update_rate( "r_shadow_half_update_rate", IsGameConsole() ? "1" : "0", 0, "Updates shadows at half the framerate", HalfUpdateRateCallback );
static void DeferredShadowToggleCallback( IConVar *var, const char *pOldValue, float flOldValue );
static void DeferredShadowDownsampleToggleCallback( IConVar *var, const char *pOldValue, float flOldValue );
ConVar r_shadow_deferred( "r_shadow_deferred", "0", FCVAR_CHEAT, "Toggle deferred shadow rendering", DeferredShadowToggleCallback );
static ConVar r_shadow_deferred_downsample( "r_shadow_deferred_downsample", "0", 0, "Toggle low-res deferred shadow rendering", DeferredShadowDownsampleToggleCallback );
static ConVar r_shadow_deferred_simd( "r_shadow_deferred_simd", "0" );
static ConVar r_shadow_debug_spew( "r_shadow_debug_spew", "0", FCVAR_CHEAT );
static ConVar r_flashlight_info( "r_flashlight_info", "0", 0, "Information about currently enabled flashlights" );
#if defined( _PS3 )
ConVar r_flashlightdepthtexture( "r_flashlightdepthtexture", "1" );
ConVar r_flashlightdepthreshigh( "r_flashlightdepthreshigh", "640" );
ConVar r_flashlightdepthres( "r_flashlightdepthres", "640" );
#elif defined( _X360 )
ConVar r_flashlightdepthtexture( "r_flashlightdepthtexture", "1" );
ConVar r_flashlightdepthreshigh( "r_flashlightdepthreshigh", "720" );
ConVar r_flashlightdepthres( "r_flashlightdepthres", "720" );
#else
ConVar r_flashlightdepthtexture( "r_flashlightdepthtexture", "1" );
ConVar r_flashlightdepthreshigh( "r_flashlightdepthreshigh", "2048" );
ConVar r_flashlightdepthres( "r_flashlightdepthres", "1024" );
#endif
#if defined( _GAMECONSOLE )
#define RTT_TEXTURE_SIZE_640
#endif
#ifdef _WIN32
#pragma warning( disable: 4701 )
#endif
//-----------------------------------------------------------------------------
// A texture allocator used to batch textures together
// At the moment, the implementation simply allocates blocks of max 256x256
// and each block stores an array of uniformly-sized textures
//-----------------------------------------------------------------------------
typedef unsigned short TextureHandle_t;
enum
{
INVALID_TEXTURE_HANDLE = (TextureHandle_t)~0
};
class CTextureAllocator
{
public:
// Initialize the allocator with something that knows how to refresh the bits
void Init();
void Shutdown();
// Resets the allocator
void Reset();
// Deallocates everything
void DeallocateAllTextures();
// Allocate, deallocate texture
TextureHandle_t AllocateTexture( int w, int h );
void DeallocateTexture( TextureHandle_t h );
// Mark texture as being used... (return true if re-render is needed)
bool UseTexture( TextureHandle_t h, bool bWillRedraw, float flArea );
bool HasValidTexture( TextureHandle_t h );
// Advance frame...
void AdvanceFrame();
// Get at the location of the texture
void GetTextureRect(TextureHandle_t handle, int& x, int& y, int& w, int& h );
// Get at the texture it's a part of
ITexture *GetTexture();
// Get at the total texture size.
void GetTotalTextureSize( int& w, int& h );
void DebugPrintCache( void );
void InitRenderTargets( void );
private:
typedef unsigned short FragmentHandle_t;
enum
{
INVALID_FRAGMENT_HANDLE = (FragmentHandle_t)~0,
#ifdef RTT_TEXTURE_SIZE_640
TEXTURE_PAGE_SIZE = 640,
MAX_TEXTURE_POWER = 7,
#else
TEXTURE_PAGE_SIZE = 1024,
MAX_TEXTURE_POWER = 8,
#endif
#if !defined( _GAMECONSOLE )
MIN_TEXTURE_POWER = 4,
#else
MIN_TEXTURE_POWER = 5, // per resolve requirements to ensure 32x32 aligned offsets
#endif
MAX_TEXTURE_SIZE = (1 << MAX_TEXTURE_POWER),
MIN_TEXTURE_SIZE = (1 << MIN_TEXTURE_POWER),
BLOCK_SIZE = MAX_TEXTURE_SIZE,
BLOCKS_PER_ROW = (TEXTURE_PAGE_SIZE / MAX_TEXTURE_SIZE),
BLOCK_COUNT = (BLOCKS_PER_ROW * BLOCKS_PER_ROW),
};
struct TextureInfo_t
{
FragmentHandle_t m_Fragment;
unsigned short m_Size;
unsigned short m_Power;
};
struct FragmentInfo_t
{
unsigned short m_Block;
unsigned short m_Index;
TextureHandle_t m_Texture;
// Makes sure we don't overflow
unsigned int m_FrameUsed;
};
struct BlockInfo_t
{
unsigned short m_FragmentPower;
};
struct Cache_t
{
unsigned short m_List;
};
// Adds a block worth of fragments to the LRU
void AddBlockToLRU( int block );
// Unlink fragment from cache
void UnlinkFragmentFromCache( Cache_t& cache, FragmentHandle_t fragment );
// Mark something as being used (MRU)..
void MarkUsed( FragmentHandle_t fragment );
// Mark something as being unused (LRU)..
void MarkUnused( FragmentHandle_t fragment );
// Disconnect texture from fragment
void DisconnectTextureFromFragment( FragmentHandle_t f );
// Returns the size of a particular fragment
int GetFragmentPower( FragmentHandle_t f ) const;
// Stores the actual texture we're writing into
CTextureReference m_TexturePage;
CUtlLinkedList< TextureInfo_t, TextureHandle_t > m_Textures;
CUtlMultiList< FragmentInfo_t, FragmentHandle_t > m_Fragments;
Cache_t m_Cache[MAX_TEXTURE_POWER+1];
BlockInfo_t m_Blocks[BLOCK_COUNT];
unsigned int m_CurrentFrame;
};
//-----------------------------------------------------------------------------
// Allocate/deallocate the texture page
//-----------------------------------------------------------------------------
void CTextureAllocator::Init()
{
for ( int i = 0; i <= MAX_TEXTURE_POWER; ++i )
{
m_Cache[i].m_List = m_Fragments.InvalidIndex();
}
InitRenderTargets();
}
void CTextureAllocator::InitRenderTargets( void )
{
#if !defined( _X360 )
// don't need depth buffer for shadows
m_TexturePage.InitRenderTarget( TEXTURE_PAGE_SIZE, TEXTURE_PAGE_SIZE, RT_SIZE_NO_CHANGE, IMAGE_FORMAT_ARGB8888, MATERIAL_RT_DEPTH_NONE, false, "_rt_Shadows" );
#else
// unfortunate explicit management required for this render target
// 32bpp edram is only largest shadow fragment, but resolved to actual shadow atlas
// because full-res 1024x1024 shadow buffer is too large for EDRAM
m_TexturePage.InitRenderTargetTexture( TEXTURE_PAGE_SIZE, TEXTURE_PAGE_SIZE, RT_SIZE_NO_CHANGE, IMAGE_FORMAT_ARGB8888, MATERIAL_RT_DEPTH_NONE, false, "_rt_Shadows" );
#ifdef RTT_TEXTURE_SIZE_640
// use 4x multisampling for smoother shadows
m_TexturePage.InitRenderTargetSurface( MAX_TEXTURE_SIZE, MAX_TEXTURE_SIZE, IMAGE_FORMAT_ARGB8888, false, RT_MULTISAMPLE_4_SAMPLES );
#else
// edram footprint is only 256x256x4 = 256K
m_TexturePage.InitRenderTargetSurface( MAX_TEXTURE_SIZE, MAX_TEXTURE_SIZE, IMAGE_FORMAT_ARGB8888, false );
#endif
// due to texture/surface size mismatch, ensure texture page is entirely cleared translucent
// otherwise border artifacts at edge of shadows due to pixel shader averaging of unwanted bits
// should also set m_bRenderTargetNeedsClear
m_TexturePage->ClearTexture( 0, 0, 0, 0 );
#endif
}
void CTextureAllocator::Shutdown()
{
m_TexturePage.Shutdown();
}
//-----------------------------------------------------------------------------
// Initialize the allocator with something that knows how to refresh the bits
//-----------------------------------------------------------------------------
void CTextureAllocator::Reset()
{
DeallocateAllTextures();
m_Textures.EnsureCapacity(256);
m_Fragments.EnsureCapacity(256);
// Set up the block sizes....
#ifdef RTT_TEXTURE_SIZE_640
// Going to 640x640 gives us roughly the same number of texture slots than the 1024x1024 texture
// and thus won't change cache thrashing patterns
m_Blocks[0].m_FragmentPower = MAX_TEXTURE_POWER-2;
m_Blocks[1].m_FragmentPower = MAX_TEXTURE_POWER-2;
m_Blocks[2].m_FragmentPower = MAX_TEXTURE_POWER-2;
m_Blocks[3].m_FragmentPower = MAX_TEXTURE_POWER-2;
m_Blocks[4].m_FragmentPower = MAX_TEXTURE_POWER-2;
m_Blocks[5].m_FragmentPower = MAX_TEXTURE_POWER-2;
m_Blocks[6].m_FragmentPower = MAX_TEXTURE_POWER-2;
m_Blocks[7].m_FragmentPower = MAX_TEXTURE_POWER-2;
m_Blocks[8].m_FragmentPower = MAX_TEXTURE_POWER-2;
m_Blocks[9].m_FragmentPower = MAX_TEXTURE_POWER-2; // 10 * 16 = 160
m_Blocks[10].m_FragmentPower = MAX_TEXTURE_POWER-1;
m_Blocks[11].m_FragmentPower = MAX_TEXTURE_POWER-1;
m_Blocks[12].m_FragmentPower = MAX_TEXTURE_POWER-1;
m_Blocks[13].m_FragmentPower = MAX_TEXTURE_POWER-1;
m_Blocks[14].m_FragmentPower = MAX_TEXTURE_POWER-1;
m_Blocks[15].m_FragmentPower = MAX_TEXTURE_POWER-1;
m_Blocks[16].m_FragmentPower = MAX_TEXTURE_POWER-1;
m_Blocks[17].m_FragmentPower = MAX_TEXTURE_POWER-1; // 8 * 4 = 32
m_Blocks[18].m_FragmentPower = MAX_TEXTURE_POWER; // 7
m_Blocks[19].m_FragmentPower = MAX_TEXTURE_POWER;
m_Blocks[20].m_FragmentPower = MAX_TEXTURE_POWER;
m_Blocks[21].m_FragmentPower = MAX_TEXTURE_POWER;
m_Blocks[22].m_FragmentPower = MAX_TEXTURE_POWER;
m_Blocks[23].m_FragmentPower = MAX_TEXTURE_POWER;
m_Blocks[24].m_FragmentPower = MAX_TEXTURE_POWER; // 199 slots total
#else
// FIXME: Improve heuristic?!?
#if !defined( _GAMECONSOLE )
m_Blocks[0].m_FragmentPower = MAX_TEXTURE_POWER-4; // 128 cells at ExE resolution
#else
m_Blocks[0].m_FragmentPower = MAX_TEXTURE_POWER-3; // 64 cells at DxD resolution
#endif
m_Blocks[1].m_FragmentPower = MAX_TEXTURE_POWER-3; // 64 cells at DxD resolution
m_Blocks[2].m_FragmentPower = MAX_TEXTURE_POWER-2; // 32 cells at CxC resolution
m_Blocks[3].m_FragmentPower = MAX_TEXTURE_POWER-2;
m_Blocks[4].m_FragmentPower = MAX_TEXTURE_POWER-1; // 24 cells at BxB resolution
m_Blocks[5].m_FragmentPower = MAX_TEXTURE_POWER-1;
m_Blocks[6].m_FragmentPower = MAX_TEXTURE_POWER-1;
m_Blocks[7].m_FragmentPower = MAX_TEXTURE_POWER-1;
m_Blocks[8].m_FragmentPower = MAX_TEXTURE_POWER-1;
m_Blocks[9].m_FragmentPower = MAX_TEXTURE_POWER-1;
m_Blocks[10].m_FragmentPower = MAX_TEXTURE_POWER; // 6 cells at AxA resolution
m_Blocks[11].m_FragmentPower = MAX_TEXTURE_POWER;
m_Blocks[12].m_FragmentPower = MAX_TEXTURE_POWER;
m_Blocks[13].m_FragmentPower = MAX_TEXTURE_POWER;
m_Blocks[14].m_FragmentPower = MAX_TEXTURE_POWER;
m_Blocks[15].m_FragmentPower = MAX_TEXTURE_POWER; // 190 slots total on 360
#endif
// Initialize the LRU
int i;
for ( i = 0; i <= MAX_TEXTURE_POWER; ++i )
{
m_Cache[i].m_List = m_Fragments.CreateList();
}
// Now that the block sizes are allocated, create LRUs for the various block sizes
for ( i = 0; i < BLOCK_COUNT; ++i)
{
// Initialize LRU
AddBlockToLRU( i );
}
m_CurrentFrame = 0;
}
void CTextureAllocator::DeallocateAllTextures()
{
m_Textures.Purge();
m_Fragments.Purge();
for ( int i = 0; i <= MAX_TEXTURE_POWER; ++i )
{
m_Cache[i].m_List = m_Fragments.InvalidIndex();
}
}
//-----------------------------------------------------------------------------
// Dump the state of the cache to debug out
//-----------------------------------------------------------------------------
void CTextureAllocator::DebugPrintCache( void )
{
// For each fragment
int nNumFragments = m_Fragments.TotalCount();
int nNumInvalidFragments = 0;
Warning("Fragments (%d):\n===============\n", nNumFragments);
for ( int f = 0; f < nNumFragments; f++ )
{
if ( ( m_Fragments[f].m_FrameUsed != 0 ) && ( m_Fragments[f].m_Texture != INVALID_TEXTURE_HANDLE ) )
Warning("Fragment %d, Block: %d, Index: %d, Texture: %d Frame Used: %d\n", f, m_Fragments[f].m_Block, m_Fragments[f].m_Index, m_Fragments[f].m_Texture, m_Fragments[f].m_FrameUsed );
else
nNumInvalidFragments++;
}
Warning("Invalid Fragments: %d\n", nNumInvalidFragments);
// for ( int c = 0; c <= MAX_TEXTURE_POWER; ++c )
// {
// Warning("Cache Index (%d)\n", m_Cache[c].m_List);
// }
}
//-----------------------------------------------------------------------------
// Adds a block worth of fragments to the LRU
//-----------------------------------------------------------------------------
void CTextureAllocator::AddBlockToLRU( int block )
{
int power = m_Blocks[block].m_FragmentPower;
int size = (1 << power);
// Compute the number of fragments in this block
int fragmentCount = MAX_TEXTURE_SIZE / size;
fragmentCount *= fragmentCount;
// For each fragment, indicate which block it's a part of (and the index)
// and then stick in at the top of the LRU
while (--fragmentCount >= 0 )
{
FragmentHandle_t f = m_Fragments.Alloc( );
m_Fragments[f].m_Block = block;
m_Fragments[f].m_Index = fragmentCount;
m_Fragments[f].m_Texture = INVALID_TEXTURE_HANDLE;
m_Fragments[f].m_FrameUsed = 0xFFFFFFFF;
m_Fragments.LinkToHead( m_Cache[power].m_List, f );
}
}
//-----------------------------------------------------------------------------
// Unlink fragment from cache
//-----------------------------------------------------------------------------
void CTextureAllocator::UnlinkFragmentFromCache( Cache_t& cache, FragmentHandle_t fragment )
{
m_Fragments.Unlink( cache.m_List, fragment);
}
//-----------------------------------------------------------------------------
// Mark something as being used (MRU)..
//-----------------------------------------------------------------------------
void CTextureAllocator::MarkUsed( FragmentHandle_t fragment )
{
int block = m_Fragments[fragment].m_Block;
int power = m_Blocks[block].m_FragmentPower;
// Hook it at the end of the LRU
Cache_t& cache = m_Cache[power];
m_Fragments.LinkToTail( cache.m_List, fragment );
m_Fragments[fragment].m_FrameUsed = m_CurrentFrame;
}
//-----------------------------------------------------------------------------
// Mark something as being unused (LRU)..
//-----------------------------------------------------------------------------
void CTextureAllocator::MarkUnused( FragmentHandle_t fragment )
{
int block = m_Fragments[fragment].m_Block;
int power = m_Blocks[block].m_FragmentPower;
// Hook it at the end of the LRU
Cache_t& cache = m_Cache[power];
m_Fragments.LinkToHead( cache.m_List, fragment );
}
//-----------------------------------------------------------------------------
// Allocate, deallocate texture
//-----------------------------------------------------------------------------
TextureHandle_t CTextureAllocator::AllocateTexture( int w, int h )
{
// Implementational detail for now
Assert( w == h );
// Clamp texture size
if (w < MIN_TEXTURE_SIZE)
w = MIN_TEXTURE_SIZE;
else if (w > MAX_TEXTURE_SIZE)
w = MAX_TEXTURE_SIZE;
TextureHandle_t handle = m_Textures.AddToTail();
m_Textures[handle].m_Fragment = INVALID_FRAGMENT_HANDLE;
m_Textures[handle].m_Size = w;
// Find the power of two
int power = 0;
int size = 1;
while(size < w)
{
size <<= 1;
++power;
}
Assert( size == w );
m_Textures[handle].m_Power = power;
return handle;
}
void CTextureAllocator::DeallocateTexture( TextureHandle_t h )
{
// Warning("Beginning of DeallocateTexture\n");
// DebugPrintCache();
if (m_Textures[h].m_Fragment != INVALID_FRAGMENT_HANDLE)
{
MarkUnused(m_Textures[h].m_Fragment);
m_Fragments[m_Textures[h].m_Fragment].m_FrameUsed = 0xFFFFFFFF; // non-zero frame
DisconnectTextureFromFragment( m_Textures[h].m_Fragment );
}
m_Textures.Remove(h);
// Warning("End of DeallocateTexture\n");
// DebugPrintCache();
}
//-----------------------------------------------------------------------------
// Disconnect texture from fragment
//-----------------------------------------------------------------------------
void CTextureAllocator::DisconnectTextureFromFragment( FragmentHandle_t f )
{
// Warning( "Beginning of DisconnectTextureFromFragment\n" );
// DebugPrintCache();
FragmentInfo_t& info = m_Fragments[f];
if (info.m_Texture != INVALID_TEXTURE_HANDLE)
{
m_Textures[info.m_Texture].m_Fragment = INVALID_FRAGMENT_HANDLE;
info.m_Texture = INVALID_TEXTURE_HANDLE;
}
// Warning( "End of DisconnectTextureFromFragment\n" );
// DebugPrintCache();
}
//-----------------------------------------------------------------------------
// Do we have a valid texture assigned?
//-----------------------------------------------------------------------------
bool CTextureAllocator::HasValidTexture( TextureHandle_t h )
{
TextureInfo_t& info = m_Textures[h];
FragmentHandle_t currentFragment = info.m_Fragment;
return (currentFragment != INVALID_FRAGMENT_HANDLE);
}
//-----------------------------------------------------------------------------
// Mark texture as being used...
//-----------------------------------------------------------------------------
bool CTextureAllocator::UseTexture( TextureHandle_t h, bool bWillRedraw, float flArea )
{
// Warning( "Top of UseTexture\n" );
// DebugPrintCache();
TextureInfo_t& info = m_Textures[h];
// spin up to the best fragment size
int nDesiredPower = MIN_TEXTURE_POWER;
int nDesiredWidth = MIN_TEXTURE_SIZE;
while ( (nDesiredWidth * nDesiredWidth) < flArea )
{
if ( nDesiredPower >= info.m_Power )
{
nDesiredPower = info.m_Power;
break;
}
++nDesiredPower;
nDesiredWidth <<= 1;
}
// If we've got a valid fragment for this texture, no worries!
int nCurrentPower = -1;
FragmentHandle_t currentFragment = info.m_Fragment;
if (currentFragment != INVALID_FRAGMENT_HANDLE)
{
// If the current fragment is at or near the desired power, we're done
nCurrentPower = GetFragmentPower(info.m_Fragment);
Assert( nCurrentPower <= info.m_Power );
bool bShouldKeepTexture = (!bWillRedraw) && (nDesiredPower < 8) && (nDesiredPower - nCurrentPower <= 1);
if ((nCurrentPower == nDesiredPower) || bShouldKeepTexture)
{
// Move to the back of the LRU
MarkUsed( currentFragment );
return false;
}
}
// Warning( "\n\nUseTexture B\n" );
// DebugPrintCache();
// Grab the LRU fragment from the appropriate cache
// If that fragment is connected to a texture, disconnect it.
int power = nDesiredPower;
FragmentHandle_t f = INVALID_FRAGMENT_HANDLE;
bool done = false;
while (!done && power >= 0)
{
f = m_Fragments.Head( m_Cache[power].m_List );
// This represents an overflow condition (used too many textures of
// the same size in a single frame). It that happens, just use a texture
// of lower res.
if ( (f != m_Fragments.InvalidIndex()) && (m_Fragments[f].m_FrameUsed != m_CurrentFrame) )
{
done = true;
}
else
{
--power;
}
}
// Warning( "\n\nUseTexture C\n" );
// DebugPrintCache();
// Ok, lets see if we're better off than we were...
if (currentFragment != INVALID_FRAGMENT_HANDLE)
{
if (power <= nCurrentPower)
{
// Oops... we're not. Let's leave well enough alone
// Move to the back of the LRU
MarkUsed( currentFragment );
return false;
}
else
{
// Clear out the old fragment
DisconnectTextureFromFragment(currentFragment);
}
}
if ( f == INVALID_FRAGMENT_HANDLE )
{
return false;
}
// Disconnect existing texture from this fragment (if necessary)
DisconnectTextureFromFragment(f);
// Connnect new texture to this fragment
info.m_Fragment = f;
m_Fragments[f].m_Texture = h;
// Move to the back of the LRU
MarkUsed( f );
// Indicate we need a redraw
return true;
}
//-----------------------------------------------------------------------------
// Returns the size of a particular fragment
//-----------------------------------------------------------------------------
int CTextureAllocator::GetFragmentPower( FragmentHandle_t f ) const
{
return m_Blocks[m_Fragments[f].m_Block].m_FragmentPower;
}
//-----------------------------------------------------------------------------
// Advance frame...
//-----------------------------------------------------------------------------
void CTextureAllocator::AdvanceFrame()
{
// Be sure that this is called as infrequently as possible (i.e. once per frame,
// NOT once per view) to prevent cache thrash when rendering multiple views in a single frame
m_CurrentFrame++;
}
//-----------------------------------------------------------------------------
// Prepare to render into texture...
//-----------------------------------------------------------------------------
ITexture* CTextureAllocator::GetTexture()
{
return m_TexturePage;
}
//-----------------------------------------------------------------------------
// Get at the total texture size.
//-----------------------------------------------------------------------------
void CTextureAllocator::GetTotalTextureSize( int& w, int& h )
{
w = h = TEXTURE_PAGE_SIZE;
}
//-----------------------------------------------------------------------------
// Returns the rectangle the texture lives in..
//-----------------------------------------------------------------------------
void CTextureAllocator::GetTextureRect(TextureHandle_t handle, int& x, int& y, int& w, int& h )
{
TextureInfo_t& info = m_Textures[handle];
Assert( info.m_Fragment != INVALID_FRAGMENT_HANDLE );
// Compute the position of the fragment in the page
FragmentInfo_t& fragment = m_Fragments[info.m_Fragment];
int blockY = fragment.m_Block / BLOCKS_PER_ROW;
int blockX = fragment.m_Block - blockY * BLOCKS_PER_ROW;
int fragmentSize = (1 << m_Blocks[fragment.m_Block].m_FragmentPower);
int fragmentsPerRow = BLOCK_SIZE / fragmentSize;
int fragmentY = fragment.m_Index / fragmentsPerRow;
int fragmentX = fragment.m_Index - fragmentY * fragmentsPerRow;
x = blockX * BLOCK_SIZE + fragmentX * fragmentSize;
y = blockY * BLOCK_SIZE + fragmentY * fragmentSize;
w = fragmentSize;
h = fragmentSize;
}
//-----------------------------------------------------------------------------
// Defines how big of a shadow texture we should be making per caster...
//-----------------------------------------------------------------------------
#define TEXEL_SIZE_PER_CASTER_SIZE 2.0f
#define MAX_FALLOFF_AMOUNT 240
#define MAX_CLIP_PLANE_COUNT 4
#define SHADOW_CULL_TOLERANCE 0.5f
static ConVar r_shadows( "r_shadows", "1" ); // hook into engine's cvars..
static ConVar r_shadowmaxrendered("r_shadowmaxrendered", "32");
static ConVar r_shadows_gamecontrol( "r_shadows_gamecontrol", "-1", FCVAR_CHEAT ); // hook into engine's cvars..
#ifdef _PS3
uint32 g_ps3_ShadowDepth_TextureCache;
#endif
//-----------------------------------------------------------------------------
// The class responsible for dealing with shadows on the client side
// Oh, and let's take a moment and notice how happy Robin and John must be
// owing to the lack of space between this lovely comment and the class name =)
//-----------------------------------------------------------------------------
class CClientShadowMgr : public IClientShadowMgr
{
friend bool ClientShadowMgrAcquireShadowDepthTexture( CTextureReference *pDummyColorTexture, CTextureReference *pShadowDepthTexture );
public:
CClientShadowMgr();
virtual char const *Name() { return "CCLientShadowMgr"; }
// Inherited from IClientShadowMgr
virtual bool Init();
virtual void InitRenderTargets();
virtual void PostInit() {}
virtual void Shutdown();
virtual void LevelInitPreEntity();
virtual void LevelInitPostEntity() {}
virtual void LevelShutdownPreEntity() {}
virtual void LevelShutdownPostEntity();
virtual bool IsPerFrame() { return true; }
virtual void PreRender() {}
virtual void Update( float frametime ) { }
virtual void PostRender() {}
virtual void OnSave() {}
virtual void OnRestore() {}
virtual void SafeRemoveIfDesired() {}
virtual void ReprojectShadows();
virtual ClientShadowHandle_t CreateShadow( ClientEntityHandle_t entity, int nEntIndex, int flags, CBitVec< MAX_SPLITSCREEN_PLAYERS > *pSplitScreenBits = NULL );
virtual void DestroyShadow( ClientShadowHandle_t handle );
// Create flashlight (projected texture light source)
virtual ClientShadowHandle_t CreateFlashlight( const FlashlightState_t &lightState );
virtual void UpdateFlashlightState( ClientShadowHandle_t shadowHandle, const FlashlightState_t &lightState );
virtual void DestroyFlashlight( ClientShadowHandle_t shadowHandle );
// Create simple projected texture. it is not a light or a shadow, but this system does most of the work already for it
virtual ClientShadowHandle_t CreateProjection( const FlashlightState_t &lightState );
virtual void UpdateProjectionState( ClientShadowHandle_t shadowHandle, const FlashlightState_t &lightState );
virtual void DestroyProjection( ClientShadowHandle_t shadowHandle );
// Update a shadow
virtual void UpdateProjectedTexture( ClientShadowHandle_t handle, bool force );
void ComputeBoundingSphere( IClientRenderable* pRenderable, Vector& origin, float& radius );
virtual void AddToDirtyShadowList( ClientShadowHandle_t handle, bool bForce );
virtual void AddToDirtyShadowList( IClientRenderable *pRenderable, bool force );
// Marks the render-to-texture shadow as needing to be re-rendered
virtual void MarkRenderToTextureShadowDirty( ClientShadowHandle_t handle );
// deals with shadows being added to shadow receivers
void AddShadowToReceiver( ClientShadowHandle_t handle,
IClientRenderable* pRenderable, ShadowReceiver_t type );
// deals with shadows being added to shadow receivers
void RemoveAllShadowsFromReceiver( IClientRenderable* pRenderable, ShadowReceiver_t type );
// Re-renders all shadow textures for shadow casters that lie in the leaf list
void ComputeShadowTextures( const CViewSetup &view, int leafCount, WorldListLeafData_t* pLeafList );
// Kicks off rendering into shadow depth maps (if any)
void ComputeShadowDepthTextures( const CViewSetup &view, bool bSetup = false );
// Kicks off rendering of volumetrics for the flashlights
void DrawVolumetrics( const CViewSetup &view );
void GetFrustumExtents( ClientShadowHandle_t handle, Vector &vecMin, Vector &vecMax );
// Frees shadow depth textures for use in subsequent view/frame
void FreeShadowDepthTextures();
// Returns the shadow texture
ITexture* GetShadowTexture( unsigned short h );
// Returns shadow information
const ShadowInfo_t& GetShadowInfo( ClientShadowHandle_t h );
// Renders the shadow texture to screen...
void RenderShadowTexture( int w, int h );
// Sets the shadow direction
virtual void SetShadowDirection( const Vector& dir );
const Vector &GetShadowDirection() const;
// Sets the shadow color
virtual void SetShadowColor( unsigned char r, unsigned char g, unsigned char b );
void GetShadowColor( unsigned char *r, unsigned char *g, unsigned char *b ) const;
// Sets the shadow distance
virtual void SetShadowDistance( float flMaxDistance );
float GetShadowDistance( ) const;
// Sets the screen area at which blobby shadows are always used
virtual void SetShadowBlobbyCutoffArea( float flMinArea );
float GetBlobbyCutoffArea( ) const;
// Set the darkness falloff bias
virtual void SetFalloffBias( ClientShadowHandle_t handle, unsigned char ucBias );
void RestoreRenderState();
// Computes a rough bounding box encompassing the volume of the shadow
void ComputeShadowBBox( IClientRenderable *pRenderable, ClientShadowHandle_t shadowHandle, const Vector &vecAbsCenter, float flRadius, Vector *pAbsMins, Vector *pAbsMaxs );
bool WillParentRenderBlobbyShadow( IClientRenderable *pRenderable );
// Are we the child of a shadow with render-to-texture?
bool ShouldUseParentShadow( IClientRenderable *pRenderable );
void SetShadowsDisabled( bool bDisabled )
{
r_shadows_gamecontrol.SetValue( bDisabled != 1 );
}
// Toggle shadow casting from world light sources
virtual void SetShadowFromWorldLightsEnabled( bool bEnable );
void SuppressShadowFromWorldLights( bool bSuppress );
bool IsShadowingFromWorldLights() const { return m_bShadowFromWorldLights && !m_bSuppressShadowFromWorldLights; }
virtual void DrawDeferredShadows( const CViewSetup &view, int leafCount, WorldListLeafData_t* pLeafList );
virtual void UpdateSplitscreenLocalPlayerShadowSkip();
private:
enum
{
SHADOW_FLAGS_TEXTURE_DIRTY = (CLIENT_SHADOW_FLAGS_LAST_FLAG << 1),
SHADOW_FLAGS_BRUSH_MODEL = (CLIENT_SHADOW_FLAGS_LAST_FLAG << 2),
SHADOW_FLAGS_USING_LOD_SHADOW = (CLIENT_SHADOW_FLAGS_LAST_FLAG << 3),
SHADOW_FLAGS_LIGHT_WORLD = (CLIENT_SHADOW_FLAGS_LAST_FLAG << 4),
};
struct ClientShadow_t
{
ClientEntityHandle_t m_Entity;
ShadowHandle_t m_ShadowHandle;
ClientLeafShadowHandle_t m_ClientLeafShadowHandle;
unsigned short m_Flags;
VMatrix m_WorldToShadow;
Vector2D m_WorldSize;
Vector m_ShadowDir;
Vector m_LastOrigin;
QAngle m_LastAngles;
Vector m_CurrentLightPos; // When shadowing from local lights, stores the position of the currently shadowing light
Vector m_TargetLightPos; // When shadowing from local lights, stores the position of the new shadowing light
float m_LightPosLerp; // Lerp progress when going from current to target light
TextureHandle_t m_ShadowTexture;
CTextureReference m_ShadowDepthTexture;
int m_nRenderFrame;
EHANDLE m_hTargetEntity;
bool m_bUseSplitScreenBits;
CBitVec< MAX_SPLITSCREEN_PLAYERS > m_SplitScreenBits;
int m_nLastUpdateFrame;
// Extra info for deferred shadows.
// FIXME: This data is also stored in CShadowMgr in the engine.
int m_FalloffBias;
float m_MaxDist;
float m_FalloffStart;
Vector2D m_TexCoordOffset;
Vector2D m_TexCoordScale;
VMatrix m_WorldToTexture;
int m_nSplitscreenOwner;
};
private:
friend void DeferredShadowToggleCallback( IConVar *var, const char *pOldValue, float flOldValue );
friend void DeferredShadowDownsampleToggleCallback( IConVar *var, const char *pOldValue, float flOldValue );
friend void HalfUpdateRateCallback( IConVar *var, const char *pOldValue, float flOldValue );
// Shadow update functions
void UpdateStudioShadow( IClientRenderable *pRenderable, ClientShadowHandle_t handle );
void UpdateBrushShadow( IClientRenderable *pRenderable, ClientShadowHandle_t handle );
void UpdateShadow( ClientShadowHandle_t handle, bool force );
// Updates shadow cast direction when shadowing from world lights
void UpdateShadowDirectionFromLocalLightSource( ClientShadowHandle_t shadowHandle );
// Gets the entity whose shadow this shadow will render into
IClientRenderable *GetParentShadowEntity( ClientShadowHandle_t handle );
// Adds the child bounds to the bounding box
void AddChildBounds( matrix3x4_t &matWorldToBBox, IClientRenderable* pParent, Vector &vecMins, Vector &vecMaxs );
// Compute a bounds for the entity + children
void ComputeHierarchicalBounds( IClientRenderable *pRenderable, Vector &vecMins, Vector &vecMaxs );
// Builds matrices transforming from world space to shadow space
void BuildGeneralWorldToShadowMatrix( VMatrix& matWorldToShadow,
const Vector& origin, const Vector& dir, const Vector& xvec, const Vector& yvec );
void BuildWorldToShadowMatrix( VMatrix& matWorldToShadow, const Vector& origin, const Quaternion& quatOrientation );
void BuildPerspectiveWorldToFlashlightMatrix( VMatrix& matWorldToShadow, const FlashlightState_t &flashlightState );
void BuildOrthoWorldToFlashlightMatrix( VMatrix& matWorldToShadow, const FlashlightState_t &flashlightState );
// Update a shadow
void UpdateProjectedTextureInternal( ClientShadowHandle_t handle, bool force );
// Compute the shadow origin and attenuation start distance
float ComputeLocalShadowOrigin( IClientRenderable* pRenderable,
const Vector& mins, const Vector& maxs, const Vector& localShadowDir, float backupFactor, Vector& origin );
// Remove a shadow from the dirty list
void RemoveShadowFromDirtyList( ClientShadowHandle_t handle );
// NOTE: this will ONLY return SHADOWS_NONE, SHADOWS_SIMPLE, or SHADOW_RENDER_TO_TEXTURE.
ShadowType_t GetActualShadowCastType( ClientShadowHandle_t handle ) const;
ShadowType_t GetActualShadowCastType( IClientRenderable *pRenderable ) const;
// Builds a simple blobby shadow
void BuildOrthoShadow( IClientRenderable* pRenderable, ClientShadowHandle_t handle, const Vector& mins, const Vector& maxs);
// Builds a more complex shadow...
void BuildRenderToTextureShadow( IClientRenderable* pRenderable,
ClientShadowHandle_t handle, const Vector& mins, const Vector& maxs );
// Build a projected-texture flashlight
void BuildFlashlight( ClientShadowHandle_t handle );
// Does all the lovely stuff we need to do to have render-to-texture shadows
void SetupRenderToTextureShadow( ClientShadowHandle_t h );
void CleanUpRenderToTextureShadow( ClientShadowHandle_t h );
// Compute the extra shadow planes
void ComputeExtraClipPlanes( IClientRenderable* pRenderable,
ClientShadowHandle_t handle, const Vector* vec,
const Vector& mins, const Vector& maxs, const Vector& localShadowDir );
// Set extra clip planes related to shadows...
void ClearExtraClipPlanes( ClientShadowHandle_t h );
void AddExtraClipPlane( ClientShadowHandle_t h, const Vector& normal, float dist );
// Cull if the origin is on the wrong side of a shadow clip plane....
bool CullReceiver( ClientShadowHandle_t handle, IClientRenderable* pRenderable, IClientRenderable* pSourceRenderable );
bool ComputeSeparatingPlane( IClientRenderable* pRend1, IClientRenderable* pRend2, cplane_t* pPlane );
// Causes all shadows to be re-updated
void UpdateAllShadows();
void RemoveAllShadowDecals();
// One of these gets called with every shadow that potentially will need to re-render
bool DrawRenderToTextureShadow( int nSlot, unsigned short clientShadowHandle, float flArea );
void DrawRenderToTextureShadowLOD( int nSlot, unsigned short clientShadowHandle );
// Draws all children shadows into our own
bool DrawShadowHierarchy( IClientRenderable *pRenderable, const ClientShadow_t &shadow, bool bChild = false );
// Setup stage for threading
bool BuildSetupListForRenderToTextureShadow( unsigned short clientShadowHandle, float flArea );
bool BuildSetupShadowHierarchy( IClientRenderable *pRenderable, const ClientShadow_t &shadow, bool bChild = false );
// Computes + sets the render-to-texture texcoords
void SetRenderToTextureShadowTexCoords( ShadowHandle_t handle, int x, int y, int w, int h );
void SetRenderToTextureShadowTexCoords( ClientShadow_t& shadow, int x, int y, int w, int h );
// Visualization....
void DrawRenderToTextureDebugInfo( IClientRenderable* pRenderable, const Vector& mins, const Vector& maxs );
// Advance frame
void AdvanceFrame();
// Returns renderable-specific shadow info
float GetShadowDistance( IClientRenderable *pRenderable ) const;
const Vector &GetShadowDirection( IClientRenderable *pRenderable ) const;
const Vector &GetShadowDirection( ClientShadowHandle_t shadowHandle ) const;
// Initialize, shutdown render-to-texture shadows
void InitDepthTextureShadows();
void ShutdownDepthTextureShadows();
// Initialize, shutdown render-to-texture shadows
void InitRenderToTextureShadows();
void ShutdownRenderToTextureShadows();
// Initialize, shutdown deferred render-to-texture shadows
void InitDeferredShadows();
void ShutdownDeferredShadows();
void ShutdownRenderTargets( void );
static bool ShadowHandleCompareFunc( const ClientShadowHandle_t& lhs, const ClientShadowHandle_t& rhs )
{
return lhs < rhs;
}
ClientShadowHandle_t CreateProjectedTexture( ClientEntityHandle_t entity, int nEntIndex, int flags, CBitVec< MAX_SPLITSCREEN_PLAYERS > *pSplitScreenBits, bool bShareProjectedTextureBetweenSplitscreenPlayers );
// Lock down the usage of a shadow depth texture...must be unlocked use on subsequent views / frames
bool LockShadowDepthTexture( CTextureReference *shadowDepthTexture, int nStartTexture );
void UnlockAllShadowDepthTextures();
// Set and clear flashlight target renderable
void SetFlashlightTarget( ClientShadowHandle_t shadowHandle, EHANDLE targetEntity );
// Set flashlight light world flag
void SetFlashlightLightWorld( ClientShadowHandle_t shadowHandle, bool bLightWorld );
bool IsFlashlightTarget( ClientShadowHandle_t shadowHandle, IClientRenderable *pRenderable );
// Builds a list of active shadows requiring shadow depth renders
int BuildActiveShadowDepthList( const CViewSetup &viewSetup, int nMaxDepthShadows, ClientShadowHandle_t *pActiveDepthShadows, int &nNumHighRes );
// Builds a list of active flashlights
int BuildActiveFlashlightList( const CViewSetup &viewSetup, int nMaxFlashlights, ClientShadowHandle_t *pActiveFlashlights );
// Sets the view's active flashlight render state
void SetViewFlashlightState( int nActiveFlashlightCount, ClientShadowHandle_t* pActiveFlashlights );
// Draw flashlight wireframe using debug overlay
void DrawFrustum( const Vector &vOrigin, const VMatrix &matWorldToFlashlight );
// Draw uberlight rig in wireframe using debug overlay
void DrawUberlightRig( const Vector &vOrigin, const VMatrix &matWorldToFlashlight, FlashlightState_t state );
// Called from PreRender to work through the dirty shadow set
void UpdateDirtyShadows();
void UpdateDirtyShadowsHalfRate();
void UpdateDirtyShadow( ClientShadowHandle_t handle );
void FlushLeftOverDirtyShadows();
void QueueShadowForDestruction( ClientShadowHandle_t handle );
void DestroyQueuedShadows();
// Deferred RTT shadow rendering support
static void BuildCubeWithDegenerateEdgeQuads( CMeshBuilder& meshBuilder, const matrix3x4_t& objToWorld, const VMatrix& projToShadow, const CClientShadowMgr::ClientShadow_t& shadow );
bool SetupDeferredShadow( const ClientShadow_t& shadow, const Vector& camPos, matrix3x4_t* pObjToWorldMat ) const;
void DownsampleDepthBuffer( IMatRenderContext* pRenderContext, const VMatrix& invViewProjMat );
void CompositeDeferredShadows( IMatRenderContext* pRenderContext );
static void ComputeFalloffInfo( const ClientShadow_t& shadow, Vector* pShadowFalloffParams );
private:
Vector m_SimpleShadowDir;
color32 m_AmbientLightColor;
CMaterialReference m_SimpleShadow;
CMaterialReference m_RenderShadow;
CMaterialReference m_RenderModelShadow;
CMaterialReference m_RenderDeferredShadowMat;
CMaterialReference m_RenderDeferredSimpleShadowMat;
CTextureReference m_DummyColorTexture;
CUtlLinkedList< ClientShadow_t, ClientShadowHandle_t > m_Shadows;
CTextureAllocator m_ShadowAllocator;
bool m_RenderToTextureActive;
bool m_bRenderTargetNeedsClear;
bool m_bUpdatingDirtyShadows;
float m_flShadowCastDist;
float m_flMinShadowArea;
typedef CUtlRBTree< ClientShadowHandle_t, unsigned short > ClientShadowHandleSet;
ClientShadowHandleSet m_DirtyShadows;
ClientShadowHandleSet m_DirtyShadowsLeftOver; // shadows left over to update from previous frame
CUtlVector< ClientShadowHandle_t > m_TransparentShadows;
CUtlVector< ClientShadowHandle_t > m_shadowsToDestroy;
int m_nPrevFrameCount;
// These members maintain current state of depth texturing (size and global active state)
// If either changes in a frame, PreRender() will catch it and do the appropriate allocation, deallocation or reallocation
bool m_bDepthTextureActive;
int m_nDepthTextureResolution; // Assume square (height == width)
int m_nDepthTextureResolutionHigh; // Assume square (height == width)
int m_nLowResStart; // Place in the shadow render target where the low res shadows start
bool m_bDepthTexturesAllocated;
uint32 m_uiDepthTextureCache;
CUtlVector< CTextureReference > m_DepthTextureCache;
CUtlVector< bool > m_DepthTextureCacheLocks;
int m_nMaxDepthTextureShadows;
bool m_bShadowFromWorldLights;
bool m_bSuppressShadowFromWorldLights;
friend class CVisibleShadowList;
friend class CVisibleShadowFrustumList;
CTextureReference m_downSampledNormals;
CTextureReference m_downSampledDepth;
void CalculateRenderTargetsAndSizes( void );
};
//-----------------------------------------------------------------------------
// Singleton
//-----------------------------------------------------------------------------
static CClientShadowMgr s_ClientShadowMgr;
IClientShadowMgr* g_pClientShadowMgr = &s_ClientShadowMgr;
//-----------------------------------------------------------------------------
// Builds a list of potential shadows that lie within our PVS + view frustum
//-----------------------------------------------------------------------------
struct VisibleShadowInfo_t
{
ClientShadowHandle_t m_hShadow;
float m_flArea;
Vector m_vecAbsCenter;
};
class CVisibleShadowList : public IClientLeafShadowEnum
{
public:
CVisibleShadowList();
int FindShadows( const CViewSetup *pView, int nLeafCount, WorldListLeafData_t *pLeafList );
int GetVisibleShadowCount() const;
int GetVisibleBlobbyShadowCount() const;
const VisibleShadowInfo_t &GetVisibleShadow( int i ) const;
const VisibleShadowInfo_t &GetVisibleBlobbyShadow( int i ) const;
private:
void EnumShadow( unsigned short clientShadowHandle );
float ComputeScreenArea( const Vector &vecCenter, float r ) const;
void PrioritySort();
CUtlVector<VisibleShadowInfo_t> m_ShadowsInView;
CUtlVector<VisibleShadowInfo_t> m_BlobbyShadowsInView;
CUtlVector<int> m_PriorityIndex;
};
//-----------------------------------------------------------------------------
// Singleton instances of shadow and shadow frustum lists
//-----------------------------------------------------------------------------
static CVisibleShadowList s_VisibleShadowList;
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
static CUtlVector<C_BaseAnimating *> s_NPCShadowBoneSetups;
static CUtlVector<C_BaseAnimating *> s_NonNPCShadowBoneSetups;
//-----------------------------------------------------------------------------
// CVisibleShadowList - Constructor and Accessors
//-----------------------------------------------------------------------------
CVisibleShadowList::CVisibleShadowList() : m_ShadowsInView( 0, 64 ), m_PriorityIndex( 0, 64 )
{
}
int CVisibleShadowList::GetVisibleShadowCount() const
{
return m_ShadowsInView.Count();
}
const VisibleShadowInfo_t &CVisibleShadowList::GetVisibleShadow( int i ) const
{
return m_ShadowsInView[m_PriorityIndex[i]];
}
int CVisibleShadowList::GetVisibleBlobbyShadowCount() const
{
return m_BlobbyShadowsInView.Count();
}
const VisibleShadowInfo_t &CVisibleShadowList::GetVisibleBlobbyShadow( int i ) const
{
return m_BlobbyShadowsInView[i];
}
//-----------------------------------------------------------------------------
// CVisibleShadowList - Computes approximate screen area of the shadow
//-----------------------------------------------------------------------------
float CVisibleShadowList::ComputeScreenArea( const Vector &vecCenter, float r ) const
{
CMatRenderContextPtr pRenderContext( materials );
float flScreenDiameter = pRenderContext->ComputePixelDiameterOfSphere( vecCenter, r );
return flScreenDiameter * flScreenDiameter;
}
//-----------------------------------------------------------------------------
// CVisibleShadowList - Visits every shadow in the list of leaves
//-----------------------------------------------------------------------------
void CVisibleShadowList::EnumShadow( unsigned short clientShadowHandle )
{
CClientShadowMgr::ClientShadow_t& shadow = s_ClientShadowMgr.m_Shadows[clientShadowHandle];
// Don't bother if we rendered it this frame, no matter which view it was rendered for
if ( shadow.m_nRenderFrame == gpGlobals->framecount )
return;
// Don't bother with flashlights
if ( ( shadow.m_Flags & ( SHADOW_FLAGS_FLASHLIGHT | SHADOW_FLAGS_SIMPLE_PROJECTION )) != 0 )
return;
// We don't need to bother with it if it's not render-to-texture
ShadowType_t shadowType = s_ClientShadowMgr.GetActualShadowCastType( clientShadowHandle );
if ( shadowType != SHADOWS_RENDER_TO_TEXTURE && shadowType != SHADOWS_SIMPLE )
return;
// Don't bother with it if the shadow is totally transparent
if ( shadow.m_FalloffBias == 255 )
return;
IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( shadow.m_Entity );
Assert( pRenderable );
// Don't bother with children of hierarchy; they will be drawn with their parents
if ( s_ClientShadowMgr.ShouldUseParentShadow( pRenderable ) || s_ClientShadowMgr.WillParentRenderBlobbyShadow( pRenderable ) )
return;
// Compute a sphere surrounding the shadow
// FIXME: This doesn't account for children of hierarchy... too bad!
Vector vecAbsCenter;
float flRadius;
s_ClientShadowMgr.ComputeBoundingSphere( pRenderable, vecAbsCenter, flRadius );
// Compute a box surrounding the shadow
Vector vecAbsMins, vecAbsMaxs;
s_ClientShadowMgr.ComputeShadowBBox( pRenderable, clientShadowHandle, vecAbsCenter, flRadius, &vecAbsMins, &vecAbsMaxs );
// FIXME: Add distance check here?
// Make sure it's in the frustum. If it isn't it's not interesting
if (engine->CullBox( vecAbsMins, vecAbsMaxs ))
return;
if ( shadowType == SHADOWS_RENDER_TO_TEXTURE )
{
int i = m_ShadowsInView.AddToTail( );
VisibleShadowInfo_t &info = m_ShadowsInView[i];
info.m_hShadow = clientShadowHandle;
info.m_flArea = ComputeScreenArea( vecAbsCenter, flRadius );
// Har, har. When water is rendering (or any multipass technique),
// we may well initially render from a viewpoint which doesn't include this shadow.
// That doesn't mean we shouldn't check it again though. Sucks that we need to compute
// the sphere + bbox multiply times though.
shadow.m_nRenderFrame = gpGlobals->framecount;
}
else
{
int i = m_BlobbyShadowsInView.AddToTail( );
VisibleShadowInfo_t &info = m_BlobbyShadowsInView[i];
info.m_hShadow = clientShadowHandle;
info.m_flArea = 0.0f;
}
}
//-----------------------------------------------------------------------------
// CVisibleShadowList - Sort based on screen area/priority
//-----------------------------------------------------------------------------
void CVisibleShadowList::PrioritySort()
{
int nCount = m_ShadowsInView.Count();
m_PriorityIndex.EnsureCapacity( nCount );
m_PriorityIndex.RemoveAll();
int i, j;
for ( i = 0; i < nCount; ++i )
{
m_PriorityIndex.AddToTail(i);
}
for ( i = 0; i < nCount - 1; ++i )
{
int nLargestInd = i;
float flLargestArea = m_ShadowsInView[m_PriorityIndex[i]].m_flArea;
for ( j = i + 1; j < nCount; ++j )
{
int nIndex = m_PriorityIndex[j];
if ( flLargestArea < m_ShadowsInView[nIndex].m_flArea )
{
nLargestInd = j;
flLargestArea = m_ShadowsInView[nIndex].m_flArea;
}
}
V_swap( m_PriorityIndex[i], m_PriorityIndex[nLargestInd] );
}
}
//-----------------------------------------------------------------------------
// CVisibleShadowList - Main entry point for finding shadows in the leaf list
//-----------------------------------------------------------------------------
int CVisibleShadowList::FindShadows( const CViewSetup *pView, int nLeafCount, WorldListLeafData_t *pLeafList )
{
VPROF_BUDGET( "CVisibleShadowList::FindShadows", VPROF_BUDGETGROUP_SHADOW_RENDERING );
m_ShadowsInView.RemoveAll();
m_BlobbyShadowsInView.RemoveAll();
ClientLeafSystem()->EnumerateShadowsInLeaves( nLeafCount, pLeafList, this );
int nCount = m_ShadowsInView.Count();
if (nCount != 0)
{
// Sort based on screen area/priority
PrioritySort();
}
return nCount;
}
// sniff the command line parameters, etc. to determine how many shadow rt's and their dimensions
void CClientShadowMgr::CalculateRenderTargetsAndSizes( void )
{
bool bTools = CommandLine()->CheckParm( "-tools" ) != NULL;
m_nDepthTextureResolution = r_flashlightdepthres.GetInt();
m_nDepthTextureResolutionHigh = r_flashlightdepthreshigh.GetInt();
if ( bTools ) // Higher resolution shadow maps in tools mode
{
char defaultRes[] = "2048";
m_nDepthTextureResolution = atoi( CommandLine()->ParmValue( "-sfm_shadowmapres", defaultRes ) );
}
m_nMaxDepthTextureShadows = bTools ? MAX_DEPTH_TEXTURE_SHADOWS_TOOLS : MAX_DEPTH_TEXTURE_SHADOWS; // Just one shadow depth texture in games, more in tools
}
//-----------------------------------------------------------------------------
// Constructor
//-----------------------------------------------------------------------------
CClientShadowMgr::CClientShadowMgr() :
m_DirtyShadows( 0, 0, ShadowHandleCompareFunc ),
m_DirtyShadowsLeftOver( 0, 0, ShadowHandleCompareFunc ),
m_nPrevFrameCount( -1 ),
m_RenderToTextureActive( false ),
m_bDepthTextureActive( false ),
m_bDepthTexturesAllocated( false ),
m_bShadowFromWorldLights( false ),
m_bSuppressShadowFromWorldLights( false ),
m_uiDepthTextureCache( 0 )
{
}
//-----------------------------------------------------------------------------
// Changes the shadow direction...
//-----------------------------------------------------------------------------
CON_COMMAND_F( r_shadowdir, "Set shadow direction", FCVAR_CHEAT )
{
Vector dir;
if ( args.ArgC() == 1 )
{
Vector dir = s_ClientShadowMgr.GetShadowDirection();
Msg( "%.2f %.2f %.2f\n", dir.x, dir.y, dir.z );
return;
}
if ( args.ArgC() == 4 )
{
dir.x = atof( args[1] );
dir.y = atof( args[2] );
dir.z = atof( args[3] );
s_ClientShadowMgr.SetShadowDirection(dir);
}
}
CON_COMMAND_F( r_shadowangles, "Set shadow angles", FCVAR_CHEAT )
{
Vector dir;
QAngle angles;
if (args.ArgC() == 1)
{
Vector dir = s_ClientShadowMgr.GetShadowDirection();
QAngle angles;
VectorAngles( dir, angles );
Msg( "%.2f %.2f %.2f\n", angles.x, angles.y, angles.z );
return;
}
if (args.ArgC() == 4)
{
angles.x = atof( args[1] );
angles.y = atof( args[2] );
angles.z = atof( args[3] );
AngleVectors( angles, &dir );
s_ClientShadowMgr.SetShadowDirection(dir);
}
}
CON_COMMAND_F( r_shadowcolor, "Set shadow color", FCVAR_CHEAT )
{
if (args.ArgC() == 1)
{
unsigned char r, g, b;
s_ClientShadowMgr.GetShadowColor( &r, &g, &b );
Msg( "Shadow color %d %d %d\n", r, g, b );
return;
}
if (args.ArgC() == 4)
{
int r = atoi( args[1] );
int g = atoi( args[2] );
int b = atoi( args[3] );
s_ClientShadowMgr.SetShadowColor(r, g, b);
}
}
CON_COMMAND_F( r_shadowdist, "Set shadow distance", FCVAR_CHEAT )
{
if (args.ArgC() == 1)
{
float flDist;
flDist = s_ClientShadowMgr.GetShadowDistance( );
Msg( "Shadow distance %.2f\n", flDist );
return;
}
if (args.ArgC() == 2)
{
float flDistance = atof( args[1] );
s_ClientShadowMgr.SetShadowDistance( flDistance );
}
}
CON_COMMAND_F( r_shadowblobbycutoff, "some shadow stuff", FCVAR_CHEAT )
{
if (args.ArgC() == 1)
{
float flArea;
flArea = s_ClientShadowMgr.GetBlobbyCutoffArea( );
Msg( "Cutoff area %.2f\n", flArea );
return;
}
if (args.ArgC() == 2)
{
float flArea = atof( args[1] );
s_ClientShadowMgr.SetShadowBlobbyCutoffArea( flArea );
}
}
void OnShadowFromWorldLights( IConVar *var, const char *pOldValue, float flOldValue );
static ConVar r_shadowfromworldlights( "r_shadowfromworldlights", "1", FCVAR_NONE, "Enable shadowing from world lights", OnShadowFromWorldLights );
void OnShadowFromWorldLights( IConVar *var, const char *pOldValue, float flOldValue )
{
s_ClientShadowMgr.SuppressShadowFromWorldLights( !r_shadowfromworldlights.GetBool() );
}
static void ShadowRestoreFunc( int nChangeFlags )
{
s_ClientShadowMgr.RestoreRenderState();
}
//-----------------------------------------------------------------------------
// Initialization, shutdown
//-----------------------------------------------------------------------------
bool CClientShadowMgr::Init()
{
return true;
}
void CClientShadowMgr::InitRenderTargets()
{
m_bRenderTargetNeedsClear = false;
m_SimpleShadow.Init( "decals/simpleshadow", TEXTURE_GROUP_DECAL );
Vector dir( 0.1, 0.1, -1 );
SetShadowDirection(dir);
SetShadowDistance( 50 );
SetShadowBlobbyCutoffArea( 0.005 );
if ( r_shadowrendertotexture.GetBool() )
{
InitRenderToTextureShadows();
}
// If someone turned shadow depth mapping on but we can't do it, force it off
if ( r_flashlightdepthtexture.GetBool() && !g_pMaterialSystemHardwareConfig->SupportsShadowDepthTextures() )
{
r_flashlightdepthtexture.SetValue( 0 );
ShutdownDepthTextureShadows();
}
InitDepthTextureShadows();
r_flashlightdepthres.SetValue( m_nDepthTextureResolution );
r_flashlightdepthreshigh.SetValue( m_nDepthTextureResolutionHigh );
if ( m_DepthTextureCache.Count() )
{
bool bTools = CommandLine()->CheckParm( "-tools" ) != NULL;
int nNumShadows = bTools ? MAX_DEPTH_TEXTURE_SHADOWS_TOOLS : MAX_DEPTH_TEXTURE_SHADOWS;
m_nLowResStart = bTools ? MAX_DEPTH_TEXTURE_HIGHRES_SHADOWS_TOOLS : MAX_DEPTH_TEXTURE_HIGHRES_SHADOWS;
if ( m_nLowResStart > nNumShadows )
{
// All shadow slots filled with high res
m_nLowResStart = 0;
}
// Shadow may be resized during allocation (due to resolution constraints etc)
m_nDepthTextureResolution = m_DepthTextureCache[ m_nLowResStart ]->GetActualWidth();
r_flashlightdepthres.SetValue( m_nDepthTextureResolution );
m_nDepthTextureResolutionHigh = m_DepthTextureCache[ 0 ]->GetActualWidth();
r_flashlightdepthreshigh.SetValue( m_nDepthTextureResolutionHigh );
}
InitDeferredShadows();
materials->AddRestoreFunc( ShadowRestoreFunc );
}
void CClientShadowMgr::ShutdownRenderTargets( void )
{
if ( materials ) // ugh - this gets called during program shutdown, but with no mat system
{
materials->RemoveRestoreFunc( ShadowRestoreFunc );
}
}
void CClientShadowMgr::Shutdown()
{
ShutdownRenderTargets();
m_SimpleShadow.Shutdown();
m_Shadows.RemoveAll();
ShutdownRenderToTextureShadows();
ShutdownDepthTextureShadows();
ShutdownDeferredShadows();
}
//-----------------------------------------------------------------------------
// Initialize, shutdown depth-texture shadows
//-----------------------------------------------------------------------------
void CClientShadowMgr::InitDepthTextureShadows()
{
VPROF_BUDGET( "CClientShadowMgr::InitDepthTextureShadows", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING );
if ( m_bDepthTextureActive )
return;
m_bDepthTextureActive = true;
if ( !r_flashlightdepthtexture.GetBool() )
return;
if( !m_bDepthTexturesAllocated || m_nDepthTextureResolution != r_flashlightdepthres.GetInt() || m_nDepthTextureResolutionHigh != r_flashlightdepthreshigh.GetInt() )
{
CalculateRenderTargetsAndSizes();
m_bDepthTexturesAllocated = true;
ImageFormat dstFormat = g_pMaterialSystemHardwareConfig->GetShadowDepthTextureFormat(); // Vendor-dependent depth texture format
#if !defined( _X360 )
ImageFormat nullFormat = g_pMaterialSystemHardwareConfig->GetNullTextureFormat(); // Vendor-dependent null texture format (takes as little memory as possible)
#endif
materials->BeginRenderTargetAllocation();
RenderTargetSizeMode_t sizeMode = RT_SIZE_OFFSCREEN;
if ( IsPS3() || IsPC() )
{
// Don't allow the shadow buffer render target's to get resized to always be <= the size of the backbuffer on the PC.
// This allows us to use 1024x1024 or larger shadow depth buffers when 1024x768 backbuffers, for example.
sizeMode = RT_SIZE_NO_CHANGE;
}
#if defined( _X360 )
// For the 360, we'll be rendering depth directly into the dummy depth and Resolve()ing to the depth texture.
// only need the dummy surface, don't care about color results
m_DummyColorTexture.InitRenderTargetTexture( m_nDepthTextureResolution, m_nDepthTextureResolution, RT_SIZE_OFFSCREEN, IMAGE_FORMAT_BGR565, MATERIAL_RT_DEPTH_SHARED, false, "_rt_ShadowDummy", CREATERENDERTARGETFLAGS_ALIASCOLORANDDEPTHSURFACES );
m_DummyColorTexture.InitRenderTargetSurface( m_nDepthTextureResolution, m_nDepthTextureResolution, IMAGE_FORMAT_BGR565, false );
#elif defined (_PS3)
m_DummyColorTexture.InitRenderTarget( 8, 8, sizeMode, nullFormat, MATERIAL_RT_DEPTH_NONE, false, "_rt_ShadowDummy" );
#else
m_DummyColorTexture.InitRenderTarget( m_nDepthTextureResolution, m_nDepthTextureResolution, sizeMode, nullFormat, MATERIAL_RT_DEPTH_NONE, false, "_rt_ShadowDummy" );
#endif
// Create some number of depth-stencil textures
m_DepthTextureCache.Purge();
m_DepthTextureCacheLocks.Purge();
for( int i=0; i < m_nMaxDepthTextureShadows; i++ )
{
CTextureReference depthTex; // Depth-stencil surface
bool bFalse = false;
char strRTName[64];
Q_snprintf( strRTName, ARRAYSIZE( strRTName ), "_rt_ShadowDepthTexture_%d", i );
int nTextureResolution = ( i < MAX_DEPTH_TEXTURE_HIGHRES_SHADOWS ? m_nDepthTextureResolutionHigh : m_nDepthTextureResolution );
#if defined( _X360 )
// create a render target to use as a resolve target to get the shared depth buffer
// surface is effectively never used
depthTex.InitRenderTargetTexture( nTextureResolution, nTextureResolution, RT_SIZE_OFFSCREEN, dstFormat, MATERIAL_RT_DEPTH_NONE, false, strRTName );
depthTex.InitRenderTargetSurface( 1, 1, dstFormat, false );
#else
depthTex.InitRenderTarget( nTextureResolution, nTextureResolution, sizeMode, dstFormat, MATERIAL_RT_DEPTH_NONE, false, strRTName );
#endif
m_DepthTextureCache.AddToTail( depthTex );
m_DepthTextureCacheLocks.AddToTail( bFalse );
}
#if 0 // 7LTODO #ifdef _PS3
AssertFatalEquals( m_nMaxDepthTextureShadows, 1 );
for( int i=0; i < m_nMaxDepthTextureShadows; i++ )
{
CTextureReference depthTex; // Depth-stencil surface
bool bFalse = false;
char strRTName[64];
Q_snprintf( strRTName, ARRAYSIZE( strRTName ), "_rt_ShadowDepthTexture_Cache%d", i );
int nTextureResolution = ( i < MAX_DEPTH_TEXTURE_HIGHRES_SHADOWS ? m_nDepthTextureResolutionHigh : m_nDepthTextureResolution );
depthTex.InitRenderTarget( nTextureResolution, nTextureResolution, sizeMode, dstFormat, MATERIAL_RT_DEPTH_NONE, false, strRTName );
m_DepthTextureCache.AddToTail( depthTex );
m_DepthTextureCacheLocks.AddToTail( bFalse );
g_ps3_ShadowDepth_TextureCache = m_uiDepthTextureCache = materials->EstablishGpuDataTransferCache( PS3GPU_DATA_TRANSFER_CREATECACHELINK, m_DepthTextureCache[0], depthTex );
}
#endif
materials->EndRenderTargetAllocation();
}
}
void CClientShadowMgr::ShutdownDepthTextureShadows()
{
if( m_bDepthTexturesAllocated )
{
// Shut down the dummy texture
m_DummyColorTexture.Shutdown();
while( m_DepthTextureCache.Count() )
{
m_DepthTextureCache[ m_DepthTextureCache.Count()-1 ].Shutdown();
m_DepthTextureCacheLocks.Remove( m_DepthTextureCache.Count()-1 );
m_DepthTextureCache.Remove( m_DepthTextureCache.Count()-1 );
}
m_bDepthTexturesAllocated = false;
}
m_bDepthTextureActive = false;
}
//-----------------------------------------------------------------------------
// Initialize, shutdown render-to-texture shadows
//-----------------------------------------------------------------------------
void CClientShadowMgr::InitRenderToTextureShadows()
{
if ( !m_RenderToTextureActive )
{
m_RenderToTextureActive = true;
g_pMaterialSystem->BeginRenderTargetAllocation();
m_ShadowAllocator.Init();
g_pMaterialSystem->EndRenderTargetAllocation();
m_RenderShadow.Init( "decals/rendershadow", TEXTURE_GROUP_DECAL );
m_RenderModelShadow.Init( "decals/rendermodelshadow", TEXTURE_GROUP_DECAL );
m_ShadowAllocator.Reset();
m_bRenderTargetNeedsClear = true;
float fr = (float)m_AmbientLightColor.r / 255.0f;
float fg = (float)m_AmbientLightColor.g / 255.0f;
float fb = (float)m_AmbientLightColor.b / 255.0f;
m_RenderShadow->ColorModulate( fr, fg, fb );
m_RenderModelShadow->ColorModulate( fr, fg, fb );
// Iterate over all existing textures and allocate shadow textures
for (ClientShadowHandle_t i = m_Shadows.Head(); i != m_Shadows.InvalidIndex(); i = m_Shadows.Next(i) )
{
ClientShadow_t& shadow = m_Shadows[i];
if ( shadow.m_Flags & SHADOW_FLAGS_USE_RENDER_TO_TEXTURE )
{
SetupRenderToTextureShadow( i );
MarkRenderToTextureShadowDirty( i );
// Switch the material to use render-to-texture shadows
shadowmgr->SetShadowMaterial( shadow.m_ShadowHandle, m_RenderShadow, m_RenderModelShadow, (void*)(uintp)i );
}
}
}
}
void CClientShadowMgr::ShutdownRenderToTextureShadows()
{
if (m_RenderToTextureActive)
{
// Iterate over all existing textures and deallocate shadow textures
for (ClientShadowHandle_t i = m_Shadows.Head(); i != m_Shadows.InvalidIndex(); i = m_Shadows.Next(i) )
{
CleanUpRenderToTextureShadow( i );
// Switch the material to use blobby shadows
ClientShadow_t& shadow = m_Shadows[i];
shadowmgr->SetShadowMaterial( shadow.m_ShadowHandle, m_SimpleShadow, m_SimpleShadow, (void*)CLIENTSHADOW_INVALID_HANDLE );
shadowmgr->SetShadowTexCoord( shadow.m_ShadowHandle, 0, 0, 1, 1 );
ClearExtraClipPlanes( i );
}
m_RenderShadow.Shutdown();
m_RenderModelShadow.Shutdown();
m_ShadowAllocator.DeallocateAllTextures();
m_ShadowAllocator.Shutdown();
// Cause the render target to go away
materials->UncacheUnusedMaterials();
m_RenderToTextureActive = false;
}
}
#define DEFERRED_SHADOW_BUFFER_WIDTH 320
#define DEFERRED_SHADOW_BUFFER_HEIGHT 180
void CClientShadowMgr::InitDeferredShadows()
{
if ( IsGameConsole() )
{
m_RenderDeferredShadowMat.Init( "engine/renderdeferredshadow", TEXTURE_GROUP_OTHER );
m_RenderDeferredSimpleShadowMat.Init( "engine/renderdeferredsimpleshadow", TEXTURE_GROUP_OTHER );
}
if ( r_shadow_deferred_downsample.GetBool() )
{
#if defined( _X360 )
m_downSampledNormals.InitRenderTargetTexture( DEFERRED_SHADOW_BUFFER_WIDTH, DEFERRED_SHADOW_BUFFER_HEIGHT, RT_SIZE_OFFSCREEN, IMAGE_FORMAT_ARGB8888, MATERIAL_RT_DEPTH_SEPARATE, false, "_rt_DownsampledNormals" );
m_downSampledNormals.InitRenderTargetSurface( DEFERRED_SHADOW_BUFFER_WIDTH, DEFERRED_SHADOW_BUFFER_HEIGHT, IMAGE_FORMAT_ARGB8888, true );
m_downSampledDepth.InitRenderTargetTexture( DEFERRED_SHADOW_BUFFER_WIDTH, DEFERRED_SHADOW_BUFFER_HEIGHT, RT_SIZE_OFFSCREEN, IMAGE_FORMAT_D24FS8, MATERIAL_RT_DEPTH_NONE, false, "_rt_DownsampledDepth" );
#endif
}
}
void CClientShadowMgr::ShutdownDeferredShadows()
{
m_RenderDeferredShadowMat.Shutdown();
m_RenderDeferredSimpleShadowMat.Shutdown();
m_downSampledNormals.Shutdown();
m_downSampledDepth.Shutdown();
}
//-----------------------------------------------------------------------------
// Sets the shadow color
//-----------------------------------------------------------------------------
void CClientShadowMgr::SetShadowColor( unsigned char r, unsigned char g, unsigned char b )
{
float fr = (float)r / 255.0f;
float fg = (float)g / 255.0f;
float fb = (float)b / 255.0f;
// Hook the shadow color into the shadow materials
m_SimpleShadow->ColorModulate( fr, fg, fb );
if (m_RenderToTextureActive)
{
if ( m_RenderShadow )
{
m_RenderShadow->ColorModulate( fr, fg, fb );
}
if ( m_RenderModelShadow )
{
m_RenderModelShadow->ColorModulate( fr, fg, fb );
}
if ( IsGameConsole() )
{
m_RenderDeferredShadowMat->ColorModulate( fr, fg, fb );
m_RenderDeferredSimpleShadowMat->ColorModulate( fr, fg, fb );
}
}
m_AmbientLightColor.r = r;
m_AmbientLightColor.g = g;
m_AmbientLightColor.b = b;
}
void CClientShadowMgr::GetShadowColor( unsigned char *r, unsigned char *g, unsigned char *b ) const
{
*r = m_AmbientLightColor.r;
*g = m_AmbientLightColor.g;
*b = m_AmbientLightColor.b;
}
//-----------------------------------------------------------------------------
// Level init... get the shadow color
//-----------------------------------------------------------------------------
void CClientShadowMgr::LevelInitPreEntity()
{
m_bUpdatingDirtyShadows = false;
// Default setting for this, can be overridden by shadow control entities
SetShadowFromWorldLightsEnabled( true );
Vector ambientColor;
engine->GetAmbientLightColor( ambientColor );
ambientColor *= 3;
ambientColor += Vector( 0.3f, 0.3f, 0.3f );
unsigned char r = ambientColor[0] > 1.0 ? 255 : 255 * ambientColor[0];
unsigned char g = ambientColor[1] > 1.0 ? 255 : 255 * ambientColor[1];
unsigned char b = ambientColor[2] > 1.0 ? 255 : 255 * ambientColor[2];
SetShadowColor(r, g, b);
// Set up the texture allocator
if ( m_RenderToTextureActive )
{
m_ShadowAllocator.Reset();
m_bRenderTargetNeedsClear = true;
}
}
//-----------------------------------------------------------------------------
// Clean up all shadows
//-----------------------------------------------------------------------------
void CClientShadowMgr::LevelShutdownPostEntity()
{
// Paranoid code to make sure all flashlights are deactivated.
// This should happen in the C_BasePlayer destructor, but I'm turning everything off to release the
// flashlight shadows just in case.
for ( int i = 0; i < MAX_SPLITSCREEN_PLAYERS; i++ )
{
FlashlightEffectManager( i ).TurnOffFlashlight( true );
}
// All shadows *should* have been cleaned up when the entities went away
// but, just in case....
Assert( m_Shadows.Count() == 0 );
ClientShadowHandle_t h = m_Shadows.Head();
while (h != CLIENTSHADOW_INVALID_HANDLE)
{
ClientShadowHandle_t next = m_Shadows.Next(h);
DestroyShadow( h );
h = next;
}
// Deallocate all textures
if (m_RenderToTextureActive)
{
m_ShadowAllocator.DeallocateAllTextures();
}
r_shadows_gamecontrol.SetValue( -1 );
}
//-----------------------------------------------------------------------------
// Deals with alt-tab
//-----------------------------------------------------------------------------
void CClientShadowMgr::RestoreRenderState()
{
// Mark all shadows dirty; they need to regenerate their state
ClientShadowHandle_t h;
for ( h = m_Shadows.Head(); h != m_Shadows.InvalidIndex(); h = m_Shadows.Next(h) )
{
m_Shadows[h].m_Flags |= SHADOW_FLAGS_TEXTURE_DIRTY;
}
SetShadowColor( m_AmbientLightColor.r, m_AmbientLightColor.g, m_AmbientLightColor.b );
m_bRenderTargetNeedsClear = true;
}
//-----------------------------------------------------------------------------
// Does all the lovely stuff we need to do to have render-to-texture shadows
//-----------------------------------------------------------------------------
void CClientShadowMgr::SetupRenderToTextureShadow( ClientShadowHandle_t h )
{
// First, compute how much texture memory we want to use.
ClientShadow_t& shadow = m_Shadows[h];
IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( shadow.m_Entity );
if ( !pRenderable )
return;
Vector mins, maxs;
pRenderable->GetShadowRenderBounds( mins, maxs, GetActualShadowCastType( h ) );
// Compute the maximum dimension
Vector size;
VectorSubtract( maxs, mins, size );
float maxSize = MAX( size.x, size.y );
maxSize = MAX( maxSize, size.z );
// Figure out the texture size
// For now, we're going to assume a fixed number of shadow texels
// per shadow-caster size; add in some extra space at the boundary.
int texelCount = TEXEL_SIZE_PER_CASTER_SIZE * maxSize;
// Pick the first power of 2 larger...
int textureSize = 1;
while (textureSize < texelCount)
{
textureSize <<= 1;
}
shadow.m_ShadowTexture = m_ShadowAllocator.AllocateTexture( textureSize, textureSize );
}
void CClientShadowMgr::CleanUpRenderToTextureShadow( ClientShadowHandle_t h )
{
ClientShadow_t& shadow = m_Shadows[h];
if (m_RenderToTextureActive && (shadow.m_Flags & SHADOW_FLAGS_USE_RENDER_TO_TEXTURE))
{
m_ShadowAllocator.DeallocateTexture( shadow.m_ShadowTexture );
shadow.m_ShadowTexture = INVALID_TEXTURE_HANDLE;
}
}
//-----------------------------------------------------------------------------
// Causes all shadows to be re-updated
//-----------------------------------------------------------------------------
void CClientShadowMgr::UpdateAllShadows()
{
for ( ClientShadowHandle_t i = m_Shadows.Head(); i != m_Shadows.InvalidIndex(); i = m_Shadows.Next(i) )
{
ClientShadow_t& shadow = m_Shadows[i];
// Don't bother with flashlights
if ( ( shadow.m_Flags & ( SHADOW_FLAGS_FLASHLIGHT | SHADOW_FLAGS_SIMPLE_PROJECTION ) ) != 0 )
continue;
IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( shadow.m_Entity );
if ( !pRenderable )
continue;
Assert( pRenderable->GetShadowHandle() == i );
AddToDirtyShadowList( pRenderable, true );
}
}
void CClientShadowMgr::RemoveAllShadowDecals()
{
for ( ClientShadowHandle_t i = m_Shadows.Head(); i != m_Shadows.InvalidIndex(); i = m_Shadows.Next(i) )
{
ClientShadow_t& shadow = m_Shadows[i];
// Don't bother with flashlights
if ( ( shadow.m_Flags & ( SHADOW_FLAGS_FLASHLIGHT | SHADOW_FLAGS_SIMPLE_PROJECTION ) ) != 0 )
continue;
shadowmgr->RemoveAllDecalsFromShadow( shadow.m_ShadowHandle );
}
}
//-----------------------------------------------------------------------------
// Sets the shadow direction
//-----------------------------------------------------------------------------
void CClientShadowMgr::SetShadowDirection( const Vector& dir )
{
VectorCopy( dir, m_SimpleShadowDir );
VectorNormalize( m_SimpleShadowDir );
if ( m_RenderToTextureActive )
{
UpdateAllShadows();
}
}
const Vector &CClientShadowMgr::GetShadowDirection() const
{
// This will cause blobby shadows to always project straight down
static Vector s_vecDown( 0, 0, -1 );
if ( !m_RenderToTextureActive )
return s_vecDown;
return m_SimpleShadowDir;
}
//-----------------------------------------------------------------------------
// Gets shadow information for a particular renderable
//-----------------------------------------------------------------------------
float CClientShadowMgr::GetShadowDistance( IClientRenderable *pRenderable ) const
{
float flDist = m_flShadowCastDist;
// Allow the renderable to override the default
pRenderable->GetShadowCastDistance( &flDist, GetActualShadowCastType( pRenderable ) );
return flDist;
}
const Vector &CClientShadowMgr::GetShadowDirection( IClientRenderable *pRenderable ) const
{
Vector &vecResult = AllocTempVector();
vecResult = GetShadowDirection();
// Allow the renderable to override the default
pRenderable->GetShadowCastDirection( &vecResult, GetActualShadowCastType( pRenderable ) );
return vecResult;
}
const Vector &CClientShadowMgr::GetShadowDirection( ClientShadowHandle_t shadowHandle ) const
{
Assert( shadowHandle != CLIENTSHADOW_INVALID_HANDLE );
IClientRenderable* pRenderable = ClientEntityList().GetClientRenderableFromHandle( m_Shadows[shadowHandle].m_Entity );
Assert( pRenderable );
if ( !IsShadowingFromWorldLights() )
{
return GetShadowDirection( pRenderable );
}
Vector &vecResult = AllocTempVector();
vecResult = m_Shadows[shadowHandle].m_ShadowDir;
// Allow the renderable to override the default
pRenderable->GetShadowCastDirection( &vecResult, GetActualShadowCastType( pRenderable ) );
return vecResult;
}
void CClientShadowMgr::UpdateShadowDirectionFromLocalLightSource( ClientShadowHandle_t shadowHandle )
{
Assert( shadowHandle != CLIENTSHADOW_INVALID_HANDLE );
ClientShadow_t& shadow = m_Shadows[shadowHandle];
IClientRenderable* pRenderable = ClientEntityList().GetClientRenderableFromHandle( shadow.m_Entity );
// TODO: Figure out why this still gets hit
Assert( pRenderable );
if ( !pRenderable )
{
DevWarning( "%s(): Skipping shadow with invalid client renderable (shadow handle %d)\n", __FUNCTION__, shadowHandle );
return;
}
Vector bbMin, bbMax;
pRenderable->GetRenderBoundsWorldspace( bbMin, bbMax );
Vector origin( 0.5f * ( bbMin + bbMax ) );
origin.z = bbMin.z; // Putting origin at the bottom of the bounding box makes the shadows a little shorter
Vector lightPos;
Vector lightBrightness;
if ( shadow.m_LightPosLerp >= 1.0f ) // skip finding new light source if we're in the middle of a lerp
{
if( modelrender->GetBrightestShadowingLightSource( pRenderable->GetRenderOrigin(), lightPos, lightBrightness,
r_shadowfromanyworldlight.GetBool() ) == false )
{
// didn't find a light source at all, use default shadow direction
// TODO: Could switch to using blobby shadow in this case
lightPos.Init( FLT_MAX, FLT_MAX, FLT_MAX );
}
}
if ( shadow.m_LightPosLerp == FLT_MAX ) // first light pos ever, just init
{
shadow.m_CurrentLightPos = lightPos;
shadow.m_TargetLightPos = lightPos;
shadow.m_LightPosLerp = 1.0f;
}
else if ( shadow.m_LightPosLerp < 1.0f )
{
// We're in the middle of a lerp from current to target light. Finish it.
shadow.m_LightPosLerp += gpGlobals->frametime * 1.0f/r_shadow_lightpos_lerptime.GetFloat();
shadow.m_LightPosLerp = clamp( shadow.m_LightPosLerp, 0.0f, 1.0f );
Vector currLightPos( shadow.m_CurrentLightPos );
Vector targetLightPos( shadow.m_TargetLightPos );
if ( currLightPos.x == FLT_MAX )
{
currLightPos = origin - 200.0f * GetShadowDirection();
}
if ( targetLightPos.x == FLT_MAX )
{
targetLightPos = origin - 200.0f * GetShadowDirection();
}
// lerp light pos
Vector v1 = origin - shadow.m_CurrentLightPos;
v1.NormalizeInPlace();
Vector v2 = origin - shadow.m_TargetLightPos;
v2.NormalizeInPlace();
if ( v1.Dot( v2 ) < 0.0f )
{
// if change in shadow angle is more than 90 degrees, lerp over the renderable's top to avoid long sweeping shadows
Vector fakeOverheadLightPos( origin.x, origin.y, origin.z + 200.0f );
if( shadow.m_LightPosLerp < 0.5f )
{
lightPos = Lerp( 2.0f * shadow.m_LightPosLerp, currLightPos, fakeOverheadLightPos );
}
else
{
lightPos = Lerp( 2.0f * shadow.m_LightPosLerp - 1.0f, fakeOverheadLightPos, targetLightPos );
}
}
else
{
lightPos = Lerp( shadow.m_LightPosLerp, currLightPos, targetLightPos );
}
if ( shadow.m_LightPosLerp >= 1.0f )
{
shadow.m_CurrentLightPos = shadow.m_TargetLightPos;
}
}
else if ( shadow.m_LightPosLerp >= 1.0f )
{
// check if we have a new closest light position and start a new lerp
float flDistSq = ( lightPos - shadow.m_CurrentLightPos ).LengthSqr();
if ( flDistSq > 1.0f )
{
// light position has changed, which means we got a new light source. Initiate a lerp
shadow.m_TargetLightPos = lightPos;
shadow.m_LightPosLerp = 0.0f;
}
lightPos = shadow.m_CurrentLightPos;
}
if ( lightPos.x == FLT_MAX )
{
lightPos = origin - 200.0f * GetShadowDirection();
}
Vector vecResult( origin - lightPos );
vecResult.NormalizeInPlace();
vecResult.z *= r_shadow_shortenfactor.GetFloat();
vecResult.NormalizeInPlace();
shadow.m_ShadowDir = vecResult;
if ( r_shadowfromworldlights_debug.GetBool() )
{
NDebugOverlay::Line( lightPos, origin, 255, 255, 0, false, 0.0f );
}
}
//-----------------------------------------------------------------------------
// Sets the shadow distance
//-----------------------------------------------------------------------------
void CClientShadowMgr::SetShadowDistance( float flMaxDistance )
{
m_flShadowCastDist = flMaxDistance;
UpdateAllShadows();
}
float CClientShadowMgr::GetShadowDistance( ) const
{
return m_flShadowCastDist;
}
//-----------------------------------------------------------------------------
// Sets the screen area at which blobby shadows are always used
//-----------------------------------------------------------------------------
void CClientShadowMgr::SetShadowBlobbyCutoffArea( float flMinArea )
{
m_flMinShadowArea = flMinArea;
}
float CClientShadowMgr::GetBlobbyCutoffArea( ) const
{
return m_flMinShadowArea;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CClientShadowMgr::SetFalloffBias( ClientShadowHandle_t handle, unsigned char ucBias )
{
shadowmgr->SetFalloffBias( m_Shadows[handle].m_ShadowHandle, ucBias );
m_Shadows[handle].m_FalloffBias = ucBias;
}
//-----------------------------------------------------------------------------
// Returns the shadow texture
//-----------------------------------------------------------------------------
ITexture* CClientShadowMgr::GetShadowTexture( unsigned short h )
{
return m_ShadowAllocator.GetTexture();
}
//-----------------------------------------------------------------------------
// Returns information needed by the model proxy
//-----------------------------------------------------------------------------
const ShadowInfo_t& CClientShadowMgr::GetShadowInfo( ClientShadowHandle_t h )
{
return shadowmgr->GetInfo( m_Shadows[h].m_ShadowHandle );
}
//-----------------------------------------------------------------------------
// Renders the shadow texture to screen...
//-----------------------------------------------------------------------------
void CClientShadowMgr::RenderShadowTexture( int w, int h )
{
if (m_RenderToTextureActive)
{
CMatRenderContextPtr pRenderContext( materials );
pRenderContext->Bind( m_RenderShadow );
IMesh* pMesh = pRenderContext->GetDynamicMesh( true );
CMeshBuilder meshBuilder;
meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 );
meshBuilder.Position3f( 0.0f, 0.0f, 0.0f );
meshBuilder.TexCoord2f( 0, 0.0f, 0.0f );
meshBuilder.Color4ub( 0, 0, 0, 0 );
meshBuilder.AdvanceVertex();
meshBuilder.Position3f( w, 0.0f, 0.0f );
meshBuilder.TexCoord2f( 0, 1.0f, 0.0f );
meshBuilder.Color4ub( 0, 0, 0, 0 );
meshBuilder.AdvanceVertex();
meshBuilder.Position3f( w, h, 0.0f );
meshBuilder.TexCoord2f( 0, 1.0f, 1.0f );
meshBuilder.Color4ub( 0, 0, 0, 0 );
meshBuilder.AdvanceVertex();
meshBuilder.Position3f( 0.0f, h, 0.0f );
meshBuilder.TexCoord2f( 0, 0.0f, 1.0f );
meshBuilder.Color4ub( 0, 0, 0, 0 );
meshBuilder.AdvanceVertex();
meshBuilder.End();
pMesh->Draw();
}
}
//-----------------------------------------------------------------------------
// Create/destroy a shadow
//-----------------------------------------------------------------------------
ClientShadowHandle_t CClientShadowMgr::CreateProjectedTexture( ClientEntityHandle_t entity, int nEntIndex, int flags, CBitVec< MAX_SPLITSCREEN_PLAYERS > *pSplitScreenBits,
bool bShareProjectedTextureBetweenSplitscreenPlayers )
{
// We need to know if it's a brush model for shadows
if( ( flags & ( SHADOW_FLAGS_FLASHLIGHT | SHADOW_FLAGS_SIMPLE_PROJECTION ) ) == 0 )
{
IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( entity );
if ( !pRenderable )
return m_Shadows.InvalidIndex();
int modelType = modelinfo->GetModelType( pRenderable->GetModel() );
if (modelType == mod_brush)
{
flags |= SHADOW_FLAGS_BRUSH_MODEL;
}
}
ClientShadowHandle_t h = m_Shadows.AddToTail();
ClientShadow_t& shadow = m_Shadows[h];
shadow.m_Entity = entity;
if ( ( flags & SHADOW_FLAGS_SIMPLE_PROJECTION ) == 0 )
{
shadow.m_ClientLeafShadowHandle = ClientLeafSystem()->AddShadow( h, flags );
}
else
{
shadow.m_ClientLeafShadowHandle = CLIENT_LEAF_SHADOW_INVALID_HANDLE;
}
shadow.m_Flags = flags;
shadow.m_nRenderFrame = -1;
shadow.m_ShadowDir = GetShadowDirection();
shadow.m_LastOrigin.Init( FLT_MAX, FLT_MAX, FLT_MAX );
shadow.m_LastAngles.Init( FLT_MAX, FLT_MAX, FLT_MAX );
shadow.m_CurrentLightPos.Init( FLT_MAX, FLT_MAX, FLT_MAX );
shadow.m_TargetLightPos.Init( FLT_MAX, FLT_MAX, FLT_MAX );
shadow.m_LightPosLerp = FLT_MAX;
Assert( ( ( shadow.m_Flags & ( SHADOW_FLAGS_FLASHLIGHT | SHADOW_FLAGS_SIMPLE_PROJECTION ) ) == 0 ) !=
( ( shadow.m_Flags & SHADOW_FLAGS_SHADOW ) == 0 ) );
shadow.m_nLastUpdateFrame = 0;
shadow.m_nSplitscreenOwner = -1; // No one owns this texture, it will be shared between all splitscreen players
if ( !bShareProjectedTextureBetweenSplitscreenPlayers )
{
if ( ( flags & ( SHADOW_FLAGS_FLASHLIGHT | SHADOW_FLAGS_SIMPLE_PROJECTION ) ) || ( flags & SHADOW_FLAGS_USE_DEPTH_TEXTURE ) )
{
// The local player isn't always resolvable if this projected texture isn't the player's flashlight, so
// if the local player isn't resolvable, leave the splitscreen owner set to -1 so all splitscreen players render it
if ( engine->IsLocalPlayerResolvable() )
{
// Set ownership to this player
shadow.m_nSplitscreenOwner = GET_ACTIVE_SPLITSCREEN_SLOT();
}
}
}
// Set up the flags....
IMaterial* pShadowMaterial = m_SimpleShadow;
IMaterial* pShadowModelMaterial = m_SimpleShadow;
void* pShadowProxyData = (void*)CLIENTSHADOW_INVALID_HANDLE;
if ( m_RenderToTextureActive && (flags & SHADOW_FLAGS_USE_RENDER_TO_TEXTURE) )
{
SetupRenderToTextureShadow(h);
pShadowMaterial = m_RenderShadow;
pShadowModelMaterial = m_RenderModelShadow;
pShadowProxyData = (void*)(uintp)h;
}
if( ( flags & SHADOW_FLAGS_USE_DEPTH_TEXTURE ) || ( flags & ( SHADOW_FLAGS_FLASHLIGHT | SHADOW_FLAGS_SIMPLE_PROJECTION ) ) )
{
pShadowMaterial = NULL; // these materials aren't used for shadow depth texture shadows.
pShadowModelMaterial = NULL;
pShadowProxyData = (void*)(uintp)h;
}
int createShadowFlags;
if( flags & SHADOW_FLAGS_SIMPLE_PROJECTION )
{
createShadowFlags = SHADOW_SIMPLE_PROJECTION;
}
else if( flags & SHADOW_FLAGS_FLASHLIGHT )
{
// don't use SHADOW_CACHE_VERTS with projective lightsources since we expect that they will change every frame.
// FIXME: might want to make it cache optionally if it's an entity light that is static.
createShadowFlags = SHADOW_FLASHLIGHT;
}
else
{
createShadowFlags = SHADOW_CACHE_VERTS;
}
if ( -1 == shadow.m_nSplitscreenOwner )
{
createShadowFlags |= SHADOW_ANY_SPLITSCREEN_SLOT;
}
shadow.m_ShadowHandle = shadowmgr->CreateShadowEx( pShadowMaterial, pShadowModelMaterial, pShadowProxyData, createShadowFlags, nEntIndex );
shadow.m_bUseSplitScreenBits = pSplitScreenBits ? true : false;
if ( pSplitScreenBits )
{
shadow.m_SplitScreenBits.Copy( *pSplitScreenBits );
}
return h;
}
ClientShadowHandle_t CClientShadowMgr::CreateFlashlight( const FlashlightState_t &lightState )
{
// We don't really need a model entity handle for a projective light source, so use an invalid one.
static ClientEntityHandle_t invalidHandle = INVALID_CLIENTENTITY_HANDLE;
int shadowFlags = SHADOW_FLAGS_FLASHLIGHT | SHADOW_FLAGS_LIGHT_WORLD;
if( lightState.m_bEnableShadows && r_flashlightdepthtexture.GetBool() )
{
shadowFlags |= SHADOW_FLAGS_USE_DEPTH_TEXTURE;
}
ClientShadowHandle_t shadowHandle = CreateProjectedTexture( invalidHandle, -1, shadowFlags, NULL, lightState.m_bShareBetweenSplitscreenPlayers );
UpdateFlashlightState( shadowHandle, lightState );
UpdateProjectedTexture( shadowHandle, true );
return shadowHandle;
}
ClientShadowHandle_t CClientShadowMgr::CreateShadow( ClientEntityHandle_t entity, int nEntIndex, int flags, CBitVec< MAX_SPLITSCREEN_PLAYERS > *pSplitScreenBits /*= NULL*/ )
{
// We don't really need a model entity handle for a projective light source, so use an invalid one.
flags &= ~SHADOW_FLAGS_PROJECTED_TEXTURE_TYPE_MASK;
flags |= SHADOW_FLAGS_SHADOW | SHADOW_FLAGS_TEXTURE_DIRTY;
ClientShadowHandle_t shadowHandle = CreateProjectedTexture( entity, nEntIndex, flags, pSplitScreenBits, false );
IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( entity );
if ( pRenderable )
{
Assert( !pRenderable->IsShadowDirty( ) );
pRenderable->MarkShadowDirty( true );
CClientAlphaProperty *pAlphaProperty = static_cast<CClientAlphaProperty*>( pRenderable->GetIClientUnknown()->GetClientAlphaProperty() );
if ( pAlphaProperty )
{
pAlphaProperty->SetShadowHandle( shadowHandle );
}
}
// NOTE: We *have* to call the version that takes a shadow handle
// even if we have an entity because this entity hasn't set its shadow handle yet
AddToDirtyShadowList( shadowHandle, true );
return shadowHandle;
}
//-----------------------------------------------------------------------------
// Updates the flashlight direction and re-computes surfaces it should lie on
//-----------------------------------------------------------------------------
void CClientShadowMgr::UpdateFlashlightState( ClientShadowHandle_t shadowHandle, const FlashlightState_t &flashlightState )
{
VPROF_BUDGET( "CClientShadowMgr::UpdateFlashlightState", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING );
if( flashlightState.m_bEnableShadows && r_flashlightdepthtexture.GetBool() )
{
m_Shadows[shadowHandle].m_Flags |= SHADOW_FLAGS_USE_DEPTH_TEXTURE;
}
else
{
m_Shadows[shadowHandle].m_Flags &= ~SHADOW_FLAGS_USE_DEPTH_TEXTURE;
}
if ( flashlightState.m_bOrtho )
{
BuildOrthoWorldToFlashlightMatrix( m_Shadows[shadowHandle].m_WorldToShadow, flashlightState );
}
else
{
BuildPerspectiveWorldToFlashlightMatrix( m_Shadows[shadowHandle].m_WorldToShadow, flashlightState );
}
shadowmgr->UpdateFlashlightState( m_Shadows[shadowHandle].m_ShadowHandle, flashlightState );
}
void CClientShadowMgr::DestroyFlashlight( ClientShadowHandle_t shadowHandle )
{
DestroyShadow( shadowHandle );
}
ClientShadowHandle_t CClientShadowMgr::CreateProjection( const FlashlightState_t &lightState )
{
// return CreateFlashlight(lightState);
// We don't really need a model entity handle for a projective light source, so use an invalid one.
static ClientEntityHandle_t invalidHandle = INVALID_CLIENTENTITY_HANDLE;
int shadowFlags = SHADOW_FLAGS_SIMPLE_PROJECTION;
ClientShadowHandle_t shadowHandle = CreateProjectedTexture( invalidHandle, -1, shadowFlags, NULL, lightState.m_bShareBetweenSplitscreenPlayers );
UpdateFlashlightState( shadowHandle, lightState );
UpdateProjectedTexture( shadowHandle, true );
return shadowHandle;
}
//-----------------------------------------------------------------------------
// Updates the flashlight direction and re-computes surfaces it should lie on
//-----------------------------------------------------------------------------
void CClientShadowMgr::UpdateProjectionState( ClientShadowHandle_t shadowHandle, const FlashlightState_t &flashlightState )
{
// UpdateFlashlightState(shadowHandle, flashlightState );
// return;
VPROF_BUDGET( "CClientShadowMgr::UpdateProjectionState", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING );
if ( flashlightState.m_bOrtho )
{
BuildOrthoWorldToFlashlightMatrix( m_Shadows[shadowHandle].m_WorldToShadow, flashlightState );
}
else
{
BuildPerspectiveWorldToFlashlightMatrix( m_Shadows[shadowHandle].m_WorldToShadow, flashlightState );
}
shadowmgr->UpdateFlashlightState( m_Shadows[shadowHandle].m_ShadowHandle, flashlightState );
}
void CClientShadowMgr::DestroyProjection( ClientShadowHandle_t shadowHandle )
{
DestroyShadow( shadowHandle );
}
//-----------------------------------------------------------------------------
// Remove a shadow from the dirty list
//-----------------------------------------------------------------------------
void CClientShadowMgr::RemoveShadowFromDirtyList( ClientShadowHandle_t handle )
{
int idx = m_DirtyShadows.Find( handle );
if ( idx != m_DirtyShadows.InvalidIndex() )
{
// Clean up the shadow update bit.
IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( m_Shadows[handle].m_Entity );
if ( pRenderable )
{
pRenderable->MarkShadowDirty( false );
}
m_DirtyShadows.RemoveAt( idx );
}
idx = m_DirtyShadowsLeftOver.Find( handle );
if ( idx != m_DirtyShadowsLeftOver.InvalidIndex() )
{
// Clean up the shadow update bit.
IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( m_Shadows[handle].m_Entity );
if ( pRenderable )
{
pRenderable->MarkShadowDirty( false );
}
m_DirtyShadowsLeftOver.RemoveAt( idx );
}
}
//-----------------------------------------------------------------------------
// Remove a shadow
//-----------------------------------------------------------------------------
void CClientShadowMgr::DestroyShadow( ClientShadowHandle_t handle )
{
if ( m_bUpdatingDirtyShadows )
{
// While we're updating dirty shadows, destroying a shadow can cause an RB-Tree we're currently iterating to be changed.
// This can cause tree corruption resulting in infinite loops or crashes. Instead, we queue the shadow handle for deletion and
// service the queue after we're done updating.
QueueShadowForDestruction( handle );
return;
}
Assert( m_Shadows.IsValidIndex(handle) );
RemoveShadowFromDirtyList( handle );
IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( m_Shadows[handle].m_Entity );
if ( pRenderable )
{
CClientAlphaProperty *pAlphaProperty = static_cast<CClientAlphaProperty*>( pRenderable->GetIClientUnknown()->GetClientAlphaProperty() );
if ( pAlphaProperty )
{
pAlphaProperty->SetShadowHandle( CLIENTSHADOW_INVALID_HANDLE );
}
}
shadowmgr->DestroyShadow( m_Shadows[handle].m_ShadowHandle );
if ( m_Shadows[handle].m_ClientLeafShadowHandle != CLIENT_LEAF_SHADOW_INVALID_HANDLE )
{
ClientLeafSystem()->RemoveShadow( m_Shadows[handle].m_ClientLeafShadowHandle );
}
CleanUpRenderToTextureShadow( handle );
m_Shadows.Remove(handle);
}
//-----------------------------------------------------------------------------
// Queues a shadow for removal
//-----------------------------------------------------------------------------
void CClientShadowMgr::QueueShadowForDestruction( ClientShadowHandle_t handle )
{
// this function should be called infrequently (it is a failsafe)
// so check to make sure it's not queued to delete twice
if ( m_shadowsToDestroy.IsValidIndex( m_shadowsToDestroy.Find(handle) ) )
{
AssertMsg1( false, "Tried to queue shadow %d for deletion twice!\n", (int)(handle) );
}
else
{
m_shadowsToDestroy.AddToTail( handle );
}
}
//-----------------------------------------------------------------------------
// Removes queued shadows
//-----------------------------------------------------------------------------
void CClientShadowMgr::DestroyQueuedShadows()
{
Assert( !m_bUpdatingDirtyShadows );
for ( int i = 0; i < m_shadowsToDestroy.Count(); i++ )
{
DestroyShadow( m_shadowsToDestroy[i] );
}
m_shadowsToDestroy.RemoveAll();
}
//-----------------------------------------------------------------------------
// Build the worldtotexture matrix
//-----------------------------------------------------------------------------
void CClientShadowMgr::BuildGeneralWorldToShadowMatrix( VMatrix& matWorldToShadow,
const Vector& origin, const Vector& dir, const Vector& xvec, const Vector& yvec )
{
// We're assuming here that xvec + yvec aren't necessary perpendicular
// The shadow->world matrix is pretty simple:
// Just stick the origin in the translation component
// and the vectors in the columns...
matWorldToShadow.SetBasisVectors( xvec, yvec, dir );
matWorldToShadow.SetTranslation( origin );
matWorldToShadow[3][0] = matWorldToShadow[3][1] = matWorldToShadow[3][2] = 0.0f;
matWorldToShadow[3][3] = 1.0f;
// Now do a general inverse to get matWorldToShadow
MatrixInverseGeneral( matWorldToShadow, matWorldToShadow );
}
void CClientShadowMgr::BuildWorldToShadowMatrix( VMatrix& matWorldToShadow, const Vector& origin, const Quaternion& quatOrientation )
{
// The shadow->world matrix is pretty simple:
// Just stick the origin in the translation component
// and the vectors in the columns...
// The inverse of this transposes the rotational component
// and the translational component = - (rotation transpose) * origin
matrix3x4_t matOrientation;
QuaternionMatrix( quatOrientation, matOrientation ); // Convert quat to matrix3x4
PositionMatrix( vec3_origin, matOrientation ); // Zero out translation elements
VMatrix matBasis( matOrientation ); // Convert matrix3x4 to VMatrix
Vector vForward, vLeft, vUp;
matBasis.GetBasisVectors( vForward, vLeft, vUp );
matBasis.SetForward( vLeft ); // Bizarre vector flip inherited from earlier code, WTF?
matBasis.SetLeft( vUp );
matBasis.SetUp( vForward );
matWorldToShadow = matBasis.Transpose(); // Transpose
Vector translation;
Vector3DMultiply( matWorldToShadow, origin, translation );
translation *= -1.0f;
matWorldToShadow.SetTranslation( translation );
// The the bottom row.
matWorldToShadow[3][0] = matWorldToShadow[3][1] = matWorldToShadow[3][2] = 0.0f;
matWorldToShadow[3][3] = 1.0f;
}
void CClientShadowMgr::BuildPerspectiveWorldToFlashlightMatrix( VMatrix& matWorldToShadow, const FlashlightState_t &flashlightState )
{
VPROF_BUDGET( "CClientShadowMgr::BuildPerspectiveWorldToFlashlightMatrix", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING );
// Buildworld to shadow matrix, then perspective projection and concatenate
VMatrix matWorldToShadowView, matPerspective;
BuildWorldToShadowMatrix( matWorldToShadowView, flashlightState.m_vecLightOrigin,
flashlightState.m_quatOrientation );
MatrixBuildPerspective( matPerspective, flashlightState.m_fHorizontalFOVDegrees,
flashlightState.m_fVerticalFOVDegrees,
flashlightState.m_NearZ, flashlightState.m_FarZ );
MatrixMultiply( matPerspective, matWorldToShadowView, matWorldToShadow );
}
void CClientShadowMgr::BuildOrthoWorldToFlashlightMatrix( VMatrix& matWorldToShadow, const FlashlightState_t &flashlightState )
{
VPROF_BUDGET( "CClientShadowMgr::BuildPerspectiveWorldToFlashlightMatrix", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING );
// Buildworld to shadow matrix, then perspective projection and concatenate
VMatrix matWorldToShadowView, matPerspective;
BuildWorldToShadowMatrix( matWorldToShadowView, flashlightState.m_vecLightOrigin,
flashlightState.m_quatOrientation );
MatrixBuildOrtho( matPerspective,
flashlightState.m_fOrthoLeft, flashlightState.m_fOrthoTop, flashlightState.m_fOrthoRight, flashlightState.m_fOrthoBottom,
flashlightState.m_NearZ, flashlightState.m_FarZ );
// Shift it z/y to 0 to -2 space
VMatrix addW;
addW.Identity();
addW[0][3] = -1.0f;
addW[1][3] = -1.0f;
addW[2][3] = 0.0f;
MatrixMultiply( addW, matPerspective, matPerspective );
// Flip x/y to positive 0 to 1... flip z to negative
VMatrix scaleHalf;
scaleHalf.Identity();
scaleHalf[0][0] = -0.5f;
scaleHalf[1][1] = -0.5f;
scaleHalf[2][2] = -1.0f;
MatrixMultiply( scaleHalf, matPerspective, matPerspective );
MatrixMultiply( matPerspective, matWorldToShadowView, matWorldToShadow );
}
//-----------------------------------------------------------------------------
// Compute the shadow origin and attenuation start distance
//-----------------------------------------------------------------------------
float CClientShadowMgr::ComputeLocalShadowOrigin( IClientRenderable* pRenderable,
const Vector& mins, const Vector& maxs, const Vector& localShadowDir, float backupFactor, Vector& origin )
{
// Compute the centroid of the object...
Vector vecCentroid;
VectorAdd( mins, maxs, vecCentroid );
vecCentroid *= 0.5f;
Vector vecSize;
VectorSubtract( maxs, mins, vecSize );
float flRadius = vecSize.Length() * 0.5f;
// NOTE: The *origin* of the shadow cast is a point on a line passing through
// the centroid of the caster. The direction of this line is the shadow cast direction,
// and the point on that line corresponds to the endpoint of the box that is
// furthest *back* along the shadow direction
// For the first point at which the shadow could possibly start falling off,
// we need to use the point at which the ray described above leaves the
// bounding sphere surrounding the entity. This is necessary because otherwise,
// tall, thin objects would have their shadows appear + disappear as then spun about their origin
// Figure out the corner corresponding to the min + max projection
// along the shadow direction
// We're basically finding the point on the cube that has the largest and smallest
// dot product with the local shadow dir. Then we're taking the dot product
// of that with the localShadowDir. lastly, we're subtracting out the
// centroid projection to give us a distance along the localShadowDir to
// the front and back of the cube along the direction of the ray.
float centroidProjection = DotProduct( vecCentroid, localShadowDir );
float minDist = -centroidProjection;
for (int i = 0; i < 3; ++i)
{
if ( localShadowDir[i] > 0.0f )
{
minDist += localShadowDir[i] * mins[i];
}
else
{
minDist += localShadowDir[i] * maxs[i];
}
}
minDist *= backupFactor;
VectorMA( vecCentroid, minDist, localShadowDir, origin );
return flRadius - minDist;
}
//-----------------------------------------------------------------------------
// Sorts the components of a vector
//-----------------------------------------------------------------------------
static inline void SortAbsVectorComponents( const Vector& src, int* pVecIdx )
{
Vector absVec( fabs(src[0]), fabs(src[1]), fabs(src[2]) );
int maxIdx = (absVec[0] > absVec[1]) ? 0 : 1;
if (absVec[2] > absVec[maxIdx])
{
maxIdx = 2;
}
// always choose something right-handed....
switch( maxIdx )
{
case 0:
pVecIdx[0] = 1;
pVecIdx[1] = 2;
pVecIdx[2] = 0;
break;
case 1:
pVecIdx[0] = 2;
pVecIdx[1] = 0;
pVecIdx[2] = 1;
break;
case 2:
pVecIdx[0] = 0;
pVecIdx[1] = 1;
pVecIdx[2] = 2;
break;
}
}
//-----------------------------------------------------------------------------
// Build the worldtotexture matrix
//-----------------------------------------------------------------------------
static void BuildWorldToTextureMatrix( const VMatrix& matWorldToShadow,
const Vector2D& size, VMatrix& matWorldToTexture )
{
// Build a matrix that maps from shadow space to (u,v) coordinates
VMatrix shadowToUnit;
MatrixBuildScale( shadowToUnit, 1.0f / size.x, 1.0f / size.y, 1.0f );
shadowToUnit[0][3] = shadowToUnit[1][3] = 0.5f;
// Store off the world to (u,v) transformation
MatrixMultiply( shadowToUnit, matWorldToShadow, matWorldToTexture );
}
static void BuildOrthoWorldToShadowMatrix( VMatrix& worldToShadow,
const Vector& origin, const Vector& dir, const Vector& xvec, const Vector& yvec )
{
// This version is faster and assumes dir, xvec, yvec are perpendicular
AssertFloatEquals( DotProduct( dir, xvec ), 0.0f, 1e-3 );
AssertFloatEquals( DotProduct( dir, yvec ), 0.0f, 1e-3 );
AssertFloatEquals( DotProduct( xvec, yvec ), 0.0f, 1e-3 );
// The shadow->world matrix is pretty simple:
// Just stick the origin in the translation component
// and the vectors in the columns...
// The inverse of this transposes the rotational component
// and the translational component = - (rotation transpose) * origin
worldToShadow.SetBasisVectors( xvec, yvec, dir );
MatrixTranspose( worldToShadow, worldToShadow );
Vector translation;
Vector3DMultiply( worldToShadow, origin, translation );
translation *= -1.0f;
worldToShadow.SetTranslation( translation );
// The the bottom row.
worldToShadow[3][0] = worldToShadow[3][1] = worldToShadow[3][2] = 0.0f;
worldToShadow[3][3] = 1.0f;
}
//-----------------------------------------------------------------------------
// Set extra clip planes related to shadows...
//-----------------------------------------------------------------------------
void CClientShadowMgr::ClearExtraClipPlanes( ClientShadowHandle_t h )
{
if ( !r_shadow_deferred.GetBool() )
shadowmgr->ClearExtraClipPlanes( m_Shadows[h].m_ShadowHandle );
}
void CClientShadowMgr::AddExtraClipPlane( ClientShadowHandle_t h, const Vector& normal, float dist )
{
if ( !r_shadow_deferred.GetBool() )
shadowmgr->AddExtraClipPlane( m_Shadows[h].m_ShadowHandle, normal, dist );
}
//-----------------------------------------------------------------------------
// Compute the extra shadow planes
//-----------------------------------------------------------------------------
void CClientShadowMgr::ComputeExtraClipPlanes( IClientRenderable* pRenderable,
ClientShadowHandle_t handle, const Vector* vec,
const Vector& mins, const Vector& maxs, const Vector& localShadowDir )
{
// Compute the world-space position of the corner of the bounding box
// that's got the highest dotproduct with the local shadow dir...
Vector origin = pRenderable->GetRenderOrigin( );
float dir[3];
int i;
for ( i = 0; i < 3; ++i )
{
if (localShadowDir[i] < 0.0f)
{
VectorMA( origin, maxs[i], vec[i], origin );
dir[i] = 1;
}
else
{
VectorMA( origin, mins[i], vec[i], origin );
dir[i] = -1;
}
}
// Now that we have it, create 3 planes...
Vector normal;
ClearExtraClipPlanes(handle);
for ( i = 0; i < 3; ++i )
{
VectorMultiply( vec[i], dir[i], normal );
float dist = DotProduct( normal, origin );
AddExtraClipPlane( handle, normal, dist );
}
ClientShadow_t& shadow = m_Shadows[handle];
C_BaseEntity *pEntity = ClientEntityList().GetBaseEntityFromHandle( shadow.m_Entity );
if ( pEntity && pEntity->m_bEnableRenderingClipPlane )
{
normal[ 0 ] = -pEntity->m_fRenderingClipPlane[ 0 ];
normal[ 1 ] = -pEntity->m_fRenderingClipPlane[ 1 ];
normal[ 2 ] = -pEntity->m_fRenderingClipPlane[ 2 ];
AddExtraClipPlane( handle, normal, -pEntity->m_fRenderingClipPlane[ 3 ] - 0.5f );
}
}
inline ShadowType_t CClientShadowMgr::GetActualShadowCastType( ClientShadowHandle_t handle ) const
{
if ( handle == CLIENTSHADOW_INVALID_HANDLE )
{
return SHADOWS_NONE;
}
if ( m_Shadows[handle].m_Flags & SHADOW_FLAGS_USE_RENDER_TO_TEXTURE )
{
return ( m_RenderToTextureActive ? SHADOWS_RENDER_TO_TEXTURE : SHADOWS_SIMPLE );
}
else if( m_Shadows[handle].m_Flags & SHADOW_FLAGS_USE_DEPTH_TEXTURE )
{
return SHADOWS_RENDER_TO_DEPTH_TEXTURE;
}
else
{
return SHADOWS_SIMPLE;
}
}
inline ShadowType_t CClientShadowMgr::GetActualShadowCastType( IClientRenderable *pEnt ) const
{
return GetActualShadowCastType( pEnt->GetShadowHandle() );
}
//-----------------------------------------------------------------------------
// Adds a shadow to all leaves along a ray
//-----------------------------------------------------------------------------
class CShadowLeafEnum : public ISpatialLeafEnumerator
{
public:
bool EnumerateLeaf( int leaf, intp context )
{
m_LeafList.AddToTail( leaf );
return true;
}
void ExtractUnconnectedLeaves( const Vector &vecOrigin )
{
VPROF_BUDGET( "ExtractUnconnectedLeaves", "ExtractUnconnectedLeaves" );
int nCount = m_LeafList.Count();
bool *pIsConnected = (bool*)stackalloc( nCount * sizeof(bool) );
engine->ComputeLeavesConnected( vecOrigin, nCount, m_LeafList.Base(), pIsConnected );
int nCountRemoved = 0;
for ( int i = nCount; --i >= 0; )
{
if ( !pIsConnected[i] )
{
++nCountRemoved;
m_LeafList.FastRemove( i );
}
}
}
CUtlVectorFixedGrowable< int, 512 > m_LeafList;
};
//-----------------------------------------------------------------------------
// Builds a list of leaves inside the shadow volume
//-----------------------------------------------------------------------------
static void BuildShadowLeafList( CShadowLeafEnum *pEnum, const Vector& origin,
const Vector& dir, const Vector2D& size, float maxDist )
{
Ray_t ray;
VectorCopy( origin, ray.m_Start );
VectorMultiply( dir, maxDist, ray.m_Delta );
ray.m_StartOffset.Init( 0, 0, 0 );
float flRadius = sqrt( size.x * size.x + size.y * size.y ) * 0.5f;
ray.m_Extents.Init( flRadius, flRadius, flRadius );
ray.m_IsRay = false;
ray.m_IsSwept = true;
ISpatialQuery* pQuery = engine->GetBSPTreeQuery();
pQuery->EnumerateLeavesAlongRay( ray, pEnum, 0 );
}
//-----------------------------------------------------------------------------
// Builds a simple blobby shadow
//-----------------------------------------------------------------------------
void CClientShadowMgr::BuildOrthoShadow( IClientRenderable* pRenderable,
ClientShadowHandle_t handle, const Vector& mins, const Vector& maxs)
{
// Get the object's basis
Vector vec[3];
AngleVectors( pRenderable->GetRenderAngles(), &vec[0], &vec[1], &vec[2] );
vec[1] *= -1.0f;
Vector vecShadowDir = GetShadowDirection( handle );
// Project the shadow casting direction into the space of the object
Vector localShadowDir;
localShadowDir[0] = DotProduct( vec[0], vecShadowDir );
localShadowDir[1] = DotProduct( vec[1], vecShadowDir );
localShadowDir[2] = DotProduct( vec[2], vecShadowDir );
// Figure out which vector has the largest component perpendicular
// to the shadow handle...
// Sort by how perpendicular it is
int vecIdx[3];
SortAbsVectorComponents( localShadowDir, vecIdx );
// Here's our shadow basis vectors; namely the ones that are
// most perpendicular to the shadow casting direction
Vector xvec = vec[vecIdx[0]];
Vector yvec = vec[vecIdx[1]];
// Project them into a plane perpendicular to the shadow direction
xvec -= vecShadowDir * DotProduct( vecShadowDir, xvec );
yvec -= vecShadowDir * DotProduct( vecShadowDir, yvec );
VectorNormalize( xvec );
VectorNormalize( yvec );
// Compute the box size
Vector boxSize;
VectorSubtract( maxs, mins, boxSize );
// We project the two longest sides into the vectors perpendicular
// to the projection direction, then add in the projection of the perp direction
Vector2D size( boxSize[vecIdx[0]], boxSize[vecIdx[1]] );
size.x *= fabs( DotProduct( vec[vecIdx[0]], xvec ) );
size.y *= fabs( DotProduct( vec[vecIdx[1]], yvec ) );
// Add the third component into x and y
size.x += boxSize[vecIdx[2]] * fabs( DotProduct( vec[vecIdx[2]], xvec ) );
size.y += boxSize[vecIdx[2]] * fabs( DotProduct( vec[vecIdx[2]], yvec ) );
// Bloat a bit, since the shadow wants to extend outside the model a bit
size.x += 10.0f;
size.y += 10.0f;
// Clamp the minimum size
Vector2DMax( size, Vector2D(10.0f, 10.0f), size );
// Place the origin at the point with min dot product with shadow dir
Vector org;
float falloffStart = ComputeLocalShadowOrigin( pRenderable, mins, maxs, localShadowDir, 2.0f, org );
// Transform the local origin into world coordinates
Vector worldOrigin = pRenderable->GetRenderOrigin( );
VectorMA( worldOrigin, org.x, vec[0], worldOrigin );
VectorMA( worldOrigin, org.y, vec[1], worldOrigin );
VectorMA( worldOrigin, org.z, vec[2], worldOrigin );
// FUNKY: A trick to reduce annoying texelization artifacts!?
float dx = 1.0f / TEXEL_SIZE_PER_CASTER_SIZE;
worldOrigin.x = (int)(worldOrigin.x / dx) * dx;
worldOrigin.y = (int)(worldOrigin.y / dx) * dx;
worldOrigin.z = (int)(worldOrigin.z / dx) * dx;
// NOTE: We gotta use the general matrix because xvec and yvec aren't perp
VMatrix matWorldToShadow, matWorldToTexture;
// negate xvec so that the matrix will have the same handedness as for an RTT shadow
BuildGeneralWorldToShadowMatrix( m_Shadows[handle].m_WorldToShadow, worldOrigin, vecShadowDir, -xvec, yvec );
BuildWorldToTextureMatrix( m_Shadows[handle].m_WorldToShadow, size, matWorldToTexture );
Vector2DCopy( size, m_Shadows[handle].m_WorldSize );
MatrixCopy( matWorldToTexture, m_Shadows[handle].m_WorldToTexture );
// Compute the falloff attenuation
// Area computation isn't exact since xvec is not perp to yvec, but close enough
// float shadowArea = size.x * size.y;
// The entity may be overriding our shadow cast distance
float flShadowCastDistance = GetShadowDistance( pRenderable );
float maxHeight = flShadowCastDistance + falloffStart; //3.0f * sqrt( shadowArea );
CShadowLeafEnum leafList;
BuildShadowLeafList( &leafList, worldOrigin, vecShadowDir, size, maxHeight );
int nCount = leafList.m_LeafList.Count();
const int *pLeafList = leafList.m_LeafList.Base();
if ( !r_shadow_deferred.GetBool() )
{
shadowmgr->ProjectShadow( m_Shadows[handle].m_ShadowHandle, worldOrigin,
vecShadowDir, matWorldToTexture, size, nCount, pLeafList, maxHeight, falloffStart, MAX_FALLOFF_AMOUNT, pRenderable->GetRenderOrigin() );
}
m_Shadows[handle].m_MaxDist = maxHeight;
m_Shadows[handle].m_FalloffStart = falloffStart;
// Compute extra clip planes to prevent poke-thru
// FIXME!!!!!!!!!!!!!! Removing this for now since it seems to mess up the blobby shadows.
// ComputeExtraClipPlanes( pEnt, handle, vec, mins, maxs, localShadowDir );
// Add the shadow to the client leaf system so it correctly marks
// leafs as being affected by a particular shadow
ClientLeafSystem()->ProjectShadow( m_Shadows[handle].m_ClientLeafShadowHandle, nCount, pLeafList );
}
//-----------------------------------------------------------------------------
// Visualization....
//-----------------------------------------------------------------------------
void CClientShadowMgr::DrawRenderToTextureDebugInfo( IClientRenderable* pRenderable, const Vector& mins, const Vector& maxs )
{
// Get the object's basis
Vector vec[3];
AngleVectors( pRenderable->GetRenderAngles(), &vec[0], &vec[1], &vec[2] );
vec[1] *= -1.0f;
Vector vecSize;
VectorSubtract( maxs, mins, vecSize );
Vector vecOrigin = pRenderable->GetRenderOrigin();
Vector start, end, end2;
VectorMA( vecOrigin, mins.x, vec[0], start );
VectorMA( start, mins.y, vec[1], start );
VectorMA( start, mins.z, vec[2], start );
VectorMA( start, vecSize.x, vec[0], end );
VectorMA( end, vecSize.z, vec[2], end2 );
debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 );
debugoverlay->AddLineOverlay( end2, end, 255, 0, 0, true, 0.01 );
VectorMA( start, vecSize.y, vec[1], end );
VectorMA( end, vecSize.z, vec[2], end2 );
debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 );
debugoverlay->AddLineOverlay( end2, end, 255, 0, 0, true, 0.01 );
VectorMA( start, vecSize.z, vec[2], end );
debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 );
start = end;
VectorMA( start, vecSize.x, vec[0], end );
debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 );
VectorMA( start, vecSize.y, vec[1], end );
debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 );
VectorMA( end, vecSize.x, vec[0], start );
VectorMA( start, -vecSize.x, vec[0], end );
debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 );
VectorMA( start, -vecSize.y, vec[1], end );
debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 );
VectorMA( start, -vecSize.z, vec[2], end );
debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 );
start = end;
VectorMA( start, -vecSize.x, vec[0], end );
debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 );
VectorMA( start, -vecSize.y, vec[1], end );
debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 );
C_BaseEntity *pEnt = pRenderable->GetIClientUnknown()->GetBaseEntity();
if ( pEnt )
{
debugoverlay->AddTextOverlay( vecOrigin, 0, "%d", pEnt->entindex() );
}
else
{
debugoverlay->AddTextOverlay( vecOrigin, 0, "%X", (size_t)pRenderable );
}
}
extern ConVar cl_drawshadowtexture;
extern ConVar cl_shadowtextureoverlaysize;
//-----------------------------------------------------------------------------
// Builds a more complex shadow...
//-----------------------------------------------------------------------------
void CClientShadowMgr::BuildRenderToTextureShadow( IClientRenderable* pRenderable,
ClientShadowHandle_t handle, const Vector& mins, const Vector& maxs)
{
if ( cl_drawshadowtexture.GetInt() )
{
// Red wireframe bounding box around objects whose RTT shadows are being updated that frame
DrawRenderToTextureDebugInfo( pRenderable, mins, maxs );
}
// Get the object's basis
Vector vec[3];
AngleVectors( pRenderable->GetRenderAngles(), &vec[0], &vec[1], &vec[2] );
vec[1] *= -1.0f;
Vector vecShadowDir = GetShadowDirection( handle );
// Debugging aid
// const model_t *pModel = pRenderable->GetModel();
// const char *pDebugName = modelinfo->GetModelName( pModel );
// Project the shadow casting direction into the space of the object
Vector localShadowDir;
localShadowDir[0] = DotProduct( vec[0], vecShadowDir );
localShadowDir[1] = DotProduct( vec[1], vecShadowDir );
localShadowDir[2] = DotProduct( vec[2], vecShadowDir );
// Compute the box size
Vector boxSize;
VectorSubtract( maxs, mins, boxSize );
Vector yvec;
float fProjMax = 0.0f;
for( int i = 0; i != 3; ++i )
{
Vector test = vec[i] - ( vecShadowDir * DotProduct( vecShadowDir, vec[i] ) );
test *= boxSize[i]; //doing after the projection to simplify projection math
float fLengthSqr = test.LengthSqr();
if( fLengthSqr > fProjMax )
{
fProjMax = fLengthSqr;
yvec = test;
}
}
VectorNormalize( yvec );
// Compute the x vector
Vector xvec;
CrossProduct( yvec, vecShadowDir, xvec );
// We project the two longest sides into the vectors perpendicular
// to the projection direction, then add in the projection of the perp direction
Vector2D size;
size.x = boxSize.x * fabs( DotProduct( vec[0], xvec ) ) +
boxSize.y * fabs( DotProduct( vec[1], xvec ) ) +
boxSize.z * fabs( DotProduct( vec[2], xvec ) );
size.y = boxSize.x * fabs( DotProduct( vec[0], yvec ) ) +
boxSize.y * fabs( DotProduct( vec[1], yvec ) ) +
boxSize.z * fabs( DotProduct( vec[2], yvec ) );
size.x += 2.0f * TEXEL_SIZE_PER_CASTER_SIZE;
size.y += 2.0f * TEXEL_SIZE_PER_CASTER_SIZE;
// Place the origin at the point with min dot product with shadow dir
Vector org;
float falloffStart = ComputeLocalShadowOrigin( pRenderable, mins, maxs, localShadowDir, 1.0f, org );
// Transform the local origin into world coordinates
Vector worldOrigin = pRenderable->GetRenderOrigin( );
VectorMA( worldOrigin, org.x, vec[0], worldOrigin );
VectorMA( worldOrigin, org.y, vec[1], worldOrigin );
VectorMA( worldOrigin, org.z, vec[2], worldOrigin );
VMatrix matWorldToTexture;
BuildOrthoWorldToShadowMatrix( m_Shadows[handle].m_WorldToShadow, worldOrigin, vecShadowDir, xvec, yvec );
BuildWorldToTextureMatrix( m_Shadows[handle].m_WorldToShadow, size, matWorldToTexture );
Vector2DCopy( size, m_Shadows[handle].m_WorldSize );
MatrixCopy( matWorldToTexture, m_Shadows[handle].m_WorldToTexture );
// Compute the falloff attenuation
// Area computation isn't exact since xvec is not perp to yvec, but close enough
// Extra factor of 4 in the maxHeight due to the size being half as big
// float shadowArea = size.x * size.y;
// The entity may be overriding our shadow cast distance
float flShadowCastDistance = GetShadowDistance( pRenderable );
float maxHeight = flShadowCastDistance + falloffStart; //3.0f * sqrt( shadowArea );
CShadowLeafEnum leafList;
BuildShadowLeafList( &leafList, worldOrigin, vecShadowDir, size, maxHeight );
int nCount = leafList.m_LeafList.Count();
const int *pLeafList = leafList.m_LeafList.Base();
if ( !r_shadow_deferred.GetBool() )
{
shadowmgr->ProjectShadow( m_Shadows[handle].m_ShadowHandle, worldOrigin,
vecShadowDir, matWorldToTexture, size, nCount, pLeafList, maxHeight, falloffStart, MAX_FALLOFF_AMOUNT, pRenderable->GetRenderOrigin() );
// Compute extra clip planes to prevent poke-thru
ComputeExtraClipPlanes( pRenderable, handle, vec, mins, maxs, localShadowDir );
}
m_Shadows[handle].m_MaxDist = maxHeight;
m_Shadows[handle].m_FalloffStart = falloffStart;
// Add the shadow to the client leaf system so it correctly marks
// leafs as being affected by a particular shadow
ClientLeafSystem()->ProjectShadow( m_Shadows[handle].m_ClientLeafShadowHandle, nCount, pLeafList );
}
static void LineDrawHelperProjective( const Vector &startShadowSpace, const Vector &endShadowSpace,
const VMatrix &shadowToWorld, unsigned char r = 255, unsigned char g = 255, unsigned char b = 255 )
{
Vector startWorldSpace, endWorldSpace;
Vector3DMultiplyPositionProjective( shadowToWorld, startShadowSpace, startWorldSpace );
Vector3DMultiplyPositionProjective( shadowToWorld, endShadowSpace, endWorldSpace );
debugoverlay->AddLineOverlay( startWorldSpace, endWorldSpace, r, g, b, false, -1 );
}
static void LineDrawHelper( const Vector &start, const Vector &end,
const VMatrix &viewMatrixInverse, unsigned char r = 255, unsigned char g = 255, unsigned char b = 255 )
{
Vector startWorldSpace, endWorldSpace;
Vector3DMultiplyPosition( viewMatrixInverse, start, startWorldSpace );
Vector3DMultiplyPosition( viewMatrixInverse, end, endWorldSpace );
debugoverlay->AddLineOverlay( startWorldSpace, endWorldSpace, r, g, b, false, -1 );
}
// We're going to sweep out an inner and outer superellipse. Each superellipse has the equation:
//
// 2 2
// - -
// ( x )d ( y )d
// (---) + (---) = 1
// ( a ) ( b )
//
// where,
// d is what we're calling m_fRoundness
//
// The inner superellipse uses a = m_fWidth and b = m_fHeight
// The outer superellipse uses a = (m_fWidth + m_fWedge) and b = (m_fHeight + m_fHedge)
//
// Controls density of wireframe uberlight
#define NUM_SUPER_ELLIPSE_POINTS 192
#define CONNECTOR_FREQ 24
void CClientShadowMgr::DrawUberlightRig( const Vector &vOrigin, const VMatrix &matWorldToFlashlight, FlashlightState_t state )
{
int i;
float fXNear, fXFar, fYNear, fYFar, fXNearEdge, fXFarEdge, fYNearEdge, fYFarEdge, m;
UberlightState_t uber = state.m_uberlightState;
VMatrix viewMatrixInverse, viewMatrix;
// A set of scratch points on the +x +y quadrants of the near and far ends of the swept superellipse wireframe
Vector vecNearInnerPoints[(NUM_SUPER_ELLIPSE_POINTS / 4) + 1]; // Inner points for four superellipses
Vector vecFarInnerPoints[(NUM_SUPER_ELLIPSE_POINTS / 4) + 1];
Vector vecNearEdgeInnerPoints[(NUM_SUPER_ELLIPSE_POINTS / 4) + 1];
Vector vecFarEdgeInnerPoints[(NUM_SUPER_ELLIPSE_POINTS / 4) + 1];
Vector vecNearOuterPoints[(NUM_SUPER_ELLIPSE_POINTS / 4) + 1]; // Outer points for four superellipses
Vector vecFarOuterPoints[(NUM_SUPER_ELLIPSE_POINTS / 4) + 1];
Vector vecNearEdgeOuterPoints[(NUM_SUPER_ELLIPSE_POINTS / 4) + 1];
Vector vecFarEdgeOuterPoints[(NUM_SUPER_ELLIPSE_POINTS / 4) + 1];
// Clock hand which sweeps out a full circle
float fTheta = 0;
float fThetaIncrement = (2.0f * 3.14159) / ((float)NUM_SUPER_ELLIPSE_POINTS);
// precompute the 2/d exponent
float r = 2.0f / uber.m_fRoundness;
// Initialize arrays of points in light's local space defining +x +y quadrants of extruded superellipses (including x==0 and y==0 vertices)
for ( i = 0; i<(NUM_SUPER_ELLIPSE_POINTS / 4) + 1; i++ )
{
if ( i == 0 ) // If this is the 0th vertex
{
fXFar = uber.m_fWidth * uber.m_fCutOff; // compute near and far x's
fXNear = uber.m_fWidth * uber.m_fCutOn;
fXFarEdge = uber.m_fWidth * (uber.m_fCutOff + uber.m_fFarEdge);
fXNearEdge = uber.m_fWidth * (uber.m_fCutOn - uber.m_fNearEdge);
fYFar = fYNear = fYFarEdge = fYNearEdge =0; // y's are zero
}
else if ( i == (NUM_SUPER_ELLIPSE_POINTS / 4) ) // If this is the vertex on the y axis, avoid numerical problems
{
fXFar = fXNear = fXFarEdge = fXNearEdge = 0; // x's are zero
fYFar = uber.m_fHeight * uber.m_fCutOff; // compute near and far y's
fYNear = uber.m_fHeight * uber.m_fCutOn;
fYFarEdge = uber.m_fHeight * (uber.m_fCutOff + uber.m_fFarEdge);
fYNearEdge = uber.m_fHeight * (uber.m_fCutOn - uber.m_fNearEdge);
}
else
{
m = sinf(fTheta) / cosf(fTheta); // compute slope of line from origin
// Solve for inner x's (intersect line of slope m with inner superellipses)
fXFar = (powf(powf(1.0f/uber.m_fWidth,r) + powf(m/uber.m_fHeight,r), -1.0f/r) * uber.m_fCutOff);
fXNear = (powf(powf(1.0f/uber.m_fWidth,r) + powf(m/uber.m_fHeight,r), -1.0f/r) * uber.m_fCutOn);
fXFarEdge = (powf(powf(1.0f/uber.m_fWidth,r) + powf(m/uber.m_fHeight,r), -1.0f/r) * (uber.m_fCutOff + uber.m_fFarEdge));
fXNearEdge = (powf(powf(1.0f/uber.m_fWidth,r) + powf(m/uber.m_fHeight,r), -1.0f/r) * (uber.m_fCutOn - uber.m_fNearEdge));
// Solve for inner y's using line equations
fYFar = m * fXFar;
fYNear = m * fXNear;
fYFarEdge = m * fXFarEdge;
fYNearEdge = m * fXNearEdge;
}
// World to Light's View matrix
BuildWorldToShadowMatrix( viewMatrix, state.m_vecLightOrigin, state.m_quatOrientation );
viewMatrixInverse = viewMatrix.InverseTR();
// Store world space positions in array
vecFarInnerPoints[i] = Vector( fXFar, fYFar, uber.m_fCutOff );
vecNearInnerPoints[i] = Vector( fXNear, fYNear, uber.m_fCutOn );
vecFarEdgeInnerPoints[i] = Vector( fXFarEdge, fYFarEdge, uber.m_fCutOff + uber.m_fFarEdge );
vecNearEdgeInnerPoints[i] = Vector( fXNearEdge, fYNearEdge, uber.m_fCutOn - uber.m_fNearEdge );
if ( i == 0 ) // If this is the 0th vertex
{
fXFar = (uber.m_fWidth + uber.m_fWedge) * uber.m_fCutOff; // compute near and far x's
fXNear = (uber.m_fWidth + uber.m_fWedge) * uber.m_fCutOn;
fXFarEdge = (uber.m_fWidth + uber.m_fWedge) * (uber.m_fCutOff + uber.m_fFarEdge);
fXNearEdge = (uber.m_fWidth + uber.m_fWedge) * (uber.m_fCutOn - uber.m_fNearEdge);
fYFar = fYNear = fYFarEdge = fYNearEdge = 0; // y's are zero
}
else if ( i == (NUM_SUPER_ELLIPSE_POINTS / 4) ) // If this is the vertex on the y axis, avoid numerical problems
{
fXFar = fXNear = fXFarEdge = fXNearEdge = 0; // x's are zero
fYFar = (uber.m_fHeight + uber.m_fHedge) * uber.m_fCutOff; // compute near and far y's
fYNear = (uber.m_fHeight + uber.m_fHedge) * uber.m_fCutOn;
fYFarEdge = (uber.m_fHeight + uber.m_fHedge) * (uber.m_fCutOff + uber.m_fFarEdge);
fYNearEdge = (uber.m_fHeight + uber.m_fHedge) * (uber.m_fCutOn - uber.m_fNearEdge);
}
else
{
m = sinf(fTheta) / cosf(fTheta); // compute slope of line from origin
// Solve for inner x's (intersect line of slope m with inner superellipses)
fXFar = (powf(powf(1.0f/(uber.m_fWidth + uber.m_fWedge),r) + powf(m/(uber.m_fHeight + uber.m_fHedge),r), -1.0f/r) * uber.m_fCutOff);
fXNear = (powf(powf(1.0f/(uber.m_fWidth + uber.m_fWedge),r) + powf(m/(uber.m_fHeight + uber.m_fHedge),r), -1.0f/r) * uber.m_fCutOn);
fXFarEdge = (powf(powf(1.0f/(uber.m_fWidth + uber.m_fWedge),r) + powf(m/(uber.m_fHeight + uber.m_fHedge),r), -1.0f/r) * (uber.m_fCutOff+ uber.m_fFarEdge));
fXNearEdge = (powf(powf(1.0f/(uber.m_fWidth + uber.m_fWedge),r) + powf(m/(uber.m_fHeight + uber.m_fHedge),r), -1.0f/r) * (uber.m_fCutOn - uber.m_fNearEdge));
// Solve for inner y's using line equations
fYFar = m * fXFar;
fYNear = m * fXNear;
fYFarEdge = m * fXFarEdge;
fYNearEdge = m * fXNearEdge;
}
// Store in array
vecFarOuterPoints[i] = Vector( fXFar, fYFar, uber.m_fCutOff );
vecNearOuterPoints[i] = Vector( fXNear, fYNear, uber.m_fCutOn );
vecFarEdgeOuterPoints[i] = Vector( fXFarEdge, fYFarEdge, uber.m_fCutOff + uber.m_fFarEdge );
vecNearEdgeOuterPoints[i] = Vector( fXNearEdge, fYNearEdge, uber.m_fCutOn - uber.m_fNearEdge );
fTheta += fThetaIncrement;
}
Vector preVector, curVector;
// Near inner superellipse
for ( i=0; i<NUM_SUPER_ELLIPSE_POINTS; i++ )
{
if ( i <= (NUM_SUPER_ELLIPSE_POINTS / 4)) // +x +y quadrant, copy from scratch array directly
{
curVector = vecNearInnerPoints[i];
curVector.x += uber.m_fShearx * curVector.z;
curVector.y += uber.m_fSheary * curVector.z;
}
else if ( i <= (NUM_SUPER_ELLIPSE_POINTS / 2)) // -x +y quadrant, negate x when copying from scratch array
{
curVector = vecNearInnerPoints[(NUM_SUPER_ELLIPSE_POINTS / 2)-i];
curVector.x *= -1;
curVector.x += uber.m_fShearx * curVector.z;
curVector.y += uber.m_fSheary * curVector.z;
}
else if ( i <= (3*NUM_SUPER_ELLIPSE_POINTS / 4)) // -x -y quadrant, negate when copying from scratch array
{
curVector = vecNearInnerPoints[i-(NUM_SUPER_ELLIPSE_POINTS / 2)];
curVector.x *= -1;
curVector.y *= -1;
curVector.x += uber.m_fShearx * curVector.z;
curVector.y += uber.m_fSheary * curVector.z;
}
else // +x -y quadrant, negate y when copying from scratch array
{
curVector = vecNearInnerPoints[NUM_SUPER_ELLIPSE_POINTS-i];
curVector.y *= -1;
curVector.x += uber.m_fShearx * curVector.z;
curVector.y += uber.m_fSheary * curVector.z;
}
if ( i != 0 )
{
LineDrawHelper( preVector, curVector, viewMatrixInverse, 255, 10, 10 );
}
preVector = curVector;
}
LineDrawHelper( preVector, vecNearInnerPoints[0], viewMatrixInverse, 255, 10, 10 );
// Far inner superellipse
for (i=0; i<NUM_SUPER_ELLIPSE_POINTS; i++)
{
if ( i <= (NUM_SUPER_ELLIPSE_POINTS / 4)) // +x +y quadrant, copy from scratch array directly
{
curVector = vecFarInnerPoints[i];
curVector.x += uber.m_fShearx * curVector.z;
curVector.y += uber.m_fSheary * curVector.z;
}
else if ( i <= (NUM_SUPER_ELLIPSE_POINTS / 2)) // -x +y quadrant, negate x when copying from scratch array
{
curVector = vecFarInnerPoints[(NUM_SUPER_ELLIPSE_POINTS / 2)-i];
curVector.x *= -1;
curVector.x += uber.m_fShearx * curVector.z;
curVector.y += uber.m_fSheary * curVector.z;
}
else if ( i <= (3*NUM_SUPER_ELLIPSE_POINTS / 4)) // -x -y quadrant, negate when copying from scratch array
{
curVector = vecFarInnerPoints[i-(NUM_SUPER_ELLIPSE_POINTS / 2)];
curVector.x *= -1;
curVector.y *= -1;
curVector.x += uber.m_fShearx * curVector.z;
curVector.y += uber.m_fSheary * curVector.z;
}
else // +x -y quadrant, negate y when copying from scratch array
{
curVector = vecFarInnerPoints[NUM_SUPER_ELLIPSE_POINTS-i];
curVector.y *= -1;
curVector.x += uber.m_fShearx * curVector.z;
curVector.y += uber.m_fSheary * curVector.z;
}
if ( i != 0 )
{
LineDrawHelper( preVector, curVector, viewMatrixInverse, 10, 10, 255 );
}
preVector = curVector;
}
LineDrawHelper( preVector, vecFarInnerPoints[0], viewMatrixInverse, 10, 10, 255 );
// Near outer superellipse
for (i=0; i<NUM_SUPER_ELLIPSE_POINTS; i++)
{
if ( i <= (NUM_SUPER_ELLIPSE_POINTS / 4)) // +x +y quadrant, copy from scratch array directly
{
curVector = vecNearOuterPoints[i];
curVector.x += uber.m_fShearx * curVector.z;
curVector.y += uber.m_fSheary * curVector.z;
}
else if ( i <= (NUM_SUPER_ELLIPSE_POINTS / 2)) // -x +y quadrant, negate x when copying from scratch array
{
curVector = vecNearOuterPoints[(NUM_SUPER_ELLIPSE_POINTS / 2)-i];
curVector.x *= -1;
curVector.x += uber.m_fShearx * curVector.z;
curVector.y += uber.m_fSheary * curVector.z;
}
else if ( i <= (3*NUM_SUPER_ELLIPSE_POINTS / 4)) // -x -y quadrant, negate when copying from scratch array
{
curVector = vecNearOuterPoints[i-(NUM_SUPER_ELLIPSE_POINTS / 2)];
curVector.x *= -1;
curVector.y *= -1;
curVector.x += uber.m_fShearx * curVector.z;
curVector.y += uber.m_fSheary * curVector.z;
}
else // +x -y quadrant, negate y when copying from scratch array
{
curVector = vecNearOuterPoints[NUM_SUPER_ELLIPSE_POINTS-i];
curVector.y *= -1;
curVector.x += uber.m_fShearx * curVector.z;
curVector.y += uber.m_fSheary * curVector.z;
}
if ( i != 0 )
{
LineDrawHelper( preVector, curVector, viewMatrixInverse, 255, 10, 10);
}
preVector = curVector;
}
LineDrawHelper( preVector, vecNearOuterPoints[0], viewMatrixInverse, 255, 10, 10 );
// Far outer superellipse
for (i=0; i<NUM_SUPER_ELLIPSE_POINTS; i++)
{
if ( i <= (NUM_SUPER_ELLIPSE_POINTS / 4)) // +x +y quadrant, copy from scratch array directly
{
curVector = vecFarOuterPoints[i];
curVector.x += uber.m_fShearx * curVector.z;
curVector.y += uber.m_fSheary * curVector.z;
}
else if ( i <= (NUM_SUPER_ELLIPSE_POINTS / 2)) // -x +y quadrant, negate x when copying from scratch array
{
curVector = vecFarOuterPoints[(NUM_SUPER_ELLIPSE_POINTS / 2)-i];
curVector.x *= -1;
curVector.x += uber.m_fShearx * curVector.z;
curVector.y += uber.m_fSheary * curVector.z;
}
else if ( i <= (3*NUM_SUPER_ELLIPSE_POINTS / 4)) // -x -y quadrant, negate when copying from scratch array
{
curVector = vecFarOuterPoints[i-(NUM_SUPER_ELLIPSE_POINTS / 2)];
curVector.x *= -1;
curVector.y *= -1;
curVector.x += uber.m_fShearx * curVector.z;
curVector.y += uber.m_fSheary * curVector.z;
}
else // +x -y quadrant, negate y when copying from scratch array
{
curVector = vecFarOuterPoints[NUM_SUPER_ELLIPSE_POINTS-i];
curVector.y *= -1;
curVector.x += uber.m_fShearx * curVector.z;
curVector.y += uber.m_fSheary * curVector.z;
}
if ( i != 0 )
{
LineDrawHelper( preVector, curVector, viewMatrixInverse, 10, 10, 255 );
}
preVector = curVector;
}
LineDrawHelper( preVector, vecFarOuterPoints[0], viewMatrixInverse, 10, 10, 255 );
// Near edge inner superellipse
for (i=0; i<NUM_SUPER_ELLIPSE_POINTS; i++)
{
if ( i <= (NUM_SUPER_ELLIPSE_POINTS / 4)) // +x +y quadrant, copy from scratch array directly
{
curVector = vecNearEdgeInnerPoints[i];
curVector.x += uber.m_fShearx * curVector.z;
curVector.y += uber.m_fSheary * curVector.z;
}
else if ( i <= (NUM_SUPER_ELLIPSE_POINTS / 2)) // -x +y quadrant, negate x when copying from scratch array
{
curVector = vecNearEdgeInnerPoints[(NUM_SUPER_ELLIPSE_POINTS / 2)-i];
curVector.x *= -1;
curVector.x += uber.m_fShearx * curVector.z;
curVector.y += uber.m_fSheary * curVector.z;
}
else if ( i <= (3*NUM_SUPER_ELLIPSE_POINTS / 4)) // -x -y quadrant, negate when copying from scratch array
{
curVector = vecNearEdgeInnerPoints[i-(NUM_SUPER_ELLIPSE_POINTS / 2)];
curVector.x *= -1;
curVector.y *= -1;
curVector.x += uber.m_fShearx * curVector.z;
curVector.y += uber.m_fSheary * curVector.z;
}
else // +x -y quadrant, negate y when copying from scratch array
{
curVector = vecNearEdgeInnerPoints[NUM_SUPER_ELLIPSE_POINTS-i];
curVector.y *= -1;
curVector.x += uber.m_fShearx * curVector.z;
curVector.y += uber.m_fSheary * curVector.z;
}
if ( i != 0 )
{
LineDrawHelper( preVector, curVector, viewMatrixInverse, 255, 10, 10 );
}
preVector = curVector;
}
LineDrawHelper( preVector, vecNearEdgeInnerPoints[0], viewMatrixInverse, 255, 10, 10 );
// Far inner superellipse
for (i=0; i<NUM_SUPER_ELLIPSE_POINTS; i++)
{
if ( i <= (NUM_SUPER_ELLIPSE_POINTS / 4)) // +x +y quadrant, copy from scratch array directly
{
curVector = vecFarEdgeInnerPoints[i];
curVector.x += uber.m_fShearx * curVector.z;
curVector.y += uber.m_fSheary * curVector.z;
}
else if ( i <= (NUM_SUPER_ELLIPSE_POINTS / 2)) // -x +y quadrant, negate x when copying from scratch array
{
curVector = vecFarEdgeInnerPoints[(NUM_SUPER_ELLIPSE_POINTS / 2)-i];
curVector.x *= -1;
curVector.x += uber.m_fShearx * curVector.z;
curVector.y += uber.m_fSheary * curVector.z;
}
else if ( i <= (3*NUM_SUPER_ELLIPSE_POINTS / 4)) // -x -y quadrant, negate when copying from scratch array
{
curVector = vecFarEdgeInnerPoints[i-(NUM_SUPER_ELLIPSE_POINTS / 2)];
curVector.x *= -1;
curVector.y *= -1;
curVector.x += uber.m_fShearx * curVector.z;
curVector.y += uber.m_fSheary * curVector.z;
}
else // +x -y quadrant, negate y when copying from scratch array
{
curVector = vecFarEdgeInnerPoints[NUM_SUPER_ELLIPSE_POINTS-i];
curVector.y *= -1;
curVector.x += uber.m_fShearx * curVector.z;
curVector.y += uber.m_fSheary * curVector.z;
}
if ( i != 0 )
{
LineDrawHelper( preVector, curVector, viewMatrixInverse, 10, 10, 255 );
}
preVector = curVector;
}
LineDrawHelper( preVector, vecFarEdgeInnerPoints[0], viewMatrixInverse, 10, 10, 255 );
// Near outer superellipse
for (i=0; i<NUM_SUPER_ELLIPSE_POINTS; i++)
{
if ( i <= (NUM_SUPER_ELLIPSE_POINTS / 4)) // +x +y quadrant, copy from scratch array directly
{
curVector = vecNearEdgeOuterPoints[i];
curVector.x += uber.m_fShearx * curVector.z;
curVector.y += uber.m_fSheary * curVector.z;
}
else if ( i <= (NUM_SUPER_ELLIPSE_POINTS / 2)) // -x +y quadrant, negate x when copying from scratch array
{
curVector = vecNearEdgeOuterPoints[(NUM_SUPER_ELLIPSE_POINTS / 2)-i];
curVector.x *= -1;
curVector.x += uber.m_fShearx * curVector.z;
curVector.y += uber.m_fSheary * curVector.z;
}
else if ( i <= (3*NUM_SUPER_ELLIPSE_POINTS / 4)) // -x -y quadrant, negate when copying from scratch array
{
curVector = vecNearEdgeOuterPoints[i-(NUM_SUPER_ELLIPSE_POINTS / 2)];
curVector.x *= -1;
curVector.y *= -1;
curVector.x += uber.m_fShearx * curVector.z;
curVector.y += uber.m_fSheary * curVector.z;
}
else // +x -y quadrant, negate y when copying from scratch array
{
curVector = vecNearEdgeOuterPoints[NUM_SUPER_ELLIPSE_POINTS-i];
curVector.y *= -1;
curVector.x += uber.m_fShearx * curVector.z;
curVector.y += uber.m_fSheary * curVector.z;
}
if ( i != 0 )
{
LineDrawHelper( preVector, curVector, viewMatrixInverse, 255, 10, 10 );
}
preVector = curVector;
}
LineDrawHelper( preVector, vecNearEdgeOuterPoints[0], viewMatrixInverse, 255, 10, 10 );
// Far outer superellipse
for ( i=0; i<NUM_SUPER_ELLIPSE_POINTS; i++ )
{
if ( i <= (NUM_SUPER_ELLIPSE_POINTS / 4)) // +x +y quadrant, copy from scratch array directly
{
curVector = vecFarEdgeOuterPoints[i];
curVector.x += uber.m_fShearx * curVector.z;
curVector.y += uber.m_fSheary * curVector.z;
}
else if ( i <= (NUM_SUPER_ELLIPSE_POINTS / 2)) // -x +y quadrant, negate x when copying from scratch array
{
curVector = vecFarEdgeOuterPoints[(NUM_SUPER_ELLIPSE_POINTS / 2)-i];
curVector.x *= -1;
curVector.x += uber.m_fShearx * curVector.z;
curVector.y += uber.m_fSheary * curVector.z;
}
else if ( i <= (3*NUM_SUPER_ELLIPSE_POINTS / 4)) // -x -y quadrant, negate when copying from scratch array
{
curVector = vecFarEdgeOuterPoints[i-(NUM_SUPER_ELLIPSE_POINTS / 2)];
curVector.x *= -1;
curVector.y *= -1;
curVector.x += uber.m_fShearx * curVector.z;
curVector.y += uber.m_fSheary * curVector.z;
}
else // +x -y quadrant, negate y when copying from scratch array
{
curVector = vecFarEdgeOuterPoints[NUM_SUPER_ELLIPSE_POINTS-i];
curVector.y *= -1;
curVector.x += uber.m_fShearx * curVector.z;
curVector.y += uber.m_fSheary * curVector.z;
}
if ( i != 0 )
{
LineDrawHelper( preVector, curVector, viewMatrixInverse, 10, 10, 255 );
}
preVector = curVector;
}
LineDrawHelper( preVector, vecFarEdgeOuterPoints[0], viewMatrixInverse, 10, 10, 255 );
// Connectors
for ( i=0; i< NUM_SUPER_ELLIPSE_POINTS; i++ )
{
if ( ( i % CONNECTOR_FREQ ) == 0 )
{
Vector vecNearEdgeOuter, vecNearOuter, vecFarOuter, vecFarEdgeOuter;
if ( i <= (NUM_SUPER_ELLIPSE_POINTS / 4)) // +x +y quadrant, copy from scratch array directly
{
vecNearEdgeOuter = vecNearEdgeOuterPoints[i];
vecNearEdgeOuter.x += uber.m_fShearx * vecNearEdgeOuter.z;
vecNearEdgeOuter.y += uber.m_fSheary * vecNearEdgeOuter.z;
vecNearOuter = vecNearOuterPoints[i];
vecNearOuter.x += uber.m_fShearx * vecNearOuter.z;
vecNearOuter.y += uber.m_fSheary * vecNearOuter.z;
vecFarEdgeOuter = vecFarEdgeOuterPoints[i];
vecFarEdgeOuter.x += uber.m_fShearx * vecFarEdgeOuter.z;
vecFarEdgeOuter.y += uber.m_fSheary * vecFarEdgeOuter.z;
vecFarOuter = vecFarOuterPoints[i];
vecFarOuter.x += uber.m_fShearx * vecFarOuter.z;
vecFarOuter.y += uber.m_fSheary * vecFarOuter.z;
}
else if ( i <= (NUM_SUPER_ELLIPSE_POINTS / 2)) // -x +y quadrant, negate x when copying from scratch array
{
vecNearEdgeOuter = vecNearEdgeOuterPoints[(NUM_SUPER_ELLIPSE_POINTS / 2)-i];
vecNearEdgeOuter.x *= -1;
vecNearEdgeOuter.x += uber.m_fShearx * vecNearEdgeOuter.z;
vecNearEdgeOuter.y += uber.m_fSheary * vecNearEdgeOuter.z;
vecNearOuter = vecNearOuterPoints[(NUM_SUPER_ELLIPSE_POINTS / 2)-i];
vecNearOuter.x *= -1;
vecNearOuter.x += uber.m_fShearx * vecNearOuter.z;
vecNearOuter.y += uber.m_fSheary * vecNearOuter.z;
vecFarEdgeOuter = vecFarEdgeOuterPoints[(NUM_SUPER_ELLIPSE_POINTS / 2)-i];
vecFarEdgeOuter.x *= -1;
vecFarEdgeOuter.x += uber.m_fShearx * vecFarEdgeOuter.z;
vecFarEdgeOuter.y += uber.m_fSheary * vecFarEdgeOuter.z;
vecFarOuter = vecFarOuterPoints[(NUM_SUPER_ELLIPSE_POINTS / 2)-i];
vecFarOuter.x *= -1;
vecFarOuter.x += uber.m_fShearx * vecFarOuter.z;
vecFarOuter.y += uber.m_fSheary * vecFarOuter.z;
}
else if ( i <= (3*NUM_SUPER_ELLIPSE_POINTS / 4)) // -x -y quadrant, negate when copying from scratch array
{
vecNearEdgeOuter = vecNearEdgeOuterPoints[i-(NUM_SUPER_ELLIPSE_POINTS / 2)];
vecNearEdgeOuter.x *= -1;
vecNearEdgeOuter.y *= -1;
vecNearEdgeOuter.x += uber.m_fShearx * vecNearEdgeOuter.z;
vecNearEdgeOuter.y += uber.m_fSheary * vecNearEdgeOuter.z;
vecNearOuter = vecNearOuterPoints[i-(NUM_SUPER_ELLIPSE_POINTS / 2)];
vecNearOuter.x *= -1;
vecNearOuter.y *= -1;
vecNearOuter.x += uber.m_fShearx * vecNearOuter.z;
vecNearOuter.y += uber.m_fSheary * vecNearOuter.z;
vecFarEdgeOuter = vecFarEdgeOuterPoints[i-(NUM_SUPER_ELLIPSE_POINTS / 2)];
vecFarEdgeOuter.x *= -1;
vecFarEdgeOuter.y *= -1;
vecFarEdgeOuter.x += uber.m_fShearx * vecFarEdgeOuter.z;
vecFarEdgeOuter.y += uber.m_fSheary * vecFarEdgeOuter.z;
vecFarOuter = vecFarOuterPoints[i-(NUM_SUPER_ELLIPSE_POINTS / 2)];
vecFarOuter.x *= -1;
vecFarOuter.y *= -1;
vecFarOuter.x += uber.m_fShearx * vecFarOuter.z;
vecFarOuter.y += uber.m_fSheary * vecFarOuter.z;
}
else // +x -y quadrant, negate y when copying from scratch array
{
vecNearEdgeOuter = vecNearEdgeOuterPoints[NUM_SUPER_ELLIPSE_POINTS-i];
vecNearEdgeOuter.y *= -1;
vecNearEdgeOuter.x += uber.m_fShearx * vecNearEdgeOuter.z;
vecNearEdgeOuter.y += uber.m_fSheary * vecNearEdgeOuter.z;
vecNearOuter = vecNearOuterPoints[NUM_SUPER_ELLIPSE_POINTS-i];
vecNearOuter.y *= -1;
vecNearOuter.x += uber.m_fShearx * vecNearOuter.z;
vecNearOuter.y += uber.m_fSheary * vecNearOuter.z;
vecFarEdgeOuter = vecFarEdgeOuterPoints[NUM_SUPER_ELLIPSE_POINTS-i];
vecFarEdgeOuter.y *= -1;
vecFarEdgeOuter.x += uber.m_fShearx * vecFarEdgeOuter.z;
vecFarEdgeOuter.y += uber.m_fSheary * vecFarEdgeOuter.z;
vecFarOuter = vecFarOuterPoints[NUM_SUPER_ELLIPSE_POINTS-i];
vecFarOuter.y *= -1;
vecFarOuter.x += uber.m_fShearx * vecFarOuter.z;
vecFarOuter.y += uber.m_fSheary * vecFarOuter.z;
}
LineDrawHelper( vecNearOuter, vecNearEdgeOuter, viewMatrixInverse, 255, 10, 10 );
LineDrawHelper( vecNearOuter, vecFarOuter, viewMatrixInverse, 220, 10, 220 );
LineDrawHelper( vecFarEdgeOuter, vecFarOuter, viewMatrixInverse, 10, 10, 255 );
}
}
}
void CClientShadowMgr::DrawFrustum( const Vector &vOrigin, const VMatrix &matWorldToFlashlight )
{
VMatrix flashlightToWorld;
MatrixInverseGeneral( matWorldToFlashlight, flashlightToWorld );
// Draw boundaries of frustum
LineDrawHelperProjective( Vector( 0.0f, 0.0f, 0.0f ), Vector( 0.0f, 0.0f, 1.0f ), flashlightToWorld, 255, 255, 255 );
LineDrawHelperProjective( Vector( 0.0f, 0.0f, 1.0f ), Vector( 0.0f, 1.0f, 1.0f ), flashlightToWorld, 255, 255, 255 );
LineDrawHelperProjective( Vector( 0.0f, 1.0f, 1.0f ), Vector( 0.0f, 1.0f, 0.0f ), flashlightToWorld, 255, 255, 255 );
LineDrawHelperProjective( Vector( 0.0f, 1.0f, 0.0f ), Vector( 0.0f, 0.0f, 0.0f ), flashlightToWorld, 255, 255, 255 );
LineDrawHelperProjective( Vector( 1.0f, 0.0f, 0.0f ), Vector( 1.0f, 0.0f, 1.0f ), flashlightToWorld, 255, 255, 255 );
LineDrawHelperProjective( Vector( 1.0f, 0.0f, 1.0f ), Vector( 1.0f, 1.0f, 1.0f ), flashlightToWorld, 255, 255, 255 );
LineDrawHelperProjective( Vector( 1.0f, 1.0f, 1.0f ), Vector( 1.0f, 1.0f, 0.0f ), flashlightToWorld, 255, 255, 255 );
LineDrawHelperProjective( Vector( 1.0f, 1.0f, 0.0f ), Vector( 1.0f, 0.0f, 0.0f ), flashlightToWorld, 255, 255, 255 );
LineDrawHelperProjective( Vector( 0.0f, 0.0f, 0.0f ), Vector( 1.0f, 0.0f, 0.0f ), flashlightToWorld, 255, 255, 255 );
LineDrawHelperProjective( Vector( 0.0f, 0.0f, 1.0f ), Vector( 1.0f, 0.0f, 1.0f ), flashlightToWorld, 255, 255, 255 );
LineDrawHelperProjective( Vector( 0.0f, 1.0f, 1.0f ), Vector( 1.0f, 1.0f, 1.0f ), flashlightToWorld, 255, 255, 255 );
LineDrawHelperProjective( Vector( 0.0f, 1.0f, 0.0f ), Vector( 1.0f, 1.0f, 0.0f ), flashlightToWorld, 255, 255, 255 );
// Draw RGB triad at front plane
LineDrawHelperProjective( Vector( 0.5f, 0.5f, 0.0f ), Vector( 1.0f, 0.5f, 0.0f ), flashlightToWorld, 255, 0, 0 );
LineDrawHelperProjective( Vector( 0.5f, 0.5f, 0.0f ), Vector( 0.5f, 1.0f, 0.0f ), flashlightToWorld, 0, 255, 0 );
LineDrawHelperProjective( Vector( 0.5f, 0.5f, 0.0f ), Vector( 0.5f, 0.5f, 0.35f ), flashlightToWorld, 0, 0, 255 );
}
//-----------------------------------------------------------------------------
// Builds a list of leaves inside the flashlight volume
//-----------------------------------------------------------------------------
static void BuildFlashlightLeafList( CShadowLeafEnum *pEnum, const Vector &vecOrigin, const VMatrix &worldToShadow )
{
// Use an AABB around the frustum to enumerate leaves.
Vector mins, maxs;
CalculateAABBFromProjectionMatrix( worldToShadow, &mins, &maxs );
ISpatialQuery* pQuery = engine->GetBSPTreeQuery();
pQuery->EnumerateLeavesInBox( mins, maxs, pEnum, 0 );
// pEnum->ExtractUnconnectedLeaves( vecOrigin );
}
void CClientShadowMgr::BuildFlashlight( ClientShadowHandle_t handle )
{
CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
// For the 360, we just draw flashlights with the main geometry
// and bypass the entire shadow casting system.
ClientShadow_t &shadow = m_Shadows[handle];
if ( shadowmgr->SinglePassFlashlightModeEnabled() && !pRenderContext->IsCullingEnabledForSinglePassFlashlight() )
{
// This will update the matrices, but not do work to add the flashlight to surfaces
shadowmgr->ProjectFlashlight( shadow.m_ShadowHandle, shadow.m_WorldToShadow, 0, NULL );
return;
}
VPROF_BUDGET( "CClientShadowMgr::BuildFlashlight", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING );
bool bLightModels = r_flashlightmodels.GetBool();
bool bLightSpecificEntity = shadow.m_hTargetEntity.Get() != NULL;
bool bLightWorld = ( shadow.m_Flags & SHADOW_FLAGS_LIGHT_WORLD ) != 0;
int nCount = 0;
const int *pLeafList = 0;
CShadowLeafEnum leafList;
if ( bLightWorld || ( bLightModels && !bLightSpecificEntity ) )
{
const FlashlightState_t& flashlightState = shadowmgr->GetFlashlightState( shadow.m_ShadowHandle );
BuildFlashlightLeafList( &leafList, flashlightState.m_vecLightOrigin, shadow.m_WorldToShadow );
nCount = leafList.m_LeafList.Count();
pLeafList = leafList.m_LeafList.Base();
}
if( bLightWorld )
{
shadowmgr->ProjectFlashlight( shadow.m_ShadowHandle, shadow.m_WorldToShadow, nCount, pLeafList );
}
else
{
// This should clear all models and surfaces from this shadow
shadowmgr->EnableShadow( shadow.m_ShadowHandle, false );
shadowmgr->EnableShadow( shadow.m_ShadowHandle, true );
}
if( ( shadow.m_Flags & ( SHADOW_FLAGS_SIMPLE_PROJECTION ) ) != 0 )
{
return;
}
if ( !bLightModels )
return;
if ( !bLightSpecificEntity )
{
// Add the shadow to the client leaf system so it correctly marks
// leafs as being affected by a particular shadow
ClientLeafSystem()->ProjectFlashlight( shadow.m_ClientLeafShadowHandle, nCount, pLeafList );
return;
}
// We know what we are focused on, so just add the shadow directly to that receiver
Assert( shadow.m_hTargetEntity->GetModel() );
C_BaseEntity *pChild = shadow.m_hTargetEntity->FirstMoveChild();
while( pChild )
{
int modelType = modelinfo->GetModelType( pChild->GetModel() );
if (modelType == mod_brush)
{
AddShadowToReceiver( handle, pChild, SHADOW_RECEIVER_BRUSH_MODEL );
}
else if ( modelType == mod_studio )
{
AddShadowToReceiver( handle, pChild, SHADOW_RECEIVER_STUDIO_MODEL );
}
pChild = pChild->NextMovePeer();
}
int modelType = modelinfo->GetModelType( shadow.m_hTargetEntity->GetModel() );
if (modelType == mod_brush)
{
AddShadowToReceiver( handle, shadow.m_hTargetEntity, SHADOW_RECEIVER_BRUSH_MODEL );
}
else if ( modelType == mod_studio )
{
AddShadowToReceiver( handle, shadow.m_hTargetEntity, SHADOW_RECEIVER_STUDIO_MODEL );
}
}
//-----------------------------------------------------------------------------
// Adds the child bounds to the bounding box
//-----------------------------------------------------------------------------
void CClientShadowMgr::AddChildBounds( matrix3x4_t &matWorldToBBox, IClientRenderable* pParent, Vector &vecMins, Vector &vecMaxs )
{
Vector vecChildMins, vecChildMaxs;
Vector vecNewChildMins, vecNewChildMaxs;
matrix3x4_t childToBBox;
IClientRenderable *pChild = pParent->FirstShadowChild();
while( pChild )
{
// Transform the child bbox into the space of the main bbox
// FIXME: Optimize this?
if ( GetActualShadowCastType( pChild ) != SHADOWS_NONE)
{
pChild->GetShadowRenderBounds( vecChildMins, vecChildMaxs, SHADOWS_RENDER_TO_TEXTURE );
ConcatTransforms( matWorldToBBox, pChild->RenderableToWorldTransform(), childToBBox );
TransformAABB( childToBBox, vecChildMins, vecChildMaxs, vecNewChildMins, vecNewChildMaxs );
VectorMin( vecMins, vecNewChildMins, vecMins );
VectorMax( vecMaxs, vecNewChildMaxs, vecMaxs );
}
AddChildBounds( matWorldToBBox, pChild, vecMins, vecMaxs );
pChild = pChild->NextShadowPeer();
}
}
//-----------------------------------------------------------------------------
// Compute a bounds for the entity + children
//-----------------------------------------------------------------------------
void CClientShadowMgr::ComputeHierarchicalBounds( IClientRenderable *pRenderable, Vector &vecMins, Vector &vecMaxs )
{
ShadowType_t shadowType = GetActualShadowCastType( pRenderable );
pRenderable->GetShadowRenderBounds( vecMins, vecMaxs, shadowType );
// We could use a good solution for this in the regular PC build, since
// it causes lots of extra bone setups for entities you can't see.
if ( IsPC() )
{
IClientRenderable *pChild = pRenderable->FirstShadowChild();
// Don't recurse down the tree when we hit a blobby shadow
if ( pChild && shadowType != SHADOWS_SIMPLE )
{
matrix3x4_t matWorldToBBox;
MatrixInvert( pRenderable->RenderableToWorldTransform(), matWorldToBBox );
AddChildBounds( matWorldToBBox, pRenderable, vecMins, vecMaxs );
}
}
}
//-----------------------------------------------------------------------------
// Shadow update functions
//-----------------------------------------------------------------------------
void CClientShadowMgr::UpdateStudioShadow( IClientRenderable *pRenderable, ClientShadowHandle_t handle )
{
if( !( m_Shadows[handle].m_Flags & ( SHADOW_FLAGS_FLASHLIGHT | SHADOW_FLAGS_SIMPLE_PROJECTION ) ) )
{
Vector mins, maxs;
ComputeHierarchicalBounds( pRenderable, mins, maxs );
ShadowType_t shadowType = GetActualShadowCastType( handle );
if ( shadowType != SHADOWS_RENDER_TO_TEXTURE )
{
BuildOrthoShadow( pRenderable, handle, mins, maxs );
}
else
{
BuildRenderToTextureShadow( pRenderable, handle, mins, maxs );
}
}
else
{
BuildFlashlight( handle );
}
}
void CClientShadowMgr::UpdateBrushShadow( IClientRenderable *pRenderable, ClientShadowHandle_t handle )
{
if( !( m_Shadows[handle].m_Flags & ( SHADOW_FLAGS_FLASHLIGHT | SHADOW_FLAGS_SIMPLE_PROJECTION ) ) )
{
// Compute the bounding box in the space of the shadow...
Vector mins, maxs;
ComputeHierarchicalBounds( pRenderable, mins, maxs );
ShadowType_t shadowType = GetActualShadowCastType( handle );
if ( shadowType != SHADOWS_RENDER_TO_TEXTURE )
{
BuildOrthoShadow( pRenderable, handle, mins, maxs );
}
else
{
BuildRenderToTextureShadow( pRenderable, handle, mins, maxs );
}
}
else
{
VPROF_BUDGET( "CClientShadowMgr::UpdateBrushShadow", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING );
BuildFlashlight( handle );
}
}
#ifdef _DEBUG
static bool s_bBreak = false;
void ShadowBreak_f()
{
s_bBreak = true;
}
static ConCommand r_shadowbreak("r_shadowbreak", ShadowBreak_f);
#endif // _DEBUG
bool CClientShadowMgr::WillParentRenderBlobbyShadow( IClientRenderable *pRenderable )
{
if ( !pRenderable )
return false;
IClientRenderable *pShadowParent = pRenderable->GetShadowParent();
if ( !pShadowParent )
return false;
// If there's *no* shadow casting type, then we want to see if we can render into its parent
ShadowType_t shadowType = GetActualShadowCastType( pShadowParent );
if ( shadowType == SHADOWS_NONE )
return WillParentRenderBlobbyShadow( pShadowParent );
return shadowType == SHADOWS_SIMPLE;
}
//-----------------------------------------------------------------------------
// Are we the child of a shadow with render-to-texture?
//-----------------------------------------------------------------------------
bool CClientShadowMgr::ShouldUseParentShadow( IClientRenderable *pRenderable )
{
if ( !pRenderable )
return false;
IClientRenderable *pShadowParent = pRenderable->GetShadowParent();
if ( !pShadowParent )
return false;
// Can't render into the parent if the parent is blobby
ShadowType_t shadowType = GetActualShadowCastType( pShadowParent );
if ( shadowType == SHADOWS_SIMPLE )
return false;
// If there's *no* shadow casting type, then we want to see if we can render into its parent
if ( shadowType == SHADOWS_NONE )
return ShouldUseParentShadow( pShadowParent );
// Here, the parent uses a render-to-texture shadow
return true;
}
//-----------------------------------------------------------------------------
// Before we render any view, make sure all shadows are re-projected vs world
//-----------------------------------------------------------------------------
void CClientShadowMgr::ReprojectShadows()
{
// only update shadows once per frame
Assert( gpGlobals->framecount != m_nPrevFrameCount );
m_nPrevFrameCount = gpGlobals->framecount;
VPROF_BUDGET( "CClientShadowMgr::ReprojectShadows", VPROF_BUDGETGROUP_SHADOW_RENDERING );
MDLCACHE_CRITICAL_SECTION();
//
// -- Shadow Depth Textures -----------------------
//
{
// VPROF scope
//VPROF_BUDGET( "CClientShadowMgr::PreRender", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING );
// If someone turned shadow depth mapping on but we can't do it, force it off
if ( r_flashlightdepthtexture.GetBool() && !g_pMaterialSystemHardwareConfig->SupportsShadowDepthTextures() )
{
r_flashlightdepthtexture.SetValue( 0 );
ShutdownDepthTextureShadows();
}
bool bDepthTextureActive = r_flashlightdepthtexture.GetBool();
int nDepthTextureResolution = r_flashlightdepthres.GetInt();
int nDepthTextureResolutionHigh = r_flashlightdepthreshigh.GetInt();
if ( ( bDepthTextureActive == true ) && ( m_bDepthTextureActive == true ) &&
( nDepthTextureResolution != m_nDepthTextureResolution || nDepthTextureResolutionHigh != m_nDepthTextureResolutionHigh ) )
{
// If shadow depth texturing remains on, but resolution changed, shut down and reinitialize depth textures
ShutdownDepthTextureShadows();
InitDepthTextureShadows();
}
else
{
if ( bDepthTextureActive && !m_bDepthTextureActive )
{
// If shadow depth texture is now needed but wasn't allocated, allocate it
// Turning on shadow depth mapping
materials->ReEnableRenderTargetAllocation_IRealizeIfICallThisAllTexturesWillBeUnloadedAndLoadTimeWillSufferHorribly();
InitDepthTextureShadows(); // only allocates buffers if they don't already exist
materials->FinishRenderTargetAllocation();
}
}
}
//
// -- Render to Texture Shadows -----------------------
//
if ( !r_shadows.GetBool() )
{
return;
}
bool bRenderToTextureActive = r_shadowrendertotexture.GetBool();
#ifdef CSTRIKE15
// Slamming this to always disabled in cstrike, because every map has CSM now and this is causing problems with reloading envmaps from BSP's on the 2nd time through maps with a dedicated server.
// 3/28 - this is now slammed to 0 in CS:GO's cpu_level.csv
//if ( g_CascadeLightManager.IsEnabledAndActive() && engine->MapHasLightMapAlphaData() )
bRenderToTextureActive = false;
#endif
if ( !m_RenderToTextureActive && bRenderToTextureActive )
{
materials->ReEnableRenderTargetAllocation_IRealizeIfICallThisAllTexturesWillBeUnloadedAndLoadTimeWillSufferHorribly();
InitRenderToTextureShadows();
materials->FinishRenderTargetAllocation();
UpdateAllShadows();
return;
}
if ( m_RenderToTextureActive && !bRenderToTextureActive )
{
materials->ReEnableRenderTargetAllocation_IRealizeIfICallThisAllTexturesWillBeUnloadedAndLoadTimeWillSufferHorribly();
ShutdownRenderToTextureShadows();
materials->FinishRenderTargetAllocation();
UpdateAllShadows();
return;
}
m_bUpdatingDirtyShadows = true;
if ( r_shadow_half_update_rate.GetBool() )
{
UpdateDirtyShadowsHalfRate();
}
else
{
UpdateDirtyShadows();
}
m_bUpdatingDirtyShadows = false;
DestroyQueuedShadows();
}
//-----------------------------------------------------------------------------
// Update all shadows in the dirty shadow set
//-----------------------------------------------------------------------------
void CClientShadowMgr::UpdateDirtyShadows()
{
if ( r_shadow_debug_spew.GetBool() )
{
DevMsg( "dirty shadows: %3d\n", m_DirtyShadows.Count() );
}
unsigned short i = m_DirtyShadows.FirstInorder();
while ( i != m_DirtyShadows.InvalidIndex() )
{
MDLCACHE_CRITICAL_SECTION();
ClientShadowHandle_t& handle = m_DirtyShadows[ i ];
UpdateDirtyShadow( handle );
i = m_DirtyShadows.NextInorder(i);
}
m_DirtyShadows.RemoveAll();
// Transparent shadows must remain dirty, since they were not re-projected
int nCount = m_TransparentShadows.Count();
for ( int i = 0; i < nCount; ++i )
{
m_DirtyShadows.Insert( m_TransparentShadows[i] );
}
m_TransparentShadows.RemoveAll();
}
//-----------------------------------------------------------------------------
// Update half the shadows in the dirty shadow set, leaving the rest for next frame
//-----------------------------------------------------------------------------
void CClientShadowMgr::UpdateDirtyShadowsHalfRate()
{
unsigned short i = m_DirtyShadowsLeftOver.FirstInorder();
while ( i != m_DirtyShadowsLeftOver.InvalidIndex() )
{
MDLCACHE_CRITICAL_SECTION();
ClientShadowHandle_t& handle = m_DirtyShadowsLeftOver[ i ];
UpdateDirtyShadow( handle );
// remove from real dirty set, so that we don't touch shadows twice
unsigned short nIdx = m_DirtyShadows.Find( handle );
if ( nIdx != m_DirtyShadows.InvalidIndex() )
{
m_DirtyShadows.RemoveAt( nIdx );
}
i = m_DirtyShadowsLeftOver.NextInorder( i );
}
int nNumShadowsProcessed = m_DirtyShadowsLeftOver.Count();
int nNumTotalDirtyShadows = nNumShadowsProcessed + m_DirtyShadows.Count();
int nNumShadowsToProcess = nNumTotalDirtyShadows / 2 + 1;
if ( r_shadow_debug_spew.GetBool() )
{
DevMsg( "dirty shadows: %3d\n", nNumShadowsToProcess );
}
nNumShadowsToProcess -= nNumShadowsProcessed;
nNumShadowsToProcess = MAX( 0, nNumShadowsToProcess );
nNumShadowsToProcess = MIN( m_DirtyShadows.Count(), nNumShadowsToProcess );
//DevMsg( "dirty: %2d leftover: %2d total: %2d from main list: %2d processed: %d\n", m_DirtyShadows.Count(), m_DirtyShadowsLeftOver.Count(), nNumTotalDirtyShadows, nNumShadowsToProcess, nNumShadowsProcessed + nNumShadowsToProcess );
m_DirtyShadowsLeftOver.RemoveAll();
//
// process any additional dirty shadows
//
i = m_DirtyShadows.FirstInorder();
for ( int j = 0; j < nNumShadowsToProcess; j++ )
{
MDLCACHE_CRITICAL_SECTION();
ClientShadowHandle_t& handle = m_DirtyShadows[ i ];
UpdateDirtyShadow( handle );
i = m_DirtyShadows.NextInorder( i );
}
// put the leftover shadows into the leftover bucket
while ( i != m_DirtyShadows.InvalidIndex() )
{
ClientShadowHandle_t& handle = m_DirtyShadows[ i ];
i = m_DirtyShadows.NextInorder( i );
m_DirtyShadowsLeftOver.Insert( handle );
}
m_DirtyShadows.RemoveAll();
// Transparent shadows must remain dirty, since they were not re-projected
int nCount = m_TransparentShadows.Count();
for ( int i = 0; i < nCount; ++i )
{
m_DirtyShadows.Insert( m_TransparentShadows[i] );
}
m_TransparentShadows.RemoveAll();
}
//-----------------------------------------------------------------------------
// Updates a single dirty shadow
//-----------------------------------------------------------------------------
void CClientShadowMgr::UpdateDirtyShadow( ClientShadowHandle_t handle )
{
Assert( m_Shadows.IsValidIndex( handle ) );
if ( IsShadowingFromWorldLights() )
{
UpdateShadowDirectionFromLocalLightSource( handle );
}
UpdateProjectedTextureInternal( handle, false );
}
//-----------------------------------------------------------------------------
// Convar callback
//-----------------------------------------------------------------------------
void HalfUpdateRateCallback( IConVar *var, const char *pOldValue, float flOldValue )
{
s_ClientShadowMgr.FlushLeftOverDirtyShadows();
}
//-----------------------------------------------------------------------------
// Flushes any leftover dirty shadows into the main dirty shadow set
//-----------------------------------------------------------------------------
void CClientShadowMgr::FlushLeftOverDirtyShadows()
{
unsigned short i = m_DirtyShadowsLeftOver.FirstInorder();
while ( i != m_DirtyShadowsLeftOver.InvalidIndex() )
{
m_DirtyShadows.InsertIfNotFound( m_DirtyShadowsLeftOver[ i ] );
i = m_DirtyShadowsLeftOver.NextInorder(i);
}
m_DirtyShadowsLeftOver.RemoveAll();
}
//-----------------------------------------------------------------------------
// Gets the entity whose shadow this shadow will render into
//-----------------------------------------------------------------------------
IClientRenderable *CClientShadowMgr::GetParentShadowEntity( ClientShadowHandle_t handle )
{
ClientShadow_t& shadow = m_Shadows[handle];
IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( shadow.m_Entity );
if ( pRenderable )
{
if ( ShouldUseParentShadow( pRenderable ) )
{
IClientRenderable *pParent = pRenderable->GetShadowParent();
while ( GetActualShadowCastType( pParent ) == SHADOWS_NONE )
{
pParent = pParent->GetShadowParent();
Assert( pParent );
}
return pParent;
}
}
return NULL;
}
//-----------------------------------------------------------------------------
// Marks a shadow as needing re-projection
//-----------------------------------------------------------------------------
CThreadFastMutex g_DirtyListAddMutex; // This should be made lock free? [3/19/2008 tom]
void CClientShadowMgr::AddToDirtyShadowList( ClientShadowHandle_t handle, bool bForce )
{
// Don't add to the dirty shadow list while we're iterating over it
// The only way this can happen is if a child is being rendered into a parent
// shadow, and we don't need it to be added to the dirty list in that case.
if ( m_bUpdatingDirtyShadows )
return;
if ( handle == CLIENTSHADOW_INVALID_HANDLE )
return;
AUTO_LOCK( g_DirtyListAddMutex );
Assert( m_DirtyShadows.Find( handle ) == m_DirtyShadows.InvalidIndex() );
m_DirtyShadows.Insert( handle );
// This pretty much guarantees we'll recompute the shadow
if ( bForce )
{
m_Shadows[handle].m_LastAngles.Init( FLT_MAX, FLT_MAX, FLT_MAX );
}
// If we use our parent shadow, then it's dirty too...
IClientRenderable *pParent = GetParentShadowEntity( handle );
if ( pParent )
{
AddToDirtyShadowList( pParent, bForce );
}
}
//-----------------------------------------------------------------------------
// Marks a shadow as needing re-projection
//-----------------------------------------------------------------------------
void CClientShadowMgr::AddToDirtyShadowList( IClientRenderable *pRenderable, bool bForce )
{
// Don't add to the dirty shadow list while we're iterating over it
// The only way this can happen is if a child is being rendered into a parent
// shadow, and we don't need it to be added to the dirty list in that case.
if ( m_bUpdatingDirtyShadows )
return;
AUTO_LOCK( g_DirtyListAddMutex );
// Are we already in the dirty list?
if ( pRenderable->IsShadowDirty( ) )
return;
ClientShadowHandle_t handle = pRenderable->GetShadowHandle();
if ( handle == CLIENTSHADOW_INVALID_HANDLE )
return;
#ifdef _DEBUG
// Make sure everything's consistent
if ( handle != CLIENTSHADOW_INVALID_HANDLE )
{
IClientRenderable *pShadowRenderable = ClientEntityList().GetClientRenderableFromHandle( m_Shadows[handle].m_Entity );
Assert( pRenderable == pShadowRenderable );
}
#endif
pRenderable->MarkShadowDirty( true );
AddToDirtyShadowList( handle, bForce );
}
//-----------------------------------------------------------------------------
// Marks the render-to-texture shadow as needing to be re-rendered
//-----------------------------------------------------------------------------
void CClientShadowMgr::MarkRenderToTextureShadowDirty( ClientShadowHandle_t handle )
{
// Don't add bogus handles!
if (handle != CLIENTSHADOW_INVALID_HANDLE)
{
// Mark the shadow has having a dirty renter-to-texture
ClientShadow_t& shadow = m_Shadows[handle];
shadow.m_Flags |= SHADOW_FLAGS_TEXTURE_DIRTY;
// If we use our parent shadow, then it's dirty too...
IClientRenderable *pParent = GetParentShadowEntity( handle );
if ( pParent )
{
ClientShadowHandle_t parentHandle = pParent->GetShadowHandle();
if ( parentHandle != CLIENTSHADOW_INVALID_HANDLE )
{
m_Shadows[parentHandle].m_Flags |= SHADOW_FLAGS_TEXTURE_DIRTY;
}
}
}
}
//-----------------------------------------------------------------------------
// Update a shadow
//-----------------------------------------------------------------------------
void CClientShadowMgr::UpdateShadow( ClientShadowHandle_t handle, bool force )
{
ClientShadow_t& shadow = m_Shadows[handle];
// Get the client entity....
IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( shadow.m_Entity );
if ( !pRenderable )
{
// Retire the shadow if the entity is gone
DestroyShadow( handle );
return;
}
if ( r_disable_update_shadow.GetBool() )
{
pRenderable->MarkShadowDirty( false );
shadowmgr->EnableShadow( shadow.m_ShadowHandle, false );
return;
}
// Don't bother if there's no model on the renderable
if ( !pRenderable->GetModel() )
{
pRenderable->MarkShadowDirty( false );
return;
}
// Mark shadow as dirty if no split screen user needs it
bool bShouldDraw = false;
FOR_EACH_VALID_SPLITSCREEN_PLAYER( i )
{
if ( pRenderable->ShouldDrawForSplitScreenUser( i ) )
{
bShouldDraw = true;
break;
}
}
if ( !bShouldDraw )
{
pRenderable->MarkShadowDirty( false );
return;
}
// FIXME: NOTE! Because this is called from PreRender, the falloff bias is
// off by a frame. We could move the code in PreRender to occur after world
// list building is done to fix this issue.
// Don't bother with it if the shadow is totally transparent
if ( shadow.m_FalloffBias == 255 )
{
if ( !r_shadow_deferred.GetBool() )
{
shadowmgr->EnableShadow( shadow.m_ShadowHandle, false );
}
m_TransparentShadows.AddToTail( handle );
return;
}
#ifdef _DEBUG
if (s_bBreak)
{
s_bBreak = false;
}
#endif
// Hierarchical children shouldn't be projecting shadows...
// Check to see if it's a child of an entity with a render-to-texture shadow...
if ( ShouldUseParentShadow( pRenderable ) || WillParentRenderBlobbyShadow( pRenderable ) )
{
if ( !r_shadow_deferred.GetBool() )
{
shadowmgr->EnableShadow( shadow.m_ShadowHandle, false );
}
pRenderable->MarkShadowDirty( false );
return;
}
if ( !r_shadow_deferred.GetBool() )
{
shadowmgr->EnableShadow( shadow.m_ShadowHandle, true );
}
// Figure out if the shadow moved...
// Even though we have dirty bits, some entities
// never clear those dirty bits
const Vector& origin = pRenderable->GetRenderOrigin();
const QAngle& angles = pRenderable->GetRenderAngles();
if (force || (origin != shadow.m_LastOrigin) || (angles != shadow.m_LastAngles))
{
// Store off the new pos/orientation
VectorCopy( origin, shadow.m_LastOrigin );
VectorCopy( angles, shadow.m_LastAngles );
CMatRenderContextPtr pRenderContext( materials );
const model_t *pModel = pRenderable->GetModel();
MaterialFogMode_t fogMode = pRenderContext->GetFogMode();
pRenderContext->FogMode( MATERIAL_FOG_NONE );
switch( modelinfo->GetModelType( pModel ) )
{
case mod_brush:
UpdateBrushShadow( pRenderable, handle );
break;
case mod_studio:
UpdateStudioShadow( pRenderable, handle );
break;
default:
// Shouldn't get here if not a brush or studio
Assert(0);
break;
}
pRenderContext->FogMode( fogMode );
}
// NOTE: We can't do this earlier because pEnt->GetRenderOrigin() can
// provoke a recomputation of render origin, which, for aiments, can cause everything
// to be marked as dirty. So don't clear the flag until this point.
pRenderable->MarkShadowDirty( false );
}
//-----------------------------------------------------------------------------
// Update a shadow
//-----------------------------------------------------------------------------
void CClientShadowMgr::UpdateProjectedTextureInternal( ClientShadowHandle_t handle, bool force )
{
ClientShadow_t& shadow = m_Shadows[handle];
if( ( shadow.m_Flags & ( SHADOW_FLAGS_FLASHLIGHT | SHADOW_FLAGS_SIMPLE_PROJECTION ) ) != 0 )
{
VPROF_BUDGET( "CClientShadowMgr::UpdateProjectedTextureInternal", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING );
Assert( ( shadow.m_Flags & SHADOW_FLAGS_SHADOW ) == 0 );
ClientShadow_t& shadow = m_Shadows[handle];
shadowmgr->EnableShadow( shadow.m_ShadowHandle, true );
// FIXME: What's the difference between brush and model shadows for light projectors? Answer: nothing.
UpdateBrushShadow( NULL, handle );
}
else
{
Assert( shadow.m_Flags & SHADOW_FLAGS_SHADOW );
Assert( ( shadow.m_Flags & SHADOW_FLAGS_FLASHLIGHT ) == 0 );
UpdateShadow( handle, force );
}
}
//-----------------------------------------------------------------------------
// Update a shadow
//-----------------------------------------------------------------------------
void CClientShadowMgr::UpdateProjectedTexture( ClientShadowHandle_t handle, bool force )
{
VPROF_BUDGET( "CClientShadowMgr::UpdateProjectedTexture", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING );
if ( handle == CLIENTSHADOW_INVALID_HANDLE )
return;
// NOTE: This can only work for flashlights, since UpdateProjectedTextureInternal
// depends on the falloff offset to cull shadows.
ClientShadow_t &shadow = m_Shadows[ handle ];
if( ( shadow.m_Flags & ( SHADOW_FLAGS_FLASHLIGHT | SHADOW_FLAGS_SIMPLE_PROJECTION ) ) == 0 )
{
Warning( "CClientShadowMgr::UpdateProjectedTexture can only be used with flashlights!\n" );
return;
}
UpdateProjectedTextureInternal( handle, force );
RemoveShadowFromDirtyList( handle );
}
//-----------------------------------------------------------------------------
// Computes bounding sphere
//-----------------------------------------------------------------------------
void CClientShadowMgr::ComputeBoundingSphere( IClientRenderable* pRenderable, Vector& origin, float& radius )
{
Assert( pRenderable );
Vector mins, maxs;
pRenderable->GetShadowRenderBounds( mins, maxs, GetActualShadowCastType( pRenderable ) );
Vector size;
VectorSubtract( maxs, mins, size );
radius = size.Length() * 0.5f;
// Compute centroid (local space)
Vector centroid;
VectorAdd( mins, maxs, centroid );
centroid *= 0.5f;
// Transform centroid into world space
Vector vec[3];
AngleVectors( pRenderable->GetRenderAngles(), &vec[0], &vec[1], &vec[2] );
vec[1] *= -1.0f;
VectorCopy( pRenderable->GetRenderOrigin(), origin );
VectorMA( origin, centroid.x, vec[0], origin );
VectorMA( origin, centroid.y, vec[1], origin );
VectorMA( origin, centroid.z, vec[2], origin );
}
//-----------------------------------------------------------------------------
// Computes a rough AABB encompassing the volume of the shadow
//-----------------------------------------------------------------------------
void CClientShadowMgr::ComputeShadowBBox( IClientRenderable *pRenderable, ClientShadowHandle_t shadowHandle, const Vector &vecAbsCenter, float flRadius, Vector *pAbsMins, Vector *pAbsMaxs )
{
// This is *really* rough. Basically we simply determine the
// maximum shadow casting length and extrude the box by that distance
Vector vecShadowDir = GetShadowDirection( shadowHandle );
for (int i = 0; i < 3; ++i)
{
float flShadowCastDistance = GetShadowDistance( pRenderable );
float flDist = flShadowCastDistance * vecShadowDir[i];
if (vecShadowDir[i] < 0)
{
(*pAbsMins)[i] = vecAbsCenter[i] - flRadius + flDist;
(*pAbsMaxs)[i] = vecAbsCenter[i] + flRadius;
}
else
{
(*pAbsMins)[i] = vecAbsCenter[i] - flRadius;
(*pAbsMaxs)[i] = vecAbsCenter[i] + flRadius + flDist;
}
}
}
//-----------------------------------------------------------------------------
// Compute a separating axis...
//-----------------------------------------------------------------------------
bool CClientShadowMgr::ComputeSeparatingPlane( IClientRenderable* pRend1, IClientRenderable* pRend2, cplane_t* pPlane )
{
Vector min1, max1, min2, max2;
pRend1->GetShadowRenderBounds( min1, max1, GetActualShadowCastType( pRend1 ) );
pRend2->GetShadowRenderBounds( min2, max2, GetActualShadowCastType( pRend2 ) );
return ::ComputeSeparatingPlane(
pRend1->GetRenderOrigin(), pRend1->GetRenderAngles(), min1, max1,
pRend2->GetRenderOrigin(), pRend2->GetRenderAngles(), min2, max2,
3.0f, pPlane );
}
//-----------------------------------------------------------------------------
// Cull shadows based on rough bounding volumes
//-----------------------------------------------------------------------------
bool CClientShadowMgr::CullReceiver( ClientShadowHandle_t handle, IClientRenderable* pRenderable,
IClientRenderable* pSourceRenderable )
{
// check flags here instead and assert !pSourceRenderable
if( m_Shadows[handle].m_Flags & ( SHADOW_FLAGS_FLASHLIGHT | SHADOW_FLAGS_SIMPLE_PROJECTION ) )
{
VPROF_BUDGET( "CClientShadowMgr::CullReceiver", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING );
Assert( !pSourceRenderable );
const Frustum_t &frustum = shadowmgr->GetFlashlightFrustum( m_Shadows[handle].m_ShadowHandle );
Vector mins, maxs;
pRenderable->GetRenderBoundsWorldspace( mins, maxs );
return frustum.CullBox( mins, maxs );
}
Assert( pSourceRenderable );
// Compute a bounding sphere for the renderable
Vector origin;
float radius;
ComputeBoundingSphere( pRenderable, origin, radius );
// Transform the sphere center into the space of the shadow
Vector localOrigin;
const ClientShadow_t& shadow = m_Shadows[handle];
Vector3DMultiplyPosition( shadow.m_WorldToShadow, origin, localOrigin );
// Compute a rough bounding box for the shadow (in shadow space)
Vector shadowMin, shadowMax;
shadowMin.Init( -shadow.m_WorldSize.x * 0.5f, -shadow.m_WorldSize.y * 0.5f, 0 );
shadowMax.Init( shadow.m_WorldSize.x * 0.5f, shadow.m_WorldSize.y * 0.5f, shadow.m_MaxDist );
// If the bounding sphere doesn't intersect with the shadow volume, cull
if (!IsBoxIntersectingSphere( shadowMin, shadowMax, localOrigin, radius ))
return true;
Vector originSource;
float radiusSource;
ComputeBoundingSphere( pSourceRenderable, originSource, radiusSource );
// Fast check for separating plane...
bool foundSeparatingPlane = false;
cplane_t plane;
if (!IsSphereIntersectingSphere( originSource, radiusSource, origin, radius ))
{
foundSeparatingPlane = true;
// the plane normal doesn't need to be normalized...
VectorSubtract( origin, originSource, plane.normal );
}
else
{
foundSeparatingPlane = ComputeSeparatingPlane( pRenderable, pSourceRenderable, &plane );
}
if (foundSeparatingPlane)
{
// Compute which side of the plane the renderable is on..
Vector vecShadowDir = GetShadowDirection( handle );
float shadowDot = DotProduct( vecShadowDir, plane.normal );
float receiverDot = DotProduct( plane.normal, origin );
float sourceDot = DotProduct( plane.normal, originSource );
if (shadowDot > 0.0f)
{
if (receiverDot <= sourceDot)
{
// Vector dest;
// VectorMA( pSourceRenderable->GetRenderOrigin(), 50, plane.normal, dest );
// debugoverlay->AddLineOverlay( pSourceRenderable->GetRenderOrigin(), dest, 255, 255, 0, true, 1.0f );
return true;
}
else
{
// Vector dest;
// VectorMA( pSourceRenderable->GetRenderOrigin(), 50, plane.normal, dest );
// debugoverlay->AddLineOverlay( pSourceRenderable->GetRenderOrigin(), dest, 255, 0, 0, true, 1.0f );
}
}
else
{
if (receiverDot >= sourceDot)
{
// Vector dest;
// VectorMA( pSourceRenderable->GetRenderOrigin(), -50, plane.normal, dest );
// debugoverlay->AddLineOverlay( pSourceRenderable->GetRenderOrigin(), dest, 255, 255, 0, true, 1.0f );
return true;
}
else
{
// Vector dest;
// VectorMA( pSourceRenderable->GetRenderOrigin(), 50, plane.normal, dest );
// debugoverlay->AddLineOverlay( pSourceRenderable->GetRenderOrigin(), dest, 255, 0, 0, true, 1.0f );
}
}
}
// No additional clip planes? ok then it's a valid receiver
/*
if (shadow.m_ClipPlaneCount == 0)
return false;
// Check the additional cull planes
int i;
for ( i = 0; i < shadow.m_ClipPlaneCount; ++i)
{
// Fast sphere cull
if (DotProduct( origin, shadow.m_ClipPlane[i] ) - radius > shadow.m_ClipDist[i])
return true;
}
// More expensive box on plane side cull...
Vector vec[3];
Vector mins, maxs;
cplane_t plane;
AngleVectors( pRenderable->GetRenderAngles(), &vec[0], &vec[1], &vec[2] );
pRenderable->GetBounds( mins, maxs );
for ( i = 0; i < shadow.m_ClipPlaneCount; ++i)
{
// Transform the plane into the space of the receiver
plane.normal.x = DotProduct( vec[0], shadow.m_ClipPlane[i] );
plane.normal.y = DotProduct( vec[1], shadow.m_ClipPlane[i] );
plane.normal.z = DotProduct( vec[2], shadow.m_ClipPlane[i] );
plane.dist = shadow.m_ClipDist[i] - DotProduct( shadow.m_ClipPlane[i], pRenderable->GetRenderOrigin() );
// If the box is on the front side of the plane, we're done.
if (BoxOnPlaneSide2( mins, maxs, &plane, 3.0f ) == 1)
return true;
}
*/
return false;
}
//-----------------------------------------------------------------------------
// deals with shadows being added to shadow receivers
//-----------------------------------------------------------------------------
void CClientShadowMgr::AddShadowToReceiver( ClientShadowHandle_t handle,
IClientRenderable* pRenderable, ShadowReceiver_t type )
{
ClientShadow_t &shadow = m_Shadows[handle];
// Don't add a shadow cast by an object to itself...
IClientRenderable* pSourceRenderable = ClientEntityList().GetClientRenderableFromHandle( shadow.m_Entity );
// NOTE: if pSourceRenderable == NULL, the source is probably a flashlight since there is no entity.
if (pSourceRenderable == pRenderable)
return;
// Don't bother if this renderable doesn't receive shadows or light from flashlights
if( !pRenderable->ShouldReceiveProjectedTextures( SHADOW_FLAGS_PROJECTED_TEXTURE_TYPE_MASK ) )
return;
// Cull if the origin is on the wrong side of a shadow clip plane....
if ( CullReceiver( handle, pRenderable, pSourceRenderable ) )
return;
// Do different things depending on the receiver type
switch( type )
{
case SHADOW_RECEIVER_BRUSH_MODEL:
if( shadow.m_Flags & ( SHADOW_FLAGS_FLASHLIGHT | SHADOW_FLAGS_SIMPLE_PROJECTION ) )
{
VPROF_BUDGET( "CClientShadowMgr::AddShadowToReceiver", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING );
if( (!shadow.m_hTargetEntity) || IsFlashlightTarget( handle, pRenderable ) )
{
shadowmgr->AddShadowToBrushModel( shadow.m_ShadowHandle,
const_cast<model_t*>(pRenderable->GetModel()),
pRenderable->GetRenderOrigin(), pRenderable->GetRenderAngles() );
}
}
else
{
if ( !r_shadow_deferred.GetBool() )
{
shadowmgr->AddShadowToBrushModel( shadow.m_ShadowHandle,
const_cast<model_t*>(pRenderable->GetModel()),
pRenderable->GetRenderOrigin(), pRenderable->GetRenderAngles() );
}
}
break;
case SHADOW_RECEIVER_STATIC_PROP:
// Don't add shadows to props if we're not using render-to-texture
if ( GetActualShadowCastType( handle ) == SHADOWS_RENDER_TO_TEXTURE )
{
if ( !r_shadow_deferred.GetBool() )
{
// Also don't add them unless an NPC or player casts them..
// They are wickedly expensive!!!
C_BaseEntity *pEnt = pSourceRenderable->GetIClientUnknown()->GetBaseEntity();
if ( pEnt && ( pEnt->GetFlags() & (FL_NPC | FL_CLIENT)) )
{
staticpropmgr->AddShadowToStaticProp( shadow.m_ShadowHandle, pRenderable );
}
}
}
else if( shadow.m_Flags & ( SHADOW_FLAGS_FLASHLIGHT | SHADOW_FLAGS_SIMPLE_PROJECTION ) )
{
VPROF_BUDGET( "CClientShadowMgr::AddShadowToReceiver", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING );
if( (!shadow.m_hTargetEntity) || IsFlashlightTarget( handle, pRenderable ) )
{
staticpropmgr->AddShadowToStaticProp( shadow.m_ShadowHandle, pRenderable );
}
}
break;
case SHADOW_RECEIVER_STUDIO_MODEL:
if( shadow.m_Flags & ( SHADOW_FLAGS_FLASHLIGHT | SHADOW_FLAGS_SIMPLE_PROJECTION ) )
{
VPROF_BUDGET( "CClientShadowMgr::AddShadowToReceiver", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING );
if( (!shadow.m_hTargetEntity) || IsFlashlightTarget( handle, pRenderable ) )
{
pRenderable->CreateModelInstance();
shadowmgr->AddShadowToModel( shadow.m_ShadowHandle, pRenderable->GetModelInstance() );
}
}
break;
// default:
}
}
//-----------------------------------------------------------------------------
// deals with shadows being added to shadow receivers
//-----------------------------------------------------------------------------
void CClientShadowMgr::RemoveAllShadowsFromReceiver(
IClientRenderable* pRenderable, ShadowReceiver_t type )
{
// Don't bother if this renderable doesn't receive shadows
if ( !pRenderable->ShouldReceiveProjectedTextures( SHADOW_FLAGS_PROJECTED_TEXTURE_TYPE_MASK ) )
return;
// Do different things depending on the receiver type
switch( type )
{
case SHADOW_RECEIVER_BRUSH_MODEL:
{
model_t* pModel = const_cast<model_t*>(pRenderable->GetModel());
shadowmgr->RemoveAllShadowsFromBrushModel( pModel );
}
break;
case SHADOW_RECEIVER_STATIC_PROP:
staticpropmgr->RemoveAllShadowsFromStaticProp(pRenderable);
break;
case SHADOW_RECEIVER_STUDIO_MODEL:
if( pRenderable && pRenderable->GetModelInstance() != MODEL_INSTANCE_INVALID )
{
Assert(shadowmgr);
shadowmgr->RemoveAllShadowsFromModel( pRenderable->GetModelInstance() );
}
break;
// default:
// // FIXME: How do deal with this stuff? Add a method to IClientRenderable?
// C_BaseEntity* pEnt = static_cast<C_BaseEntity*>(pRenderable);
// pEnt->RemoveAllShadows();
}
}
//-----------------------------------------------------------------------------
// Computes + sets the render-to-texture texcoords
//-----------------------------------------------------------------------------
void CClientShadowMgr::SetRenderToTextureShadowTexCoords( ShadowHandle_t handle, int x, int y, int w, int h )
{
// Let the shadow mgr know about the texture coordinates...
// That way it'll be able to batch rendering better.
int textureW, textureH;
m_ShadowAllocator.GetTotalTextureSize( textureW, textureH );
// Go in a half-pixel to avoid blending with neighboring textures..
float u, v, du, dv;
u = ((float)x + 0.5f) / (float)textureW;
v = ((float)y + 0.5f) / (float)textureH;
du = ((float)w - 1) / (float)textureW;
dv = ((float)h - 1) / (float)textureH;
shadowmgr->SetShadowTexCoord( handle, u, v, du, dv );
}
void CClientShadowMgr::SetRenderToTextureShadowTexCoords( ClientShadow_t& shadow, int x, int y, int w, int h )
{
// Let the shadow mgr know about the texture coordinates...
// That way it'll be able to batch rendering better.
int textureW, textureH;
m_ShadowAllocator.GetTotalTextureSize( textureW, textureH );
// Go in a half-pixel to avoid blending with neighboring textures..
float u, v, du, dv;
u = ((float)x + 0.5f) / (float)textureW;
v = ((float)y + 0.5f) / (float)textureH;
du = ((float)w - 1) / (float)textureW;
dv = ((float)h - 1) / (float)textureH;
shadow.m_TexCoordOffset.Init( u, v );
shadow.m_TexCoordScale.Init( du, dv );
}
//-----------------------------------------------------------------------------
// Setup all children shadows
//-----------------------------------------------------------------------------
bool CClientShadowMgr::BuildSetupShadowHierarchy( IClientRenderable *pRenderable, const ClientShadow_t &shadow, bool bChild )
{
bool bDrewTexture = false;
// Stop traversing when we hit a blobby shadow
ShadowType_t shadowType = GetActualShadowCastType( pRenderable );
if ( pRenderable && shadowType == SHADOWS_SIMPLE )
return false;
if ( !pRenderable || shadowType != SHADOWS_NONE )
{
bool bDrawModelShadow;
if ( !bChild )
{
bDrawModelShadow = ((shadow.m_Flags & SHADOW_FLAGS_BRUSH_MODEL) == 0);
}
else
{
int nModelType = modelinfo->GetModelType( pRenderable->GetModel() );
bDrawModelShadow = nModelType == mod_studio;
}
if ( bDrawModelShadow )
{
C_BaseEntity *pEntity = pRenderable->GetIClientUnknown()->GetBaseEntity();
if ( pEntity )
{
if ( pEntity->IsNPC() )
{
s_NPCShadowBoneSetups.AddToTail( assert_cast<C_BaseAnimating *>( pEntity ) );
}
else if ( pEntity->GetBaseAnimating() )
{
s_NonNPCShadowBoneSetups.AddToTail( assert_cast<C_BaseAnimating *>( pEntity ) );
}
}
bDrewTexture = true;
}
}
if ( !pRenderable )
return bDrewTexture;
IClientRenderable *pChild;
for ( pChild = pRenderable->FirstShadowChild(); pChild; pChild = pChild->NextShadowPeer() )
{
if ( BuildSetupShadowHierarchy( pChild, shadow, true ) )
{
bDrewTexture = true;
}
}
return bDrewTexture;
}
//-----------------------------------------------------------------------------
// Draws all children shadows into our own
//-----------------------------------------------------------------------------
bool CClientShadowMgr::DrawShadowHierarchy( IClientRenderable *pRenderable, const ClientShadow_t &shadow, bool bChild )
{
bool bDrewTexture = false;
// Stop traversing when we hit a blobby shadow
ShadowType_t shadowType = GetActualShadowCastType( pRenderable );
if ( pRenderable && shadowType == SHADOWS_SIMPLE )
return false;
if ( !pRenderable || shadowType != SHADOWS_NONE )
{
bool bDrawModelShadow;
bool bDrawBrushShadow;
if ( !bChild )
{
bDrawModelShadow = ((shadow.m_Flags & SHADOW_FLAGS_BRUSH_MODEL) == 0);
bDrawBrushShadow = !bDrawModelShadow;
}
else
{
int nModelType = modelinfo->GetModelType( pRenderable->GetModel() );
bDrawModelShadow = nModelType == mod_studio;
bDrawBrushShadow = nModelType == mod_brush;
}
if ( pRenderable && ( shadow.m_Flags & SHADOW_FLAGS_CUSTOM_DRAW ) )
{
RenderableInstance_t instance;
instance.m_nAlpha = 255;
pRenderable->DrawModel( STUDIO_SHADOWTEXTURE, instance );
bDrewTexture = true;
}
else if ( bDrawModelShadow )
{
CMatRenderContextPtr pRenderContext( materials );
CMatRenderDataReference lock( pRenderContext );
DrawModelInfo_t info;
matrix3x4a_t *pBoneToWorld = modelrender->DrawModelShadowSetup( pRenderable, pRenderable->GetBody(), pRenderable->GetSkin(), &info );
if ( pBoneToWorld )
{
modelrender->DrawModelShadow( pRenderable, info, pBoneToWorld );
}
bDrewTexture = true;
}
else if ( bDrawBrushShadow )
{
render->DrawBrushModelShadow( pRenderable );
bDrewTexture = true;
}
}
if ( !pRenderable )
return bDrewTexture;
IClientRenderable *pChild;
for ( pChild = pRenderable->FirstShadowChild(); pChild; pChild = pChild->NextShadowPeer() )
{
if ( DrawShadowHierarchy( pChild, shadow, true ) )
{
bDrewTexture = true;
}
}
return bDrewTexture;
}
//-----------------------------------------------------------------------------
// This gets called with every shadow that potentially will need to re-render
//-----------------------------------------------------------------------------
bool CClientShadowMgr::BuildSetupListForRenderToTextureShadow( unsigned short clientShadowHandle, float flArea )
{
ClientShadow_t& shadow = m_Shadows[clientShadowHandle];
bool bDirtyTexture = (shadow.m_Flags & SHADOW_FLAGS_TEXTURE_DIRTY) != 0;
bool bNeedsRedraw = m_ShadowAllocator.UseTexture( shadow.m_ShadowTexture, bDirtyTexture, flArea );
if ( bNeedsRedraw || bDirtyTexture )
{
shadow.m_Flags |= SHADOW_FLAGS_TEXTURE_DIRTY;
if ( !m_ShadowAllocator.HasValidTexture( shadow.m_ShadowTexture ) )
return false;
// shadow to be redrawn; for now, we'll always do it.
IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( shadow.m_Entity );
if ( BuildSetupShadowHierarchy( pRenderable, shadow ) )
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// This gets called with every shadow that potentially will need to re-render
//-----------------------------------------------------------------------------
bool CClientShadowMgr::DrawRenderToTextureShadow( int nSlot, unsigned short clientShadowHandle, float flArea )
{
ClientShadow_t& shadow = m_Shadows[clientShadowHandle];
if ( shadow.m_bUseSplitScreenBits &&
!shadow.m_SplitScreenBits.IsBitSet( nSlot ) )
{
return false;
}
// If we were previously using the LOD shadow, set the material
bool bPreviouslyUsingLODShadow = ( shadow.m_Flags & SHADOW_FLAGS_USING_LOD_SHADOW ) != 0;
shadow.m_Flags &= ~SHADOW_FLAGS_USING_LOD_SHADOW;
if ( bPreviouslyUsingLODShadow )
{
shadowmgr->SetShadowMaterial( shadow.m_ShadowHandle, m_RenderShadow, m_RenderModelShadow, (void*)(uintp)clientShadowHandle );
}
// Mark texture as being used...
bool bDirtyTexture = (shadow.m_Flags & SHADOW_FLAGS_TEXTURE_DIRTY) != 0;
bool bDrewTexture = false;
bool bNeedsRedraw = m_ShadowAllocator.UseTexture( shadow.m_ShadowTexture, bDirtyTexture, flArea );
if ( !m_ShadowAllocator.HasValidTexture( shadow.m_ShadowTexture ) )
{
DrawRenderToTextureShadowLOD( nSlot, clientShadowHandle );
return false;
}
if ( bNeedsRedraw || bDirtyTexture )
{
// shadow to be redrawn; for now, we'll always do it.
IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( shadow.m_Entity );
CMatRenderContextPtr pRenderContext( materials );
// Sets the viewport state
int x, y, w, h;
m_ShadowAllocator.GetTextureRect( shadow.m_ShadowTexture, x, y, w, h );
pRenderContext->Viewport( IsGameConsole() ? 0 : x, IsGameConsole() ? 0 : y, w, h );
// Clear the selected viewport only (don't need to clear depth)
pRenderContext->ClearBuffers( true, false );
pRenderContext->MatrixMode( MATERIAL_VIEW );
pRenderContext->LoadMatrix( shadow.m_WorldToTexture );
if ( DrawShadowHierarchy( pRenderable, shadow ) )
{
bDrewTexture = true;
if ( IsGameConsole() )
{
// resolve render target to system memory texture
Rect_t srcRect = { 0, 0, w, h };
Rect_t dstRect = { x, y, w, h };
pRenderContext->CopyRenderTargetToTextureEx( m_ShadowAllocator.GetTexture(), 0, &srcRect, &dstRect );
}
}
else
{
// NOTE: Think the flags reset + texcoord set should only happen in DrawShadowHierarchy
// but it's 2 days before 360 ship.. not going to change this now.
DevMsg( "Didn't draw shadow hierarchy.. bad shadow texcoords probably going to happen..grab Brian!\n" );
}
// Only clear the dirty flag if the caster isn't animating
if ( (shadow.m_Flags & SHADOW_FLAGS_ANIMATING_SOURCE) == 0 )
{
shadow.m_Flags &= ~SHADOW_FLAGS_TEXTURE_DIRTY;
}
SetRenderToTextureShadowTexCoords( shadow.m_ShadowHandle, x, y, w, h );
SetRenderToTextureShadowTexCoords( shadow, x, y, w, h );
}
else if ( bPreviouslyUsingLODShadow )
{
// In this case, we were previously using the LOD shadow, but we didn't
// have to reconstitute the texture. In this case, we need to reset the texcoord
int x, y, w, h;
m_ShadowAllocator.GetTextureRect( shadow.m_ShadowTexture, x, y, w, h );
SetRenderToTextureShadowTexCoords( shadow.m_ShadowHandle, x, y, w, h );
SetRenderToTextureShadowTexCoords( shadow, x, y, w, h );
}
return bDrewTexture;
}
//-----------------------------------------------------------------------------
// "Draws" the shadow LOD, which really means just set up the blobby shadow
//-----------------------------------------------------------------------------
void CClientShadowMgr::DrawRenderToTextureShadowLOD( int nSlot, unsigned short clientShadowHandle )
{
if ( r_shadow_deferred.GetBool() )
{
return;
}
ClientShadow_t &shadow = m_Shadows[clientShadowHandle];
if ( shadow.m_bUseSplitScreenBits &&
!shadow.m_SplitScreenBits.IsBitSet( nSlot ) )
{
return;
}
if ( (shadow.m_Flags & SHADOW_FLAGS_USING_LOD_SHADOW) == 0 )
{
shadowmgr->SetShadowMaterial( shadow.m_ShadowHandle, m_SimpleShadow, m_SimpleShadow, (void*)CLIENTSHADOW_INVALID_HANDLE );
shadowmgr->SetShadowTexCoord( shadow.m_ShadowHandle, 0, 0, 1, 1 );
ClearExtraClipPlanes( shadow.m_ShadowHandle );
shadow.m_Flags |= SHADOW_FLAGS_USING_LOD_SHADOW;
}
}
//-----------------------------------------------------------------------------
// Advances to the next frame,
//-----------------------------------------------------------------------------
void CClientShadowMgr::AdvanceFrame()
{
// We're starting the next frame
m_ShadowAllocator.AdvanceFrame();
}
//-----------------------------------------------------------------------------
// Re-render shadow depth textures that lie in the leaf list
//-----------------------------------------------------------------------------
int CClientShadowMgr::BuildActiveShadowDepthList( const CViewSetup &viewSetup, int nMaxDepthShadows, ClientShadowHandle_t *pActiveDepthShadows, int &nNumHighRes )
{
float fDots[ 1024 ];
nNumHighRes = 0;
Frustum_t viewFrustum;
GeneratePerspectiveFrustum( viewSetup.origin, viewSetup.angles, viewSetup.zNear, viewSetup.zFar, viewSetup.fov, viewSetup.m_flAspectRatio, viewFrustum );
// Get a general look position for
Vector vViewForward;
AngleVectors( viewSetup.angles, &vViewForward );
int nActiveDepthShadowCount = 0;
for ( ClientShadowHandle_t i = m_Shadows.Head(); i != m_Shadows.InvalidIndex(); i = m_Shadows.Next(i) )
{
if ( nActiveDepthShadowCount >= nMaxDepthShadows && nNumHighRes == nActiveDepthShadowCount )
{
// Easy out! There's nothing more we can do
break;
}
ClientShadow_t& shadow = m_Shadows[i];
// If this is not a flashlight which should use a shadow depth texture, skip!
if ( ( shadow.m_Flags & SHADOW_FLAGS_USE_DEPTH_TEXTURE ) == 0 )
continue;
ASSERT_LOCAL_PLAYER_RESOLVABLE();
if ( ( shadow.m_nSplitscreenOwner >= 0 ) && ( shadow.m_nSplitscreenOwner != GET_ACTIVE_SPLITSCREEN_SLOT() ) )
continue;
const FlashlightState_t& flashlightState = shadowmgr->GetFlashlightState( shadow.m_ShadowHandle );
// Bail if this flashlight doesn't want shadows
if ( !flashlightState.m_bEnableShadows )
continue;
if ( r_flashlightenableculling.GetBool() )
{
// Calculate an AABB around the shadow frustum
Vector vecAbsMins, vecAbsMaxs;
CalculateAABBFromProjectionMatrix( shadow.m_WorldToShadow, &vecAbsMins, &vecAbsMaxs );
// FIXME: Could do other sorts of culling here, such as frustum-frustum test, distance etc.
// If it's not in the view frustum, move on
if ( !flashlightState.m_bOrtho && viewFrustum.CullBox( vecAbsMins, vecAbsMaxs ) )
{
shadowmgr->SetFlashlightDepthTexture( shadow.m_ShadowHandle, NULL, 0 );
continue;
}
}
if ( nActiveDepthShadowCount >= nMaxDepthShadows )
{
if ( !flashlightState.m_bShadowHighRes )
{
// All active shadows are high res
static bool s_bOverflowWarning = false;
if ( !s_bOverflowWarning )
{
Warning( "Too many depth textures rendered in a single view!\n" );
Assert( 0 );
s_bOverflowWarning = true;
}
shadowmgr->SetFlashlightDepthTexture( shadow.m_ShadowHandle, NULL, 0 );
continue;
}
else
{
// Lets take the place of other non high res active shadows
for ( int j = nActiveDepthShadowCount - 1; j >= 0; --j )
{
// Find a low res one to replace
ClientShadow_t& prevShadow = m_Shadows[ pActiveDepthShadows[ j ] ];
const FlashlightState_t& prevFlashlightState = shadowmgr->GetFlashlightState( prevShadow.m_ShadowHandle );
if ( !prevFlashlightState.m_bShadowHighRes )
{
pActiveDepthShadows[ j ] = i;
++nNumHighRes;
break;
}
}
continue;
}
}
if ( flashlightState.m_bShadowHighRes )
{
++nNumHighRes;
}
// Calculate the approximate distance to the nearest side
Vector vLightDirection = flashlightState.m_vecLightOrigin - viewSetup.origin;
VectorNormalize( vLightDirection );
fDots[ nActiveDepthShadowCount ] = vLightDirection.Dot( vViewForward );
pActiveDepthShadows[ nActiveDepthShadowCount++ ] = i;
}
// sort them
for ( int i = 0; i < nActiveDepthShadowCount - 1; i++ )
{
for ( int j = 0; j < nActiveDepthShadowCount - i - 1; j++ )
{
if ( fDots[ j ] < fDots[ j + 1 ] )
{
ClientShadowHandle_t nTemp = pActiveDepthShadows[ j ];
pActiveDepthShadows[ j ] = pActiveDepthShadows[ j + 1 ];
pActiveDepthShadows[ j + 1 ] = nTemp;
}
}
}
return nActiveDepthShadowCount;
}
//-----------------------------------------------------------------------------
// Re-render shadow depth textures that lie in the leaf list
//-----------------------------------------------------------------------------
int CClientShadowMgr::BuildActiveFlashlightList( const CViewSetup &viewSetup, int nMaxFlashlights, ClientShadowHandle_t *pActiveFlashlights )
{
int nActiveFlashlightCount = 0;
for ( ClientShadowHandle_t i = m_Shadows.Head(); i != m_Shadows.InvalidIndex(); i = m_Shadows.Next(i) )
{
ClientShadow_t& shadow = m_Shadows[i];
if ( ( shadow.m_Flags & ( SHADOW_FLAGS_FLASHLIGHT | SHADOW_FLAGS_SIMPLE_PROJECTION ) ) == 0 )
continue;
ASSERT_LOCAL_PLAYER_RESOLVABLE();
if ( ( shadow.m_nSplitscreenOwner >= 0 ) && ( shadow.m_nSplitscreenOwner != GET_ACTIVE_SPLITSCREEN_SLOT() ) )
continue;
// Calculate an AABB around the shadow frustum
Vector vecAbsMins, vecAbsMaxs;
CalculateAABBFromProjectionMatrix( shadow.m_WorldToShadow, &vecAbsMins, &vecAbsMaxs );
Frustum_t viewFrustum;
GeneratePerspectiveFrustum( viewSetup.origin, viewSetup.angles, viewSetup.zNear, viewSetup.zFar, viewSetup.fov, viewSetup.m_flAspectRatio, viewFrustum );
// FIXME: Could do other sorts of culling here, such as frustum-frustum test, distance etc.
// If it's not in the view frustum, move on
if ( viewFrustum.CullBox( vecAbsMins, vecAbsMaxs ) )
{
continue;
}
if ( nActiveFlashlightCount >= nMaxFlashlights )
{
static bool s_bOverflowWarning = false;
if ( !s_bOverflowWarning )
{
Warning( "Too many flashlights rendered in a single view!\n" );
Assert( 0 );
s_bOverflowWarning = true;
}
//shadowmgr->SetFlashlightDepthTexture( shadow.m_ShadowHandle, NULL, 0 );
continue;
}
pActiveFlashlights[nActiveFlashlightCount++] = i;
}
return nActiveFlashlightCount;
}
//-----------------------------------------------------------------------------
// Sets the view's active flashlight render state
//-----------------------------------------------------------------------------
void CClientShadowMgr::SetViewFlashlightState( int nActiveFlashlightCount, ClientShadowHandle_t* pActiveFlashlights )
{
// NOTE: On the 360, we render the entire scene with the flashlight state
// set and don't render flashlights additively in the shadow mgr at a far later time
// because the CPU costs are prohibitive
shadowmgr->PushSinglePassFlashlightStateEnabled( IsGameConsole() );
AssertOnce( nActiveFlashlightCount <= m_nMaxDepthTextureShadows );
if ( nActiveFlashlightCount > 0 )
{
Assert( ( m_Shadows[ pActiveFlashlights[0] ].m_Flags & ( SHADOW_FLAGS_FLASHLIGHT | SHADOW_FLAGS_SIMPLE_PROJECTION ) ) != 0 );
shadowmgr->SetSinglePassFlashlightRenderState( m_Shadows[ pActiveFlashlights[0] ].m_ShadowHandle );
}
else
{
shadowmgr->SetSinglePassFlashlightRenderState( SHADOW_HANDLE_INVALID );
}
}
//-----------------------------------------------------------------------------
// Kicks off rendering of volumetrics for the flashlights
//-----------------------------------------------------------------------------
void CClientShadowMgr::DrawVolumetrics( const CViewSetup &viewSetup )
{
CMatRenderContextPtr pRenderContext( materials );
PIXEVENT( pRenderContext, "Draw Flashlight Volumetrics" );
shadowmgr->DrawVolumetrics();
}
void AddPointToExtentsHelper( const VMatrix &flashlightToWorld, const Vector &vecPos, Vector &vecMin, Vector &vecMax )
{
Vector worldSpacePos;
Vector3DMultiplyPositionProjective( flashlightToWorld, vecPos, worldSpacePos );
VectorMin( vecMin, worldSpacePos, vecMin );
VectorMax( vecMax, worldSpacePos, vecMax );
}
void CClientShadowMgr::GetFrustumExtents( ClientShadowHandle_t handle, Vector &vecMin, Vector &vecMax )
{
Assert( m_Shadows.IsValidIndex( handle ) );
CClientShadowMgr::ClientShadow_t &shadow = m_Shadows[ handle ];
VMatrix flashlightToWorld;
MatrixInverseGeneral( shadow.m_WorldToShadow, flashlightToWorld );
vecMin = Vector( FLT_MAX, FLT_MAX, FLT_MAX );
vecMax = Vector( -FLT_MAX, -FLT_MAX, -FLT_MAX );
AddPointToExtentsHelper( flashlightToWorld, Vector( 0.0f, 0.0f, 0.0f ), vecMin, vecMax );
AddPointToExtentsHelper( flashlightToWorld, Vector( 0.0f, 0.0f, 1.0f ), vecMin, vecMax );
AddPointToExtentsHelper( flashlightToWorld, Vector( 0.0f, 1.0f, 0.0f ), vecMin, vecMax );
AddPointToExtentsHelper( flashlightToWorld, Vector( 1.0f, 0.0f, 0.0f ), vecMin, vecMax );
AddPointToExtentsHelper( flashlightToWorld, Vector( 0.0f, 1.0f, 1.0f ), vecMin, vecMax );
AddPointToExtentsHelper( flashlightToWorld, Vector( 1.0f, 0.0f, 1.0f ), vecMin, vecMax );
AddPointToExtentsHelper( flashlightToWorld, Vector( 1.0f, 1.0f, 0.0f ), vecMin, vecMax );
AddPointToExtentsHelper( flashlightToWorld, Vector( 1.0f, 1.0f, 1.0f ), vecMin, vecMax );
}
//-----------------------------------------------------------------------------
// Re-render shadow depth textures that lie in the leaf list
//
// bSetup currently only used on PS3 - to support 2 pass Build/Draw rendering
// bSetup indicates whether to execute once per frame setup code, do this in the buildlist (1st pass) pass only
// Needed since this will get called twice on PS3 if 2 pass drawing is on
// bSetup = true otherwise
//
//-----------------------------------------------------------------------------
void CClientShadowMgr::ComputeShadowDepthTextures( const CViewSetup &viewSetup, bool bSetup )
{
if ( !r_flashlightdepthtexture.GetBool() )
{
// Build list of active flashlights
ClientShadowHandle_t pActiveFlashlights[16];
int nActiveFlashlights = BuildActiveFlashlightList( viewSetup, ARRAYSIZE( pActiveFlashlights ), pActiveFlashlights );
SetViewFlashlightState( nActiveFlashlights, pActiveFlashlights );
return;
}
VPROF_BUDGET( "CClientShadowMgr::ComputeShadowDepthTextures", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING );
CMatRenderContextPtr pRenderContext( materials );
PIXEVENT( pRenderContext, "Shadow Depth Textures" );
// Build list of active shadow depth textures / flashlights
int iNumHighRes = 0;
ClientShadowHandle_t pActiveDepthShadows[1024];
int nActiveDepthShadowCount = BuildActiveShadowDepthList( viewSetup, ARRAYSIZE( pActiveDepthShadows ), pActiveDepthShadows, iNumHighRes );
int iLowResStart = ( iNumHighRes >= m_nMaxDepthTextureShadows ? 0 : iNumHighRes );
// Iterate over all existing textures and allocate shadow textures
bool bDebugFrustum = r_flashlightdrawfrustum.GetBool();
bool bDebugFrustumBBox = r_flashlightdrawfrustumbbox.GetBool();
bool bPrintFlashlightInfo = r_flashlight_info.GetBool();
if ( bPrintFlashlightInfo )
{
engine->Con_NPrintf( 0, "%d active flashlights", nActiveDepthShadowCount );
}
for ( int j = 0; j < nActiveDepthShadowCount; ++j )
{
ClientShadow_t& shadow = m_Shadows[ pActiveDepthShadows[j] ];
FlashlightState_t& flashlightState = const_cast<FlashlightState_t&>( shadowmgr->GetFlashlightState( shadow.m_ShadowHandle ) );
CViewSetup shadowView;
CTextureReference shadowDepthTexture;
if( !bSetup )
{
bool bGotShadowDepthTexture = LockShadowDepthTexture( &shadowDepthTexture, flashlightState.m_bShadowHighRes ? 0 : iLowResStart );
if ( !bGotShadowDepthTexture )
{
// If we don't get one, that means we have too many this frame so bind no depth texture
static int bitchCount = 0;
if( bitchCount < 10 )
{
Warning( "Too many shadow maps this frame!\n" );
bitchCount++;
}
AssertOnce(0);
shadowmgr->SetFlashlightDepthTexture( shadow.m_ShadowHandle, NULL, 0 );
if ( bPrintFlashlightInfo )
{
engine->Con_NPrintf( j + 1, "[ERROR - no shadow] %f %f %f ", flashlightState.m_vecLightOrigin.x, flashlightState.m_vecLightOrigin.y, flashlightState.m_vecLightOrigin.z );
}
continue;
}
if ( bPrintFlashlightInfo )
{
engine->Con_NPrintf( j + 1, "[shadow] %f %f %f ", flashlightState.m_vecLightOrigin.x, flashlightState.m_vecLightOrigin.y, flashlightState.m_vecLightOrigin.z );
}
shadowView.m_flAspectRatio = 1.0f;
shadowView.x = shadowView.y = 0;
shadowView.width = shadowDepthTexture->GetActualWidth();
shadowView.height = shadowDepthTexture->GetActualHeight();
}
else
{
shadowView.m_flAspectRatio = 1.0f;
shadowView.x = shadowView.y = 0;
shadowView.width = 0;
shadowView.height = 0;
}
// Copy flashlight parameters
if ( !flashlightState.m_bOrtho )
{
shadowView.m_bOrtho = false;
}
else
{
shadowView.m_bOrtho = true;
shadowView.m_OrthoLeft = flashlightState.m_fOrthoLeft;
shadowView.m_OrthoTop = flashlightState.m_fOrthoTop;
shadowView.m_OrthoRight = flashlightState.m_fOrthoRight;
shadowView.m_OrthoBottom = flashlightState.m_fOrthoBottom;
}
shadowView.m_bDoBloomAndToneMapping = false;
shadowView.m_nMotionBlurMode = MOTION_BLUR_DISABLE;
shadowView.fov = shadowView.fovViewmodel = flashlightState.m_fHorizontalFOVDegrees;
shadowView.origin = flashlightState.m_vecLightOrigin;
QuaternionAngles( flashlightState.m_quatOrientation, shadowView.angles ); // Convert from Quaternion to QAngle
shadowView.zNear = shadowView.zNearViewmodel = flashlightState.m_NearZ;
shadowView.zFar = shadowView.zFarViewmodel = flashlightState.m_FarZ;
// Can turn on all light frustum overlays or per light with flashlightState parameter...
if ( ( bDebugFrustum || flashlightState.m_bDrawShadowFrustum ) && !bSetup )
{
if ( flashlightState.m_bUberlight )
{
DrawUberlightRig( shadowView.origin, shadow.m_WorldToShadow, flashlightState );
}
DrawFrustum( shadowView.origin, shadow.m_WorldToShadow );
}
if ( bDebugFrustumBBox && !bSetup )
{
Vector vecExtentsMin, vecExtentsMax;
GetFrustumExtents( pActiveDepthShadows[j], vecExtentsMin, vecExtentsMax );
float flVisibleBBoxMinHeight = MIN( vecExtentsMax.z - 1.0f, C_EnvProjectedTexture::GetVisibleBBoxMinHeight() );
vecExtentsMin.z = MAX( vecExtentsMin.z, flVisibleBBoxMinHeight );
NDebugOverlay::Box( Vector( 0.0f, 0.0f, 0.0f ), vecExtentsMin, vecExtentsMax, 0, 0, 255, 100, 0.0f );
}
// Set depth bias factors specific to this flashlight
if( !bSetup )
{
CMatRenderContextPtr pRenderContext( materials );
pRenderContext->SetShadowDepthBiasFactors( flashlightState.m_flShadowSlopeScaleDepthBias, flashlightState.m_flShadowDepthBias );
// Render to the shadow depth texture with appropriate view
view->UpdateShadowDepthTexture( m_DummyColorTexture, shadowDepthTexture, shadowView );
}
else
{
// just build world/renderable lists
view->UpdateShadowDepthTexture( NULL, NULL, shadowView );
}
// Associate the shadow depth texture and stencil bit with the flashlight for use during scene rendering
if( !bSetup )
{
shadowmgr->SetFlashlightDepthTexture( shadow.m_ShadowHandle, shadowDepthTexture, 0 );
}
}
SetViewFlashlightState( nActiveDepthShadowCount, pActiveDepthShadows );
}
//-----------------------------------------------------------------------------
// Re-renders all shadow textures for shadow casters that lie in the leaf list
//-----------------------------------------------------------------------------
static void SetupBonesOnBaseAnimating( C_BaseAnimating *&pBaseAnimating )
{
pBaseAnimating->SetupBones( NULL, -1, -1, gpGlobals->curtime );
}
void CClientShadowMgr::ComputeShadowTextures( const CViewSetup &view, int leafCount, WorldListLeafData_t* pLeafList )
{
ASSERT_LOCAL_PLAYER_RESOLVABLE();
int nSlot = GET_ACTIVE_SPLITSCREEN_SLOT();
VPROF_BUDGET( "CClientShadowMgr::ComputeShadowTextures", VPROF_BUDGETGROUP_SHADOW_RENDERING );
if ( !m_RenderToTextureActive || (r_shadows.GetInt() == 0) || r_shadows_gamecontrol.GetInt() == 0 )
return;
MDLCACHE_CRITICAL_SECTION();
// First grab all shadow textures we may want to render
int nCount = s_VisibleShadowList.FindShadows( &view, leafCount, pLeafList );
if ( nCount == 0 )
return;
// FIXME: Add heuristics based on distance, etc. to futz with
// the shadow allocator + to select blobby shadows
CMatRenderContextPtr pRenderContext( materials );
PIXEVENT( pRenderContext, "Render-To-Texture Shadows" );
// Clear to white (color unused), black alpha
pRenderContext->ClearColor4ub( 255, 255, 255, 0 );
// No height clip mode please.
MaterialHeightClipMode_t oldHeightClipMode = pRenderContext->GetHeightClipMode();
pRenderContext->SetHeightClipMode( MATERIAL_HEIGHTCLIPMODE_DISABLE );
// No projection matrix (orthographic mode)
// FIXME: Make it work for projective shadows?
pRenderContext->MatrixMode( MATERIAL_PROJECTION );
pRenderContext->PushMatrix();
pRenderContext->LoadIdentity();
pRenderContext->Scale( 1, -1, 1 );
pRenderContext->Ortho( 0, 0, 1, 1, -9999, 0 );
pRenderContext->MatrixMode( MATERIAL_VIEW );
pRenderContext->PushMatrix();
pRenderContext->PushRenderTargetAndViewport( m_ShadowAllocator.GetTexture() );
if ( m_bRenderTargetNeedsClear )
{
// don't need to clear absent depth buffer
pRenderContext->ClearBuffers( true, false );
m_bRenderTargetNeedsClear = false;
}
int nMaxShadows = r_shadowmaxrendered.GetInt();
int nModelsRendered = 0;
int i;
for (i = 0; i < nCount; ++i)
{
const VisibleShadowInfo_t &info = s_VisibleShadowList.GetVisibleShadow(i);
if ( nModelsRendered < nMaxShadows )
{
if ( DrawRenderToTextureShadow( nSlot, info.m_hShadow, info.m_flArea ) )
{
++nModelsRendered;
}
}
else
{
DrawRenderToTextureShadowLOD( nSlot, info.m_hShadow );
}
}
// Render to the backbuffer again
pRenderContext->PopRenderTargetAndViewport();
// Restore the matrices
pRenderContext->MatrixMode( MATERIAL_PROJECTION );
pRenderContext->PopMatrix();
pRenderContext->MatrixMode( MATERIAL_VIEW );
pRenderContext->PopMatrix();
pRenderContext->SetHeightClipMode( oldHeightClipMode );
pRenderContext->SetHeightClipMode( oldHeightClipMode );
// Restore the clear color
pRenderContext->ClearColor3ub( 0, 0, 0 );
}
//-------------------------------------------------------------------------------------------------------
// Lock down the usage of a shadow depth texture...must be unlocked for use on subsequent views / frames
//-------------------------------------------------------------------------------------------------------
bool CClientShadowMgr::LockShadowDepthTexture( CTextureReference *shadowDepthTexture, int nStartTexture )
{
for ( int i = nStartTexture; i < m_DepthTextureCache.Count(); i++ ) // Search for cached shadow depth texture
{
if ( m_DepthTextureCacheLocks[i] == false && m_DepthTextureCache[i].IsValid() ) // If a free one is found
{
*shadowDepthTexture = m_DepthTextureCache[i];
m_DepthTextureCacheLocks[i] = true;
return true;
}
}
return false; // Didn't find it...
}
bool ClientShadowMgrAcquireShadowDepthTexture( CTextureReference *pDummyColorTexture, CTextureReference *pShadowDepthTexture )
{
if ( pDummyColorTexture )
{
*pDummyColorTexture = s_ClientShadowMgr.m_DummyColorTexture;
}
if ( pShadowDepthTexture )
{
if ( s_ClientShadowMgr.m_DepthTextureCache.Count() && s_ClientShadowMgr.m_DepthTextureCache[0].IsValid() )
{
*pShadowDepthTexture = s_ClientShadowMgr.m_DepthTextureCache[0];
return true;
}
}
else
{
s_ClientShadowMgr.UnlockAllShadowDepthTextures();
}
return false;
}
//------------------------------------------------------------------
// Unlock shadow depth texture for use on subsequent views / frames
//------------------------------------------------------------------
void CClientShadowMgr::UnlockAllShadowDepthTextures()
{
for (int i=0; i< m_DepthTextureCache.Count(); i++ )
{
m_DepthTextureCacheLocks[i] = false;
}
shadowmgr->SetSinglePassFlashlightRenderState( SHADOW_HANDLE_INVALID );
shadowmgr->PopSinglePassFlashlightStateEnabled();
}
void CClientShadowMgr::SetFlashlightTarget( ClientShadowHandle_t shadowHandle, EHANDLE targetEntity )
{
Assert( m_Shadows.IsValidIndex( shadowHandle ) );
CClientShadowMgr::ClientShadow_t &shadow = m_Shadows[ shadowHandle ];
if( ( shadow.m_Flags & ( SHADOW_FLAGS_FLASHLIGHT | SHADOW_FLAGS_SIMPLE_PROJECTION ) ) == 0 )
return;
// shadow.m_pTargetRenderable = pRenderable;
shadow.m_hTargetEntity = targetEntity;
}
void CClientShadowMgr::SetFlashlightLightWorld( ClientShadowHandle_t shadowHandle, bool bLightWorld )
{
Assert( m_Shadows.IsValidIndex( shadowHandle ) );
ClientShadow_t &shadow = m_Shadows[ shadowHandle ];
if( ( shadow.m_Flags & ( SHADOW_FLAGS_FLASHLIGHT | SHADOW_FLAGS_SIMPLE_PROJECTION ) ) == 0 )
return;
if ( bLightWorld )
{
shadow.m_Flags |= SHADOW_FLAGS_LIGHT_WORLD;
}
else
{
shadow.m_Flags &= ~SHADOW_FLAGS_LIGHT_WORLD;
}
}
bool CClientShadowMgr::IsFlashlightTarget( ClientShadowHandle_t shadowHandle, IClientRenderable *pRenderable )
{
ClientShadow_t &shadow = m_Shadows[ shadowHandle ];
if( shadow.m_hTargetEntity->GetClientRenderable() == pRenderable )
return true;
C_BaseEntity *pChild = shadow.m_hTargetEntity->FirstMoveChild();
while( pChild )
{
if( pChild->GetClientRenderable()==pRenderable )
return true;
pChild = pChild->NextMovePeer();
}
return false;
}
void CClientShadowMgr::SetShadowFromWorldLightsEnabled( bool bEnable )
{
bool bIsShadowingFromWorldLights = IsShadowingFromWorldLights();
m_bShadowFromWorldLights = bEnable;
if ( bIsShadowingFromWorldLights != IsShadowingFromWorldLights() )
{
UpdateAllShadows();
}
}
void CClientShadowMgr::SuppressShadowFromWorldLights( bool bSuppress )
{
bool bIsShadowingFromWorldLights = IsShadowingFromWorldLights();
m_bSuppressShadowFromWorldLights = bSuppress;
if ( bIsShadowingFromWorldLights != IsShadowingFromWorldLights() )
{
UpdateAllShadows();
}
}
///////////////////////////////////////////////////////////////////////
// Vertex and index data for cube with degenerate faces on each edge
///////////////////////////////////////////////////////////////////////
ALIGN16 static const float pShadowBoundVerts[] =
{
// +X face
1.0f, -1.0f, -1.0f, 1.0f,
1.0f, 1.0f, -1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,
1.0f, -1.0f, 1.0f, 1.0f,
// +Y face
-1.0f, 1.0f, -1.0f, 1.0f,
-1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 1.0f, -1.0f, 1.0f,
// -X face
-1.0f, -1.0f, 1.0f, 1.0f,
-1.0f, 1.0f, 1.0f, 1.0f,
-1.0f, 1.0f, -1.0f, 1.0f,
-1.0f, -1.0f, -1.0f, 1.0f,
// -Y face
-1.0f, -1.0f, -1.0f, 1.0f,
1.0f, -1.0f, -1.0f, 1.0f,
1.0f, -1.0f, 1.0f, 1.0f,
-1.0f, -1.0f, 1.0f, 1.0f,
// +Z face
-1.0f, -1.0f, 1.0f, 1.0f,
1.0f, -1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,
-1.0f, 1.0f, 1.0f, 1.0f,
// -Z face
1.0f, -1.0f, -1.0f, 1.0f,
-1.0f, -1.0f, -1.0f, 1.0f,
-1.0f, 1.0f, -1.0f, 1.0f,
1.0f, 1.0f, -1.0f, 1.0f
};
ALIGN16 static const float pShadowBoundNormals[] =
{
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
-1.0f, 0.0f, 0.0f, 0.0f,
0.0f, -1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, -1.0f, 0.0f,
};
ALIGN16 static const unsigned short pShadowBoundIndices[] =
{
// box faces
0, 2, 1, 0, 3, 2,
4, 6, 5, 4, 7, 6,
8, 10, 9, 8, 11, 10,
12, 14, 13, 12, 15, 14,
16, 18, 17, 16, 19, 18,
20, 22, 21, 20, 23, 22,
// degenerate faces on edges
2, 7, 1, 2, 6, 7,
5, 10, 4, 5, 9, 10,
8, 12, 11, 8, 15, 12,
14, 0, 13, 14, 3, 0,
17, 2, 3, 17, 18, 2,
18, 5, 6, 18, 19, 5,
8, 19, 16, 8, 9, 19,
14, 16, 17, 14, 15, 16,
0, 23, 20, 0, 1, 23,
7, 22, 23, 7, 4, 22,
11, 21, 22, 11, 22, 10,
13, 21, 12, 13, 20, 21
};
// Adds a cube with degenerate edge quads to a mesh builder
void CClientShadowMgr::BuildCubeWithDegenerateEdgeQuads( CMeshBuilder& meshBuilder, const matrix3x4_t& objToWorld, const VMatrix& projToShadow, const CClientShadowMgr::ClientShadow_t& shadow )
{
static bool bSIMDDataInitialized = false;
static FourVectors srcPositions[6];
static FourVectors srcNormals[2];
if ( !bSIMDDataInitialized )
{
// convert vertex data into SIMD data
const float* RESTRICT pPos = pShadowBoundVerts;
const float* RESTRICT pNormal = pShadowBoundNormals;
for( int v = 0; v < 6; ++v )
{
srcPositions[v].LoadAndSwizzleAligned( pPos, pPos+4, pPos+8, pPos+12);
pPos += 16;
}
srcNormals[0].LoadAndSwizzleAligned( pNormal, pNormal+4, pNormal+8, pNormal+12);
pNormal += 16;
srcNormals[1].LoadAndSwizzleAligned( pNormal, pNormal+4, pNormal+4, pNormal+4);
bSIMDDataInitialized = true;
}
Vector fallOffParams;
ComputeFalloffInfo( shadow, &fallOffParams );
int nBaseVertIdx = meshBuilder.GetCurrentVertex();
float texCoord[4] = { shadow.m_TexCoordOffset.x, shadow.m_TexCoordOffset.y, shadow.m_TexCoordScale.x, shadow.m_TexCoordScale.y };
Vector shadowDir = shadow.m_ShadowDir * shadow.m_MaxDist;
if ( r_shadow_deferred_simd.GetBool() )
{
// FIXME: Something in here is buggy
FourVectors positions[6];
FourVectors normals[2];
positions[0] = srcPositions[0];
positions[1] = srcPositions[1];
positions[2] = srcPositions[2];
positions[3] = srcPositions[3];
positions[4] = srcPositions[4];
positions[5] = srcPositions[5];
positions[0].TransformBy( objToWorld );
positions[1].TransformBy( objToWorld );
positions[2].TransformBy( objToWorld );
positions[3].TransformBy( objToWorld );
positions[4].TransformBy( objToWorld );
positions[5].TransformBy( objToWorld );
//FourVectors::TransformManyBy( srcPositions, 6, objToWorld, positions ); // this doesn't work but the above does. What gives???
FourVectors::RotateManyBy( srcNormals, 2, objToWorld, normals );
normals[0].VectorNormalizeFast(); // optional, will throw asserts in debug if we don't normalize
normals[1].VectorNormalizeFast();
for( int v = 0; v < ARRAYSIZE( pShadowBoundVerts ) / 4; ++v )
{
meshBuilder.Position3fv( positions[v/4].Vec( v%4 ).Base() );
meshBuilder.Normal3fv( normals[v/16].Vec( (v/4)%4 ).Base() );
meshBuilder.TexCoord3fv( 0, shadowDir.Base() );
meshBuilder.TexCoord4fv( 1, texCoord );
meshBuilder.TexCoord4fv( 2, projToShadow[0] );
meshBuilder.TexCoord4fv( 3, projToShadow[1] );
meshBuilder.TexCoord4fv( 4, projToShadow[2] );
meshBuilder.TexCoord4fv( 5, projToShadow[3] );
meshBuilder.TexCoord3fv( 6, fallOffParams.Base() );
meshBuilder.AdvanceVertex();
}
}
else
{
const float* pPos = pShadowBoundVerts;
const float* pNormal = pShadowBoundNormals;
for( int v = 0; v < ARRAYSIZE( pShadowBoundVerts ) / 4; ++v )
{
Vector pos;
Vector normal;
VectorTransform( pPos, objToWorld, (float*)&pos );
VectorRotate( pNormal, objToWorld, (float*)&normal );
meshBuilder.Position3fv( pos.Base() );
meshBuilder.Normal3fv( normal.Base() );
meshBuilder.TexCoord3fv( 0, shadowDir.Base() );
meshBuilder.TexCoord4fv( 1, texCoord );
meshBuilder.TexCoord4fv( 2, projToShadow[0] );
meshBuilder.TexCoord4fv( 3, projToShadow[1] );
meshBuilder.TexCoord4fv( 4, projToShadow[2] );
meshBuilder.TexCoord4fv( 5, projToShadow[3] );
meshBuilder.TexCoord3fv( 6, fallOffParams.Base() );
meshBuilder.AdvanceVertex();
pPos += 4;
if ( v % 4 == 3)
pNormal += 4;
}
}
for( int i = 0; i < ARRAYSIZE( pShadowBoundIndices ) / 2; ++i )
{
// this causes alignment exception on 360?
//meshBuilder.FastIndex2( nBaseVertIdx + pShadowBoundIndices[2*i], nBaseVertIdx + pShadowBoundIndices[2*i+1] );
meshBuilder.FastIndex( nBaseVertIdx + pShadowBoundIndices[2*i] );
meshBuilder.FastIndex( nBaseVertIdx + pShadowBoundIndices[2*i+1] );
}
}
struct IntersectingShadowInfo_t
{
ClientShadowHandle_t h;
matrix3x4_t objToWorld;
};
// Sets up rendering info for deferred shadows
bool CClientShadowMgr::SetupDeferredShadow( const ClientShadow_t& shadow, const Vector& camPos, matrix3x4_t* pObjToWorldMat ) const
{
IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( shadow.m_Entity );
Vector mins;
Vector maxs;
pRenderable->GetShadowRenderBounds( mins, maxs, GetActualShadowCastType( pRenderable ) );
Vector center = mins + maxs;
center *= 0.5f;
Vector dims = maxs - mins;
dims *= 0.5f;
matrix3x4_t scaleAndOffset ( dims.x, 0.0f, 0.0f, center.x,
0.0f, dims.y, 0.0f, center.y,
0.0f, 0.0f, dims.z, center.z );
matrix3x4_t objToWorld;
AngleMatrix( pRenderable->GetRenderAngles(), pRenderable->GetRenderOrigin(), objToWorld );
matrix3x4_t worldToObj;
MatrixInvert( objToWorld, worldToObj );
MatrixMultiply( objToWorld, scaleAndOffset, objToWorld );
MatrixCopy( objToWorld, *pObjToWorldMat );
// test if camera is inside shadow volume
Vector shadowDirObjSpace;
Vector camPosObjSpace;
VectorRotate( shadow.m_ShadowDir, worldToObj, shadowDirObjSpace );
VectorTransform( camPos, worldToObj, camPosObjSpace );
BoxTraceInfo_t ti;
if ( IntersectRayWithBox( camPosObjSpace, -shadow.m_MaxDist*shadowDirObjSpace, mins, maxs, 0.0f, &ti ) )
{
return true;
}
return false;
}
void CClientShadowMgr::DownsampleDepthBuffer( IMatRenderContext* pRenderContext, const VMatrix& invViewProjMat )
{
int nScreenWidth, nScreenHeight, nDummy;
pRenderContext->GetViewport( nDummy, nDummy, nScreenWidth, nScreenHeight );
int nWidth = m_downSampledNormals->GetActualWidth();
int nHeight = m_downSampledNormals->GetActualHeight();
pRenderContext->PushRenderTargetAndViewport( m_downSampledNormals, 0, 0, nWidth, nHeight );
IMaterial* pMat = materials->FindMaterial( "dev/downsampledepth", TEXTURE_GROUP_OTHER );
// yes, this is stupid
IMaterialVar* pVar = pMat->FindVar( "$c0_x", NULL );
pVar->SetFloatValue( invViewProjMat[0][0] );
pVar = pMat->FindVar( "$c0_y", NULL );
pVar->SetFloatValue( invViewProjMat[0][1] );
pVar = pMat->FindVar( "$c0_z", NULL );
pVar->SetFloatValue( invViewProjMat[0][2] );
pVar = pMat->FindVar( "$c0_w", NULL );
pVar->SetFloatValue( invViewProjMat[0][3] );
pVar = pMat->FindVar( "$c1_x", NULL );
pVar->SetFloatValue( invViewProjMat[1][0] );
pVar = pMat->FindVar( "$c1_y", NULL );
pVar->SetFloatValue( invViewProjMat[1][1] );
pVar = pMat->FindVar( "$c1_z", NULL );
pVar->SetFloatValue( invViewProjMat[1][2] );
pVar = pMat->FindVar( "$c1_w", NULL );
pVar->SetFloatValue( invViewProjMat[1][3] );
pVar = pMat->FindVar( "$c2_x", NULL );
pVar->SetFloatValue( invViewProjMat[2][0] );
pVar = pMat->FindVar( "$c2_y", NULL );
pVar->SetFloatValue( invViewProjMat[2][1] );
pVar = pMat->FindVar( "$c2_z", NULL );
pVar->SetFloatValue( invViewProjMat[2][2] );
pVar = pMat->FindVar( "$c2_w", NULL );
pVar->SetFloatValue( invViewProjMat[2][3] );
pVar = pMat->FindVar( "$c3_x", NULL );
pVar->SetFloatValue( invViewProjMat[3][0] );
pVar = pMat->FindVar( "$c3_y", NULL );
pVar->SetFloatValue( invViewProjMat[3][1] );
pVar = pMat->FindVar( "$c3_z", NULL );
pVar->SetFloatValue( invViewProjMat[3][2] );
pVar = pMat->FindVar( "$c3_w", NULL );
pVar->SetFloatValue( invViewProjMat[3][3] );
pVar = pMat->FindVar( "$c4_x", NULL );
pVar->SetFloatValue( 1.0f / float( nScreenWidth ) );
pVar = pMat->FindVar( "$c4_y", NULL );
pVar->SetFloatValue( 1.0f / float( nScreenHeight ) );
pRenderContext->DrawScreenSpaceRectangle( pMat, 0, 0, nWidth, nHeight,
0, 0, 2*nWidth-2, 2*nHeight-2,
2*nWidth, 2*nHeight );
if ( IsGameConsole() )
{
pRenderContext->CopyRenderTargetToTextureEx( m_downSampledNormals, 0, NULL, NULL );
pRenderContext->CopyRenderTargetToTextureEx( m_downSampledDepth, -1, NULL, NULL );
}
pRenderContext->PopRenderTargetAndViewport();
}
void CClientShadowMgr::CompositeDeferredShadows( IMatRenderContext* pRenderContext )
{
int nWidth, nHeight, nDummy;
pRenderContext->GetViewport( nDummy, nDummy, nWidth, nHeight );
IMaterial* pMat = materials->FindMaterial( "dev/compositedeferredshadow", TEXTURE_GROUP_OTHER );
/*
IMaterialVar* pMatVar = pMat->FindVar( "$basetexture", NULL, false );
if ( pMatVar )
{
ITexture *pFullScreen = materials->FindTexture( "_rt_FullFrameFB1", TEXTURE_GROUP_RENDER_TARGET );
pMatVar->SetTextureValue( pFullScreen );
}
*/
pRenderContext->DrawScreenSpaceRectangle( pMat, 0, 0, nWidth, nHeight,
0, 0, 2*nWidth-2, 2*nHeight-2,
2*nWidth, 2*nHeight );
}
void CClientShadowMgr::ComputeFalloffInfo( const ClientShadow_t& shadow, Vector* pShadowFalloffParams )
{
float flFalloffOffset = 0.7f * shadow.m_FalloffStart; // pull the offset in a little to hide the shadow darkness discontinuity
float flFalloffDist = shadow.m_MaxDist - flFalloffOffset;
float flOOZFalloffDist = ( flFalloffDist > 0.0f ) ? 1.0f / flFalloffDist : 1.0f;
// for use in the shader
pShadowFalloffParams->x = -flFalloffOffset * flOOZFalloffDist;
pShadowFalloffParams->y = flOOZFalloffDist;
pShadowFalloffParams->z = 1.0f/255.0f * shadow.m_FalloffBias;
}
void CClientShadowMgr::DrawDeferredShadows( const CViewSetup &view, int leafCount, WorldListLeafData_t* pLeafList )
{
VPROF_BUDGET( __FUNCTION__, VPROF_BUDGETGROUP_SHADOW_RENDERING );
if ( !IsGameConsole() )
{
return;
}
// We assume the visible shadow list was updated in ComputeShadowTextures. This is only correct if we're not rendering from multiple view points.
int nNumShadows = s_VisibleShadowList.GetVisibleShadowCount();
if ( nNumShadows == 0 )
{
return;
}
IntersectingShadowInfo_t* pInsideVolume = (IntersectingShadowInfo_t*)stackalloc( nNumShadows * sizeof(IntersectingShadowInfo_t) );
int nNumInsideVolumes = 0;
CMatRenderContextPtr pRenderContext( materials );
PIXEVENT( pRenderContext, "DEFERRED_SHADOWS" );
matrix3x4_t viewMatrix;
pRenderContext->GetMatrix( MATERIAL_VIEW, &viewMatrix );
VMatrix projMatrix;
pRenderContext->GetMatrix( MATERIAL_PROJECTION, &projMatrix );
VMatrix viewProjMatrix;
VMatrix invViewProjMatrix;
MatrixMultiply( projMatrix, VMatrix(viewMatrix), viewProjMatrix );
MatrixInverseGeneral( viewProjMatrix, invViewProjMatrix );
ShaderStencilState_t state;
state.m_bEnable = true;
state.m_nWriteMask = 0xFF;
state.m_nTestMask = 0x1 << 2;
state.m_nReferenceValue = 0x0;
state.m_CompareFunc = SHADER_STENCILFUNC_EQUAL;
state.m_PassOp = SHADER_STENCILOP_KEEP;
state.m_FailOp = SHADER_STENCILOP_KEEP;
state.m_ZFailOp = SHADER_STENCILOP_KEEP;
#if defined( _X360 )
state.m_bHiStencilEnable = true;
state.m_bHiStencilWriteEnable = false;
state.m_HiStencilCompareFunc = SHADER_HI_STENCILFUNC_EQUAL;
state.m_nHiStencilReferenceValue = 0;
#endif
#ifdef _X360
pRenderContext->PushVertexShaderGPRAllocation( 16 );
#endif
if ( r_shadow_deferred_downsample.GetBool() )
{
DownsampleDepthBuffer( pRenderContext, invViewProjMatrix );
int nWidth = m_downSampledNormals->GetActualWidth();
int nHeight = m_downSampledNormals->GetActualHeight();
pRenderContext->PushRenderTargetAndViewport( m_downSampledNormals, 0, 0, nWidth, nHeight );
}
else
{
pRenderContext->SetStencilState( state );
#if defined( _X360 )
pRenderContext->FlushHiStencil();
#endif
}
pRenderContext->MatrixMode( MATERIAL_MODEL );
pRenderContext->PushMatrix();
pRenderContext->LoadIdentity();
IMaterialVar* pTextureVar = m_RenderDeferredShadowMat->FindVar( "$basetexture", NULL, false );
if( pTextureVar )
{
pTextureVar->SetTextureValue( s_ClientShadowMgr.GetShadowTexture( CLIENTSHADOW_INVALID_HANDLE ) );
}
pTextureVar = m_RenderDeferredShadowMat->FindVar( "$depthtexture", NULL, false );
if( pTextureVar )
{
//pTextureVar->SetTextureValue( s_ClientShadowMgr.GetShadowTexture( CLIENTSHADOW_INVALID_HANDLE ) );
pTextureVar->SetTextureValue( GetFullFrameDepthTexture() );
}
IMaterialVar* pZFailVar = m_RenderDeferredShadowMat->FindVar( "$zfailenable", NULL, false );
if( pZFailVar )
{
pZFailVar->SetIntValue( 0 );
}
pRenderContext->Bind( m_RenderDeferredShadowMat );
Vector camPos;
pRenderContext->GetWorldSpaceCameraPosition( &camPos );
IMesh* pMesh = pRenderContext->GetDynamicMesh();
CMeshBuilder meshBuilder;
int nMaxVerts, nMaxIndices;
pRenderContext->GetMaxToRender( pMesh, false, &nMaxVerts, &nMaxIndices );
int nMaxShadowsPerBatch = MIN( nMaxVerts / ( ARRAYSIZE( pShadowBoundVerts ) / 4 ), nMaxIndices / ARRAYSIZE( pShadowBoundIndices ) );
meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, nMaxShadowsPerBatch * ARRAYSIZE( pShadowBoundVerts ) / 4, nMaxShadowsPerBatch * ARRAYSIZE( pShadowBoundIndices ) );
int nNumShadowsBatched = 0;
// Z-Pass shadow volumes
for ( int i = 0; i < nNumShadows; ++i )
{
const VisibleShadowInfo_t& vsi = s_VisibleShadowList.GetVisibleShadow( i );
ClientShadow_t& shadow = m_Shadows[vsi.m_hShadow];
matrix3x4_t objToWorld;
if ( SetupDeferredShadow( shadow, camPos, &objToWorld ) )
{
pInsideVolume[nNumInsideVolumes].h = vsi.m_hShadow;
MatrixCopy( objToWorld, pInsideVolume[nNumInsideVolumes].objToWorld );
nNumInsideVolumes++;
continue;
}
VMatrix projToTextureMatrix;
MatrixMultiply( shadow.m_WorldToTexture, invViewProjMatrix, projToTextureMatrix );
// create extruded bounding geometry
BuildCubeWithDegenerateEdgeQuads( meshBuilder, objToWorld, projToTextureMatrix, shadow );
nNumShadowsBatched++;
if ( nNumShadowsBatched == nMaxShadowsPerBatch )
{
// flush
meshBuilder.End();
pMesh->Draw();
meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, nMaxShadowsPerBatch * ARRAYSIZE( pShadowBoundVerts ) / 4, nMaxShadowsPerBatch * ARRAYSIZE( pShadowBoundIndices ) );
nNumShadowsBatched = 0;
}
}
// render
meshBuilder.End();
if ( nNumShadowsBatched > 0 )
{
pMesh->Draw();
nNumShadowsBatched = 0;
}
else
{
pMesh->MarkAsDrawn();
}
// draw deferred blobby shadow volumes
if ( s_VisibleShadowList.GetVisibleBlobbyShadowCount() > 0 )
{
pRenderContext->Bind( m_RenderDeferredSimpleShadowMat );
pTextureVar = m_RenderDeferredSimpleShadowMat->FindVar( "$depthtexture", NULL, false );
if( pTextureVar )
{
pTextureVar->SetTextureValue( GetFullFrameDepthTexture() );
}
int nNumShadows = s_VisibleShadowList.GetVisibleBlobbyShadowCount();
meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, nMaxShadowsPerBatch * ARRAYSIZE( pShadowBoundVerts ) / 4, nMaxShadowsPerBatch * ARRAYSIZE( pShadowBoundIndices ) );
for ( int i = 0; i < nNumShadows; ++i )
{
const VisibleShadowInfo_t& vsi = s_VisibleShadowList.GetVisibleBlobbyShadow( i );
ClientShadow_t& shadow = m_Shadows[vsi.m_hShadow];
matrix3x4_t objToWorld;
if ( SetupDeferredShadow( shadow, camPos, &objToWorld ) )
{
//DevWarning( "Blobby shadow needs z-fail rendering. Skipped.\n" );
continue;
}
VMatrix projToTextureMatrix;
MatrixMultiply( shadow.m_WorldToTexture, invViewProjMatrix, projToTextureMatrix );
// create extruded bounding geometry
BuildCubeWithDegenerateEdgeQuads( meshBuilder, objToWorld, projToTextureMatrix, shadow );
nNumShadowsBatched++;
if ( nNumShadowsBatched == nMaxShadowsPerBatch )
{
// flush
meshBuilder.End();
pMesh->Draw();
meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, nMaxShadowsPerBatch * ARRAYSIZE( pShadowBoundVerts ) / 4, nMaxShadowsPerBatch * ARRAYSIZE( pShadowBoundIndices ) );
nNumShadowsBatched = 0;
}
}
// render
meshBuilder.End();
if ( nNumShadowsBatched > 0 )
{
pMesh->Draw();
nNumShadowsBatched = 0;
}
else
{
pMesh->MarkAsDrawn();
}
}
// draw zfail volumes
if ( nNumInsideVolumes > 0 )
{
pRenderContext->CullMode( MATERIAL_CULLMODE_CW );
if( pZFailVar )
{
pZFailVar->SetIntValue( 1 );
}
pRenderContext->Bind( m_RenderDeferredShadowMat );
//IMesh* pMesh = pRenderContext->GetDynamicMesh();
meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, nMaxShadowsPerBatch * ARRAYSIZE( pShadowBoundVerts ) / 4, nMaxShadowsPerBatch * ARRAYSIZE( pShadowBoundIndices ) );
for ( int i = 0; i < nNumInsideVolumes; ++i )
{
ClientShadow_t& shadow = m_Shadows[pInsideVolume[i].h];
VMatrix projToTextureMatrix;
MatrixMultiply( shadow.m_WorldToTexture, invViewProjMatrix, projToTextureMatrix );
// create extruded bounding geometry
BuildCubeWithDegenerateEdgeQuads( meshBuilder, pInsideVolume[i].objToWorld, projToTextureMatrix, shadow );
nNumShadowsBatched++;
if ( nNumShadowsBatched == nMaxShadowsPerBatch )
{
// flush
meshBuilder.End();
pMesh->Draw();
//pMesh = pRenderContext->GetDynamicMesh();
meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, nMaxShadowsPerBatch * ARRAYSIZE( pShadowBoundVerts ) / 4, nMaxShadowsPerBatch * ARRAYSIZE( pShadowBoundIndices ) );
nNumShadowsBatched = 0;
}
}
meshBuilder.End();
if ( nNumShadowsBatched > 0 )
{
pMesh->Draw();
nNumShadowsBatched = 0;
}
else
{
pMesh->MarkAsDrawn();
}
}
pRenderContext->PopMatrix();
// reset culling
pRenderContext->CullMode( MATERIAL_CULLMODE_CCW );
if ( r_shadow_deferred_downsample.GetBool() )
{
pRenderContext->CopyRenderTargetToTextureEx( m_downSampledNormals, 0, NULL, NULL );
pRenderContext->PopRenderTargetAndViewport();
pRenderContext->SetStencilState( state );
CompositeDeferredShadows( pRenderContext );
}
ShaderStencilState_t stateDisable;
stateDisable.m_bEnable = false;
#if defined( _X360 )
stateDisable.m_bHiStencilEnable = false;
stateDisable.m_bHiStencilWriteEnable = false;
#endif
pRenderContext->SetStencilState( stateDisable );
#ifdef _X360
pRenderContext->PopVertexShaderGPRAllocation();
#endif
// NOTE: We could use geometry instancing for drawing the extruded bounding boxes
}
void DeferredShadowToggleCallback( IConVar*, const char *, float )
{
if ( !IsGameConsole() )
{
DevMsg( "Deferred shadow rendering only supported on the 360.\n" );
return;
}
s_ClientShadowMgr.UpdateAllShadows();
s_ClientShadowMgr.RemoveAllShadowDecals();
}
void DeferredShadowDownsampleToggleCallback( IConVar *var, const char *pOldValue, float flOldValue )
{
s_ClientShadowMgr.ShutdownDeferredShadows();
s_ClientShadowMgr.InitDeferredShadows();
}
void CClientShadowMgr::UpdateSplitscreenLocalPlayerShadowSkip()
{
// Don't draw the current splitscreen player's own shadow
if ( engine->IsSplitScreenActive() )
{
ASSERT_LOCAL_PLAYER_RESOLVABLE();
C_BasePlayer* pPlayer = C_BasePlayer::GetLocalPlayer();
C_BasePlayer* pFirstPersonEnt = pPlayer;
if ( pPlayer )
{
if ( pPlayer->GetObserverMode() == OBS_MODE_IN_EYE )
{
pFirstPersonEnt = ToBasePlayer( pPlayer->GetObserverTarget() );
}
else if ( pPlayer->GetObserverMode() != OBS_MODE_NONE )
{
pFirstPersonEnt = NULL;
}
}
// pFirstPersonEnt NULL only if the player is spectating, but not first-person-spectating
if ( pFirstPersonEnt )
{
shadowmgr->SkipShadowForEntity( pFirstPersonEnt->entindex() );
}
else
{
shadowmgr->SkipShadowForEntity( INT_MIN ); // pick an entindex that is guaranteed not to be used by anything
}
}
else
{
shadowmgr->SkipShadowForEntity( INT_MIN ); // pick an entindex that is guaranteed not to be used by anything
}
}
//-----------------------------------------------------------------------------
// A material proxy that resets the base texture to use the rendered shadow
//-----------------------------------------------------------------------------
class CShadowProxy : public IMaterialProxy
{
public:
CShadowProxy();
virtual ~CShadowProxy();
virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues );
virtual void OnBind( void *pProxyData );
virtual void Release( void ) { delete this; }
virtual IMaterial *GetMaterial();
private:
IMaterialVar* m_BaseTextureVar;
IMaterialVar* m_MaxFalloffAmountVar;
};
CShadowProxy::CShadowProxy()
: m_BaseTextureVar( NULL ),
m_MaxFalloffAmountVar( NULL )
{
}
CShadowProxy::~CShadowProxy()
{
}
bool CShadowProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues )
{
bool foundVar;
m_BaseTextureVar = pMaterial->FindVar( "$basetexture", &foundVar, false );
if ( !foundVar )
return false;
m_MaxFalloffAmountVar = pMaterial->FindVar( "$maxfalloffamount", &foundVar, false );
return foundVar;
}
void CShadowProxy::OnBind( void *pProxyData )
{
unsigned short clientShadowHandle = ( unsigned short )(intp)pProxyData&0xffff;
ITexture* pTex = s_ClientShadowMgr.GetShadowTexture( clientShadowHandle );
m_BaseTextureVar->SetTextureValue( pTex );
m_MaxFalloffAmountVar->SetFloatValue( MAX_FALLOFF_AMOUNT );
}
IMaterial *CShadowProxy::GetMaterial()
{
return m_BaseTextureVar->GetOwningMaterial();
}
EXPOSE_MATERIAL_PROXY( CShadowProxy, Shadow );
//-----------------------------------------------------------------------------
// A material proxy that resets the base texture to use the rendered shadow
//-----------------------------------------------------------------------------
class CShadowModelProxy : public IMaterialProxy
{
public:
CShadowModelProxy();
virtual ~CShadowModelProxy();
virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues );
virtual void OnBind( void *pProxyData );
virtual void Release( void ) { delete this; }
virtual IMaterial *GetMaterial();
private:
IMaterialVar* m_BaseTextureVar;
IMaterialVar* m_BaseTextureOffsetVar;
IMaterialVar* m_BaseTextureScaleVar;
IMaterialVar* m_BaseTextureMatrixVar;
IMaterialVar* m_FalloffOffsetVar;
IMaterialVar* m_FalloffDistanceVar;
IMaterialVar* m_FalloffAmountVar;
};
CShadowModelProxy::CShadowModelProxy()
{
m_BaseTextureVar = NULL;
m_BaseTextureOffsetVar = NULL;
m_BaseTextureScaleVar = NULL;
m_BaseTextureMatrixVar = NULL;
m_FalloffOffsetVar = NULL;
m_FalloffDistanceVar = NULL;
m_FalloffAmountVar = NULL;
}
CShadowModelProxy::~CShadowModelProxy()
{
}
bool CShadowModelProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues )
{
bool foundVar;
m_BaseTextureVar = pMaterial->FindVar( "$basetexture", &foundVar, false );
if (!foundVar)
return false;
m_BaseTextureOffsetVar = pMaterial->FindVar( "$basetextureoffset", &foundVar, false );
if (!foundVar)
return false;
m_BaseTextureScaleVar = pMaterial->FindVar( "$basetexturescale", &foundVar, false );
if (!foundVar)
return false;
m_BaseTextureMatrixVar = pMaterial->FindVar( "$basetexturetransform", &foundVar, false );
if (!foundVar)
return false;
m_FalloffOffsetVar = pMaterial->FindVar( "$falloffoffset", &foundVar, false );
if (!foundVar)
return false;
m_FalloffDistanceVar = pMaterial->FindVar( "$falloffdistance", &foundVar, false );
if (!foundVar)
return false;
m_FalloffAmountVar = pMaterial->FindVar( "$falloffamount", &foundVar, false );
return foundVar;
}
void CShadowModelProxy::OnBind( void *pProxyData )
{
unsigned short clientShadowHandle = ( unsigned short )((intp)pProxyData&0xffff);
ITexture* pTex = s_ClientShadowMgr.GetShadowTexture( clientShadowHandle );
m_BaseTextureVar->SetTextureValue( pTex );
const ShadowInfo_t& info = s_ClientShadowMgr.GetShadowInfo( clientShadowHandle );
m_BaseTextureMatrixVar->SetMatrixValue( info.m_WorldToShadow );
m_BaseTextureOffsetVar->SetVecValue( info.m_TexOrigin.Base(), 2 );
m_BaseTextureScaleVar->SetVecValue( info.m_TexSize.Base(), 2 );
m_FalloffOffsetVar->SetFloatValue( info.m_FalloffOffset );
m_FalloffDistanceVar->SetFloatValue( info.m_MaxDist );
m_FalloffAmountVar->SetFloatValue( info.m_FalloffAmount );
}
IMaterial *CShadowModelProxy::GetMaterial()
{
return m_BaseTextureVar->GetOwningMaterial();
}
EXPOSE_MATERIAL_PROXY( CShadowModelProxy, ShadowModel );