|
|
(28 intermediate revisions by 13 users not shown) |
Line 1: |
Line 1: |
| | {{LanguageBar|title=Dynamic RTT shadow angles in Source 2007 and Source 2013}} |
| [[File:Dynamic_rtt.jpg|thumb|The difference between a mod with this code(shadows facing away from light) and a mod/game without it(shadows directly under entities).]] | | [[File:Dynamic_rtt.jpg|thumb|The difference between a mod with this code(shadows facing away from light) and a mod/game without it(shadows directly under entities).]] |
| | ==Introduction== |
| | This article explains how to implement dynamic [[Dynamic Shadows|render-to-texture]] shadow angles in both {{src07|1}}, {{src13|1}} and {{tf2branch|1}} mods. Render-to-texture shadows are cast by most non-static [[Entity|renderables]] (dynamic props, players, NPCs, etc.) in the Source Engine. The direction of the shadow they cast is generally given by the {{ent|shadow_control}} entity, but it affects all entities in a level. |
|
| |
|
| ==Introduction==
| | In {{l4d|4}}, {{portal2|4}}, {{as|4}}, {{dmmm|4}} and {{mapbase|4}}, the direction of a shadow is calculated on a per-entity basis, and dictated by the closest light to the entity. By using parts of the [[Alien Swarm]] codebase in your mod, along with some custom files (see below), it is possible to implement this feature in {{src07|1}}, {{src13|1}} as well as {{tf2branch|1}}. |
| This article explains how to implement dynamic [[Dynamic Shadows|render-to-texture]] shadow angles in [[Source 2007]] mods. Render-to-texture shadows are cast by most non-static [[Entity|renderables]] (dynamic props, players, NPCs, etc.) in the Source Engine. The direction of the shadow they cast is generally given by the [[shadow_control]] entity, but it affects all entities in a level.
| |
|
| |
|
| In [[Left 4 Dead]], [[Portal 2]] and [[Alien Swarm]], the direction of a shadow is calculated on a per-entity basis, and dictated by the closest light to the entity. By using parts of the [[Alien Swarm]] codebase in your mod, along with some custom files (see below), it is possible to implement this feature in [[Source 2007]].
| | {{bug|hidetested=1|This dynamic shadow angle implementation doesn't work correctly, the shadows have lerping issues when standing in between 2 lights.}} |
|
| |
|
| ===game/client/worldlight.cpp=== | | ===game/client/worldlight.cpp=== |
| Create a new file in your '''/game/client''' folder called '''worldlight.cpp''', and paste the following code into it. | | Create a new file in your '''/game/client''' folder called '''worldlight.cpp''', and paste the following code into it. |
|
| |
|
| {{Warning|by using the '''worldlight.cpp''' and or '''worldlight.h''' files in your mod, then I (Saul Rennison) must be attributed as a contributor within your mod credits.}} | | {{Warning|By using the '''worldlight.cpp''' and or '''worldlight.h''' files in your mod, then I (Saul Rennison) must be attributed as a contributor within your mod credits.}} |
|
| |
|
| <source lang="cpp"> | | <source lang="cpp"> |
| //========= Copyright (C) 2011, CSProMod Team, All rights reserved. =========// | | //========= Copyright (C) 2021, CSProMod Team, All rights reserved. =========// |
| // | | // |
| // Purpose: provide world light related functions to the client | | // Purpose: Provide world light-related functions to the client |
| // | | // |
| // As the engine provides no access to brush/model data (brushdata_t, model_t), | | // As the engine provides no access to brush/model data (brushdata_t, model_t), |
| // we hence have no access to dworldlight_t. Therefore, we manually extract the | | // we hence have no access to dworldlight_t. Therefore, we manually extract the |
| // world light data from the BSP itself, before entities are initialised on map | | // world light data from the BSP itself, before entities are initialized on map |
| // load. | | // load. |
| // | | // |
| // To find the brightest light at a point, all world lights are iterated. | | // To find the brightest light at a point, all world lights are iterated. |
| // Lights whose radii do not encompass our sample point are quickly rejected, | | // Lights whose radii do not encompass our sample point are quickly rejected, |
| // as are lights which are not in our PVS, or visible from the sample point. | | // as are lights that are not in our PVS, or visible from the sample point. |
| // If the sky light is visible from the sample point, then it shall supersede | | // If the sky light is visible from the sample point, then it shall supersede |
| // all other world lights. | | // all other world lights. |
Line 33: |
Line 35: |
|
| |
|
| #include "cbase.h" | | #include "cbase.h" |
| #include "worldlight.h"
| |
| #include "bspfile.h" | | #include "bspfile.h" |
| #include "filesystem.h"
| |
| #include "client_factorylist.h" // FactoryList_Retrieve | | #include "client_factorylist.h" // FactoryList_Retrieve |
| #include "eiface.h" // IVEngineServer | | #include "eiface.h" // IVEngineServer |
| | #include "filesystem.h" |
| | #include "worldlight.h" |
|
| |
|
| static IVEngineServer *g_pEngineServer = NULL;
| | // memdbgon must be the last include file in a .cpp file!!! |
| | #include "tier0/memdbgon.h" |
|
| |
|
| //-----------------------------------------------------------------------------
| | static IVEngineServer *g_pEngineServer = nullptr; |
| // Singleton exposure
| |
| //-----------------------------------------------------------------------------
| |
| static CWorldLights s_WorldLights; | |
| CWorldLights *g_pWorldLights = &s_WorldLights;
| |
|
| |
|
| //----------------------------------------------------------------------------- | | //----------------------------------------------------------------------------- |
| // Purpose: calculate intensity ratio for a worldlight by distance | | // Purpose: Calculate intensity ratio for a worldlight by distance |
| // Author: Valve Software
| |
| //----------------------------------------------------------------------------- | | //----------------------------------------------------------------------------- |
| static float Engine_WorldLightDistanceFalloff( const dworldlight_t *wl, const Vector& delta ) | | static float Engine_WorldLightDistanceFalloff( const dworldlight_t *wl, const Vector &delta ) |
| { | | { |
| float falloff; | | float falloff; |
|
| |
|
| switch (wl->type) | | switch ( wl->type ) |
| { | | { |
| case emit_surface: | | case emit_surface: |
| // Cull out stuff that's too far | | // Cull out stuff that's too far |
| if(wl->radius != 0) | | if ( wl->radius != 0 ) |
| { | | { |
| if(DotProduct( delta, delta ) > (wl->radius * wl->radius)) | | if ( DotProduct( delta, delta ) > ( wl->radius * wl->radius ) ) |
| return 0.0f; | | return 0.0f; |
| } | | } |
|
| |
|
| return InvRSquared(delta); | | return InvRSquared( delta ); |
| break;
| |
|
| |
|
| case emit_skylight: | | case emit_skylight: |
| return 1.f; | | return 1.0f; |
| break;
| |
|
| |
|
| case emit_quakelight: | | case emit_quakelight: |
| // X - r; | | // X - r; |
| falloff = wl->linear_attn - FastSqrt( DotProduct( delta, delta ) ); | | falloff = wl->linear_attn - FastSqrt( DotProduct( delta, delta ) ); |
| if(falloff < 0) | | if ( falloff < 0 ) |
| return 0.f; | | return 0.0f; |
|
| |
|
| return falloff; | | return falloff; |
| break;
| |
|
| |
|
| case emit_skyambient: | | case emit_skyambient: |
| return 1.f; | | return 1.0f; |
| break;
| |
|
| |
|
| case emit_point: | | case emit_point: |
| case emit_spotlight: // directional & positional | | case emit_spotlight: // Directional & positional |
| { | | { |
| float dist2, dist; | | float dist2, dist; |
|
| |
|
| dist2 = DotProduct(delta, delta); | | dist2 = DotProduct( delta, delta ); |
| dist = FastSqrt(dist2); | | dist = FastSqrt( dist2 ); |
|
| |
|
| // Cull out stuff that's too far | | // Cull out stuff that's too far |
| if(wl->radius != 0 && dist > wl->radius) | | if ( wl->radius != 0 && dist > wl->radius ) |
| return 0.f; | | return 0.0f; |
|
| |
|
| return 1.f / (wl->constant_attn + wl->linear_attn * dist + wl->quadratic_attn * dist2); | | return 1.0f / ( wl->constant_attn + wl->linear_attn * dist + wl->quadratic_attn * dist2 ); |
| } | | } |
|
| |
| break;
| |
| } | | } |
|
| |
|
| return 1.f; | | return 1.0f; |
| } | | } |
|
| |
|
| //----------------------------------------------------------------------------- | | //----------------------------------------------------------------------------- |
| // Purpose: initialise game system and members | | // Purpose: Initialize game system and members |
| //----------------------------------------------------------------------------- | | //----------------------------------------------------------------------------- |
| CWorldLights::CWorldLights() : CAutoGameSystem("World lights") | | CWorldLights::CWorldLights() : CAutoGameSystem( "World lights" ) |
| { | | { |
| m_nWorldLights = 0; | | m_nWorldLights = 0; |
| m_pWorldLights = NULL; | | m_pWorldLights = nullptr; |
| } | | } |
|
| |
|
| //----------------------------------------------------------------------------- | | //----------------------------------------------------------------------------- |
| // Purpose: clear worldlights, free memory | | // Purpose: Clear worldlights, free memory |
| //----------------------------------------------------------------------------- | | //----------------------------------------------------------------------------- |
| void CWorldLights::Clear() | | void CWorldLights::Clear() |
Line 122: |
Line 114: |
| m_nWorldLights = 0; | | m_nWorldLights = 0; |
|
| |
|
| if(m_pWorldLights) | | if ( m_pWorldLights ) |
| { | | { |
| delete [] m_pWorldLights; | | delete[] m_pWorldLights; |
| m_pWorldLights = NULL; | | m_pWorldLights = nullptr; |
| } | | } |
| } | | } |
|
| |
|
| //----------------------------------------------------------------------------- | | //----------------------------------------------------------------------------- |
| // Purpose: get the IVEngineServer, we need this for the PVS functions | | // Purpose: Get the IVEngineServer, we need this for the PVS functions |
| //----------------------------------------------------------------------------- | | //----------------------------------------------------------------------------- |
| bool CWorldLights::Init() | | bool CWorldLights::Init() |
| { | | { |
| factorylist_t factories; | | factorylist_t factories; |
| FactoryList_Retrieve(factories); | | FactoryList_Retrieve( factories ); |
|
| |
|
| if((g_pEngineServer = (IVEngineServer*)factories.appSystemFactory(INTERFACEVERSION_VENGINESERVER, NULL)) == NULL) | | if ( ( g_pEngineServer = static_cast<IVEngineServer *>( factories.appSystemFactory( INTERFACEVERSION_VENGINESERVER, nullptr ) ) ) == nullptr ) |
| return false; | | return false; |
|
| |
|
Line 144: |
Line 136: |
|
| |
|
| //----------------------------------------------------------------------------- | | //----------------------------------------------------------------------------- |
| // Purpose: get all world lights from the BSP | | // Purpose: Get all world lights from the BSP |
| //----------------------------------------------------------------------------- | | //----------------------------------------------------------------------------- |
| void CWorldLights::LevelInitPreEntity() | | void CWorldLights::LevelInitPreEntity() |
| { | | { |
| // Get the map path | | // Get the map path |
| const char *pszMapName = modelinfo->GetModelName(modelinfo->GetModel(1)); | | const char *pszMapName = modelinfo->GetModelName( modelinfo->GetModel( 1 ) ); |
|
| |
|
| // Open map | | // Open map |
| FileHandle_t hFile = g_pFullFileSystem->Open(pszMapName, "rb"); | | FileHandle_t hFile = g_pFullFileSystem->Open( pszMapName, "rb" ); |
| if(!hFile) | | if ( !hFile ) |
| { | | { |
| Warning("CWorldLights: unable to open map\n"); | | Warning( "CWorldLights: Unable to open map\n" ); |
| return; | | return; |
| } | | } |
Line 162: |
Line 154: |
| // can safely assume that the engine did this for us | | // can safely assume that the engine did this for us |
| dheader_t hdr; | | dheader_t hdr; |
| g_pFullFileSystem->Read(&hdr, sizeof(hdr), hFile); | | g_pFullFileSystem->Read( &hdr, sizeof( hdr ), hFile ); |
|
| |
|
| // Grab the light lump and seek to it | | // Grab the light lump and seek to it |
| lump_t &lightLump = hdr.lumps[LUMP_WORLDLIGHTS]; | | lump_t &lightLump = hdr.lumps[LUMP_WORLDLIGHTS]; |
| | |
| | // If the worldlights lump is empty, that means theres no normal, LDR lights to extract |
| | // This can happen when, for example, the map is compiled in HDR mode only |
| | // So move on to the HDR worldlights lump |
| | if ( lightLump.filelen == 0 ) |
| | { |
| | lightLump = hdr.lumps[LUMP_WORLDLIGHTS_HDR]; |
| | } |
|
| |
|
| // If we can't divide the lump data into a whole number of worldlights, | | // If we can't divide the lump data into a whole number of worldlights, |
| // then the BSP format changed and we're unaware | | // then the BSP format changed and we're unaware |
| if(lightLump.filelen % sizeof(dworldlight_t)) | | if ( lightLump.filelen % sizeof( dworldlight_t ) ) |
| { | | { |
| Warning("CWorldLights: unknown world light lump\n"); | | Warning( "CWorldLights: Unknown world light lump\n" ); |
|
| |
|
| // Close file | | // Close file |
| g_pFullFileSystem->Close(hFile); | | g_pFullFileSystem->Close( hFile ); |
| return; | | return; |
| } | | } |
|
| |
|
| g_pFullFileSystem->Seek(hFile, lightLump.fileofs, FILESYSTEM_SEEK_HEAD); | | g_pFullFileSystem->Seek( hFile, lightLump.fileofs, FILESYSTEM_SEEK_HEAD ); |
|
| |
|
| // Allocate memory for the worldlights | | // Allocate memory for the worldlights |
| m_nWorldLights = lightLump.filelen / sizeof(dworldlight_t); | | m_nWorldLights = lightLump.filelen / sizeof( dworldlight_t ); |
| m_pWorldLights = new dworldlight_t[m_nWorldLights]; | | m_pWorldLights = new dworldlight_t[m_nWorldLights]; |
|
| |
|
| // Read worldlights then close | | // Read worldlights then close |
| g_pFullFileSystem->Read(m_pWorldLights, lightLump.filelen, hFile); | | g_pFullFileSystem->Read( m_pWorldLights, lightLump.filelen, hFile ); |
| g_pFullFileSystem->Close(hFile); | | g_pFullFileSystem->Close( hFile ); |
|
| |
|
| DevMsg("CWorldLights: load successful (%d lights at 0x%p)\n", m_nWorldLights, m_pWorldLights); | | DevMsg( "CWorldLights: Load successful (%d lights at 0x%p)\n", m_nWorldLights, m_pWorldLights ); |
| } | | } |
|
| |
|
| //----------------------------------------------------------------------------- | | //----------------------------------------------------------------------------- |
| // Purpose: find the brightest light source at a point | | // Purpose: Find the brightest light source at a point |
| //----------------------------------------------------------------------------- | | //----------------------------------------------------------------------------- |
| bool CWorldLights::GetBrightestLightSource(const Vector &vecPosition, Vector &vecLightPos, Vector &vecLightBrightness) | | bool CWorldLights::GetBrightestLightSource( const Vector &vecPosition, Vector &vecLightPos, Vector &vecLightBrightness ) |
| { | | { |
| if(!m_nWorldLights || !m_pWorldLights) | | if ( !m_nWorldLights || !m_pWorldLights ) |
| return false; | | return false; |
|
| |
|
Line 204: |
Line 204: |
|
| |
|
| // Find the size of the PVS for our current position | | // Find the size of the PVS for our current position |
| int nCluster = g_pEngineServer->GetClusterForOrigin(vecPosition); | | int nCluster = g_pEngineServer->GetClusterForOrigin( vecPosition ); |
| int nPVSSize = g_pEngineServer->GetPVSForCluster(nCluster, 0, NULL); | | int nPVSSize = g_pEngineServer->GetPVSForCluster( nCluster, 0, nullptr ); |
|
| |
|
| // Get the PVS at our position | | // Get the PVS at our position |
| byte *pvs = new byte[nPVSSize]; | | byte *pvs = new byte[nPVSSize]; |
| g_pEngineServer->GetPVSForCluster(nCluster, nPVSSize, pvs); | | g_pEngineServer->GetPVSForCluster( nCluster, nPVSSize, pvs ); |
|
| |
|
| // Iterate through all the worldlights | | // Iterate through all the worldlights |
| for(int i = 0; i < m_nWorldLights; ++i) | | for ( int i = 0; i < m_nWorldLights; ++i ) |
| { | | { |
| dworldlight_t *light = &m_pWorldLights[i]; | | dworldlight_t *light = &m_pWorldLights[i]; |
|
| |
|
| // Skip skyambient | | // Skip skyambient |
| if(light->type == emit_skyambient) | | if ( light->type == emit_skyambient ) |
| { | | { |
| //engine->Con_NPrintf(i, "%d: skyambient", i); | | //engine->Con_NPrintf( i, "%d: skyambient", i ); |
| continue; | | continue; |
| } | | } |
|
| |
|
| // Handle sun | | // Handle sun |
| if(light->type == emit_skylight) | | if ( light->type == emit_skylight ) |
| { | | { |
| // Calculate sun position | | // Calculate sun position |
| Vector vecAbsStart = vecPosition + Vector(0,0,30); | | Vector vecAbsStart = vecPosition + Vector( 0, 0, 30 ); |
| Vector vecAbsEnd = vecAbsStart - (light->normal * MAX_TRACE_LENGTH); | | Vector vecAbsEnd = vecAbsStart - ( light->normal * MAX_TRACE_LENGTH ); |
|
| |
|
| trace_t tr; | | trace_t tr; |
| UTIL_TraceLine(vecPosition, vecAbsEnd, MASK_OPAQUE, NULL, COLLISION_GROUP_NONE, &tr); | | UTIL_TraceLine( vecPosition, vecAbsEnd, MASK_OPAQUE, nullptr, COLLISION_GROUP_NONE, &tr ); |
|
| |
|
| // If we didn't hit anything then we have a problem | | // If we didn't hit anything then we have a problem |
| if(!tr.DidHit()) | | if ( !tr.DidHit() ) |
| { | | { |
| //engine->Con_NPrintf(i, "%d: skylight: couldn't touch sky", i); | | //engine->Con_NPrintf( i, "%d: skylight: couldn't touch sky", i ); |
| continue; | | continue; |
| } | | } |
Line 242: |
Line 242: |
| // If we did hit something, and it wasn't the skybox, then skip | | // If we did hit something, and it wasn't the skybox, then skip |
| // this worldlight | | // this worldlight |
| if(!(tr.surface.flags & SURF_SKY) && !(tr.surface.flags & SURF_SKY2D)) | | if ( !( tr.surface.flags & SURF_SKY ) && !( tr.surface.flags & SURF_SKY2D ) ) |
| { | | { |
| //engine->Con_NPrintf(i, "%d: skylight: no sight to sun", i); | | //engine->Con_NPrintf( i, "%d: skylight: no sight to sun", i ); |
| continue; | | continue; |
| } | | } |
Line 251: |
Line 251: |
| // manager uses the default shadow direction instead (should be the | | // manager uses the default shadow direction instead (should be the |
| // sun direction) | | // sun direction) |
| | |
| | delete[] pvs; |
| | |
| return false; | | return false; |
| } | | } |
Line 260: |
Line 263: |
|
| |
|
| // Skip lights that are out of our radius | | // Skip lights that are out of our radius |
| if(flRadiusSqr > 0 && flDistSqr >= flRadiusSqr) | | if ( flRadiusSqr > 0 && flDistSqr >= flRadiusSqr ) |
| { | | { |
| //engine->Con_NPrintf(i, "%d: out-of-radius (dist: %d, radius: %d)", i, sqrt(flDistSqr), light->radius); | | //engine->Con_NPrintf( i, "%d: out-of-radius (dist: %d, radius: %d)", i, sqrt( flDistSqr ), light->radius ); |
| continue; | | continue; |
| } | | } |
|
| |
|
| // Is it out of our PVS? | | // Is it out of our PVS? |
| if(!g_pEngineServer->CheckOriginInPVS(light->origin, pvs, nPVSSize)) | | if ( !g_pEngineServer->CheckOriginInPVS( light->origin, pvs, nPVSSize ) ) |
| { | | { |
| //engine->Con_NPrintf(i, "%d: out of PVS", i); | | //engine->Con_NPrintf( i, "%d: out of PVS", i ); |
| continue; | | continue; |
| } | | } |
|
| |
|
| // Calculate intensity at our position | | // Calculate intensity at our position |
| float flRatio = Engine_WorldLightDistanceFalloff(light, vecDelta); | | float flRatio = Engine_WorldLightDistanceFalloff( light, vecDelta ); |
| Vector vecIntensity = light->intensity * flRatio; | | Vector vecIntensity = light->intensity * flRatio; |
|
| |
|
| // Is this light more intense than the one we already found? | | // Is this light more intense than the one we already found? |
| if(vecIntensity.LengthSqr() <= vecLightBrightness.LengthSqr()) | | if ( vecIntensity.LengthSqr() <= vecLightBrightness.LengthSqr() ) |
| { | | { |
| //engine->Con_NPrintf(i, "%d: too dim", i); | | //engine->Con_NPrintf( i, "%d: too dim", i ); |
| continue; | | continue; |
| } | | } |
Line 286: |
Line 289: |
| // Can we see the light? | | // Can we see the light? |
| trace_t tr; | | trace_t tr; |
| Vector vecAbsStart = vecPosition + Vector(0,0,30); | | Vector vecAbsStart = vecPosition + Vector( 0, 0, 30 ); |
| UTIL_TraceLine(vecAbsStart, light->origin, MASK_OPAQUE, NULL, COLLISION_GROUP_NONE, &tr); | | UTIL_TraceLine( vecAbsStart, light->origin, MASK_OPAQUE, nullptr, COLLISION_GROUP_NONE, &tr ); |
|
| |
|
| if(tr.DidHit()) | | if ( tr.DidHit() ) |
| { | | { |
| //engine->Con_NPrintf(i, "%d: trace failed", i); | | //engine->Con_NPrintf( i, "%d: trace failed", i ); |
| continue; | | continue; |
| } | | } |
Line 298: |
Line 301: |
| vecLightBrightness = vecIntensity; | | vecLightBrightness = vecIntensity; |
|
| |
|
| //engine->Con_NPrintf(i, "%d: set (%.2f)", i, vecIntensity.Length()); | | //engine->Con_NPrintf( i, "%d: set (%.2f)", i, vecIntensity.Length() ); |
| } | | } |
|
| |
|
| //engine->Con_NPrintf(m_nWorldLights, "result: %d", !vecLightBrightness.IsZero()); | | delete[] pvs; |
| | |
| | //engine->Con_NPrintf( m_nWorldLights, "result: %d", !vecLightBrightness.IsZero() ); |
| return !vecLightBrightness.IsZero(); | | return !vecLightBrightness.IsZero(); |
| } | | } |
| | |
| | //----------------------------------------------------------------------------- |
| | // Singleton accessor |
| | //----------------------------------------------------------------------------- |
| | static CWorldLights s_WorldLights; |
| | CWorldLights *g_pWorldLights = &s_WorldLights; |
| </source> | | </source> |
|
| |
|
Line 309: |
Line 320: |
| Now create '''worldlight.h''' within '''/game/client''', pasting in the following code: | | Now create '''worldlight.h''' within '''/game/client''', pasting in the following code: |
|
| |
|
| {{Note|don’t forget to add these files to your client project}} | | {{Note|Don’t forget to add these files to your client project}} |
|
| |
|
| <source lang="cpp"> | | <source lang="cpp"> |
| //========= Copyright (C) 2011, CSProMod Team, All rights reserved. =========// | | //========= Copyright (C) 2021, CSProMod Team, All rights reserved. =========// |
| | // |
| | // Purpose: Provide world light-related functions to the client |
| // | | // |
| // Purpose: provide world light related functions to the client
| |
| //
| |
| // Written: November 2011 | | // Written: November 2011 |
| // Author: Saul Rennison | | // Author: Saul Rennison |
Line 321: |
Line 332: |
| //===========================================================================// | | //===========================================================================// |
|
| |
|
| | #ifndef WORLDLIGHT_H |
| | #define WORLDLIGHT_H |
| | #ifdef _WIN32 |
| #pragma once | | #pragma once |
| | #endif |
|
| |
|
| #include "igamesystem.h" // CAutoGameSystem | | #include "igamesystem.h" // CAutoGameSystem |
Line 337: |
Line 352: |
| ~CWorldLights() { Clear(); } | | ~CWorldLights() { Clear(); } |
|
| |
|
| //-------------------------------------------------------------------------
| | bool GetBrightestLightSource( const Vector &vecPosition, Vector &vecLightPos, Vector &vecLightBrightness ); |
| // Find the brightest light source at a point
| |
| //-------------------------------------------------------------------------
| |
| bool GetBrightestLightSource(const Vector &vecPosition, Vector &vecLightPos, Vector &vecLightBrightness); | |
|
| |
|
| // CAutoGameSystem overrides | | // CAutoGameSystem overrides |
| public:
| | bool Init() OVERRIDE; |
| virtual bool Init(); | | void LevelInitPreEntity() OVERRIDE; |
| virtual void LevelInitPreEntity(); | | void LevelShutdownPostEntity() OVERRIDE { Clear(); } |
| virtual void LevelShutdownPostEntity() { Clear(); } | |
|
| |
|
| private: | | private: |
| void Clear(); | | void Clear(); |
|
| |
|
| | private: |
| int m_nWorldLights; | | int m_nWorldLights; |
| dworldlight_t *m_pWorldLights; | | dworldlight_t *m_pWorldLights; |
| }; | | }; |
|
| |
|
| //-----------------------------------------------------------------------------
| | // Singleton accessor |
| // Singleton exposure | |
| //-----------------------------------------------------------------------------
| |
| extern CWorldLights *g_pWorldLights; | | extern CWorldLights *g_pWorldLights; |
| | |
| | #endif // WORLDLIGHT_H |
| </source> | | </source> |
|
| |
|
Line 376: |
Line 388: |
| <source lang="cpp">static ConVar r_flashlight_version2( "r_flashlight_version2", "0" );</source> | | <source lang="cpp">static ConVar r_flashlight_version2( "r_flashlight_version2", "0" );</source> |
| Add: | | Add: |
| <source lang="cpp">void WorldLightCastShadowCallback(IConVar *pVar, const char *pszOldValue, float flOldValue); | | <source lang="cpp"> |
| | void WorldLightCastShadowCallback( IConVar *pVar, const char *pszOldValue, float flOldValue ); |
| static ConVar r_worldlight_castshadows( "r_worldlight_castshadows", "1", FCVAR_CHEAT, "Allow world lights to cast shadows", true, 0, true, 1, WorldLightCastShadowCallback ); | | static ConVar r_worldlight_castshadows( "r_worldlight_castshadows", "1", FCVAR_CHEAT, "Allow world lights to cast shadows", true, 0, true, 1, WorldLightCastShadowCallback ); |
| static ConVar r_worldlight_lerptime( "r_worldlight_lerptime", "0.5", FCVAR_CHEAT ); | | static ConVar r_worldlight_lerptime( "r_worldlight_lerptime", "0.5", FCVAR_CHEAT ); |
| static ConVar r_worldlight_debug( "r_worldlight_debug", "0", FCVAR_CHEAT ); | | static ConVar r_worldlight_debug( "r_worldlight_debug", "0", FCVAR_CHEAT ); |
| static ConVar r_worldlight_shortenfactor( "r_worldlight_shortenfactor", "2" , FCVAR_CHEAT, "Makes shadows cast from local lights shorter" ); | | static ConVar r_worldlight_shortenfactor( "r_worldlight_shortenfactor", "2" , FCVAR_CHEAT, "Makes shadows cast from local lights shorter" ); |
| static ConVar r_worldlight_mincastintensity( "r_worldlight_mincastintensity", "0.3", FCVAR_CHEAT, "Minimum brightness of a light to be classed as shadow casting", true, 0, false, 0 );</source> | | static ConVar r_worldlight_mincastintensity( "r_worldlight_mincastintensity", "0.3", FCVAR_CHEAT, "Minimum brightness of a light to be classed as shadow casting", true, 0, false, 0 ); |
| | </source> |
|
| |
|
| At approximately line 794, replace the line: | | At approximately line 794, replace the line: |
Line 389: |
Line 403: |
|
| |
|
| At approximately line(s) 803, below: | | At approximately line(s) 803, below: |
| <source lang="cpp">void SetShadowsDisabled( bool bDisabled ) | | <source lang="cpp"> |
| | void SetShadowsDisabled( bool bDisabled ) |
| { | | { |
| r_shadows_gamecontrol.SetValue( bDisabled != 1 ); | | r_shadows_gamecontrol.SetValue( bDisabled != 1 ); |
| } | | } |
| </source> | | </source> |
Line 402: |
Line 417: |
|
| |
|
| At approximately line 825, below: | | At approximately line 825, below: |
| <source lang="cpp">Vector2D m_WorldSize; </source> | | <source lang="cpp">Vector2D m_WorldSize;</source> |
| Add: | | Add: |
| <source lang="cpp">Vector m_ShadowDir; </source> | | <source lang="cpp">Vector m_ShadowDir;</source> |
|
| |
|
| At approximately line 829, below: | | At approximately line 829, below: |
| <source lang="cpp">QAngle m_LastAngles; </source> | | <source lang="cpp">QAngle m_LastAngles;</source> |
| Add: | | Add: |
| <source lang="cpp">Vector m_CurrentLightPos; // When shadowing from local lights, stores the position of the currently shadowing light | | <source lang="cpp"> |
| | 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 | | 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</source> | | float m_LightPosLerp; // Lerp progress when going from current to target light |
| | </source> |
|
| |
|
| At approximately line 931, below: | | At approximately line 931, below: |
Line 442: |
Line 459: |
| <source lang="cpp">shadow.m_nRenderFrame = -1;</source> | | <source lang="cpp">shadow.m_nRenderFrame = -1;</source> |
| Add: | | Add: |
| <source lang="cpp">shadow.m_ShadowDir = GetShadowDirection(); | | <source lang="cpp"> |
| | shadow.m_ShadowDir = GetShadowDirection(); |
| shadow.m_CurrentLightPos.Init( FLT_MAX, FLT_MAX, FLT_MAX ); | | shadow.m_CurrentLightPos.Init( FLT_MAX, FLT_MAX, FLT_MAX ); |
| shadow.m_TargetLightPos.Init( FLT_MAX, FLT_MAX, FLT_MAX ); | | shadow.m_TargetLightPos.Init( FLT_MAX, FLT_MAX, FLT_MAX ); |
| shadow.m_LightPosLerp = FLT_MAX;</source> | | shadow.m_LightPosLerp = FLT_MAX; |
| | </source> |
|
| |
|
| At approximately line 2328, replace the line: | | At approximately line 2328, replace the line: |
Line 458: |
Line 477: |
|
| |
|
| At approximately line 2975, replace the lines: | | At approximately line 2975, replace the lines: |
| <source lang="cpp">Assert( m_Shadows.IsValidIndex( handle ) ); | | <source lang="cpp"> |
| UpdateProjectedTextureInternal( handle, false );</source> | | Assert( m_Shadows.IsValidIndex( handle ) ); |
| | UpdateProjectedTextureInternal( handle, false ); |
| | </source> |
| With: | | With: |
| <source lang="cpp">UpdateDirtyShadow(handle);</source> | | <source lang="cpp">UpdateDirtyShadow( handle );</source> |
|
| |
|
| At approximately line 3138, replace the line: | | At approximately line 3138, replace the line: |
| <source lang="cpp">if (force || (origin != shadow.m_LastOrigin) || (angles != shadow.m_LastAngles))</source> | | <source lang="cpp">if (force || (origin != shadow.m_LastOrigin) || (angles != shadow.m_LastAngles))</source> |
| With: | | With: |
| <source lang="cpp">if (force || (origin != shadow.m_LastOrigin) || (angles != shadow.m_LastAngles) || shadow.m_LightPosLerp < 1.0f)</source> | | <source lang="cpp">if ( force || (origin != shadow.m_LastOrigin) || (angles != shadow.m_LastAngles) || shadow.m_LightPosLerp < 1.0f )</source> |
|
| |
|
| At approximately line 3284, replace the line: | | At approximately line 3284, replace the line: |
| <source lang="cpp">void CClientShadowMgr::ComputeShadowBBox( IClientRenderable *pRenderable, const Vector &vecAbsCenter, float flRadius, Vector *pAbsMins, Vector *pAbsMaxs ) </source> | | <source lang="cpp">void CClientShadowMgr::ComputeShadowBBox( IClientRenderable *pRenderable, const Vector &vecAbsCenter, float flRadius, Vector *pAbsMins, Vector *pAbsMaxs )</source> |
| With: | | With: |
| <source lang="cpp">void CClientShadowMgr::ComputeShadowBBox( IClientRenderable *pRenderable, ClientShadowHandle_t shadowHandle, const Vector &vecAbsCenter, float flRadius, Vector *pAbsMins, Vector *pAbsMaxs ) </source> | | <source lang="cpp">void CClientShadowMgr::ComputeShadowBBox( IClientRenderable *pRenderable, ClientShadowHandle_t shadowHandle, const Vector &vecAbsCenter, float flRadius, Vector *pAbsMins, Vector *pAbsMaxs )</source> |
|
| |
|
| At approximately line 3289, replace the line: | | At approximately line 3289, replace the line: |
| <source lang="cpp">Vector vecShadowDir = GetShadowDirection( pRenderable ); </source> | | <source lang="cpp">Vector vecShadowDir = GetShadowDirection( pRenderable );</source> |
| With: | | With: |
| <source lang="cpp">Vector vecShadowDir = GetShadowDirection( shadowHandle ); </source> | | <source lang="cpp">Vector vecShadowDir = GetShadowDirection( shadowHandle );</source> |
|
| |
|
| At approximately line 3387, replace the line: | | At approximately line 3387, replace the line: |
| <source lang="cpp">Vector vecShadowDir = GetShadowDirection( pSourceRenderable ); </source> | | <source lang="cpp">Vector vecShadowDir = GetShadowDirection( pSourceRenderable );</source> |
| With: | | With: |
| <source lang="cpp">Vector vecShadowDir = GetShadowDirection( handle );</source> | | <source lang="cpp">Vector vecShadowDir = GetShadowDirection( handle );</source> |
|
| |
|
| At approximately line 4222, below: | | At approximately line 4222, below: |
| <source lang="cpp"> while( pChild ) | | <source lang="cpp"> |
| | while( pChild ) |
| { | | { |
| if( pChild->GetClientRenderable()==pRenderable ) | | if( pChild->GetClientRenderable()==pRenderable ) |
Line 495: |
Line 517: |
| }</source> | | }</source> |
| Add: | | Add: |
| <source lang="cpp">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_worldlight_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_worldlight_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_worldlight_shortenfactor.GetFloat();
| |
| vecResult.NormalizeInPlace();
| |
|
| |
| shadow.m_ShadowDir = vecResult;
| |
|
| |
| if ( r_worldlight_debug.GetBool() )
| |
| {
| |
| NDebugOverlay::Line( lightPos, origin, 255, 255, 0, false, 0.0f );
| |
| }
| |
| }
| |
|
| |
| void CClientShadowMgr::UpdateDirtyShadow( ClientShadowHandle_t handle )
| |
| {
| |
| Assert( m_Shadows.IsValidIndex( handle ) );
| |
|
| |
| if( IsShadowingFromWorldLights() )
| |
| UpdateShadowDirectionFromLocalLightSource( handle );
| |
|
| |
| UpdateProjectedTextureInternal( handle, false );
| |
| }
| |
|
| |
| void WorldLightCastShadowCallback(IConVar *pVar, const char *pszOldValue, float flOldValue)
| |
| {
| |
| s_ClientShadowMgr.SetShadowFromWorldLightsEnabled(r_worldlight_castshadows.GetBool());
| |
| }
| |
|
| |
| void CClientShadowMgr::SetShadowFromWorldLightsEnabled( bool bEnabled )
| |
| {
| |
| if(bEnabled == IsShadowingFromWorldLights())
| |
| return;
| |
|
| |
| m_bShadowFromWorldLights = bEnabled;
| |
| UpdateAllShadows();
| |
| }</source>
| |
|
| |
|
| |
| =Source of editable clientshadowmgr.cpp=
| |
|
| |
| <source lang="cpp"> | | <source lang="cpp"> |
| //===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// | | //----------------------------------------------------------------------------- |
| //
| |
| // Purpose: | | // 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"
| |
| #include "debugoverlay_shared.h"
| |
| #include "worldlight.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" );
| |
| static ConVar r_shadowrendertotexture( "r_shadowrendertotexture", "0" );
| |
| static ConVar r_flashlight_version2( "r_flashlight_version2", "0" );
| |
| void WorldLightCastShadowCallback(IConVar *pVar, const char *pszOldValue, float flOldValue);
| |
| static ConVar r_worldlight_castshadows( "r_worldlight_castshadows", "1", FCVAR_CHEAT, "Allow world lights to cast shadows", true, 0, true, 1, WorldLightCastShadowCallback );
| |
| static ConVar r_worldlight_lerptime( "r_worldlight_lerptime", "0.5", FCVAR_CHEAT );
| |
| static ConVar r_worldlight_debug( "r_worldlight_debug", "0", FCVAR_CHEAT );
| |
| static ConVar r_worldlight_shortenfactor( "r_worldlight_shortenfactor", "2" , FCVAR_CHEAT, "Makes shadows cast from local lights shorter" );
| |
| static ConVar r_worldlight_mincastintensity( "r_worldlight_mincastintensity", "0.3", FCVAR_CHEAT, "Minimum brightness of a light to be classed as shadow casting", true, 0, false, 0 );
| |
|
| |
| ConVar r_flashlightdepthtexture( "r_flashlightdepthtexture", "1" );
| |
|
| |
| #if defined( _X360 )
| |
| ConVar r_flashlightdepthres( "r_flashlightdepthres", "512" );
| |
| #else
| |
| ConVar r_flashlightdepthres( "r_flashlightdepthres", "1024" );
| |
| #endif
| |
|
| |
| ConVar r_threaded_client_shadow_manager( "r_threaded_client_shadow_manager", "0" );
| |
|
| |
| #ifdef _WIN32
| |
| #pragma warning( disable: 4701 )
| |
| #endif
| |
|
| |
| // forward declarations
| |
| void ToolFramework_RecordMaterialParams( IMaterial *pMaterial );
| |
|
| |
|
| |
| //----------------------------------------------------------------------------- | | //----------------------------------------------------------------------------- |
| // A texture allocator used to batch textures together
| | const Vector &CClientShadowMgr::GetShadowDirection( ClientShadowHandle_t shadowHandle ) const |
| // 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 | | Assert( shadowHandle != CLIENTSHADOW_INVALID_HANDLE ); |
| };
| |
| | |
| 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 | | IClientRenderable *pRenderable = ClientEntityList().GetClientRenderableFromHandle( m_Shadows[shadowHandle].m_Entity ); |
| int power = 0;
| |
| int size = 1;
| |
| while(size < w)
| |
| {
| |
| size <<= 1;
| |
| ++power;
| |
| }
| |
| Assert( size == w );
| |
| | |
| m_Textures[handle].m_Power = power;
| |
| | |
| return handle;
| |
| }
| |
| | |
| void CTextureAllocator::DeallocateTexture( TextureHandle_t h )
| |
| {
| |
| // Warning("Beginning of DeallocateTexture\n");
| |
| // DebugPrintCache();
| |
| | |
| if (m_Textures[h].m_Fragment != INVALID_FRAGMENT_HANDLE)
| |
| {
| |
| MarkUnused(m_Textures[h].m_Fragment);
| |
| m_Fragments[m_Textures[h].m_Fragment].m_FrameUsed = 0xFFFFFFFF; // non-zero frame
| |
| DisconnectTextureFromFragment( m_Textures[h].m_Fragment );
| |
| }
| |
| m_Textures.Remove(h);
| |
| | |
| // Warning("End of DeallocateTexture\n");
| |
| // DebugPrintCache();
| |
| }
| |
| | |
| | |
| //-----------------------------------------------------------------------------
| |
| // Disconnect texture from fragment
| |
| //-----------------------------------------------------------------------------
| |
| void CTextureAllocator::DisconnectTextureFromFragment( FragmentHandle_t f )
| |
| {
| |
| // Warning( "Beginning of DisconnectTextureFromFragment\n" );
| |
| // DebugPrintCache();
| |
| | |
| FragmentInfo_t& info = m_Fragments[f];
| |
| if (info.m_Texture != INVALID_TEXTURE_HANDLE)
| |
| {
| |
| m_Textures[info.m_Texture].m_Fragment = INVALID_FRAGMENT_HANDLE;
| |
| info.m_Texture = INVALID_TEXTURE_HANDLE;
| |
| }
| |
| | |
| | |
| // Warning( "End of DisconnectTextureFromFragment\n" );
| |
| // DebugPrintCache();
| |
| }
| |
| | |
| | |
| //-----------------------------------------------------------------------------
| |
| // Do we have a valid texture assigned?
| |
| //-----------------------------------------------------------------------------
| |
| bool CTextureAllocator::HasValidTexture( TextureHandle_t h )
| |
| {
| |
| TextureInfo_t& info = m_Textures[h];
| |
| FragmentHandle_t currentFragment = info.m_Fragment;
| |
| return (currentFragment != INVALID_FRAGMENT_HANDLE);
| |
| }
| |
| | |
| | |
| //-----------------------------------------------------------------------------
| |
| // Mark texture as being used...
| |
| //-----------------------------------------------------------------------------
| |
| bool CTextureAllocator::UseTexture( TextureHandle_t h, bool bWillRedraw, float flArea )
| |
| {
| |
| // Warning( "Top of UseTexture\n" );
| |
| // DebugPrintCache();
| |
| | |
| TextureInfo_t& info = m_Textures[h];
| |
| | |
| // spin up to the best fragment size
| |
| int nDesiredPower = MIN_TEXTURE_POWER;
| |
| int nDesiredWidth = MIN_TEXTURE_SIZE;
| |
| while ( (nDesiredWidth * nDesiredWidth) < flArea )
| |
| {
| |
| if ( nDesiredPower >= info.m_Power )
| |
| {
| |
| nDesiredPower = info.m_Power;
| |
| break;
| |
| }
| |
| | |
| ++nDesiredPower;
| |
| nDesiredWidth <<= 1;
| |
| }
| |
| | |
| // If we've got a valid fragment for this texture, no worries!
| |
| int nCurrentPower = -1;
| |
| FragmentHandle_t currentFragment = info.m_Fragment;
| |
| if (currentFragment != INVALID_FRAGMENT_HANDLE)
| |
| {
| |
| // If the current fragment is at or near the desired power, we're done
| |
| nCurrentPower = GetFragmentPower(info.m_Fragment);
| |
| Assert( nCurrentPower <= info.m_Power );
| |
| bool bShouldKeepTexture = (!bWillRedraw) && (nDesiredPower < 8) && (nDesiredPower - nCurrentPower <= 1);
| |
| if ((nCurrentPower == nDesiredPower) || bShouldKeepTexture)
| |
| {
| |
| // Move to the back of the LRU
| |
| MarkUsed( currentFragment );
| |
| return false;
| |
| }
| |
| }
| |
| | |
| // Warning( "\n\nUseTexture B\n" );
| |
| // DebugPrintCache();
| |
| | |
| // Grab the LRU fragment from the appropriate cache
| |
| // If that fragment is connected to a texture, disconnect it.
| |
| int power = nDesiredPower;
| |
| | |
| FragmentHandle_t f = INVALID_FRAGMENT_HANDLE;
| |
| bool done = false;
| |
| while (!done && power >= 0)
| |
| {
| |
| f = m_Fragments.Head( m_Cache[power].m_List );
| |
|
| |
| // This represents an overflow condition (used too many textures of
| |
| // the same size in a single frame). It that happens, just use a texture
| |
| // of lower res.
| |
| if ( (f != m_Fragments.InvalidIndex()) && (m_Fragments[f].m_FrameUsed != m_CurrentFrame) )
| |
| {
| |
| done = true;
| |
| }
| |
| else
| |
| {
| |
| --power;
| |
| }
| |
| }
| |
| | |
| | |
| // Warning( "\n\nUseTexture C\n" );
| |
| // DebugPrintCache();
| |
| | |
| // Ok, lets see if we're better off than we were...
| |
| if (currentFragment != INVALID_FRAGMENT_HANDLE)
| |
| {
| |
| if (power <= nCurrentPower)
| |
| {
| |
| // Oops... we're not. Let's leave well enough alone
| |
| // Move to the back of the LRU
| |
| MarkUsed( currentFragment );
| |
| return false;
| |
| }
| |
| else
| |
| {
| |
| // Clear out the old fragment
| |
| DisconnectTextureFromFragment(currentFragment);
| |
| }
| |
| }
| |
| | |
| if ( f == INVALID_FRAGMENT_HANDLE )
| |
| {
| |
| return false;
| |
| }
| |
| | |
| // Disconnect existing texture from this fragment (if necessary)
| |
| DisconnectTextureFromFragment(f);
| |
| | |
| // Connnect new texture to this fragment
| |
| info.m_Fragment = f;
| |
| m_Fragments[f].m_Texture = h;
| |
| | |
| // Move to the back of the LRU
| |
| MarkUsed( f );
| |
| | |
| // Indicate we need a redraw
| |
| return true;
| |
| }
| |
| | |
| | |
| //-----------------------------------------------------------------------------
| |
| // Returns the size of a particular fragment
| |
| //-----------------------------------------------------------------------------
| |
| int CTextureAllocator::GetFragmentPower( FragmentHandle_t f ) const
| |
| {
| |
| return m_Blocks[m_Fragments[f].m_Block].m_FragmentPower;
| |
| }
| |
| | |
| | |
| //-----------------------------------------------------------------------------
| |
| // Advance frame...
| |
| //-----------------------------------------------------------------------------
| |
| void CTextureAllocator::AdvanceFrame()
| |
| {
| |
| // Be sure that this is called as infrequently as possible (i.e. once per frame,
| |
| // NOT once per view) to prevent cache thrash when rendering multiple views in a single frame
| |
| m_CurrentFrame++;
| |
| }
| |
| | |
| | |
| //-----------------------------------------------------------------------------
| |
| // Prepare to render into texture...
| |
| //-----------------------------------------------------------------------------
| |
| ITexture* CTextureAllocator::GetTexture()
| |
| {
| |
| return m_TexturePage;
| |
| }
| |
| | |
| //-----------------------------------------------------------------------------
| |
| // Get at the total texture size.
| |
| //-----------------------------------------------------------------------------
| |
| void CTextureAllocator::GetTotalTextureSize( int& w, int& h )
| |
| {
| |
| w = h = TEXTURE_PAGE_SIZE;
| |
| }
| |
| | |
| | |
| //-----------------------------------------------------------------------------
| |
| // Returns the rectangle the texture lives in..
| |
| //-----------------------------------------------------------------------------
| |
| void CTextureAllocator::GetTextureRect(TextureHandle_t handle, int& x, int& y, int& w, int& h )
| |
| {
| |
| TextureInfo_t& info = m_Textures[handle];
| |
| Assert( info.m_Fragment != INVALID_FRAGMENT_HANDLE );
| |
| | |
| // Compute the position of the fragment in the page
| |
| FragmentInfo_t& fragment = m_Fragments[info.m_Fragment];
| |
| int blockY = fragment.m_Block / BLOCKS_PER_ROW;
| |
| int blockX = fragment.m_Block - blockY * BLOCKS_PER_ROW;
| |
| | |
| int fragmentSize = (1 << m_Blocks[fragment.m_Block].m_FragmentPower);
| |
| int fragmentsPerRow = BLOCK_SIZE / fragmentSize;
| |
| int fragmentY = fragment.m_Index / fragmentsPerRow;
| |
| int fragmentX = fragment.m_Index - fragmentY * fragmentsPerRow;
| |
| | |
| x = blockX * BLOCK_SIZE + fragmentX * fragmentSize;
| |
| y = blockY * BLOCK_SIZE + fragmentY * fragmentSize;
| |
| w = fragmentSize;
| |
| h = fragmentSize;
| |
| }
| |
| | |
| | |
| //-----------------------------------------------------------------------------
| |
| // Defines how big of a shadow texture we should be making per caster...
| |
| //-----------------------------------------------------------------------------
| |
| #define TEXEL_SIZE_PER_CASTER_SIZE 2.0f
| |
| #define MAX_FALLOFF_AMOUNT 240
| |
| #define MAX_CLIP_PLANE_COUNT 4
| |
| #define SHADOW_CULL_TOLERANCE 0.5f
| |
| | |
| static ConVar r_shadows( "r_shadows", "1" ); // hook into engine's cvars..
| |
| static ConVar r_shadowmaxrendered("r_shadowmaxrendered", "32");
| |
| static ConVar r_shadows_gamecontrol( "r_shadows_gamecontrol", "-1", FCVAR_CHEAT ); // hook into engine's cvars..
| |
| | |
| //-----------------------------------------------------------------------------
| |
| // 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() {}
| |
| 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
| |
| void ComputeShadowBBox( IClientRenderable *pRenderable, ClientShadowHandle_t shadowHandle, const Vector &vecAbsCenter, float flRadius, Vector *pAbsMins, Vector *pAbsMaxs );
| |
| | |
| bool WillParentRenderBlobbyShadow( IClientRenderable *pRenderable );
| |
| | |
| // Are we the child of a shadow with render-to-texture?
| |
| bool ShouldUseParentShadow( IClientRenderable *pRenderable );
| |
| | |
| void SetShadowsDisabled( bool bDisabled )
| |
| {
| |
| r_shadows_gamecontrol.SetValue( bDisabled != 1 );
| |
| }
| |
| | |
| void SuppressShadowFromWorldLights( bool bSuppress );
| |
| void SetShadowFromWorldLightsEnabled( bool bEnabled );
| |
| bool IsShadowingFromWorldLights() const { return m_bShadowFromWorldLights; }
| |
| | |
| private:
| |
| | |
| enum
| |
| {
| |
| SHADOW_FLAGS_TEXTURE_DIRTY = (CLIENT_SHADOW_FLAGS_LAST_FLAG << 1),
| |
| SHADOW_FLAGS_BRUSH_MODEL = (CLIENT_SHADOW_FLAGS_LAST_FLAG << 2),
| |
| SHADOW_FLAGS_USING_LOD_SHADOW = (CLIENT_SHADOW_FLAGS_LAST_FLAG << 3),
| |
| SHADOW_FLAGS_LIGHT_WORLD = (CLIENT_SHADOW_FLAGS_LAST_FLAG << 4),
| |
| };
| |
| | |
| struct ClientShadow_t
| |
| {
| |
| ClientEntityHandle_t m_Entity;
| |
| ShadowHandle_t m_ShadowHandle;
| |
| ClientLeafShadowHandle_t m_ClientLeafShadowHandle;
| |
| unsigned short m_Flags;
| |
| VMatrix m_WorldToShadow;
| |
| Vector2D m_WorldSize;
| |
| Vector m_ShadowDir;
| |
| Vector m_LastOrigin;
| |
| QAngle m_LastAngles;
| |
| Vector m_CurrentLightPos; // When shadowing from local lights, stores the position of the currently shadowing light
| |
| Vector m_TargetLightPos; // When shadowing from local lights, stores the position of the new shadowing light
| |
| float m_LightPosLerp; // Lerp progress when going from current to target light
| |
| TextureHandle_t m_ShadowTexture;
| |
| CTextureReference m_ShadowDepthTexture;
| |
| int m_nRenderFrame;
| |
| EHANDLE m_hTargetEntity;
| |
| };
| |
| | |
| 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 );
| |
| | |
| // 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 );
| |
| | |
| // 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;
| |
| const Vector &GetShadowDirection( ClientShadowHandle_t shadowHandle ) const;
| |
| | |
| // Initialize, shutdown render-to-texture shadows
| |
| void InitDepthTextureShadows();
| |
| void ShutdownDepthTextureShadows();
| |
| | |
| // Initialize, shutdown render-to-texture shadows
| |
| void InitRenderToTextureShadows();
| |
| void ShutdownRenderToTextureShadows();
| |
| | |
| 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 );
| |
| | |
| // 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 );
| |
| | |
| // Sets the view's active flashlight render state
| |
| void SetViewFlashlightState( int nActiveFlashlightCount, ClientShadowHandle_t* pActiveFlashlights );
| |
| void UpdateDirtyShadow( ClientShadowHandle_t handle );
| |
| void UpdateShadowDirectionFromLocalLightSource( ClientShadowHandle_t shadowHandle );
| |
| | |
| 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;
| |
| | |
| // 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;
| |
| bool m_bShadowFromWorldLights;
| |
| | |
| friend class CVisibleShadowList;
| |
| friend class CVisibleShadowFrustumList;
| |
| };
| |
| | |
| //-----------------------------------------------------------------------------
| |
| // Singleton
| |
| //-----------------------------------------------------------------------------
| |
| static CClientShadowMgr s_ClientShadowMgr;
| |
| IClientShadowMgr* g_pClientShadowMgr = &s_ClientShadowMgr;
| |
| | |
| | |
| //-----------------------------------------------------------------------------
| |
| // Builds a list of potential shadows that lie within our PVS + view frustum
| |
| //-----------------------------------------------------------------------------
| |
| struct VisibleShadowInfo_t
| |
| {
| |
| ClientShadowHandle_t m_hShadow;
| |
| float m_flArea;
| |
| Vector m_vecAbsCenter;
| |
| };
| |
| | |
| class CVisibleShadowList : public IClientLeafShadowEnum
| |
| {
| |
| public:
| |
| | |
| CVisibleShadowList();
| |
| int FindShadows( const CViewSetup *pView, int nLeafCount, 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<VisibleShadowInfo_t> m_ShadowsInView;
| |
| CUtlVector<int> m_PriorityIndex;
| |
| };
| |
| | |
| | |
| //-----------------------------------------------------------------------------
| |
| // Singleton instances of shadow and shadow frustum lists
| |
| //-----------------------------------------------------------------------------
| |
| static CVisibleShadowList s_VisibleShadowList;
| |
| | |
| //-----------------------------------------------------------------------------
| |
| //
| |
| //-----------------------------------------------------------------------------
| |
| static CUtlVector<C_BaseAnimating *> s_NPCShadowBoneSetups;
| |
| static CUtlVector<C_BaseAnimating *> s_NonNPCShadowBoneSetups;
| |
| | |
| //-----------------------------------------------------------------------------
| |
| // CVisibleShadowList - Constructor and Accessors
| |
| //-----------------------------------------------------------------------------
| |
| CVisibleShadowList::CVisibleShadowList() : m_ShadowsInView( 0, 64 ), m_PriorityIndex( 0, 64 )
| |
| {
| |
| }
| |
| | |
| int CVisibleShadowList::GetVisibleShadowCount() const
| |
| {
| |
| return m_ShadowsInView.Count();
| |
| }
| |
| | |
| const VisibleShadowInfo_t &CVisibleShadowList::GetVisibleShadow( int i ) const
| |
| {
| |
| return m_ShadowsInView[m_PriorityIndex[i]];
| |
| }
| |
| | |
| | |
| //-----------------------------------------------------------------------------
| |
| // 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;
| |
| | |
| // 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 ); | | Assert( pRenderable ); |
|
| |
|
| // Don't bother with children of hierarchy; they will be drawn with their parents
| | if ( !IsShadowingFromWorldLights() ) |
| if ( s_ClientShadowMgr.ShouldUseParentShadow( pRenderable ) || s_ClientShadowMgr.WillParentRenderBlobbyShadow( pRenderable ) ) | |
| return;
| |
| | |
| // Compute a sphere surrounding the shadow
| |
| // FIXME: This doesn't account for children of hierarchy... too bad!
| |
| Vector vecAbsCenter;
| |
| float flRadius;
| |
| s_ClientShadowMgr.ComputeBoundingSphere( pRenderable, vecAbsCenter, flRadius );
| |
| | |
| // Compute a box surrounding the shadow
| |
| Vector vecAbsMins, vecAbsMaxs;
| |
| s_ClientShadowMgr.ComputeShadowBBox( pRenderable, shadow.m_ShadowHandle, vecAbsCenter, flRadius, &vecAbsMins, &vecAbsMaxs );
| |
| | |
| // FIXME: Add distance check here?
| |
| | |
| // Make sure it's in the frustum. If it isn't it's not interesting
| |
| if (engine->CullBox( vecAbsMins, vecAbsMaxs ))
| |
| return;
| |
| | |
| 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); | | return GetShadowDirection( pRenderable ); |
| } | | } |
|
| |
|
| 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;
| |
| }
| |
| }
| |
| 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 ),
| |
| m_RenderToTextureActive( false ),
| |
| m_bDepthTextureActive( false )
| |
| {
| |
| m_nDepthTextureResolution = r_flashlightdepthres.GetInt();
| |
| m_bThreaded = false;
| |
| m_bShadowFromWorldLights = r_worldlight_castshadows.GetBool();
| |
| }
| |
|
| |
|
| |
| //-----------------------------------------------------------------------------
| |
| // 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 );
| |
| }
| |
| }
| |
|
| |
| static void ShadowRestoreFunc( int nChangeFlags )
| |
| {
| |
| s_ClientShadowMgr.RestoreRenderState();
| |
| }
| |
|
| |
| //-----------------------------------------------------------------------------
| |
| // 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 );
| |
|
| |
| bool bTools = CommandLine()->CheckParm( "-tools" ) != NULL;
| |
| m_nMaxDepthTextureShadows = bTools ? 4 : 1; // Just one shadow depth texture in games, more in tools
| |
|
| |
| bool bLowEnd = ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() < 80 );
| |
|
| |
| if ( !bLowEnd && r_shadowrendertotexture.GetBool() )
| |
| {
| |
| 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( !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
| |
| m_DummyColorTexture.InitRenderTarget( r_flashlightdepthres.GetInt(), r_flashlightdepthres.GetInt(), RT_SIZE_OFFSCREEN, nullFormat, MATERIAL_RT_DEPTH_NONE, false, "_rt_ShadowDummy" );
| |
| #endif
| |
|
| |
| // Create some number of depth-stencil textures
| |
| m_DepthTextureCache.Purge();
| |
| m_DepthTextureCacheLocks.Purge();
| |
| for( int i=0; i < m_nMaxDepthTextureShadows; i++ )
| |
| {
| |
| CTextureReference depthTex; // Depth-stencil surface
| |
| bool bFalse = false;
| |
|
| |
| char strRTName[64];
| |
| sprintf( 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
| |
| depthTex.InitRenderTarget( m_nDepthTextureResolution, m_nDepthTextureResolution, RT_SIZE_OFFSCREEN, dstFormat, MATERIAL_RT_DEPTH_NONE, false, strRTName );
| |
| #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 );
| |
| }
| |
|
| |
| materials->EndRenderTargetAllocation();
| |
| }
| |
| }
| |
|
| |
| 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*)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;
| |
|
| |
| 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(); | | Vector &vecResult = AllocTempVector(); |
| vecResult = GetShadowDirection(); | | vecResult = m_Shadows[shadowHandle].m_ShadowDir; |
|
| |
|
| // Allow the renderable to override the default | | // Allow the renderable to override the default |
Line 2,393: |
Line 540: |
|
| |
|
| return vecResult; | | return vecResult; |
| }
| |
|
| |
|
| |
| //-----------------------------------------------------------------------------
| |
| // 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;
| |
| } | | } |
|
| |
|
Line 2,427: |
Line 545: |
| // Purpose: | | // Purpose: |
| //----------------------------------------------------------------------------- | | //----------------------------------------------------------------------------- |
| void CClientShadowMgr::SetFalloffBias( ClientShadowHandle_t handle, unsigned char ucBias ) | | void CClientShadowMgr::UpdateShadowDirectionFromLocalLightSource( ClientShadowHandle_t shadowHandle ) |
| { | | { |
| shadowmgr->SetFalloffBias( m_Shadows[handle].m_ShadowHandle, ucBias );
| | Assert( shadowHandle != CLIENTSHADOW_INVALID_HANDLE ); |
| }
| |
| | |
| //-----------------------------------------------------------------------------
| |
| // 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;
| |
| 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;
| |
| 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*)h;
| |
| }
| |
| | |
| if( flags & SHADOW_FLAGS_USE_DEPTH_TEXTURE )
| |
| {
| |
| pShadowMaterial = m_RenderShadow;
| |
| pShadowModelMaterial = m_RenderModelShadow;
| |
| pShadowProxyData = (void*)h;
| |
| }
| |
| | |
| 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 );
| |
| | |
| BuildPerspectiveWorldToFlashlightMatrix( m_Shadows[shadowHandle].m_WorldToShadow, flashlightState );
| |
|
| |
| 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 );
| |
| }
| |
| | |
| //-----------------------------------------------------------------------------
| |
| // 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;
| |
| | |
| Vector vecShadowDir = GetShadowDirection( handle );
| |
| | |
| // Project the shadow casting direction into the space of the object
| |
| Vector localShadowDir;
| |
| localShadowDir[0] = DotProduct( vec[0], vecShadowDir );
| |
| localShadowDir[1] = DotProduct( vec[1], vecShadowDir );
| |
| localShadowDir[2] = DotProduct( vec[2], vecShadowDir );
| |
| | |
| // Figure out which vector has the largest component perpendicular
| |
| // to the shadow handle...
| |
| // Sort by how perpendicular it is
| |
| int vecIdx[3];
| |
| SortAbsVectorComponents( localShadowDir, vecIdx );
| |
| | |
| // Here's our shadow basis vectors; namely the ones that are
| |
| // most perpendicular to the shadow casting direction
| |
| Vector xvec = vec[vecIdx[0]];
| |
| Vector yvec = vec[vecIdx[1]];
| |
| | |
| // Project them into a plane perpendicular to the shadow direction
| |
| xvec -= vecShadowDir * DotProduct( vecShadowDir, xvec );
| |
| yvec -= vecShadowDir * DotProduct( vecShadowDir, yvec );
| |
| VectorNormalize( xvec );
| |
| VectorNormalize( yvec );
| |
| | |
| // Compute the box size
| |
| Vector boxSize;
| |
| VectorSubtract( maxs, mins, boxSize );
| |
| | |
| // We project the two longest sides into the vectors perpendicular
| |
| // to the projection direction, then add in the projection of the perp direction
| |
| Vector2D size( boxSize[vecIdx[0]], boxSize[vecIdx[1]] );
| |
| size.x *= fabs( DotProduct( vec[vecIdx[0]], xvec ) );
| |
| size.y *= fabs( DotProduct( vec[vecIdx[1]], yvec ) );
| |
| | |
| // Add the third component into x and y
| |
| size.x += boxSize[vecIdx[2]] * fabs( DotProduct( vec[vecIdx[2]], xvec ) );
| |
| size.y += boxSize[vecIdx[2]] * fabs( DotProduct( vec[vecIdx[2]], yvec ) );
| |
| | |
| // Bloat a bit, since the shadow wants to extend outside the model a bit
| |
| size.x += 10.0f;
| |
| size.y += 10.0f;
| |
| | |
| // Clamp the minimum size
| |
| Vector2DMax( size, Vector2D(10.0f, 10.0f), size );
| |
| | |
| // Place the origin at the point with min dot product with shadow dir
| |
| Vector org;
| |
| float falloffStart = ComputeLocalShadowOrigin( pRenderable, mins, maxs, localShadowDir, 2.0f, org );
| |
| | |
| // Transform the local origin into world coordinates
| |
| Vector worldOrigin = pRenderable->GetRenderOrigin( );
| |
| VectorMA( worldOrigin, org.x, vec[0], worldOrigin );
| |
| VectorMA( worldOrigin, org.y, vec[1], worldOrigin );
| |
| VectorMA( worldOrigin, org.z, vec[2], worldOrigin );
| |
| | |
| // FUNKY: A trick to reduce annoying texelization artifacts!?
| |
| float dx = 1.0f / TEXEL_SIZE_PER_CASTER_SIZE;
| |
| worldOrigin.x = (int)(worldOrigin.x / dx) * dx;
| |
| worldOrigin.y = (int)(worldOrigin.y / dx) * dx;
| |
| worldOrigin.z = (int)(worldOrigin.z / dx) * dx;
| |
| | |
| // NOTE: We gotta use the general matrix because xvec and yvec aren't perp
| |
| VMatrix matWorldToShadow, matWorldToTexture;
| |
| 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.
| |
| // ComputeExtraClipPlanes( pEnt, handle, vec, mins, maxs, localShadowDir );
| |
| | |
| // Add the shadow to the client leaf system so it correctly marks
| |
| // leafs as being affected by a particular shadow
| |
| ClientLeafSystem()->ProjectShadow( m_Shadows[handle].m_ClientLeafShadowHandle, nCount, pLeafList );
| |
| }
| |
| | |
| | |
| //-----------------------------------------------------------------------------
| |
| // Visualization....
| |
| //-----------------------------------------------------------------------------
| |
| void CClientShadowMgr::DrawRenderToTextureDebugInfo( IClientRenderable* pRenderable, const Vector& mins, const Vector& maxs )
| |
| {
| |
| // Get the object's basis
| |
| Vector vec[3];
| |
| AngleVectors( pRenderable->GetRenderAngles(), &vec[0], &vec[1], &vec[2] );
| |
| vec[1] *= -1.0f;
| |
| | |
| Vector vecSize;
| |
| VectorSubtract( maxs, mins, vecSize );
| |
| | |
| Vector vecOrigin = pRenderable->GetRenderOrigin();
| |
| Vector start, end, end2;
| |
| | |
| VectorMA( vecOrigin, mins.x, vec[0], start );
| |
| VectorMA( start, mins.y, vec[1], start );
| |
| VectorMA( start, mins.z, vec[2], start );
| |
| | |
| VectorMA( start, vecSize.x, vec[0], end );
| |
| VectorMA( end, vecSize.z, vec[2], end2 );
| |
| debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 );
| |
| debugoverlay->AddLineOverlay( end2, end, 255, 0, 0, true, 0.01 );
| |
| | |
| VectorMA( start, vecSize.y, vec[1], end );
| |
| VectorMA( end, vecSize.z, vec[2], end2 );
| |
| debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 );
| |
| debugoverlay->AddLineOverlay( end2, end, 255, 0, 0, true, 0.01 );
| |
| | |
| VectorMA( start, vecSize.z, vec[2], end );
| |
| debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 );
| |
|
| |
| start = end;
| |
| VectorMA( start, vecSize.x, vec[0], end );
| |
| debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 );
| |
| | |
| VectorMA( start, vecSize.y, vec[1], end );
| |
| debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 );
| |
| | |
| VectorMA( end, vecSize.x, vec[0], start );
| |
| VectorMA( start, -vecSize.x, vec[0], end );
| |
| debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 );
| |
| | |
| VectorMA( start, -vecSize.y, vec[1], end );
| |
| debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 );
| |
| | |
| VectorMA( start, -vecSize.z, vec[2], end );
| |
| debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 );
| |
| | |
| start = end;
| |
| VectorMA( start, -vecSize.x, vec[0], end );
| |
| debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 );
| |
| | |
| VectorMA( start, -vecSize.y, vec[1], end );
| |
| debugoverlay->AddLineOverlay( start, end, 255, 0, 0, true, 0.01 );
| |
| | |
| C_BaseEntity *pEnt = pRenderable->GetIClientUnknown()->GetBaseEntity();
| |
| if ( pEnt )
| |
| {
| |
| debugoverlay->AddTextOverlay( vecOrigin, 0, "%d", pEnt->entindex() );
| |
| }
| |
| else
| |
| {
| |
| debugoverlay->AddTextOverlay( vecOrigin, 0, "%X", (size_t)pRenderable );
| |
| }
| |
| }
| |
| | |
| | |
| extern ConVar cl_drawshadowtexture;
| |
| extern ConVar cl_shadowtextureoverlaysize;
| |
| | |
| //-----------------------------------------------------------------------------
| |
| // Builds a more complex shadow...
| |
| //-----------------------------------------------------------------------------
| |
| void CClientShadowMgr::BuildRenderToTextureShadow( IClientRenderable* pRenderable,
| |
| ClientShadowHandle_t handle, const Vector& mins, const Vector& maxs)
| |
| {
| |
| if ( cl_drawshadowtexture.GetInt() )
| |
| {
| |
| // Red wireframe bounding box around objects whose RTT shadows are being updated that frame
| |
| DrawRenderToTextureDebugInfo( pRenderable, mins, maxs );
| |
| }
| |
| | |
| // Get the object's basis
| |
| Vector vec[3];
| |
| AngleVectors( pRenderable->GetRenderAngles(), &vec[0], &vec[1], &vec[2] );
| |
| vec[1] *= -1.0f;
| |
| | |
| Vector vecShadowDir = GetShadowDirection( handle );
| |
| | |
| // Debugging aid
| |
| // const model_t *pModel = pRenderable->GetModel();
| |
| // const char *pDebugName = modelinfo->GetModelName( pModel );
| |
| | |
| // Project the shadow casting direction into the space of the object
| |
| Vector localShadowDir;
| |
| localShadowDir[0] = DotProduct( vec[0], vecShadowDir );
| |
| localShadowDir[1] = DotProduct( vec[1], vecShadowDir );
| |
| localShadowDir[2] = DotProduct( vec[2], vecShadowDir );
| |
| | |
| // Compute the box size
| |
| Vector boxSize;
| |
| VectorSubtract( maxs, mins, boxSize );
| |
|
| |
| Vector yvec;
| |
| float fProjMax = 0.0f;
| |
| for( int i = 0; i != 3; ++i )
| |
| {
| |
| Vector test = vec[i] - ( vecShadowDir * DotProduct( vecShadowDir, vec[i] ) );
| |
| test *= boxSize[i]; //doing after the projection to simplify projection math
| |
| float fLengthSqr = test.LengthSqr();
| |
| if( fLengthSqr > fProjMax )
| |
| {
| |
| fProjMax = fLengthSqr;
| |
| yvec = test;
| |
| }
| |
| }
| |
| | |
| VectorNormalize( yvec );
| |
| | |
| // Compute the x vector
| |
| Vector xvec;
| |
| CrossProduct( yvec, vecShadowDir, xvec );
| |
| | |
| // We project the two longest sides into the vectors perpendicular
| |
| // to the projection direction, then add in the projection of the perp direction
| |
| Vector2D size;
| |
| size.x = boxSize.x * fabs( DotProduct( vec[0], xvec ) ) +
| |
| boxSize.y * fabs( DotProduct( vec[1], xvec ) ) +
| |
| boxSize.z * fabs( DotProduct( vec[2], xvec ) );
| |
| size.y = boxSize.x * fabs( DotProduct( vec[0], yvec ) ) +
| |
| boxSize.y * fabs( DotProduct( vec[1], yvec ) ) +
| |
| boxSize.z * fabs( DotProduct( vec[2], yvec ) );
| |
| | |
| size.x += 2.0f * TEXEL_SIZE_PER_CASTER_SIZE;
| |
| size.y += 2.0f * TEXEL_SIZE_PER_CASTER_SIZE;
| |
| | |
| // Place the origin at the point with min dot product with shadow dir
| |
| Vector org;
| |
| float falloffStart = ComputeLocalShadowOrigin( pRenderable, mins, maxs, localShadowDir, 1.0f, org );
| |
| | |
| // Transform the local origin into world coordinates
| |
| Vector worldOrigin = pRenderable->GetRenderOrigin( );
| |
| VectorMA( worldOrigin, org.x, vec[0], worldOrigin );
| |
| VectorMA( worldOrigin, org.y, vec[1], worldOrigin );
| |
| VectorMA( worldOrigin, org.z, vec[2], worldOrigin );
| |
| | |
| VMatrix matWorldToTexture;
| |
| BuildOrthoWorldToShadowMatrix( m_Shadows[handle].m_WorldToShadow, worldOrigin, vecShadowDir, xvec, yvec );
| |
| BuildWorldToTextureMatrix( m_Shadows[handle].m_WorldToShadow, size, matWorldToTexture );
| |
| Vector2DCopy( size, m_Shadows[handle].m_WorldSize );
| |
| | |
| // 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()
| |
| {
| |
| 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();
| | ClientShadow_t &shadow = m_Shadows[shadowHandle]; |
| 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_RenderToTextureActive )
| |
| {
| |
| ShutdownRenderToTextureShadows();
| |
| }
| |
| else
| |
| {
| |
| InitRenderToTextureShadows();
| |
| }
| |
|
| |
| 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 ];
| |
| UpdateDirtyShadow(handle);
| |
| i = m_DirtyShadows.NextInorder(i);
| |
| }
| |
| m_DirtyShadows.RemoveAll();
| |
|
| |
| // Transparent shadows must remain dirty, since they were not re-projected
| |
| int nCount = m_TransparentShadows.Count();
| |
| for ( int i = 0; i < nCount; ++i )
| |
| {
| |
| m_DirtyShadows.Insert( m_TransparentShadows[i] );
| |
| }
| |
| m_TransparentShadows.RemoveAll();
| |
|
| |
| m_bUpdatingDirtyShadows = false;
| |
| }
| |
|
| |
|
| |
| //-----------------------------------------------------------------------------
| |
| // 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 ); | | 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();
| |
|
| |
| if (force || (origin != shadow.m_LastOrigin) || (angles != shadow.m_LastAngles) || shadow.m_LightPosLerp < 1.0f)
| |
| {
| |
| // 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
| |
| //-----------------------------------------------------------------------------
| |
| void CClientShadowMgr::ComputeShadowBBox( IClientRenderable *pRenderable, ClientShadowHandle_t shadowHandle, const Vector &vecAbsCenter, float flRadius, Vector *pAbsMins, Vector *pAbsMaxs )
| |
| {
| |
| // This is *really* rough. Basically we simply determine the
| |
| // maximum shadow casting length and extrude the box by that distance
| |
|
| |
| Vector vecShadowDir = GetShadowDirection( shadowHandle );
| |
| for (int i = 0; i < 3; ++i)
| |
| {
| |
| float flShadowCastDistance = GetShadowDistance( pRenderable );
| |
| float flDist = flShadowCastDistance * vecShadowDir[i];
| |
|
| |
| if (vecShadowDir[i] < 0)
| |
| {
| |
| (*pAbsMins)[i] = vecAbsCenter[i] - flRadius + flDist;
| |
| (*pAbsMaxs)[i] = vecAbsCenter[i] + flRadius;
| |
| }
| |
| else
| |
| {
| |
| (*pAbsMins)[i] = vecAbsCenter[i] - flRadius;
| |
| (*pAbsMaxs)[i] = vecAbsCenter[i] + flRadius + flDist;
| |
| }
| |
| }
| |
| }
| |
|
| |
|
| |
| //-----------------------------------------------------------------------------
| |
| // Compute a separating axis...
| |
| //-----------------------------------------------------------------------------
| |
| bool CClientShadowMgr::ComputeSeparatingPlane( IClientRenderable* pRend1, IClientRenderable* pRend2, cplane_t* pPlane )
| |
| {
| |
| Vector min1, max1, min2, max2;
| |
| pRend1->GetShadowRenderBounds( min1, max1, GetActualShadowCastType( pRend1 ) );
| |
| pRend2->GetShadowRenderBounds( min2, max2, GetActualShadowCastType( pRend2 ) );
| |
| return ::ComputeSeparatingPlane(
| |
| pRend1->GetRenderOrigin(), pRend1->GetRenderAngles(), min1, max1,
| |
| pRend2->GetRenderOrigin(), pRend2->GetRenderAngles(), min2, max2,
| |
| 3.0f, pPlane );
| |
| }
| |
|
| |
|
| |
| //-----------------------------------------------------------------------------
| |
| // Cull shadows based on rough bounding volumes
| |
| //-----------------------------------------------------------------------------
| |
| bool CClientShadowMgr::CullReceiver( ClientShadowHandle_t handle, IClientRenderable* pRenderable,
| |
| IClientRenderable* pSourceRenderable )
| |
| {
| |
| // check flags here instead and assert !pSourceRenderable
| |
| if( m_Shadows[handle].m_Flags & SHADOW_FLAGS_FLASHLIGHT )
| |
| {
| |
| 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..
| |
| Vector vecShadowDir = GetShadowDirection( handle );
| |
| float shadowDot = DotProduct( vecShadowDir, plane.normal );
| |
| float receiverDot = DotProduct( plane.normal, origin );
| |
| float sourceDot = DotProduct( plane.normal, originSource );
| |
|
| |
| if (shadowDot > 0.0f)
| |
| {
| |
| if (receiverDot <= sourceDot)
| |
| {
| |
| // Vector dest;
| |
| // VectorMA( pSourceRenderable->GetRenderOrigin(), 50, plane.normal, dest );
| |
| // debugoverlay->AddLineOverlay( pSourceRenderable->GetRenderOrigin(), dest, 255, 255, 0, true, 1.0f );
| |
| return true;
| |
| }
| |
| else
| |
| {
| |
| // Vector dest;
| |
| // VectorMA( pSourceRenderable->GetRenderOrigin(), 50, plane.normal, dest );
| |
| // debugoverlay->AddLineOverlay( pSourceRenderable->GetRenderOrigin(), dest, 255, 0, 0, true, 1.0f );
| |
| }
| |
| }
| |
| else
| |
| {
| |
| if (receiverDot >= sourceDot)
| |
| {
| |
| // Vector dest;
| |
| // VectorMA( pSourceRenderable->GetRenderOrigin(), -50, plane.normal, dest );
| |
| // debugoverlay->AddLineOverlay( pSourceRenderable->GetRenderOrigin(), dest, 255, 255, 0, true, 1.0f );
| |
| return true;
| |
| }
| |
| else
| |
| {
| |
| // Vector dest;
| |
| // VectorMA( pSourceRenderable->GetRenderOrigin(), 50, plane.normal, dest );
| |
| // debugoverlay->AddLineOverlay( pSourceRenderable->GetRenderOrigin(), dest, 255, 0, 0, true, 1.0f );
| |
| }
| |
| }
| |
| }
| |
|
| |
| // No additional clip planes? ok then it's a valid receiver
| |
| /*
| |
| if (shadow.m_ClipPlaneCount == 0)
| |
| return false;
| |
|
| |
| // Check the additional cull planes
| |
| int i;
| |
| for ( i = 0; i < shadow.m_ClipPlaneCount; ++i)
| |
| {
| |
| // Fast sphere cull
| |
| if (DotProduct( origin, shadow.m_ClipPlane[i] ) - radius > shadow.m_ClipDist[i])
| |
| return true;
| |
| }
| |
|
| |
| // More expensive box on plane side cull...
| |
| Vector vec[3];
| |
| Vector mins, maxs;
| |
| cplane_t plane;
| |
| AngleVectors( pRenderable->GetRenderAngles(), &vec[0], &vec[1], &vec[2] );
| |
| pRenderable->GetBounds( mins, maxs );
| |
|
| |
| for ( i = 0; i < shadow.m_ClipPlaneCount; ++i)
| |
| {
| |
| // Transform the plane into the space of the receiver
| |
| plane.normal.x = DotProduct( vec[0], shadow.m_ClipPlane[i] );
| |
| plane.normal.y = DotProduct( vec[1], shadow.m_ClipPlane[i] );
| |
| plane.normal.z = DotProduct( vec[2], shadow.m_ClipPlane[i] );
| |
|
| |
| plane.dist = shadow.m_ClipDist[i] - DotProduct( shadow.m_ClipPlane[i], pRenderable->GetRenderOrigin() );
| |
|
| |
| // If the box is on the front side of the plane, we're done.
| |
| if (BoxOnPlaneSide2( mins, maxs, &plane, 3.0f ) == 1)
| |
| return true;
| |
| }
| |
| */
| |
|
| |
| return false;
| |
| }
| |
|
| |
|
| |
| //-----------------------------------------------------------------------------
| |
| // deals with shadows being added to shadow receivers
| |
| //-----------------------------------------------------------------------------
| |
| void CClientShadowMgr::AddShadowToReceiver( ClientShadowHandle_t handle,
| |
| IClientRenderable* pRenderable, ShadowReceiver_t type )
| |
| {
| |
| ClientShadow_t &shadow = m_Shadows[handle];
| |
|
| |
| // Don't add a shadow cast by an object to itself...
| |
| IClientRenderable* pSourceRenderable = ClientEntityList().GetClientRenderableFromHandle( shadow.m_Entity );
| |
|
| |
| // NOTE: if pSourceRenderable == NULL, the source is probably a flashlight since there is no entity.
| |
| if (pSourceRenderable == pRenderable)
| |
| return;
| |
|
| |
| // Don't bother if this renderable doesn't receive shadows or light from flashlights
| |
| if( !pRenderable->ShouldReceiveProjectedTextures( SHADOW_FLAGS_PROJECTED_TEXTURE_TYPE_MASK ) )
| |
| return;
| |
|
| |
| // Cull if the origin is on the wrong side of a shadow clip plane....
| |
| if ( CullReceiver( handle, pRenderable, pSourceRenderable ) )
| |
| return;
| |
|
| |
| // Do different things depending on the receiver type
| |
| switch( type )
| |
| {
| |
| case SHADOW_RECEIVER_BRUSH_MODEL:
| |
|
| |
| if( shadow.m_Flags & SHADOW_FLAGS_FLASHLIGHT )
| |
| {
| |
| VPROF_BUDGET( "CClientShadowMgr::AddShadowToReceiver", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING );
| |
|
| |
| if( (!shadow.m_hTargetEntity) || IsFlashlightTarget( handle, pRenderable ) )
| |
| {
| |
| shadowmgr->AddShadowToBrushModel( shadow.m_ShadowHandle,
| |
| const_cast<model_t*>(pRenderable->GetModel()),
| |
| pRenderable->GetRenderOrigin(), pRenderable->GetRenderAngles() );
| |
|
| |
| shadowmgr->AddFlashlightRenderable( shadow.m_ShadowHandle, pRenderable );
| |
| }
| |
| }
| |
| else
| |
| {
| |
| shadowmgr->AddShadowToBrushModel( shadow.m_ShadowHandle,
| |
| const_cast<model_t*>(pRenderable->GetModel()),
| |
| pRenderable->GetRenderOrigin(), pRenderable->GetRenderAngles() );
| |
| }
| |
| break;
| |
|
| |
| case SHADOW_RECEIVER_STATIC_PROP:
| |
| // Don't add shadows to props if we're not using render-to-texture
| |
| if ( GetActualShadowCastType( handle ) == SHADOWS_RENDER_TO_TEXTURE )
| |
| {
| |
| // 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<model_t*>(pRenderable->GetModel());
| |
| shadowmgr->RemoveAllShadowsFromBrushModel( pModel );
| |
| }
| |
| break;
| |
|
| |
| case SHADOW_RECEIVER_STATIC_PROP:
| |
| staticpropmgr->RemoveAllShadowsFromStaticProp(pRenderable);
| |
| break;
| |
|
| |
| case SHADOW_RECEIVER_STUDIO_MODEL:
| |
| if( pRenderable && pRenderable->GetModelInstance() != MODEL_INSTANCE_INVALID )
| |
| {
| |
| shadowmgr->RemoveAllShadowsFromModel( pRenderable->GetModelInstance() );
| |
| }
| |
| break;
| |
|
| |
| // default:
| |
| // // FIXME: How do deal with this stuff? Add a method to IClientRenderable?
| |
| // C_BaseEntity* pEnt = static_cast<C_BaseEntity*>(pRenderable);
| |
| // pEnt->RemoveAllShadows();
| |
| }
| |
| }
| |
|
| |
|
| |
| //-----------------------------------------------------------------------------
| |
| // Computes + sets the render-to-texture texcoords
| |
| //-----------------------------------------------------------------------------
| |
| void CClientShadowMgr::SetRenderToTextureShadowTexCoords( ShadowHandle_t handle, int x, int y, int w, int h )
| |
| {
| |
| // Let the shadow mgr know about the texture coordinates...
| |
| // That way it'll be able to batch rendering better.
| |
| int textureW, textureH;
| |
| m_ShadowAllocator.GetTotalTextureSize( textureW, textureH );
| |
|
| |
| // Go in a half-pixel to avoid blending with neighboring textures..
| |
| float u, v, du, dv;
| |
|
| |
| u = ((float)x + 0.5f) / (float)textureW;
| |
| v = ((float)y + 0.5f) / (float)textureH;
| |
| du = ((float)w - 1) / (float)textureW;
| |
| dv = ((float)h - 1) / (float)textureH;
| |
|
| |
| shadowmgr->SetShadowTexCoord( handle, u, v, du, dv );
| |
| }
| |
|
| |
|
| |
| //-----------------------------------------------------------------------------
| |
| // Setup all children shadows
| |
| //-----------------------------------------------------------------------------
| |
| bool CClientShadowMgr::BuildSetupShadowHierarchy( IClientRenderable *pRenderable, const ClientShadow_t &shadow, bool bChild )
| |
| {
| |
| bool bDrewTexture = false;
| |
|
| |
| // Stop traversing when we hit a blobby shadow
| |
| ShadowType_t shadowType = GetActualShadowCastType( pRenderable );
| |
| if ( pRenderable && shadowType == SHADOWS_SIMPLE )
| |
| return false;
| |
|
| |
| if ( !pRenderable || shadowType != SHADOWS_NONE )
| |
| {
| |
| bool bDrawModelShadow;
| |
| if ( !bChild )
| |
| {
| |
| bDrawModelShadow = ((shadow.m_Flags & SHADOW_FLAGS_BRUSH_MODEL) == 0);
| |
| }
| |
| else
| |
| {
| |
| int nModelType = modelinfo->GetModelType( pRenderable->GetModel() );
| |
| bDrawModelShadow = nModelType == mod_studio;
| |
| }
| |
|
| |
| if ( bDrawModelShadow )
| |
| {
| |
| C_BaseEntity *pEntity = pRenderable->GetIClientUnknown()->GetBaseEntity();
| |
| if ( pEntity )
| |
| {
| |
| if ( pEntity->IsNPC() )
| |
| {
| |
| s_NPCShadowBoneSetups.AddToTail( assert_cast<C_BaseAnimating *>( pEntity ) );
| |
| }
| |
| else if ( pEntity->GetBaseAnimating() )
| |
| {
| |
| s_NonNPCShadowBoneSetups.AddToTail( assert_cast<C_BaseAnimating *>( pEntity ) );
| |
| }
| |
|
| |
| }
| |
| bDrewTexture = true;
| |
| }
| |
| }
| |
|
| |
| if ( !pRenderable )
| |
| return bDrewTexture;
| |
|
| |
| IClientRenderable *pChild;
| |
| for ( pChild = pRenderable->FirstShadowChild(); pChild; pChild = pChild->NextShadowPeer() )
| |
| {
| |
| if ( BuildSetupShadowHierarchy( pChild, shadow, true ) )
| |
| {
| |
| bDrewTexture = true;
| |
| }
| |
| }
| |
| return bDrewTexture;
| |
| }
| |
|
| |
| //-----------------------------------------------------------------------------
| |
| // Draws all children shadows into our own
| |
| //-----------------------------------------------------------------------------
| |
| bool CClientShadowMgr::DrawShadowHierarchy( IClientRenderable *pRenderable, const ClientShadow_t &shadow, bool bChild )
| |
| {
| |
| bool bDrewTexture = false;
| |
|
| |
| // Stop traversing when we hit a blobby shadow
| |
| ShadowType_t shadowType = GetActualShadowCastType( pRenderable );
| |
| if ( pRenderable && shadowType == SHADOWS_SIMPLE )
| |
| return false;
| |
|
| |
| if ( !pRenderable || shadowType != SHADOWS_NONE )
| |
| {
| |
| bool bDrawModelShadow;
| |
| bool bDrawBrushShadow;
| |
| if ( !bChild )
| |
| {
| |
| bDrawModelShadow = ((shadow.m_Flags & SHADOW_FLAGS_BRUSH_MODEL) == 0);
| |
| bDrawBrushShadow = !bDrawModelShadow;
| |
| }
| |
| else
| |
| {
| |
| int nModelType = modelinfo->GetModelType( pRenderable->GetModel() );
| |
| bDrawModelShadow = nModelType == mod_studio;
| |
| bDrawBrushShadow = nModelType == mod_brush;
| |
| }
| |
|
| |
| if ( 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*)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( shadow.m_ShadowHandle );
| |
| shadow.m_Flags |= SHADOW_FLAGS_USING_LOD_SHADOW;
| |
| }
| |
| }
| |
|
| |
|
| |
| //-----------------------------------------------------------------------------
| |
| // Advances to the next frame,
| |
| //-----------------------------------------------------------------------------
| |
| void CClientShadowMgr::AdvanceFrame()
| |
| {
| |
| // We're starting the next frame
| |
| m_ShadowAllocator.AdvanceFrame();
| |
| }
| |
|
| |
|
| |
| //-----------------------------------------------------------------------------
| |
| // Re-render shadow depth textures that lie in the leaf list
| |
| //-----------------------------------------------------------------------------
| |
| int CClientShadowMgr::BuildActiveShadowDepthList( const CViewSetup &viewSetup, int nMaxDepthShadows, ClientShadowHandle_t *pActiveDepthShadows )
| |
| {
| |
| int 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
| |
| if ( R_CullBox( vecAbsMins, vecAbsMaxs, viewFrustum ) )
| |
| {
| |
| 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;
| |
| }
| |
|
| |
|
| |
| //-----------------------------------------------------------------------------
| |
| // 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 );
| |
| }
| |
| }
| |
|
| |
|
| |
| //-----------------------------------------------------------------------------
| |
| // Re-render shadow depth textures that lie in the leaf list
| |
| //-----------------------------------------------------------------------------
| |
| void CClientShadowMgr::ComputeShadowDepthTextures( const CViewSetup &viewSetup )
| |
| {
| |
| 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] ];
| |
|
| |
| 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 );
| |
| continue;
| |
| }
| |
|
| |
| CViewSetup shadowView;
| |
| shadowView.m_flAspectRatio = 1.0f;
| |
| shadowView.x = shadowView.y = 0;
| |
| shadowView.width = shadowDepthTexture->GetActualWidth();
| |
| shadowView.height = shadowDepthTexture->GetActualHeight();
| |
| shadowView.m_bOrtho = false;
| |
| shadowView.m_bDoBloomAndToneMapping = false;
| |
|
| |
| // Copy flashlight parameters
| |
| const FlashlightState_t& flashlightState = shadowmgr->GetFlashlightState( shadow.m_ShadowHandle );
| |
| 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 );
| |
|
| |
| // 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;
| |
|
| |
| m_bThreaded = ( r_threaded_client_shadow_manager.GetBool() && g_pThreadPool->NumIdleThreads() );
| |
|
| |
| 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( s_NPCShadowBoneSetups.Base(), s_NPCShadowBoneSetups.Count(), &SetupBonesOnBaseAnimating );
| |
| ParallelProcess( 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;
| |
| }
| |
| 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 | | // TODO: Figure out why this still gets hit |
| Assert( pRenderable ); | | Assert( pRenderable ); |
Line 4,916: |
Line 560: |
| return; | | return; |
| } | | } |
|
| | |
| Vector bbMin, bbMax; | | Vector bbMin, bbMax; |
| pRenderable->GetRenderBoundsWorldspace( bbMin, bbMax ); | | pRenderable->GetRenderBoundsWorldspace( bbMin, bbMax ); |
| Vector origin( 0.5f * ( 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 | | origin.z = bbMin.z; // Putting origin at the bottom of the bounding box makes the shadows a little shorter |
|
| | |
| Vector lightPos; | | Vector lightPos; |
| Vector lightBrightness; | | Vector lightBrightness; |
|
| | |
| if ( shadow.m_LightPosLerp >= 1.0f ) // skip finding new light source if we're in the middle of a lerp | | if ( shadow.m_LightPosLerp >= 1.0f ) // Skip finding new light source if we're in the middle of a lerp |
| { | | { |
| // Calculate minimum brightness squared | | // Calculate minimum brightness squared |
| float flMinBrightnessSqr = r_worldlight_mincastintensity.GetFloat(); | | float flMinBrightnessSqr = r_worldlight_mincastintensity.GetFloat(); |
| flMinBrightnessSqr *= flMinBrightnessSqr; | | flMinBrightnessSqr *= flMinBrightnessSqr; |
|
| | |
| if(g_pWorldLights->GetBrightestLightSource(pRenderable->GetRenderOrigin(), lightPos, lightBrightness) == false || | | if ( g_pWorldLights->GetBrightestLightSource( pRenderable->GetRenderOrigin(), lightPos, lightBrightness ) == false || lightBrightness.LengthSqr() < flMinBrightnessSqr ) |
| lightBrightness.LengthSqr() < flMinBrightnessSqr )
| |
| { | | { |
| // didn't find a light source at all, use default shadow direction | | // Didn't find a light source at all, use default shadow direction |
| // TODO: Could switch to using blobby shadow in this case | | // TODO: Could switch to using blobby shadow in this case |
| lightPos.Init( FLT_MAX, FLT_MAX, FLT_MAX ); | | lightPos.Init( FLT_MAX, FLT_MAX, FLT_MAX ); |
| } | | } |
| } | | } |
|
| | |
| if ( shadow.m_LightPosLerp == FLT_MAX ) // first light pos ever, just init | | if ( shadow.m_LightPosLerp == FLT_MAX ) // First light pos ever, just init |
| { | | { |
| shadow.m_CurrentLightPos = lightPos; | | shadow.m_CurrentLightPos = lightPos; |
Line 4,949: |
Line 592: |
| { | | { |
| // We're in the middle of a lerp from current to target light. Finish it. | | // We're in the middle of a lerp from current to target light. Finish it. |
| shadow.m_LightPosLerp += gpGlobals->frametime * 1.0f/r_worldlight_lerptime.GetFloat(); | | shadow.m_LightPosLerp += gpGlobals->frametime * 1.0f / r_worldlight_lerptime.GetFloat(); |
| shadow.m_LightPosLerp = clamp( shadow.m_LightPosLerp, 0.0f, 1.0f ); | | shadow.m_LightPosLerp = clamp( shadow.m_LightPosLerp, 0.0f, 1.0f ); |
|
| | |
| Vector currLightPos( shadow.m_CurrentLightPos ); | | Vector currLightPos( shadow.m_CurrentLightPos ); |
| Vector targetLightPos( shadow.m_TargetLightPos ); | | Vector targetLightPos( shadow.m_TargetLightPos ); |
Line 4,962: |
Line 605: |
| targetLightPos = origin - 200.0f * GetShadowDirection(); | | targetLightPos = origin - 200.0f * GetShadowDirection(); |
| } | | } |
|
| | |
| // lerp light pos | | // Lerp light pos |
| Vector v1 = origin - shadow.m_CurrentLightPos; | | Vector v1 = origin - shadow.m_CurrentLightPos; |
| v1.NormalizeInPlace(); | | v1.NormalizeInPlace(); |
|
| | |
| Vector v2 = origin - shadow.m_TargetLightPos; | | Vector v2 = origin - shadow.m_TargetLightPos; |
| v2.NormalizeInPlace(); | | v2.NormalizeInPlace(); |
|
| | |
| // SAULUNDONE: caused over top sweeping far too often | | // SAULUNDONE: Caused over top sweeping far too often |
| #if 0 | | #if 0 |
| if ( v1.Dot( v2 ) < 0.0f ) | | 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 | | // 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 ); | | Vector fakeOverheadLightPos( origin.x, origin.y, origin.z + 200.0f ); |
| if( shadow.m_LightPosLerp < 0.5f ) | | if ( shadow.m_LightPosLerp < 0.5f ) |
| { | | { |
| lightPos = Lerp( 2.0f * shadow.m_LightPosLerp, currLightPos, fakeOverheadLightPos ); | | lightPos = Lerp( 2.0f * shadow.m_LightPosLerp, currLightPos, fakeOverheadLightPos ); |
Line 4,990: |
Line 633: |
| lightPos = Lerp( shadow.m_LightPosLerp, currLightPos, targetLightPos ); | | lightPos = Lerp( shadow.m_LightPosLerp, currLightPos, targetLightPos ); |
| } | | } |
|
| | |
| if ( shadow.m_LightPosLerp >= 1.0f ) | | if ( shadow.m_LightPosLerp >= 1.0f ) |
| { | | { |
Line 4,998: |
Line 641: |
| else if ( shadow.m_LightPosLerp >= 1.0f ) | | else if ( shadow.m_LightPosLerp >= 1.0f ) |
| { | | { |
| // check if we have a new closest light position and start a new lerp | | // Check if we have a new closest light position and start a new lerp |
| float flDistSq = ( lightPos - shadow.m_CurrentLightPos ).LengthSqr(); | | float flDistSq = ( lightPos - shadow.m_CurrentLightPos ).LengthSqr(); |
|
| | |
| if ( flDistSq > 1.0f ) | | if ( flDistSq > 1.0f ) |
| { | | { |
| // light position has changed, which means we got a new light source. Initiate a lerp | | // Light position has changed, which means we got a new light source. Initiate a lerp |
| shadow.m_TargetLightPos = lightPos; | | shadow.m_TargetLightPos = lightPos; |
| shadow.m_LightPosLerp = 0.0f; | | shadow.m_LightPosLerp = 0.0f; |
| } | | } |
|
| | |
| lightPos = shadow.m_CurrentLightPos; | | lightPos = shadow.m_CurrentLightPos; |
| } | | } |
|
| | |
| if ( lightPos.x == FLT_MAX ) | | if ( lightPos.x == FLT_MAX ) |
| { | | { |
| lightPos = origin - 200.0f * GetShadowDirection(); | | lightPos = origin - 200.0f * GetShadowDirection(); |
| } | | } |
|
| | |
| Vector vecResult( origin - lightPos ); | | Vector vecResult( origin - lightPos ); |
| vecResult.NormalizeInPlace(); | | vecResult.NormalizeInPlace(); |
|
| | |
| vecResult.z *= r_worldlight_shortenfactor.GetFloat(); | | vecResult.z *= r_worldlight_shortenfactor.GetFloat(); |
| vecResult.NormalizeInPlace(); | | vecResult.NormalizeInPlace(); |
|
| | |
| shadow.m_ShadowDir = vecResult; | | shadow.m_ShadowDir = vecResult; |
|
| | |
| if ( r_worldlight_debug.GetBool() ) | | if ( r_worldlight_debug.GetBool() ) |
| { | | { |
Line 5,029: |
Line 672: |
| } | | } |
| } | | } |
|
| | |
| | //----------------------------------------------------------------------------- |
| | // Purpose: |
| | //----------------------------------------------------------------------------- |
| void CClientShadowMgr::UpdateDirtyShadow( ClientShadowHandle_t handle ) | | void CClientShadowMgr::UpdateDirtyShadow( ClientShadowHandle_t handle ) |
| { | | { |
| Assert( m_Shadows.IsValidIndex( handle ) ); | | Assert( m_Shadows.IsValidIndex( handle ) ); |
| | | |
| if( IsShadowingFromWorldLights() ) | | if ( IsShadowingFromWorldLights() ) |
| UpdateShadowDirectionFromLocalLightSource( handle ); | | UpdateShadowDirectionFromLocalLightSource( handle ); |
| | | |
| UpdateProjectedTextureInternal( handle, false ); | | UpdateProjectedTextureInternal( handle, false ); |
| }
| |
|
| |
| void WorldLightCastShadowCallback(IConVar *pVar, const char *pszOldValue, float flOldValue)
| |
| {
| |
| s_ClientShadowMgr.SetShadowFromWorldLightsEnabled(r_worldlight_castshadows.GetBool());
| |
| }
| |
|
| |
| void CClientShadowMgr::SetShadowFromWorldLightsEnabled( bool bEnabled )
| |
| {
| |
| if(bEnabled == IsShadowingFromWorldLights())
| |
| return;
| |
|
| |
| m_bShadowFromWorldLights = bEnabled;
| |
| UpdateAllShadows();
| |
| } | | } |
|
| |
|
| //----------------------------------------------------------------------------- | | //----------------------------------------------------------------------------- |
| // A material proxy that resets the base texture to use the rendered shadow | | // Purpose: |
| //----------------------------------------------------------------------------- | | //----------------------------------------------------------------------------- |
| class CShadowProxy : public IMaterialProxy
| | void WorldLightCastShadowCallback( IConVar *pVar, const char *pszOldValue, float flOldValue ) |
| { | | { |
| public:
| | s_ClientShadowMgr.SetShadowFromWorldLightsEnabled( r_worldlight_castshadows.GetBool() ); |
| 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 )pProxyData;
| |
| 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 | | // Purpose: |
| //----------------------------------------------------------------------------- | | //----------------------------------------------------------------------------- |
| class CShadowModelProxy : public IMaterialProxy
| | void CClientShadowMgr::SetShadowFromWorldLightsEnabled( bool bEnabled ) |
| { | | { |
| public:
| | if ( bEnabled == IsShadowingFromWorldLights() ) |
| CShadowModelProxy();
| | return; |
| virtual ~CShadowModelProxy();
| | |
| virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues );
| | m_bShadowFromWorldLights = bEnabled; |
| virtual void OnBind( void *pProxyData );
| | UpdateAllShadows(); |
| 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 )pProxyData;
| |
| 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();
| |
| } | | } |
| | </source> |
|
| |
|
| EXPOSE_INTERFACE( CShadowModelProxy, IMaterialProxy, "ShadowModel" IMATERIAL_PROXY_INTERFACE_VERSION );
| | ==Complete source code of clientshadowmgr.cpp== |
|
| |
|
| </source>
| | See [[Dynamic RTT shadow angles in Source 2007/Clientshadowmgr.cpp]]. |
|
| |
|
| ==Conclusion== | | ==Conclusion== |
Line 5,210: |
Line 720: |
| Happy modding! | | Happy modding! |
|
| |
|
| | [[Category:Lighting]] |
| [[Category:Tutorials]] | | [[Category:Tutorials]] |
| [[Category:Programming]] | | [[Category:Programming]] |