//========= Copyright 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 //===========================================================================// #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 "toolframework_client.h" #include "bonetoworldarray.h" #include "cmodel.h" #ifdef MAPBASE #include "renderparm.h" #endif #ifdef ASW_PROJECTED_TEXTURES #include "flashlighteffect.h" #endif #ifdef DYNAMIC_RTT_SHADOWS #include "debugoverlay_shared.h" #include "worldlight.h" #endif //#include "filesystem.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" static ConVar r_flashlightdrawfrustum("r_flashlightdrawfrustum", "0"); static ConVar r_flashlightmodels("r_flashlightmodels", "1"); void UpdateShadowRender(); ConVar r_shadowrendertotexture("r_shadowrendertotexture", "0", FCVAR_ARCHIVE, "", (FnChangeCallback_t)UpdateShadowRender); void UpdateShadowRender() { if (cvar->FindVar("oc_global_lightning_enabled")->GetBool()) r_shadowrendertotexture.SetValue(1); } #ifdef ASW_PROJECTED_TEXTURES 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_shadow_shortenfactor("r_shadow_shortenfactor", "2", 0, "Makes shadows cast from local lights shorter"); static ConVar r_shadow_mincastintensity("r_shadow_mincastintensity", "0.3", FCVAR_CHEAT, "Minimum brightness of a light to be classed as shadow casting", true, 0, false, 0); #endif static ConVar r_flashlight_version2("r_flashlight_version2", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY); void UpdateFlashlightdepthtexture(); ConVar r_flashlightdepthtexture("r_flashlightdepthtexture", "1", FCVAR_ARCHIVE, "", (FnChangeCallback_t)UpdateFlashlightdepthtexture); void UpdateFlashlightdepthtexture() { if (cvar->FindVar("oc_global_lightning_enabled")->GetBool()) r_flashlightdepthtexture.SetValue(1); } #if defined( _X360 ) ConVar r_flashlightdepthres("r_flashlightdepthres", "512", FCVAR_ARCHIVE); // BJ over twi tweak - archive #else #ifdef MAPBASE ConVar r_flashlightdepthres("r_flashlightdepthres", "2048", FCVAR_ARCHIVE); // BJ over twi tweak - archive #else ConVar r_flashlightdepthres("r_flashlightdepthres", "1024", FCVAR_ARCHIVE); // BJ over twi tweak - archive #endif #endif #ifdef ASW_PROJECTED_TEXTURES ConVar r_threaded_client_shadow_manager("r_threaded_client_shadow_manager", "1"); #else ConVar r_threaded_client_shadow_manager("r_threaded_client_shadow_manager", "0"); #endif #ifdef _WIN32 #pragma warning( disable: 4701 ) #endif // forward declarations void ToolFramework_RecordMaterialParams(IMaterial *pMaterial); //----------------------------------------------------------------------------- // 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); private: typedef unsigned short FragmentHandle_t; enum { INVALID_FRAGMENT_HANDLE = (FragmentHandle_t)~0, TEXTURE_PAGE_SIZE = 1024, MAX_TEXTURE_POWER = 8, #if !defined( _X360 ) 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(); } #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"); // edram footprint is only 256x256x4 = 256K m_TexturePage.InitRenderTargetSurface(MAX_TEXTURE_SIZE, MAX_TEXTURE_SIZE, IMAGE_FORMAT_ARGB8888, false); // 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 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.... // FIXME: Improve heuristic?!? #if !defined( _X360 ) 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; // 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_ARCHIVE);// FCVAR_CHEAT); // hook into engine's cvars.. //----------------------------------------------------------------------------- // 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 { public: CClientShadowMgr(); virtual char const *Name() { return "CCLientShadowMgr"; } // Inherited from IClientShadowMgr virtual bool Init(); virtual void PostInit() { /*m_bEnableDynamicShadows = oc_enable_global_illumination.GetBool(); UpdateGlobalIllum(); */ } 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 ClientShadowHandle_t CreateShadow(ClientEntityHandle_t entity, int flags); 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); // 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, LeafIndex_t* pLeafList); // Kicks off rendering into shadow depth maps (if any) void ComputeShadowDepthTextures(const CViewSetup &view); // 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 #ifdef DYNAMIC_RTT_SHADOWS void ComputeShadowBBox(IClientRenderable *pRenderable, ClientShadowHandle_t shadowHandle, const Vector &vecAbsCenter, float flRadius, Vector *pAbsMins, Vector *pAbsMaxs); #else void ComputeShadowBBox(IClientRenderable *pRenderable, const Vector &vecAbsCenter, float flRadius, Vector *pAbsMins, Vector *pAbsMaxs); #endif 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); } #ifdef DYNAMIC_RTT_SHADOWS // Toggle shadow casting from world light sources virtual void SetShadowFromWorldLightsEnabled(bool bEnable); void SuppressShadowFromWorldLights(bool bSuppress); bool IsShadowingFromWorldLights() const { return m_bShadowFromWorldLights && !m_bSuppressShadowFromWorldLights; } bool m_bEnableDynamicShadows; #endif 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; #ifdef DYNAMIC_RTT_SHADOWS Vector m_ShadowDir; #endif Vector m_LastOrigin; QAngle m_LastAngles; #ifdef DYNAMIC_RTT_SHADOWS 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 #endif TextureHandle_t m_ShadowTexture; CTextureReference m_ShadowDepthTexture; int m_nRenderFrame; EHANDLE m_hTargetEntity; }; private: // Shadow update functions void UpdateStudioShadow(IClientRenderable *pRenderable, ClientShadowHandle_t handle); void UpdateBrushShadow(IClientRenderable *pRenderable, ClientShadowHandle_t handle); void UpdateShadow(ClientShadowHandle_t handle, bool force); #ifdef DYNAMIC_RTT_SHADOWS // Updates shadow cast direction when shadowing from world lights void UpdateShadowDirectionFromLocalLightSource(ClientShadowHandle_t shadowHandle); #endif // 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); #ifdef ASW_PROJECTED_TEXTURES void BuildOrthoWorldToFlashlightMatrix(VMatrix& matWorldToShadow, const FlashlightState_t &flashlightState); #endif // 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(); // One of these gets called with every shadow that potentially will need to re-render bool DrawRenderToTextureShadow(unsigned short clientShadowHandle, float flArea); void DrawRenderToTextureShadowLOD(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); // 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; #ifdef DYNAMIC_RTT_SHADOWS const Vector &GetShadowDirection(ClientShadowHandle_t shadowHandle) const; #endif // Initialize, shutdown render-to-texture shadows void InitDepthTextureShadows(); void ShutdownDepthTextureShadows(); public: // Initialize, shutdown render-to-texture shadows void InitRenderToTextureShadows(); void ShutdownRenderToTextureShadows(); private: static bool ShadowHandleCompareFunc(const ClientShadowHandle_t& lhs, const ClientShadowHandle_t& rhs) { return lhs < rhs; } ClientShadowHandle_t CreateProjectedTexture(ClientEntityHandle_t entity, int flags); // Lock down the usage of a shadow depth texture...must be unlocked use on subsequent views / frames bool LockShadowDepthTexture(CTextureReference *shadowDepthTexture); void UnlockAllShadowDepthTextures(); // Set and clear flashlight target renderable void SetFlashlightTarget(ClientShadowHandle_t shadowHandle, EHANDLE targetEntity); #ifdef ASW_PROJECTED_TEXTURES // Get current frustum extents void GetFrustumExtents(ClientShadowHandle_t handle, Vector &vecMin, Vector &vecMax); #endif // 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); #ifdef ASW_PROJECTED_TEXTURES // Builds a list of active flashlights int BuildActiveFlashlightList(const CViewSetup &viewSetup, int nMaxFlashlights, ClientShadowHandle_t *pActiveFlashlights); #endif // Sets the view's active flashlight render state void SetViewFlashlightState(int nActiveFlashlightCount, ClientShadowHandle_t* pActiveFlashlights); #ifdef DYNAMIC_RTT_SHADOWS void UpdateDirtyShadow(ClientShadowHandle_t handle); #endif private: Vector m_SimpleShadowDir; color32 m_AmbientLightColor; CMaterialReference m_SimpleShadow; CMaterialReference m_RenderShadow; CMaterialReference m_RenderModelShadow; CTextureReference m_DummyColorTexture; CUtlLinkedList< ClientShadow_t, ClientShadowHandle_t > m_Shadows; CTextureAllocator m_ShadowAllocator; bool m_RenderToTextureActive; bool m_bRenderTargetNeedsClear; bool m_bUpdatingDirtyShadows; bool m_bThreaded; float m_flShadowCastDist; float m_flMinShadowArea; CUtlRBTree< ClientShadowHandle_t, unsigned short > m_DirtyShadows; CUtlVector< ClientShadowHandle_t > m_TransparentShadows; #ifdef ASW_PROJECTED_TEXTURES int m_nPrevFrameCount; #endif // 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) CUtlVector< CTextureReference > m_DepthTextureCache; CUtlVector< bool > m_DepthTextureCacheLocks; int m_nMaxDepthTextureShadows; #ifdef DYNAMIC_RTT_SHADOWS bool m_bShadowFromWorldLights; bool m_bSuppressShadowFromWorldLights; #endif friend class CVisibleShadowList; friend class CVisibleShadowFrustumList; }; //----------------------------------------------------------------------------- // Singleton //----------------------------------------------------------------------------- static CClientShadowMgr s_ClientShadowMgr; IClientShadowMgr* g_pClientShadowMgr = &s_ClientShadowMgr; /*void UpdateGlobalIllum() {*/ /*if (!oc_enable_global_illumination.GetBool()) s_ClientShadowMgr.InitRenderToTextureShadows(); else s_ClientShadowMgr.ShutdownRenderToTextureShadows();*/ /*r_shadows_gamecontrol.SetValue(oc_enable_global_illumination.GetInt() ? 0 : -1); }*/ //----------------------------------------------------------------------------- // 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, LeafIndex_t *pLeafList); int GetVisibleShadowCount() const; const VisibleShadowInfo_t &GetVisibleShadow(int i) const; private: void EnumShadow(unsigned short clientShadowHandle); float ComputeScreenArea(const Vector &vecCenter, float r) const; void PrioritySort(); CUtlVector m_ShadowsInView; CUtlVector m_PriorityIndex; }; //----------------------------------------------------------------------------- // Singleton instances of shadow and shadow frustum lists //----------------------------------------------------------------------------- static CVisibleShadowList s_VisibleShadowList; //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- static CUtlVector s_NPCShadowBoneSetups; static CUtlVector 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]]; } //----------------------------------------------------------------------------- // 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; #ifdef ASW_PROJECTED_TEXTURES // Don't bother with flashlights if ((shadow.m_Flags & SHADOW_FLAGS_FLASHLIGHT) != 0) return; #endif // We don't need to bother with it if it's not render-to-texture if (s_ClientShadowMgr.GetActualShadowCastType(clientShadowHandle) != SHADOWS_RENDER_TO_TEXTURE) return; // Don't bother with it if the shadow is totally transparent const ShadowInfo_t &shadowInfo = shadowmgr->GetInfo(shadow.m_ShadowHandle); if (shadowInfo.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; #ifdef DYNAMIC_RTT_SHADOWS s_ClientShadowMgr.ComputeShadowBBox(pRenderable, shadow.m_ShadowHandle, vecAbsCenter, flRadius, &vecAbsMins, &vecAbsMaxs); #else s_ClientShadowMgr.ComputeShadowBBox(pRenderable, vecAbsCenter, flRadius, &vecAbsMins, &vecAbsMaxs); #endif // 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; int i = m_ShadowsInView.AddToTail(); VisibleShadowInfo_t &info = m_ShadowsInView[i]; info.m_hShadow = clientShadowHandle; m_ShadowsInView[i].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; } //----------------------------------------------------------------------------- // 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, LeafIndex_t *pLeafList) { VPROF_BUDGET("CVisibleShadowList::FindShadows", VPROF_BUDGETGROUP_SHADOW_RENDERING); m_ShadowsInView.RemoveAll(); ClientLeafSystem()->EnumerateShadowsInLeaves(nLeafCount, pLeafList, this); int nCount = m_ShadowsInView.Count(); if (nCount != 0) { // Sort based on screen area/priority PrioritySort(); } return nCount; } //----------------------------------------------------------------------------- // Constructor //----------------------------------------------------------------------------- CClientShadowMgr::CClientShadowMgr() : m_DirtyShadows(0, 0, ShadowHandleCompareFunc), #ifdef ASW_PROJECTED_TEXTURES m_nPrevFrameCount(-1), #endif m_RenderToTextureActive(false), #ifdef DYNAMIC_RTT_SHADOWS m_bShadowFromWorldLights(false), m_bSuppressShadowFromWorldLights(false), #endif m_bDepthTextureActive(false) { m_nDepthTextureResolution = r_flashlightdepthres.GetInt(); m_bThreaded = false; } //----------------------------------------------------------------------------- // 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 = 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 = s_ClientShadowMgr.GetBlobbyCutoffArea(); Msg("Cutoff area %.2f\n", flArea); return; } if (args.ArgC() == 2) { float flArea = atof(args[1]); s_ClientShadowMgr.SetShadowBlobbyCutoffArea(flArea); } } #ifdef DYNAMIC_RTT_SHADOWS 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()); } #endif static void ShadowRestoreFunc(int nChangeFlags) { s_ClientShadowMgr.RestoreRenderState(); } /*ConVar oc_enable_global_illumination("oc_enable_global_illumination", "0", FCVAR_ARCHIVE, ""); bool GetShadowsValue(void) { // Now grab the hl2/cfg/config.cfg and suss out the sv_unlockedchapters cvar to estimate how far they got in HL2 char fullpath[MAX_PATH]; g_pFullFileSystem->RelativePathToFullPath("cfg/config.cfg", "GAME", fullpath, sizeof(fullpath)); Q_FixSlashes(fullpath, '/'); if (filesystem->FileExists(fullpath)) { FileHandle_t fh = filesystem->Open(fullpath, "rb"); if (FILESYSTEM_INVALID_HANDLE != fh) { // read file into memory int size = filesystem->Size(fh); char *configBuffer = new char[size + 1]; filesystem->Read(configBuffer, size, fh); configBuffer[size] = 0; filesystem->Close(fh); // loop through looking for all the cvars to apply const char *search = Q_stristr(configBuffer, "oc_enable_global_illumination"); if (search) { // read over the token search = strtok((char *)search, " \n"); search = strtok(NULL, " \n"); if (search[0] == '\"') ++search; // read the value int iValue = Q_atoi(search); oc_enable_global_illumination.SetValue(iValue); } // free delete[] configBuffer; } } return oc_enable_global_illumination.GetBool(); }*/ //----------------------------------------------------------------------------- // Initialization, shutdown //----------------------------------------------------------------------------- bool CClientShadowMgr::Init() { 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); #ifndef MAPBASE bool bTools = CommandLine()->CheckParm("-tools") != NULL; m_nMaxDepthTextureShadows = bTools ? 4 : 1; // Just one shadow depth texture in games, more in tools #else // 5 lets mappers use up to 4 shadow-casting projected textures, which is better than 3. int iNumShadows = CommandLine()->ParmValue("-numshadowtextures", 5); m_nMaxDepthTextureShadows = iNumShadows; #endif //CommandLine()->AppendParm("-num_edicts", "20480"); //int idx = CommandLine()->FindParm("-num_edicts"); //CommandLine()->SetParm(idx, "20480"); //CommandLine()-> //const char *val = CommandLine()->ParmValue("-num_edicts"); /*KeyValuesAD gameinfo("GameInfo"); gameinfo->LoadFromFile(filesystem, "gameinfo.txt");*/ m_bEnableDynamicShadows = false;// gameinfo->GetBool("oc_enable_dynamic_shadows", false); //r_shadows_gamecontrol.SetValue(cvar->FindVar("oc_global_lightning_enabled")->GetBool()); //m_bEnableDynamicShadows = GetShadowsValue(); bool bLowEnd = (g_pMaterialSystemHardwareConfig->GetDXSupportLevel() < 80); if (!bLowEnd && r_shadowrendertotexture.GetBool() && !m_bEnableDynamicShadows) { InitRenderToTextureShadows(); } // If someone turned shadow depth mapping on but we can't do it, force it off if (r_flashlightdepthtexture.GetBool() && !materials->SupportsShadowDepthTextures()) { r_flashlightdepthtexture.SetValue(0); ShutdownDepthTextureShadows(); } if (!bLowEnd && r_flashlightdepthtexture.GetBool()) { InitDepthTextureShadows(); } materials->AddRestoreFunc(ShadowRestoreFunc); return true; } void CClientShadowMgr::Shutdown() { m_SimpleShadow.Shutdown(); m_Shadows.RemoveAll(); ShutdownRenderToTextureShadows(); ShutdownDepthTextureShadows(); materials->RemoveRestoreFunc(ShadowRestoreFunc); } //----------------------------------------------------------------------------- // Initialize, shutdown depth-texture shadows //----------------------------------------------------------------------------- void CClientShadowMgr::InitDepthTextureShadows() { VPROF_BUDGET("CClientShadowMgr::InitDepthTextureShadows", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING); #if defined(MAPBASE) //&& !defined(ASW_PROJECTED_TEXTURES) // SAUL: set m_nDepthTextureResolution to the depth resolution we want m_nDepthTextureResolution = r_flashlightdepthres.GetInt(); #endif if (!m_bDepthTextureActive) { m_bDepthTextureActive = true; ImageFormat dstFormat = materials->GetShadowDepthTextureFormat(); // Vendor-dependent depth texture format #if !defined( _X360 ) ImageFormat nullFormat = materials->GetNullTextureFormat(); // Vendor-dependent null texture format (takes as little memory as possible) #endif materials->BeginRenderTargetAllocation(); #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(r_flashlightdepthres.GetInt(), r_flashlightdepthres.GetInt(), RT_SIZE_OFFSCREEN, IMAGE_FORMAT_BGR565, MATERIAL_RT_DEPTH_SHARED, false, "_rt_ShadowDummy"); m_DummyColorTexture.InitRenderTargetSurface(r_flashlightdepthres.GetInt(), r_flashlightdepthres.GetInt(), IMAGE_FORMAT_BGR565, true); #else #if defined(MAPBASE) //&& !defined(ASW_PROJECTED_TEXTURES) // SAUL: we want to create a render target of specific size, so use RT_SIZE_NO_CHANGE m_DummyColorTexture.InitRenderTarget(m_nDepthTextureResolution, m_nDepthTextureResolution, RT_SIZE_NO_CHANGE, nullFormat, MATERIAL_RT_DEPTH_NONE, false, "_rt_ShadowDummy"); #else m_DummyColorTexture.InitRenderTarget(r_flashlightdepthres.GetInt(), r_flashlightdepthres.GetInt(), RT_SIZE_OFFSCREEN, nullFormat, MATERIAL_RT_DEPTH_NONE, false, "_rt_ShadowDummy"); #endif #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); #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(m_nDepthTextureResolution, m_nDepthTextureResolution, RT_SIZE_OFFSCREEN, dstFormat, MATERIAL_RT_DEPTH_NONE, false, strRTName); depthTex.InitRenderTargetSurface(1, 1, dstFormat, false); #else #if defined(MAPBASE) //&& !defined(ASW_PROJECTED_TEXTURES) // SAUL: we want to create a *DEPTH TEXTURE* of specific size, so use RT_SIZE_NO_CHANGE and MATERIAL_RT_DEPTH_ONLY // However, MATERIAL_RT_DEPTH_ONLY forces point filtering to be enabled which negatively affect PCF, so the standard MATERIAL_RT_DEPTH_NONE works better. depthTex.InitRenderTarget(m_nDepthTextureResolution, m_nDepthTextureResolution, RT_SIZE_NO_CHANGE, dstFormat, MATERIAL_RT_DEPTH_NONE, false, strRTName); #else depthTex.InitRenderTarget(m_nDepthTextureResolution, m_nDepthTextureResolution, RT_SIZE_OFFSCREEN, dstFormat, MATERIAL_RT_DEPTH_NONE, false, strRTName); #endif #endif #if defined(MAPBASE) //&& !defined(ASW_PROJECTED_TEXTURES) // SAUL: ensure the depth texture size wasn't changed Assert(depthTex->GetActualWidth() == m_nDepthTextureResolution); #endif if (i == 0) { // Shadow may be resized during allocation (due to resolution constraints etc) m_nDepthTextureResolution = depthTex->GetActualWidth(); r_flashlightdepthres.SetValue(m_nDepthTextureResolution); } m_DepthTextureCache.AddToTail(depthTex); m_DepthTextureCacheLocks.AddToTail(bFalse); } /*try { materials->EndRenderTargetAllocation(); } catch (...) { } */ } } void CClientShadowMgr::ShutdownDepthTextureShadows() { if (m_bDepthTextureActive) { // 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_bDepthTextureActive = false; } } //----------------------------------------------------------------------------- // Initialize, shutdown render-to-texture shadows //----------------------------------------------------------------------------- void CClientShadowMgr::InitRenderToTextureShadows() { if (!m_RenderToTextureActive) { m_RenderToTextureActive = true; m_RenderShadow.Init("decals/rendershadow", TEXTURE_GROUP_DECAL); m_RenderModelShadow.Init("decals/rendermodelshadow", TEXTURE_GROUP_DECAL); m_ShadowAllocator.Init(); 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; } } //----------------------------------------------------------------------------- // 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) { m_RenderShadow->ColorModulate(fr, fg, fb); m_RenderModelShadow->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; #ifdef DYNAMIC_RTT_SHADOWS // Default setting for this, can be overridden by shadow control entities #ifdef MAPBASE SetShadowFromWorldLightsEnabled(false); #else SetShadowFromWorldLightsEnabled(true); #endif #endif 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() { // 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) != 0) continue; IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle(shadow.m_Entity); if (!pRenderable) continue; Assert(pRenderable->GetShadowHandle() == i); AddToDirtyShadowList(pRenderable, true); } } //----------------------------------------------------------------------------- // 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; } #ifdef DYNAMIC_RTT_SHADOWS 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 { // Calculate minimum brightness squared float flMinBrightnessSqr = r_shadow_mincastintensity.GetFloat(); flMinBrightnessSqr *= flMinBrightnessSqr; if (g_pWorldLights->GetBrightestLightSource(pRenderable->GetRenderOrigin(), lightPos, lightBrightness) == false || lightBrightness.LengthSqr() < flMinBrightnessSqr) { // 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(); // SAULUNDONE: caused over top sweeping far too often #if 0 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 #endif { 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); } } #endif //----------------------------------------------------------------------------- // 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); } //----------------------------------------------------------------------------- // 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 flags) { // We need to know if it's a brush model for shadows if (!(flags & SHADOW_FLAGS_FLASHLIGHT)) { IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle(entity); 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; shadow.m_ClientLeafShadowHandle = ClientLeafSystem()->AddShadow(h, flags); shadow.m_Flags = flags; shadow.m_nRenderFrame = -1; #ifdef DYNAMIC_RTT_SHADOWS shadow.m_ShadowDir = GetShadowDirection(); 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; #endif shadow.m_LastOrigin.Init(FLT_MAX, FLT_MAX, FLT_MAX); shadow.m_LastAngles.Init(FLT_MAX, FLT_MAX, FLT_MAX); Assert(((shadow.m_Flags & SHADOW_FLAGS_FLASHLIGHT) == 0) != ((shadow.m_Flags & SHADOW_FLAGS_SHADOW) == 0)); // 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; } #ifdef ASW_PROJECTED_TEXTURES if ((flags & SHADOW_FLAGS_USE_DEPTH_TEXTURE) || (flags & (SHADOW_FLAGS_FLASHLIGHT))) { pShadowMaterial = NULL; // these materials aren't used for shadow depth texture shadows. pShadowModelMaterial = NULL; pShadowProxyData = (void*)(uintp)h; } #else if (flags & SHADOW_FLAGS_USE_DEPTH_TEXTURE) { pShadowMaterial = m_RenderShadow; pShadowModelMaterial = m_RenderModelShadow; pShadowProxyData = (void*)(uintp)h; } #endif int createShadowFlags; 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; } shadow.m_ShadowHandle = shadowmgr->CreateShadowEx(pShadowMaterial, pShadowModelMaterial, pShadowProxyData, createShadowFlags); 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, shadowFlags); UpdateFlashlightState(shadowHandle, lightState); UpdateProjectedTexture(shadowHandle, true); return shadowHandle; } ClientShadowHandle_t CClientShadowMgr::CreateShadow(ClientEntityHandle_t entity, int flags) { // 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, flags); IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle(entity); if (pRenderable) { Assert(!pRenderable->IsShadowDirty()); pRenderable->MarkShadowDirty(true); } // 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); #ifdef ASW_PROJECTED_TEXTURES 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); } #else BuildPerspectiveWorldToFlashlightMatrix(m_Shadows[shadowHandle].m_WorldToShadow, flashlightState); #endif shadowmgr->UpdateFlashlightState(m_Shadows[shadowHandle].m_ShadowHandle, flashlightState); } void CClientShadowMgr::DestroyFlashlight(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); } } //----------------------------------------------------------------------------- // Remove a shadow //----------------------------------------------------------------------------- void CClientShadowMgr::DestroyShadow(ClientShadowHandle_t handle) { Assert(m_Shadows.IsValidIndex(handle)); RemoveShadowFromDirtyList(handle); shadowmgr->DestroyShadow(m_Shadows[handle].m_ShadowHandle); ClientLeafSystem()->RemoveShadow(m_Shadows[handle].m_ClientLeafShadowHandle); CleanUpRenderToTextureShadow(handle); m_Shadows.Remove(handle); } //----------------------------------------------------------------------------- // 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); } #ifdef ASW_PROJECTED_TEXTURES 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); } #endif //----------------------------------------------------------------------------- // 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) { shadowmgr->ClearExtraClipPlanes(m_Shadows[h].m_ShadowHandle); } void CClientShadowMgr::AddExtraClipPlane(ClientShadowHandle_t h, const Vector& normal, float dist) { 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, int context) { m_LeafList.AddToTail(leaf); return true; } 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; #ifdef DYNAMIC_RTT_SHADOWS Vector vecShadowDir = GetShadowDirection(handle); #else Vector vecShadowDir = GetShadowDirection(pRenderable); #endif // 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; 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); // 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(); 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 // FIXME!!!!!!!!!!!!!! Removing this for now since it seems to mess up the blobby shadows. #ifdef ASW_PROJECTED_TEXTURES ComputeExtraClipPlanes(pRenderable, handle, vec, mins, maxs, localShadowDir); #else // ComputeExtraClipPlanes( pEnt, handle, vec, mins, maxs, localShadowDir ); #endif // 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; #ifdef DYNAMIC_RTT_SHADOWS Vector vecShadowDir = GetShadowDirection(handle); #else Vector vecShadowDir = GetShadowDirection(pRenderable); #endif // 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); // 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(); 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); // 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 LineDrawHelper(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 + Vector(0.0f, 0.0f, 1.0f), endWorldSpace + Vector(0.0f, 0.0f, 1.0f), r, g, b, false, -1); } static void DebugDrawFrustum(const Vector &vOrigin, const VMatrix &matWorldToFlashlight) { VMatrix flashlightToWorld; MatrixInverseGeneral(matWorldToFlashlight, flashlightToWorld); // Draw boundaries of frustum LineDrawHelper(Vector(0.0f, 0.0f, 0.0f), Vector(0.0f, 0.0f, 1.0f), flashlightToWorld, 255, 255, 255); LineDrawHelper(Vector(0.0f, 0.0f, 1.0f), Vector(0.0f, 1.0f, 1.0f), flashlightToWorld, 255, 255, 255); LineDrawHelper(Vector(0.0f, 1.0f, 1.0f), Vector(0.0f, 1.0f, 0.0f), flashlightToWorld, 255, 255, 255); LineDrawHelper(Vector(0.0f, 1.0f, 0.0f), Vector(0.0f, 0.0f, 0.0f), flashlightToWorld, 255, 255, 255); LineDrawHelper(Vector(1.0f, 0.0f, 0.0f), Vector(1.0f, 0.0f, 1.0f), flashlightToWorld, 255, 255, 255); LineDrawHelper(Vector(1.0f, 0.0f, 1.0f), Vector(1.0f, 1.0f, 1.0f), flashlightToWorld, 255, 255, 255); LineDrawHelper(Vector(1.0f, 1.0f, 1.0f), Vector(1.0f, 1.0f, 0.0f), flashlightToWorld, 255, 255, 255); LineDrawHelper(Vector(1.0f, 1.0f, 0.0f), Vector(1.0f, 0.0f, 0.0f), flashlightToWorld, 255, 255, 255); LineDrawHelper(Vector(0.0f, 0.0f, 0.0f), Vector(1.0f, 0.0f, 0.0f), flashlightToWorld, 255, 255, 255); LineDrawHelper(Vector(0.0f, 0.0f, 1.0f), Vector(1.0f, 0.0f, 1.0f), flashlightToWorld, 255, 255, 255); LineDrawHelper(Vector(0.0f, 1.0f, 1.0f), Vector(1.0f, 1.0f, 1.0f), flashlightToWorld, 255, 255, 255); LineDrawHelper(Vector(0.0f, 1.0f, 0.0f), Vector(1.0f, 1.0f, 0.0f), flashlightToWorld, 255, 255, 255); // Draw RGB triad at front plane LineDrawHelper(Vector(0.5f, 0.5f, 0.0f), Vector(1.0f, 0.5f, 0.0f), flashlightToWorld, 255, 0, 0); LineDrawHelper(Vector(0.5f, 0.5f, 0.0f), Vector(0.5f, 1.0f, 0.0f), flashlightToWorld, 0, 255, 0); LineDrawHelper(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 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); } void CClientShadowMgr::BuildFlashlight(ClientShadowHandle_t handle) { // 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 (IsX360() || r_flashlight_version2.GetInt()) { // 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)) { BuildFlashlightLeafList(&leafList, 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 (!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)) { 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)) { // 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::PreRender() { #ifdef ASW_PROJECTED_TEXTURES // only update shadows once per frame if (gpGlobals->framecount == m_nPrevFrameCount) return; m_nPrevFrameCount = gpGlobals->framecount; #endif VPROF_BUDGET("CClientShadowMgr::PreRender", 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() && !materials->SupportsShadowDepthTextures()) { r_flashlightdepthtexture.SetValue(0); ShutdownDepthTextureShadows(); } bool bDepthTextureActive = r_flashlightdepthtexture.GetBool(); int nDepthTextureResolution = r_flashlightdepthres.GetInt(); // If shadow depth texture size or enable/disable changed, do appropriate deallocation/(re)allocation if ((bDepthTextureActive != m_bDepthTextureActive) || (nDepthTextureResolution != m_nDepthTextureResolution)) { // If shadow depth texturing remains on, but resolution changed, shut down and reinitialize depth textures if ((bDepthTextureActive == true) && (m_bDepthTextureActive == true) && (nDepthTextureResolution != m_nDepthTextureResolution)) { ShutdownDepthTextureShadows(); InitDepthTextureShadows(); } else { if (m_bDepthTextureActive && !bDepthTextureActive) // Turning off shadow depth texturing { ShutdownDepthTextureShadows(); } else if (bDepthTextureActive && !m_bDepthTextureActive) // Turning on shadow depth mapping { InitDepthTextureShadows(); } } } } // // -- Render to Texture Shadows ----------------------- // bool bRenderToTextureActive = r_shadowrendertotexture.GetBool(); if (bRenderToTextureActive != m_RenderToTextureActive) { if (!m_bEnableDynamicShadows) { if (m_RenderToTextureActive) { ShutdownRenderToTextureShadows(); } else { InitRenderToTextureShadows(); } } else ShutdownRenderToTextureShadows(); UpdateAllShadows(); return; } m_bUpdatingDirtyShadows = true; unsigned short i = m_DirtyShadows.FirstInorder(); while (i != m_DirtyShadows.InvalidIndex()) { MDLCACHE_CRITICAL_SECTION(); ClientShadowHandle_t& handle = m_DirtyShadows[i]; #ifdef DYNAMIC_RTT_SHADOWS UpdateDirtyShadow(handle); #else Assert(m_Shadows.IsValidIndex(handle)); UpdateProjectedTextureInternal(handle, false); #endif 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(); m_bUpdatingDirtyShadows = false; } #ifdef DYNAMIC_RTT_SHADOWS //----------------------------------------------------------------------------- // Updates a single dirty shadow //----------------------------------------------------------------------------- void CClientShadowMgr::UpdateDirtyShadow(ClientShadowHandle_t handle) { Assert(m_Shadows.IsValidIndex(handle)); if (IsShadowingFromWorldLights()) { UpdateShadowDirectionFromLocalLightSource(handle); } UpdateProjectedTextureInternal(handle, false); } #endif //----------------------------------------------------------------------------- // 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 //----------------------------------------------------------------------------- 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; 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; // 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; } // Don't bother if there's no model on the renderable if (!pRenderable->GetModel()) { 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 const ShadowInfo_t &shadowInfo = shadowmgr->GetInfo(shadow.m_ShadowHandle); if (shadowInfo.m_FalloffBias == 255) { 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)) { shadowmgr->EnableShadow(shadow.m_ShadowHandle, false); pRenderable->MarkShadowDirty(false); return; } 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(); #ifdef DYNAMIC_RTT_SHADOWS if (force || (origin != shadow.m_LastOrigin) || (angles != shadow.m_LastAngles) || shadow.m_LightPosLerp < 1.0f) #else if (force || (origin != shadow.m_LastOrigin) || (angles != shadow.m_LastAngles)) #endif { // 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) { 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) == 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 //----------------------------------------------------------------------------- #ifdef DYNAMIC_RTT_SHADOWS void CClientShadowMgr::ComputeShadowBBox(IClientRenderable *pRenderable, ClientShadowHandle_t shadowHandle, const Vector &vecAbsCenter, float flRadius, Vector *pAbsMins, Vector *pAbsMaxs) #else void CClientShadowMgr::ComputeShadowBBox(IClientRenderable *pRenderable, const Vector &vecAbsCenter, float flRadius, Vector *pAbsMins, Vector *pAbsMaxs) #endif { // This is *really* rough. Basically we simply determine the // maximum shadow casting length and extrude the box by that distance #ifdef DYNAMIC_RTT_SHADOWS Vector vecShadowDir = GetShadowDirection(shadowHandle); #else Vector vecShadowDir = GetShadowDirection(pRenderable); #endif 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) { 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 R_CullBox(mins, maxs, frustum); } 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]; const ShadowInfo_t& info = shadowmgr->GetInfo(shadow.m_ShadowHandle); 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, info.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.. #ifdef DYNAMIC_RTT_SHADOWS Vector vecShadowDir = GetShadowDirection(handle); #else Vector vecShadowDir = GetShadowDirection(pSourceRenderable); #endif 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) { VPROF_BUDGET("CClientShadowMgr::AddShadowToReceiver", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING); if ((!shadow.m_hTargetEntity) || IsFlashlightTarget(handle, pRenderable)) { shadowmgr->AddShadowToBrushModel(shadow.m_ShadowHandle, const_cast(pRenderable->GetModel()), pRenderable->GetRenderOrigin(), pRenderable->GetRenderAngles()); shadowmgr->AddFlashlightRenderable(shadow.m_ShadowHandle, pRenderable); } } else { shadowmgr->AddShadowToBrushModel(shadow.m_ShadowHandle, const_cast(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) { // 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) { VPROF_BUDGET("CClientShadowMgr::AddShadowToReceiver", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING); if ((!shadow.m_hTargetEntity) || IsFlashlightTarget(handle, pRenderable)) { staticpropmgr->AddShadowToStaticProp(shadow.m_ShadowHandle, pRenderable); shadowmgr->AddFlashlightRenderable(shadow.m_ShadowHandle, pRenderable); } } break; case SHADOW_RECEIVER_STUDIO_MODEL: if (shadow.m_Flags & SHADOW_FLAGS_FLASHLIGHT) { 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()); shadowmgr->AddFlashlightRenderable(shadow.m_ShadowHandle, pRenderable); } } 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(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) { shadowmgr->RemoveAllShadowsFromModel(pRenderable->GetModelInstance()); } break; // default: // // FIXME: How do deal with this stuff? Add a method to IClientRenderable? // C_BaseEntity* pEnt = static_cast(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); } //----------------------------------------------------------------------------- // 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(pEntity)); } else if (pEntity->GetBaseAnimating()) { s_NonNPCShadowBoneSetups.AddToTail(assert_cast(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 (bDrawModelShadow) { DrawModelInfo_t info; matrix3x4_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(unsigned short clientShadowHandle, float flArea) { ClientShadow_t& shadow = m_Shadows[clientShadowHandle]; // 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_bThreaded && m_ShadowAllocator.UseTexture(shadow.m_ShadowTexture, bDirtyTexture, flArea)); if (!m_ShadowAllocator.HasValidTexture(shadow.m_ShadowTexture)) { DrawRenderToTextureShadowLOD(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(IsX360() ? 0 : x, IsX360() ? 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(shadowmgr->GetInfo(shadow.m_ShadowHandle).m_WorldToShadow); if (DrawShadowHierarchy(pRenderable, shadow)) { bDrewTexture = true; if (IsX360()) { // 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); } 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); } return bDrewTexture; } //----------------------------------------------------------------------------- // "Draws" the shadow LOD, which really means just set up the blobby shadow //----------------------------------------------------------------------------- void CClientShadowMgr::DrawRenderToTextureShadowLOD(unsigned short clientShadowHandle) { ClientShadow_t &shadow = m_Shadows[clientShadowHandle]; 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(clientShadowHandle); // this was ClearExtraClipPlanes( shadow.m_ShadowHandle ), fix is from Joe Demers 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) { #ifdef ASW_PROJECTED_TEXTURES Frustum_t viewFrustum; GeneratePerspectiveFrustum(viewSetup.origin, viewSetup.angles, viewSetup.zNear, viewSetup.zFar, viewSetup.fov, viewSetup.m_flAspectRatio, viewFrustum); #endif int nActiveDepthShadowCount = 0; for (ClientShadowHandle_t i = m_Shadows.Head(); i != m_Shadows.InvalidIndex(); i = m_Shadows.Next(i)) { 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; const FlashlightState_t& flashlightState = shadowmgr->GetFlashlightState(shadow.m_ShadowHandle); // Bail if this flashlight doesn't want shadows if (!flashlightState.m_bEnableShadows) 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 //#ifdef MAPBASE if (!flashlightState.m_bAlwaysDraw && !flashlightState.m_bOrtho && R_CullBox(vecAbsMins, vecAbsMaxs, viewFrustum)) /*#elif*/ #ifdef ASW_PROJECTED_TEXTURES if (!flashlightState.m_bOrtho && R_CullBox(vecAbsMins, vecAbsMaxs, viewFrustum)) #else if (R_CullBox(vecAbsMins, vecAbsMaxs, viewFrustum)) #endif { shadowmgr->SetFlashlightDepthTexture(shadow.m_ShadowHandle, NULL, 0); continue; } if (nActiveDepthShadowCount >= nMaxDepthShadows) { 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; } pActiveDepthShadows[nActiveDepthShadowCount++] = i; } return nActiveDepthShadowCount; } #ifdef ASW_PROJECTED_TEXTURES //----------------------------------------------------------------------------- // 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) == 0) 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 (R_CullBox(vecAbsMins, vecAbsMaxs, viewFrustum)) { 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; } #endif //----------------------------------------------------------------------------- // 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 if (!IsX360() && !r_flashlight_version2.GetInt()) return; Assert(nActiveFlashlightCount <= 1); if (nActiveFlashlightCount > 0) { Assert((m_Shadows[pActiveFlashlights[0]].m_Flags & SHADOW_FLAGS_FLASHLIGHT) != 0); shadowmgr->SetFlashlightRenderState(pActiveFlashlights[0]); } else { shadowmgr->SetFlashlightRenderState(SHADOW_HANDLE_INVALID); } } #ifdef ASW_PROJECTED_TEXTURES 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); } #endif //----------------------------------------------------------------------------- // Re-render shadow depth textures that lie in the leaf list //----------------------------------------------------------------------------- void CClientShadowMgr::ComputeShadowDepthTextures(const CViewSetup &viewSetup) { #ifdef ASW_PROJECTED_TEXTURES if (!r_flashlightdepthtexture.GetBool()) { // Build list of active flashlights ClientShadowHandle_t pActiveFlashlights[16]; int nActiveFlashlights = BuildActiveFlashlightList(viewSetup, ARRAYSIZE(pActiveFlashlights), pActiveFlashlights); SetViewFlashlightState(nActiveFlashlights, pActiveFlashlights); return; } #endif VPROF_BUDGET("CClientShadowMgr::ComputeShadowDepthTextures", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING); CMatRenderContextPtr pRenderContext(materials); PIXEVENT(pRenderContext, "Shadow Depth Textures"); // Build list of active render-to-texture shadows ClientShadowHandle_t pActiveDepthShadows[1024]; int nActiveDepthShadowCount = BuildActiveShadowDepthList(viewSetup, ARRAYSIZE(pActiveDepthShadows), pActiveDepthShadows); // Iterate over all existing textures and allocate shadow textures bool bDebugFrustum = r_flashlightdrawfrustum.GetBool(); for (int j = 0; j < nActiveDepthShadowCount; ++j) { ClientShadow_t& shadow = m_Shadows[pActiveDepthShadows[j]]; #ifdef ASW_PROJECTED_TEXTURES FlashlightState_t& flashlightState = const_cast(shadowmgr->GetFlashlightState(shadow.m_ShadowHandle)); #endif CTextureReference shadowDepthTexture; bool bGotShadowDepthTexture = LockShadowDepthTexture(&shadowDepthTexture); 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++; } Assert(0); shadowmgr->SetFlashlightDepthTexture(shadow.m_ShadowHandle, NULL, 0); #ifdef MAPBASE if (j <= (INT_FLASHLIGHT_DEPTHTEXTURE_FALLBACK_LAST - INT_FLASHLIGHT_DEPTHTEXTURE_FALLBACK_FIRST)) { pRenderContext->SetIntRenderingParameter(INT_FLASHLIGHT_DEPTHTEXTURE_FALLBACK_FIRST + j, 0); } #endif continue; } CViewSetup shadowView; shadowView.m_flAspectRatio = 1.0f; shadowView.x = shadowView.y = 0; shadowView.width = shadowDepthTexture->GetActualWidth(); shadowView.height = shadowDepthTexture->GetActualHeight(); #ifndef ASW_PROJECTED_TEXTURES shadowView.m_bOrtho = false; shadowView.m_bDoBloomAndToneMapping = false; #endif // Copy flashlight parameters #ifdef ASW_PROJECTED_TEXTURES 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; #else const FlashlightState_t& flashlightState = shadowmgr->GetFlashlightState(shadow.m_ShadowHandle); #endif 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) { DebugDrawFrustum(shadowView.origin, shadow.m_WorldToShadow); } // Set depth bias factors specific to this flashlight 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); #ifdef MAPBASE if (j <= (INT_FLASHLIGHT_DEPTHTEXTURE_FALLBACK_LAST - INT_FLASHLIGHT_DEPTHTEXTURE_FALLBACK_FIRST)) { pRenderContext->SetIntRenderingParameter(INT_FLASHLIGHT_DEPTHTEXTURE_FALLBACK_FIRST + j, int((ITexture*)shadowDepthTexture)); FlashlightState_t state = shadowmgr->GetFlashlightState(shadow.m_ShadowHandle); state.m_nShadowQuality = state.m_nShadowQuality | ((j + 1) << 16); shadowmgr->UpdateFlashlightState(shadow.m_ShadowHandle, state); } #endif // Associate the shadow depth texture and stencil bit with the flashlight for use during scene rendering 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, LeafIndex_t* pLeafList) { VPROF_BUDGET("CClientShadowMgr::ComputeShadowTextures", VPROF_BUDGETGROUP_SHADOW_RENDERING); if (!m_RenderToTextureActive || (r_shadows.GetInt() == 0) || r_shadows_gamecontrol.GetInt() == 0) return; #ifdef ASW_PROJECTED_TEXTURES m_bThreaded = (r_threaded_client_shadow_manager.GetBool() && g_pThreadPool->NumIdleThreads()); #else m_bThreaded = false;//( r_threaded_client_shadow_manager.GetBool() && g_pThreadPool->NumIdleThreads() ); #endif 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 (!IsX360() && 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; if (m_bThreaded && g_pThreadPool->NumIdleThreads()) { s_NPCShadowBoneSetups.RemoveAll(); s_NonNPCShadowBoneSetups.RemoveAll(); for (i = 0; i < nCount; ++i) { const VisibleShadowInfo_t &info = s_VisibleShadowList.GetVisibleShadow(i); if (nModelsRendered < nMaxShadows) { if (BuildSetupListForRenderToTextureShadow(info.m_hShadow, info.m_flArea)) { ++nModelsRendered; } } } ParallelProcess("NPCShadowBoneSetups", s_NPCShadowBoneSetups.Base(), s_NPCShadowBoneSetups.Count(), &SetupBonesOnBaseAnimating); ParallelProcess("NonNPCShadowBoneSetups", s_NonNPCShadowBoneSetups.Base(), s_NonNPCShadowBoneSetups.Count(), &SetupBonesOnBaseAnimating); nModelsRendered = 0; } for (i = 0; i < nCount; ++i) { const VisibleShadowInfo_t &info = s_VisibleShadowList.GetVisibleShadow(i); if (nModelsRendered < nMaxShadows) { if (DrawRenderToTextureShadow(info.m_hShadow, info.m_flArea)) { ++nModelsRendered; } } else { DrawRenderToTextureShadowLOD(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) { for (int i = 0; i < m_DepthTextureCache.Count(); i++) // Search for cached shadow depth texture { if (m_DepthTextureCacheLocks[i] == false) // If a free one is found { *shadowDepthTexture = m_DepthTextureCache[i]; m_DepthTextureCacheLocks[i] = true; return true; } } return false; // Didn't find it... } //------------------------------------------------------------------ // 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; } SetViewFlashlightState(0, NULL); } 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) == 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) == 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; } #ifdef DYNAMIC_RTT_SHADOWS 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(); } } #endif //----------------------------------------------------------------------------- // 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; }; CShadowProxy::CShadowProxy() { m_BaseTextureVar = NULL; } CShadowProxy::~CShadowProxy() { } bool CShadowProxy::Init(IMaterial *pMaterial, KeyValues *pKeyValues) { bool foundVar; m_BaseTextureVar = pMaterial->FindVar("$basetexture", &foundVar, false); return foundVar; } void CShadowProxy::OnBind(void *pProxyData) { unsigned short clientShadowHandle = (unsigned short)(int)pProxyData & 0xffff; ITexture* pTex = s_ClientShadowMgr.GetShadowTexture(clientShadowHandle); m_BaseTextureVar->SetTextureValue(pTex); if (ToolsEnabled()) { ToolFramework_RecordMaterialParams(GetMaterial()); } } IMaterial *CShadowProxy::GetMaterial() { return m_BaseTextureVar->GetOwningMaterial(); } EXPOSE_INTERFACE(CShadowProxy, IMaterialProxy, "Shadow" IMATERIAL_PROXY_INTERFACE_VERSION); //----------------------------------------------------------------------------- // 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)((int)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); if (ToolsEnabled()) { ToolFramework_RecordMaterialParams(GetMaterial()); } } IMaterial *CShadowModelProxy::GetMaterial() { return m_BaseTextureVar->GetOwningMaterial(); } EXPOSE_INTERFACE(CShadowModelProxy, IMaterialProxy, "ShadowModel" IMATERIAL_PROXY_INTERFACE_VERSION);