//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include "quakedef.h" #include "lightcache.h" #include "cmodel_engine.h" #include "istudiorender.h" #include "studio_internal.h" #include "bspfile.h" #include "cdll_engine_int.h" #include "tier1/mempool.h" #include "gl_model_private.h" #include "r_local.h" #include "materialsystem/imaterialsystemhardwareconfig.h" #include "materialsystem/imaterialsystem.h" #include "materialsystem/imaterial.h" #include "materialsystem/imaterialvar.h" #include "l_studio.h" #include "debugoverlay.h" #include "worldsize.h" #include "ispatialpartitioninternal.h" #include "staticpropmgr.h" #include "cmodel_engine.h" #include "icliententitylist.h" #include "icliententity.h" #include "enginetrace.h" #include "client.h" #include "cl_main.h" #include "collisionutils.h" #include "tier0/vprof.h" #include "filesystem_engine.h" #include "mathlib/anorms.h" #include "gl_matsysiface.h" #include "materialsystem/materialsystem_config.h" #include "tier2/tier2.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" // EMIT_SURFACE LIGHTS: // // Dim emit_surface lights go in the ambient cube because there are a ton of them and they are often so dim that // they get filtered out by r_worldlightmin. // // (Dim) emit_surface lights only get calculated at runtime for static props because static props // do the full calculation of ambient lighting at runtime instead of using vrad's per-leaf // calculation. Vrad's calculation includes the emit_surfaces, so if we're NOT using it, then // we want to include emit_surface lights here. // this should be prime to make the hash better #define MAX_CACHE_ENTRY 200 #define MAX_CACHE_BUCKETS MAX_CACHE_ENTRY // number of bits per grid in x, y, z #define HASH_GRID_SIZEX 5 #define HASH_GRID_SIZEY 5 #define HASH_GRID_SIZEZ 7 #define LIGHTCACHE_SNAP_EPSILON 0.5f float Engine_WorldLightDistanceFalloff( const dworldlight_t *wl, const Vector& delta, bool bNoRadiusCheck = false ); float Engine_WorldLightAngle( const dworldlight_t *wl, const Vector& lnormal, const Vector& snormal, const Vector& delta ); #define MAX_LIGHTSTYLE_BITS MAX_LIGHTSTYLES #define MAX_LIGHTSTYLE_BYTES ( (MAX_LIGHTSTYLE_BITS + 7) / 8 ) static byte g_FrameMissCount = 0; static int g_FrameIndex = 0; ConVar lightcache_maxmiss("lightcache_maxmiss","2", FCVAR_CHEAT); #define NUMRANDOMNORMALS 162 static Vector s_raddir[NUMRANDOMNORMALS] = { #include "randomnormals.h" }; static ConVar r_lightcache_numambientsamples( "r_lightcache_numambientsamples", "162", FCVAR_CHEAT, "number of random directions to fire rays when computing ambient lighting", true, 1.0f, true, ( float )NUMRANDOMNORMALS ); ConVar r_ambientlightingonly( "r_ambientlightingonly", "0", FCVAR_CHEAT, "Set this to 1 to light models with only ambient lighting (and no static lighting)." ); ConVar r_oldlightselection("r_oldlightselection", "0", FCVAR_CHEAT, "Set this to revert to HL2's method of selecting lights"); static void ComputeAmbientFromSphericalSamples( const Vector& start, Vector* lightBoxColor ); //----------------------------------------------------------------------------- // Cache used to compute which lightcache entries computed this frame // may be able to be used temporarily for lighting other objects in the // case where we've got too many new lightcache samples in a single frame //----------------------------------------------------------------------------- struct CacheInfo_t { int x; int y; int z; int leaf; }; //----------------------------------------------------------------------------- // Flags to pass into LightIntensityAndDirectionAtPoint + LightIntensityAndDirectionInBox //----------------------------------------------------------------------------- enum LightIntensityFlags_t { LIGHT_NO_OCCLUSION_CHECK = 0x1, LIGHT_NO_RADIUS_CHECK = 0x2, LIGHT_OCCLUDE_VS_PROPS = 0x4, LIGHT_IGNORE_LIGHTSTYLE_VALUE = 0x8, }; //----------------------------------------------------------------------------- // Lightcache entry //----------------------------------------------------------------------------- enum { HACKLIGHTCACHEFLAGS_HASSWITCHABLELIGHTSTYLE = 0x1, HACKLIGHTCACHEFLAGS_HASNONSWITCHABLELIGHTSTYLE = 0x2, // flickering lights HACKLIGHTCACHEFLAGS_HASDONESTATICLIGHTING = 0x4, // for static props }; struct LightingStateInfo_t { float m_pIllum[MAXLOCALLIGHTS]; bool m_LightingStateHasSkylight; LightingStateInfo_t() { memset( this, 0, sizeof( *this ) ); } void Clear() { memset( this, 0, sizeof( *this ) ); } }; // This holds the shared data between lightcache_t and PropLightcache_t. // This way, PropLightcache_t can be about half the size, since it doesn't need a bunch of data in lightcache_t. class CBaseLightCache : public LightingStateInfo_t { public: CBaseLightCache() { m_pEnvCubemapTexture = NULL; memset( m_pLightstyles, 0, sizeof( m_pLightstyles ) ); m_LightingFlags = 0; m_LastFrameUpdated_LightStyles = -1; } bool HasLightStyle() { return ( m_LightingFlags & ( HACKLIGHTCACHEFLAGS_HASSWITCHABLELIGHTSTYLE | HACKLIGHTCACHEFLAGS_HASNONSWITCHABLELIGHTSTYLE ) ) ? true : false; } bool HasSwitchableLightStyle() { return ( m_LightingFlags & HACKLIGHTCACHEFLAGS_HASSWITCHABLELIGHTSTYLE ) ? true : false; } bool HasNonSwitchableLightStyle() { return ( m_LightingFlags & HACKLIGHTCACHEFLAGS_HASNONSWITCHABLELIGHTSTYLE ) ? true : false; } public: // cache for static lighting . . never changes after cache creation // preserved because static prop's color meshes are under cache control LightingState_t m_StaticLightingState; // cache for light styles LightingState_t m_LightStyleLightingState; // This includes m_StaticLightingState int m_LastFrameUpdated_LightStyles; LightingState_t m_DynamicLightingState; // This includes m_LightStyleLightingState int m_LastFrameUpdated_DynamicLighting; // FIXME: could just use m_LightStyleWorldLights.Count() if we are a static prop int m_LightingFlags; /* LightCacheFlags_t */ int leaf; unsigned char m_pLightstyles[MAX_LIGHTSTYLE_BYTES]; // for a dynamic prop, ideally the cache center if valid space, otherwise initial origin // for a static prop, the provided origin Vector m_LightingOrigin; // env_cubemap texture associated with this entry. ITexture * m_pEnvCubemapTexture; }; class lightcache_t : public CBaseLightCache { public: lightcache_t() { m_LastFrameUpdated_DynamicLighting = -1; } public: // Precalculated for the static lighting from AddWorldLightToLightingState. dworldlight_t *m_StaticPrecalc_LocalLight[MAXLOCALLIGHTS]; unsigned short m_StaticPrecalc_NumLocalLights; LightingStateInfo_t m_StaticPrecalc_LightingStateInfo; // the boxcolor is stored in m_StaticLightingState. // bucket singly linked list. unsigned short next; // index into lightcache unsigned short bucket; // index into lightbuckets // lru links unsigned short lru_prev; unsigned short lru_next; int x,y,z; }; struct PropLightcache_t : public CBaseLightCache { public: // Linked into s_pAllStaticProps. PropLightcache_t *m_pNextPropLightcache; unsigned int m_Flags; // corresponds to LIGHTCACHEFLAGS_* // stuff for pushing lights onto static props int m_DLightActive; // bit field for which dlights currently affect us. // recomputed by AddDlightsForStaticProps int m_DLightMarkFrame; // last frame in which a dlight was marked on this prop (helps detect lights that are marked but have moved away from this prop) CUtlVector m_LightStyleWorldLights; // This is a list of lights that affect this static prop cache entry. int m_SwitchableLightFrame; // This is the last frame that switchable lights were calculated. Vector mins; // fixme: make these smaller Vector maxs; // fixme: make these smaller bool HasDlights() { return m_DLightActive ? true : false; } PropLightcache_t() { m_Flags = 0; m_SwitchableLightFrame = -1; m_DLightActive = 0; m_DLightMarkFrame = 0; } }; ConVar r_worldlights ("r_worldlights", "4", 0, "number of world lights to use per vertex" ); ConVar r_radiosity ("r_radiosity", "4", FCVAR_CHEAT, "0: no radiosity\n1: radiosity with ambient cube (6 samples)\n2: radiosity with 162 samples\n3: 162 samples for static props, 6 samples for everything else" ); ConVar r_worldlightmin ("r_worldlightmin", "0.0002" ); ConVar r_avglight ("r_avglight", "1", FCVAR_CHEAT); static ConVar r_drawlightcache ("r_drawlightcache", "0", FCVAR_CHEAT, "0: off\n1: draw light cache entries\n2: draw rays\n"); static ConVar r_minnewsamples ("r_minnewsamples", "3"); static ConVar r_maxnewsamples ("r_maxnewsamples", "6"); static ConVar r_maxsampledist ("r_maxsampledist", "128"); static ConVar r_lightcachecenter ("r_lightcachecenter", "1", FCVAR_CHEAT ); // head and tail sentinels of the LRU #define LIGHT_LRU_HEAD_INDEX MAX_CACHE_ENTRY #define LIGHT_LRU_TAIL_INDEX (MAX_CACHE_ENTRY+1) static lightcache_t lightcache[MAX_CACHE_ENTRY + 2]; // the extra 2 are the head and tail static unsigned short lightbuckets[MAX_CACHE_BUCKETS]; static CClassMemoryPool s_PropCache( 256, CClassMemoryPool::GROW_SLOW ); // A memory pool of lightcache entries that is static int cached_r_worldlights = -1; static int cached_r_radiosity = -1; static int cached_r_avglight = -1; static int cached_mat_fullbright = -1; static int cached_r_lightcache_numambientsamples = -1; static PropLightcache_t* s_pAllStaticProps = NULL; // Used to convert RGB colors to greyscale intensity static Vector s_Grayscale( 0.299f, 0.587f, 0.114f ); #define BIT_SET( a, b ) ((a)[(b)>>3] & (1<<((b)&7))) inline unsigned short GetLightCacheIndex( const lightcache_t *pCache ) { return pCache - lightcache; } inline lightcache_t& GetLightLRUHead() { return lightcache[LIGHT_LRU_HEAD_INDEX]; } inline lightcache_t& GetLightLRUTail() { return lightcache[LIGHT_LRU_TAIL_INDEX]; } //----------------------------------------------------------------------------- // Purpose: Set up the LRU //----------------------------------------------------------------------------- void R_StudioInitLightingCache( void ) { unsigned short i; memset( lightcache, 0, sizeof(lightcache) ); for ( i=0; i < ARRAYSIZE( lightcache ); i++ ) lightcache[i].bucket = 0xFFFF; for ( i=0; i < ARRAYSIZE( lightbuckets ); i++ ) lightbuckets[i] = 0xFFFF; unsigned short last = LIGHT_LRU_HEAD_INDEX; // Link every node into the LRU for ( i = 0; i < MAX_CACHE_ENTRY-1; i++) { lightcache[i].lru_prev = last; lightcache[i].lru_next = i + 1; last = i; } // terminate the lru list lightcache[i].lru_prev = last; lightcache[i].lru_next = LIGHT_LRU_TAIL_INDEX; // link the sentinels lightcache[LIGHT_LRU_HEAD_INDEX].lru_next = 0; lightcache[LIGHT_LRU_TAIL_INDEX].lru_prev = i; // Lower number of lights on older hardware if ( g_pMaterialSystemHardwareConfig->MaxNumLights() < r_worldlights.GetInt() ) { r_worldlights.SetValue( g_pMaterialSystemHardwareConfig->MaxNumLights() ); } cached_r_worldlights = r_worldlights.GetInt(); cached_r_radiosity = r_radiosity.GetInt(); cached_r_avglight = r_avglight.GetInt(); cached_mat_fullbright = g_pMaterialSystemConfig->nFullbright; cached_r_lightcache_numambientsamples = r_lightcache_numambientsamples.GetInt(); // Recompute all static lighting InvalidateStaticLightingCache(); } void R_StudioCheckReinitLightingCache() { // Make sure this stays clamped to match hardware capabilities if ( g_pMaterialSystemHardwareConfig->MaxNumLights() < r_worldlights.GetInt() ) { r_worldlights.SetValue( g_pMaterialSystemHardwareConfig->MaxNumLights() ); } // Flush the lighting cache, if necessary if (cached_r_worldlights != r_worldlights.GetInt() || cached_r_radiosity != r_radiosity.GetInt() || cached_r_avglight != r_avglight.GetInt() || cached_mat_fullbright != g_pMaterialSystemConfig->nFullbright || cached_r_lightcache_numambientsamples != r_lightcache_numambientsamples.GetInt() ) { R_StudioInitLightingCache(); } } //----------------------------------------------------------------------------- // Purpose: Moves this cache entry to the end of the lru, i.e. marks it recently used // Input : *pcache - //----------------------------------------------------------------------------- static void LightcacheMark( lightcache_t *pcache ) { // don't link in static lighting if ( !pcache->lru_next && !pcache->lru_prev ) return; // already at tail if ( GetLightCacheIndex( pcache ) == lightcache[LIGHT_LRU_TAIL_INDEX].lru_prev ) return; // unlink pcache lightcache[pcache->lru_prev].lru_next = pcache->lru_next; lightcache[pcache->lru_next].lru_prev = pcache->lru_prev; // link to tail // patch backward link lightcache[GetLightLRUTail().lru_prev].lru_next = GetLightCacheIndex( pcache ); pcache->lru_prev = GetLightLRUTail().lru_prev; // patch forward link pcache->lru_next = LIGHT_LRU_TAIL_INDEX; GetLightLRUTail().lru_prev = GetLightCacheIndex( pcache ); } //----------------------------------------------------------------------------- // Purpose: Unlink a cache entry from its current bucket // Input : *pcache - //----------------------------------------------------------------------------- static void LightcacheUnlink( lightcache_t *pcache ) { unsigned short iBucket = pcache->bucket; // not used yet? if ( iBucket == 0xFFFF ) return; unsigned short iCache = GetLightCacheIndex( pcache ); // unlink it unsigned short plist = lightbuckets[iBucket]; if ( plist == iCache ) { // head of bucket? move bucket down lightbuckets[iBucket] = pcache->next; } else { bool found = false; // walk the bucket while ( plist != 0xFFFF ) { // if next is pcache, unlink pcache if ( lightcache[plist].next == iCache ) { lightcache[plist].next = pcache->next; found = true; break; } plist = lightcache[plist].next; } assert(found); } } //----------------------------------------------------------------------------- // Purpose: Get the least recently used cache entry //----------------------------------------------------------------------------- static lightcache_t *LightcacheGetLRU( void ) { // grab head lightcache_t *pcache = &lightcache[GetLightLRUHead().lru_next]; // move to tail LightcacheMark( pcache ); // unlink from the bucket LightcacheUnlink( pcache ); pcache->leaf = -1; return pcache; } //----------------------------------------------------------------------------- // Purpose: Quick & Dirty hashing function to bucket the cube in 4d parameter space //----------------------------------------------------------------------------- static int LightcacheHashKey( int x, int y, int z, int leaf ) { unsigned int key = (((x<<20) + (y<<8) + z) ^ (leaf)); key = key % MAX_CACHE_BUCKETS; return (int)key; } //----------------------------------------------------------------------------- // Compute the lightcache bucket given a position //----------------------------------------------------------------------------- static lightcache_t* FindInCache( int bucket, int x, int y, int z, int leaf ) { // loop over the entries in this bucket unsigned short iCache; for ( iCache = lightbuckets[bucket]; iCache != 0xFFFF; iCache = lightcache[iCache].next ) { lightcache_t *pCache = &lightcache[iCache]; // hit? if (pCache->x == x && pCache->y == y && pCache->z == z && pCache->leaf == leaf ) { return pCache; } } return 0; } //----------------------------------------------------------------------------- // Links to a bucket //----------------------------------------------------------------------------- static inline void LinkToBucket( int bucket, lightcache_t* pcache ) { pcache->next = lightbuckets[bucket]; lightbuckets[bucket] = GetLightCacheIndex( pcache ); // point back to the bucket pcache->bucket = (unsigned short)bucket; } //----------------------------------------------------------------------------- // Links in a new lightcache entry //----------------------------------------------------------------------------- static lightcache_t* NewLightcacheEntry( int bucket ) { // re-use the LRU cache entry lightcache_t* pcache = LightcacheGetLRU(); LinkToBucket( bucket, pcache ); return pcache; } //----------------------------------------------------------------------------- // Compute the lightcache origin //----------------------------------------------------------------------------- #if 0 static inline void ComputeLightcacheOrigin( int x, int y, int z, Vector& org ) { // this is suspicious and *maybe* wrong // the bucket origin can't re-establish the correct negative numbers // because of the non-arithmetic shift down? int ix = x << HASH_GRID_SIZEX; int iy = y << HASH_GRID_SIZEY; int iz = z << HASH_GRID_SIZEZ; org.Init( ix, iy, iz ); } #endif //----------------------------------------------------------------------------- // Compute the lightcache bounds given a point //----------------------------------------------------------------------------- void ComputeLightcacheBounds( const Vector &vecOrigin, Vector *pMins, Vector *pMaxs ) { bool bXPos = (vecOrigin[0] >= 0); bool bYPos = (vecOrigin[1] >= 0); bool bZPos = (vecOrigin[2] >= 0); // can't snap and shift negative values // truncate positive number and shift int ix = ((int)(fabs(vecOrigin[0]))) >> HASH_GRID_SIZEX; int iy = ((int)(fabs(vecOrigin[1]))) >> HASH_GRID_SIZEY; int iz = ((int)(fabs(vecOrigin[2]))) >> HASH_GRID_SIZEZ; // mins is floored as fixup depending on <0 or >0 pMins->x = (bXPos ? ix : -(ix + 1)) << HASH_GRID_SIZEX; pMins->y = (bYPos ? iy : -(iy + 1)) << HASH_GRID_SIZEY; pMins->z = (bZPos ? iz : -(iz + 1)) << HASH_GRID_SIZEZ; // maxs is exactly one grid increasing from mins pMaxs->x = pMins->x + (1 << HASH_GRID_SIZEX ); pMaxs->y = pMins->y + (1 << HASH_GRID_SIZEY ); pMaxs->z = pMins->z + (1 << HASH_GRID_SIZEZ ); Assert( (pMins->x <= vecOrigin.x) && (pMins->y <= vecOrigin.y) && (pMins->z <= vecOrigin.z) ); Assert( (pMaxs->x >= vecOrigin.x) && (pMaxs->y >= vecOrigin.y) && (pMaxs->z >= vecOrigin.z) ); } //----------------------------------------------------------------------------- // Compute the cache origin suitable for key //----------------------------------------------------------------------------- static inline void OriginToCacheOrigin( const Vector &origin, int &x, int &y, int &z ) { x = ((int)origin[0] + 32768) >> HASH_GRID_SIZEX; y = ((int)origin[1] + 32768) >> HASH_GRID_SIZEY; z = ((int)origin[2] + 32768) >> HASH_GRID_SIZEZ; } //----------------------------------------------------------------------------- // Finds ambient lights //----------------------------------------------------------------------------- dworldlight_t* FindAmbientLight() { // find any ambient lights for (int i = 0; i < host_state.worldbrush->numworldlights; i++) { if (host_state.worldbrush->worldlights[i].type == emit_skyambient) { return &host_state.worldbrush->worldlights[i]; } } return 0; } //----------------------------------------------------------------------------- // Computes the ambient term from a particular surface //----------------------------------------------------------------------------- static void ComputeAmbientFromSurface( SurfaceHandle_t surfID, dworldlight_t* pSkylight, Vector& radcolor ) { if (IS_SURF_VALID( surfID ) ) { // If we hit the sky, use the sky ambient if (MSurf_Flags( surfID ) & SURFDRAW_SKY) { if (pSkylight) { // add in sky ambient VectorCopy( pSkylight->intensity, radcolor ); } } else { Vector reflectivity; MSurf_TexInfo( surfID )->material->GetReflectivity( reflectivity ); VectorMultiply( radcolor, reflectivity, radcolor ); } } } //----------------------------------------------------------------------------- // Computes the ambient term from a large number of spherical samples //----------------------------------------------------------------------------- static void ComputeAmbientFromSphericalSamples( const Vector& start, Vector* lightBoxColor ) { VPROF( "ComputeAmbientFromSphericalSamples" ); // find any ambient lights dworldlight_t *pSkylight = FindAmbientLight(); Vector radcolor[NUMRANDOMNORMALS]; Assert( cached_r_lightcache_numambientsamples <= ARRAYSIZE( radcolor ) ); // sample world by casting N rays distributed across a sphere Vector upend; int i; for ( i = 0; i < cached_r_lightcache_numambientsamples; i++) { // FIXME: a good optimization would be to scale this per leaf VectorMA( start, COORD_EXTENT * 1.74, g_anorms[i], upend ); // Now that we've got a ray, see what surface we've hit SurfaceHandle_t surfID = R_LightVec (start, upend, false, radcolor[i] ); if (!IS_SURF_VALID(surfID) ) continue; ComputeAmbientFromSurface( surfID, pSkylight, radcolor[i] ); } // accumulate samples into radiant box const Vector* pBoxDirs = g_pStudioRender->GetAmbientLightDirections(); for (int j = g_pStudioRender->GetNumAmbientLightSamples(); --j >= 0; ) { float c, t; t = 0; lightBoxColor[j][0] = 0; lightBoxColor[j][1] = 0; lightBoxColor[j][2] = 0; for (i = 0; i < cached_r_lightcache_numambientsamples; i++) { c = DotProduct( g_anorms[i], pBoxDirs[j] ); if (c > 0) { t += c; VectorMA( lightBoxColor[j], c, radcolor[i], lightBoxColor[j] ); } } VectorMultiply( lightBoxColor[j], 1/t, lightBoxColor[j] ); } } static void ComputeAmbientFromLeaf( const Vector &start, int leafID, Vector *lightBoxColor, bool *bAddedLeafAmbientCube ) { if( leafID >= 0 ) { Mod_LeafAmbientColorAtPos( lightBoxColor, start, leafID ); // The ambient lighting in the leaves has the emit_surface lights factored in. *bAddedLeafAmbientCube = true; } else { int i; for( i = 0; i < 6; i++ ) { lightBoxColor[i].Init( 0.0f, 0.0f, 0.0f ); } } } //----------------------------------------------------------------------------- // Computes the ambient term from 6 cardinal directions //----------------------------------------------------------------------------- static void ComputeAmbientFromAxisAlignedSamples( const Vector& start, Vector* lightBoxColor ) { Vector upend; // find any ambient lights dworldlight_t *pSkylight = FindAmbientLight(); // sample world only along cardinal axes const Vector* pBoxDirs = g_pStudioRender->GetAmbientLightDirections(); for (int i = 0; i < 6; i++) { VectorMA( start, COORD_EXTENT * 1.74, pBoxDirs[i], upend ); // Now that we've got a ray, see what surface we've hit SurfaceHandle_t surfID = R_LightVec (start, upend, false, lightBoxColor[i] ); if (!IS_SURF_VALID( surfID ) ) continue; ComputeAmbientFromSurface( surfID, pSkylight, lightBoxColor[i] ); } } //----------------------------------------------------------------------------- // Computes the ambient lighting at a point, and sets the lightstyles bitfield //----------------------------------------------------------------------------- static void R_StudioGetAmbientLightForPoint( int leafID, const Vector& start, Vector* pLightBoxColor, bool bIsStaticProp, bool *bAddedLeafAmbientCube ) { *bAddedLeafAmbientCube = false; VPROF( "R_StudioGetAmbientLightForPoint" ); int i; if ( g_pMaterialSystemConfig->nFullbright == 1 ) { for (i = g_pStudioRender->GetNumAmbientLightSamples(); --i >= 0; ) { VectorFill( pLightBoxColor[i], 1.0 ); } return; } switch( r_radiosity.GetInt() ) { case 1: ComputeAmbientFromAxisAlignedSamples( start, pLightBoxColor ); break; case 2: ComputeAmbientFromSphericalSamples( start, pLightBoxColor ); break; case 3: if (bIsStaticProp) ComputeAmbientFromSphericalSamples( start, pLightBoxColor ); else ComputeAmbientFromAxisAlignedSamples( start, pLightBoxColor ); break; case 4: if (bIsStaticProp) ComputeAmbientFromSphericalSamples( start, pLightBoxColor ); else ComputeAmbientFromLeaf( start, leafID, pLightBoxColor, bAddedLeafAmbientCube ); break; default: // assume no bounced light from the world for (i = g_pStudioRender->GetNumAmbientLightSamples(); --i >= 0; ) { VectorFill( pLightBoxColor[i], 0 ); } } } //----------------------------------------------------------------------------- // This filter bumps against the world + all but one prop //----------------------------------------------------------------------------- class CTraceFilterWorldAndProps : public ITraceFilter { public: CTraceFilterWorldAndProps( IHandleEntity *pHandleEntity ) : m_pIgnoreProp( pHandleEntity ) {} bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) { // We only bump against props + we ignore one particular prop if ( !StaticPropMgr()->IsStaticProp( pHandleEntity ) ) return false; return ( pHandleEntity != m_pIgnoreProp ); } virtual TraceType_t GetTraceType() const { return TRACE_EVERYTHING_FILTER_PROPS; } private: IHandleEntity *m_pIgnoreProp; }; static float LightIntensityAndDirectionAtPointOld( dworldlight_t* pLight, const Vector& mid, int fFlags, IHandleEntity *pIgnoreEnt, Vector *pDirection ) { CTraceFilterWorldOnly worldTraceFilter; CTraceFilterWorldAndProps propTraceFilter( pIgnoreEnt ); ITraceFilter *pTraceFilter = &worldTraceFilter; if (fFlags & LIGHT_OCCLUDE_VS_PROPS) { pTraceFilter = &propTraceFilter; } // Special case lights switch (pLight->type) { case emit_skylight: { // There can be more than one skylight, but we should only // ever be affected by one of them (multiple ones are created from // a single light in vrad) VectorFill( *pDirection, 0 ); // check to see if you can hit the sky texture Vector end; VectorMA( mid, -COORD_EXTENT * 1.74f, pLight->normal, end ); // max_range * sqrt(3) trace_t tr; Ray_t ray; ray.Init( mid, end ); g_pEngineTraceClient->TraceRay( ray, MASK_OPAQUE, pTraceFilter, &tr ); // Here, we didn't hit the sky, so we must be in shadow if ( !(tr.surface.flags & SURF_SKY) ) return 0.0f; // fudge delta and dist for skylights *pDirection = -pLight->normal; return 1.0f; } case emit_skyambient: // always ignore these return 0.0f; } // all other lights // check distance VectorSubtract( pLight->origin, mid, *pDirection ); float ratio = Engine_WorldLightDistanceFalloff( pLight, *pDirection, (fFlags & LIGHT_NO_RADIUS_CHECK) != 0 ); // Add in light style component if( !( fFlags & LIGHT_IGNORE_LIGHTSTYLE_VALUE ) ) { ratio *= LightStyleValue( pLight->style ); } // Early out for really low-intensity lights // That way we don't need to ray-cast or normalize float intensity = max( pLight->intensity[0], pLight->intensity[1] ); intensity = max(intensity, pLight->intensity[2] ); // This is about 1/256 // See the comment titled "EMIT_SURFACE LIGHTS" at the top for info about why we don't // test emit_surface lights here. if ( pLight->type != emit_surface ) { if (intensity * ratio < r_worldlightmin.GetFloat() ) return 0.0f; } float dist = VectorNormalize( *pDirection ); if ( fFlags & LIGHT_NO_OCCLUSION_CHECK ) return ratio; trace_t pm; Ray_t ray; ray.Init( mid, pLight->origin ); g_pEngineTraceClient->TraceRay( ray, MASK_OPAQUE, pTraceFilter, &pm ); // hack if ( (1.f-pm.fraction) * dist > 8 ) { #ifndef SWDS if (r_drawlightcache.GetInt() == 2) { CDebugOverlay::AddLineOverlay( mid, pm.endpos, 255, 0, 0, 255, true, 3 ); } #endif return 0.f; } return ratio; } //----------------------------------------------------------------------------- // This method returns the effective intensity of a light as seen from // a particular point. PVS is used to speed up the task. //----------------------------------------------------------------------------- static float LightIntensityAndDirectionAtPointNew( dworldlight_t* pLight, lightzbuffer_t *pZBuf, const Vector& mid, int fFlags, IHandleEntity *pIgnoreEnt, Vector *pDirection ) { CTraceFilterWorldOnly worldTraceFilter; CTraceFilterWorldAndProps propTraceFilter( pIgnoreEnt ); ITraceFilter *pTraceFilter = &worldTraceFilter; if (fFlags & LIGHT_OCCLUDE_VS_PROPS) { pTraceFilter = &propTraceFilter; } // Special case lights switch (pLight->type) { case emit_skylight: { // There can be more than one skylight, but we should only // ever be affected by one of them (multiple ones are created from // a single light in vrad) VectorFill( *pDirection, 0 ); // check to see if you can hit the sky texture Vector end; VectorMA( mid, -COORD_EXTENT * 1.74f, pLight->normal, end ); // max_range * sqrt(3) trace_t tr; Ray_t ray; ray.Init( mid, end ); g_pEngineTraceClient->TraceRay( ray, MASK_OPAQUE, pTraceFilter, &tr ); // Here, we didn't hit the sky, so we must be in shadow if ( !(tr.surface.flags & SURF_SKY) ) return 0.0f; // fudge delta and dist for skylights *pDirection = -pLight->normal; return 1.0f; } case emit_skyambient: // always ignore these return 0.0f; } // all other lights // check distance VectorSubtract( pLight->origin, mid, *pDirection ); float ratio = Engine_WorldLightDistanceFalloff( pLight, *pDirection, (fFlags & LIGHT_NO_RADIUS_CHECK) != 0 ); // Add in light style component if( !( fFlags & LIGHT_IGNORE_LIGHTSTYLE_VALUE ) ) { ratio *= LightStyleValue( pLight->style ); } // Early out for really low-intensity lights // That way we don't need to ray-cast or normalize float intensity = fpmax( pLight->intensity[0], pLight->intensity[1] ); intensity = fpmax(intensity, pLight->intensity[2] ); // This is about 1/256 // See the comment titled "EMIT_SURFACE LIGHTS" at the top for info about why we don't // test emit_surface lights here. if ( pLight->type != emit_surface ) { if (intensity * ratio < r_worldlightmin.GetFloat() ) return 0.0f; } float dist = VectorNormalize( *pDirection ); if ( fFlags & LIGHT_NO_OCCLUSION_CHECK ) return ratio; float flTraceDistance = dist; // check if we are so close to the light that we shouldn't use our coarse z buf if ( dist - ( 1 << HASH_GRID_SIZEZ ) < 8 * SHADOW_ZBUF_RES ) pZBuf = NULL; Vector epnt = mid; LightShadowZBufferSample_t *pSample = NULL; if ( pZBuf ) { pSample = &( pZBuf->GetSample( *pDirection ) ); if ( ( pSample->m_flHitDistance < pSample->m_flTraceDistance ) || ( pSample->m_flTraceDistance >= dist ) ) { // hit! if ( dist > pSample->m_flHitDistance + 8 ) // shadow hit { #ifndef SWDS if (r_drawlightcache.GetInt() == 2 ) { CDebugOverlay::AddLineOverlay( mid, pLight->origin, 0, 0, 0, 255, true, 3 ); } #endif return 0; } else { #ifndef SWDS if (r_drawlightcache.GetInt() == 2 ) { CDebugOverlay::AddLineOverlay( mid, pLight->origin, 0, 255, 0, 255, true, 3 ); } #endif return ratio; } } // cache miss flTraceDistance = max( 100.0, 2.0 * dist ); // trace a little further for better caching epnt += ( dist - flTraceDistance ) * ( *pDirection ); } trace_t pm; Ray_t ray; ray.Init( pLight->origin, epnt ); // trace from light to object g_pEngineTraceClient->TraceRay( ray, MASK_OPAQUE, pTraceFilter, &pm ); float flHitDistance = ( pm.startsolid ) ? FLT_EPSILON : ( pm.fraction ) * flTraceDistance; if ( pSample ) { pSample->m_flTraceDistance = flTraceDistance; pSample->m_flHitDistance = ( pm.fraction >= 1.0 ) ? 1.0e23 : flHitDistance; } if ( dist > flHitDistance + 8) { #ifndef SWDS if (r_drawlightcache.GetInt() == 2 ) { CDebugOverlay::AddLineOverlay( mid, pLight->origin, 255, 0, 0, 255, true, 3 ); } #endif return 0.f; } return ratio; } static float LightIntensityAndDirectionAtPoint( dworldlight_t* pLight, lightzbuffer_t *pZBuf, const Vector& mid, int fFlags, IHandleEntity *pIgnoreEnt, Vector *pDirection ) { if ( pZBuf ) return LightIntensityAndDirectionAtPointNew( pLight, pZBuf, mid, fFlags, pIgnoreEnt, pDirection ); else return LightIntensityAndDirectionAtPointOld( pLight, mid, fFlags, pIgnoreEnt, pDirection ); #if 0 float old = LightIntensityAndDirectionAtPointOld( pLight, mid, fFlags, pIgnoreEnt, pDirection ); float newf = LightIntensityAndDirectionAtPointNew( pLight, pZBuf, mid, fFlags, pIgnoreEnt, pDirection ); if ( old != newf ) { float old2 = LightIntensityAndDirectionAtPointOld( pLight, mid, fFlags, pIgnoreEnt, pDirection ); float newf2 = LightIntensityAndDirectionAtPointNew( pLight, pZBuf, mid, fFlags, pIgnoreEnt, pDirection ); } return newf; #endif } //----------------------------------------------------------------------------- // This method returns the effective intensity of a light as seen within // a particular box... // a particular point. PVS is used to speed up the task. //----------------------------------------------------------------------------- static float LightIntensityAndDirectionInBox( dworldlight_t* pLight, lightzbuffer_t *pZBuf, const Vector &mid, const Vector &mins, const Vector &maxs, int fFlags, Vector *pDirection ) { // Choose the point closest on the box to the light to get max intensity // within the box.... if ( !r_oldlightselection.GetBool() ) { switch (pLight->type) { case emit_spotlight: // directional & positional { float sphereRadius = (maxs-mid).Length(); // first do a sphere/sphere check float dist = (pLight->origin - mid).Length(); if ( dist > (sphereRadius + pLight->radius) ) return 0; // PERFORMANCE: precalc this and store in the light? float angle = acos(pLight->stopdot2); float sinAngle = sin(angle); if ( !IsSphereIntersectingCone( mid, sphereRadius, pLight->origin, pLight->normal, sinAngle, pLight->stopdot2 ) ) return 0; } // NOTE: fall through to radius check in point case case emit_point: { float distSqr = CalcSqrDistanceToAABB( mins, maxs, pLight->origin ); if ( distSqr > pLight->radius * pLight->radius ) return 0; } break; case emit_surface: // directional & positional, fixed cone size { float sphereRadius = (maxs-mid).Length(); // first do a sphere/sphere check float dist = (pLight->origin - mid).Length(); if ( dist > (sphereRadius + pLight->radius) ) return 0; // PERFORMANCE: precalc this and store in the light? if ( !IsSphereIntersectingCone( mid, sphereRadius, pLight->origin, pLight->normal, 1.0f, 0.0f ) ) return 0; } break; } } else { // NOTE: Here, we do radius check to check to see if we should even care about the light // because we want to check the closest point in the box switch (pLight->type) { case emit_point: case emit_spotlight: // directional & positional { Vector vecClosestPoint; vecClosestPoint.Init(); for ( int i = 0; i < 3; ++i ) { vecClosestPoint[i] = clamp( pLight->origin[i], mins[i], maxs[i] ); } vecClosestPoint -= pLight->origin; if ( vecClosestPoint.LengthSqr() > pLight->radius * pLight->radius ) return 0; } break; } } return LightIntensityAndDirectionAtPoint( pLight, pZBuf, mid, fFlags | LIGHT_NO_RADIUS_CHECK, NULL, pDirection ); } //----------------------------------------------------------------------------- // Computes the static vertex lighting term from a large number of spherical samples //----------------------------------------------------------------------------- bool ComputeVertexLightingFromSphericalSamples( const Vector& vecVertex, const Vector &vecNormal, IHandleEntity *pIgnoreEnt, Vector *pLinearColor ) { if ( IsX360() ) return false; // Check to see if this vertex is in solid trace_t tr; CTraceFilterWorldAndProps filter( pIgnoreEnt ); Ray_t ray; ray.Init( vecVertex, vecVertex ); g_pEngineTraceClient->TraceRay( ray, MASK_OPAQUE, &filter, &tr ); if ( tr.startsolid || tr.allsolid ) return false; pLinearColor->Init( 0, 0, 0 ); // find any ambient lights dworldlight_t *pSkylight = FindAmbientLight(); // sample world by casting N rays distributed across a sphere float t = 0.0f; Vector upend, color; int i; for ( i = 0; i < cached_r_lightcache_numambientsamples; i++) { float flDot = DotProduct( vecNormal, s_raddir[i] ); if ( flDot < 0.0f ) continue; // FIXME: a good optimization would be to scale this per leaf VectorMA( vecVertex, COORD_EXTENT * 1.74, s_raddir[i], upend ); // Now that we've got a ray, see what surface we've hit SurfaceHandle_t surfID = R_LightVec( vecVertex, upend, false, color ); if ( !IS_SURF_VALID(surfID) ) continue; // FIXME: Maybe make sure we aren't obstructed by static props? // To do this, R_LightVec would need to return distance of hit... // Or, we need another arg to R_LightVec to return black when hitting a static prop ComputeAmbientFromSurface( surfID, pSkylight, color ); t += flDot; VectorMA( *pLinearColor, flDot, color, *pLinearColor ); } if (t != 0.0f) { *pLinearColor /= t; } // Now deal with direct lighting bool bHasSkylight = false; // Figure out the PVS info for this location int leaf = CM_PointLeafnum( vecVertex ); const byte* pVis = CM_ClusterPVS( CM_LeafCluster( leaf ) ); // Now add in the direct lighting Vector vecDirection; for (i = 0; i < host_state.worldbrush->numworldlights; ++i) { dworldlight_t *wl = &host_state.worldbrush->worldlights[i]; // FIXME: This is sort of a hack; only one skylight is allowed in the // lighting... if ((wl->type == emit_skylight) && bHasSkylight) continue; // only do it if the entity can see into the lights leaf if ((wl->cluster < 0) || (!BIT_SET( pVis, wl->cluster )) ) continue; float flRatio = LightIntensityAndDirectionAtPoint( wl, NULL, vecVertex, LIGHT_OCCLUDE_VS_PROPS, pIgnoreEnt, &vecDirection ); // No light contribution? Get outta here! if ( flRatio <= 0.0f ) continue; // Check if we've got a skylight if ( wl->type == emit_skylight ) bHasSkylight = true; // Figure out spotlight attenuation float flAngularRatio = Engine_WorldLightAngle( wl, wl->normal, vecNormal, vecDirection ); // Add in the direct lighting VectorMAInline( *pLinearColor, flAngularRatio * flRatio, wl->intensity, *pLinearColor ); } return true; } //----------------------------------------------------------------------------- // Finds the minimum light //----------------------------------------------------------------------------- static int FindDarkestWorldLight( int numLights, float* pLightIllum, float newIllum ) { // FIXME: make the list sorted? int minLightIndex = -1; float minillum = newIllum; for (int j = 0; j < numLights; ++j) { // only check ones dimmer than have already been checked if (pLightIllum[j] < minillum) { minillum = pLightIllum[j]; minLightIndex = j; } } return minLightIndex; } //----------------------------------------------------------------------------- // Adds a world light to the ambient cube //----------------------------------------------------------------------------- static void AddWorldLightToLightCube( dworldlight_t* pWorldLight, Vector* pBoxColor, const Vector& direction, float ratio ) { if (ratio == 0.0f) return; // add whatever didn't stay in the list to lightBoxColor // FIXME: This method is a guess, I don't know how it should be done const Vector* pBoxDir = g_pStudioRender->GetAmbientLightDirections(); for (int j = g_pStudioRender->GetNumAmbientLightSamples(); --j >= 0; ) { float t = DotProduct( pBoxDir[j], direction ); if (t > 0) { VectorMAInline( pBoxColor[j], ratio * t, pWorldLight->intensity, pBoxColor[j] ); } } } //----------------------------------------------------------------------------- // Adds a world light to the ambient cube //----------------------------------------------------------------------------- void AddWorldLightToAmbientCube( dworldlight_t* pWorldLight, const Vector &vecLightingOrigin, AmbientCube_t &ambientCube ) { Vector vecDirection; float ratio = LightIntensityAndDirectionAtPoint( pWorldLight, NULL, vecLightingOrigin, 0, NULL, &vecDirection ); float angularRatio = Engine_WorldLightAngle( pWorldLight, pWorldLight->normal, vecDirection, vecDirection ); AddWorldLightToLightCube( pWorldLight, ambientCube, vecDirection, ratio * angularRatio ); } static inline const byte* FastRejectLightSource( bool bIgnoreVis, const byte *pVis, const Vector &bucketOrigin, int lightType, int lightCluster, bool &bReject ) { bReject = false; if( !bIgnoreVis ) { // This is an optimization to avoid decompressing Vis twice if (!pVis) { // Figure out the PVS info for this location int bucketOriginLeaf = CM_PointLeafnum( bucketOrigin ); pVis = CM_ClusterPVS( CM_LeafCluster( bucketOriginLeaf ) ); } if ( lightType == emit_skylight ) { int bucketOriginLeaf = CM_PointLeafnum( bucketOrigin ); mleaf_t *pLeaf = &host_state.worldbrush->leafs[bucketOriginLeaf]; if ( pLeaf && !( pLeaf->flags & ( LEAF_FLAGS_SKY | LEAF_FLAGS_SKY2D ) ) ) { bReject = true; } } else { if ((lightCluster < 0) || (!BIT_SET( pVis, lightCluster )) ) bReject = true; } } return pVis; } //----------------------------------------------------------------------------- // Adds a world light to the list of lights //----------------------------------------------------------------------------- static const byte *AddWorldLightToLightingState( dworldlight_t* pWorldLight, lightzbuffer_t* pZBuf, LightingState_t& lightingState, LightingStateInfo_t& info, const Vector& bucketOrigin, const byte* pVis, bool dynamic = false, bool bIgnoreVis = false, bool bIgnoreVisTest = false ) { Assert( lightingState.numlights >= 0 && lightingState.numlights <= MAXLOCALLIGHTS ); // FIXME: This is sort of a hack; only one skylight is allowed in the // lighting... if ((pWorldLight->type == emit_skylight) && info.m_LightingStateHasSkylight) return pVis; // only do it if the entity can see into the lights leaf if ( !bIgnoreVisTest ) { bool bReject; pVis = FastRejectLightSource( bIgnoreVis, pVis, bucketOrigin, pWorldLight->type, pWorldLight->cluster, bReject ); if ( bReject ) return pVis; } // Get the lighting ratio Vector direction; float ratio; if (!dynamic && r_oldlightselection.GetBool()) { ratio = LightIntensityAndDirectionAtPoint( pWorldLight, pZBuf, bucketOrigin, 0, NULL, &direction ); } else { Vector mins, maxs; ComputeLightcacheBounds( bucketOrigin, &mins, &maxs ); ratio = LightIntensityAndDirectionInBox( pWorldLight, pZBuf, bucketOrigin, mins, maxs, dynamic ? LIGHT_NO_OCCLUSION_CHECK : 0, &direction ); } // No light contribution? Get outta here! if ( ratio <= 0.0f ) return pVis; // Check if we've got a skylight if (pWorldLight->type == emit_skylight) info.m_LightingStateHasSkylight = true; // Figure out spotlight attenuation float angularRatio = Engine_WorldLightAngle( pWorldLight, pWorldLight->normal, direction, direction ); // Use standard RGB to gray conversion float illum = ratio * DotProduct( pWorldLight->intensity, s_Grayscale ); // Don't multiply by cone angle? // It can't be a local light if it's too dark // See the comment titled "EMIT_SURFACE LIGHTS" at the top for info. if (pWorldLight->type == emit_surface || illum >= r_worldlightmin.GetFloat()) // FIXME: tune this value { int nWorldLights = min( g_pMaterialSystemHardwareConfig->MaxNumLights(), r_worldlights.GetInt() ); // if remaining slots, add to list if ( lightingState.numlights < nWorldLights ) { // save pointer to world light lightingState.locallight[lightingState.numlights] = pWorldLight; info.m_pIllum[lightingState.numlights] = illum; ++lightingState.numlights; return pVis; } // no remaining slots // find the dimmest existing light and replace // If dynamic, make sure that it stays as a local light if possible. int minLightIndex = FindDarkestWorldLight( lightingState.numlights, info.m_pIllum, dynamic ? 100000 : illum ); if (minLightIndex != -1) { // FIXME: We're sorting by ratio here instead of illum cause we either // have to store more memory or do more computations; I'm not // convinced it's any better of a metric though, so ratios for now... // found a light was was dimmer, swap it with the current light V_swap( pWorldLight, lightingState.locallight[minLightIndex] ); V_swap( illum, info.m_pIllum[minLightIndex] ); // FIXME: Avoid these recomputations // But I don't know how to do it without storing a ton of data // per cache entry... yuck! // NOTE: We know the dot product can't be zero or illum would have been 0 to start with! ratio = illum / DotProduct( pWorldLight->intensity, s_Grayscale ); if (pWorldLight->type == emit_skylight) { VectorFill( direction, 0 ); angularRatio = 1.0f; } else { VectorSubtract( pWorldLight->origin, bucketOrigin, direction ); VectorNormalize( direction ); // Recompute the ratios angularRatio = Engine_WorldLightAngle( pWorldLight, pWorldLight->normal, direction, direction ); } } } // Add the low light to the ambient box color AddWorldLightToLightCube( pWorldLight, lightingState.r_boxcolor, direction, ratio * angularRatio ); return pVis; } //----------------------------------------------------------------------------- // Construct a world light from a dynamic light //----------------------------------------------------------------------------- static void WorldLightFromDynamicLight( dlight_t const& dynamicLight, dworldlight_t& worldLight ) { VectorCopy( dynamicLight.origin, worldLight.origin ); worldLight.type = emit_point; worldLight.intensity[0] = TexLightToLinear( dynamicLight.color.r, dynamicLight.color.exponent ); worldLight.intensity[1] = TexLightToLinear( dynamicLight.color.g, dynamicLight.color.exponent ); worldLight.intensity[2] = TexLightToLinear( dynamicLight.color.b, dynamicLight.color.exponent ); worldLight.style = dynamicLight.style; // Compute cluster associated with the dynamic light worldLight.cluster = CM_LeafCluster( CM_PointLeafnum(worldLight.origin) ); // Assume a quadratic attenuation factor; atten so we hit minlight // at radius away... float minlight = fpmax( dynamicLight.minlight, g_flMinLightingValue ); // NOTE: Previous implementation turned off attenuation at radius zero. // clamping is more continuous float radius = dynamicLight.GetRadius(); if ( radius < 0.1f ) radius = 0.1f; worldLight.constant_attn = 0; worldLight.linear_attn = 0; worldLight.quadratic_attn = 1.0f / (minlight * radius * radius); // Set the max radius worldLight.radius = radius; // Spotlights... if (dynamicLight.m_OuterAngle > 0.0f) { worldLight.type = emit_spotlight; VectorCopy( dynamicLight.m_Direction, worldLight.normal ); worldLight.stopdot = cos( dynamicLight.m_InnerAngle * M_PI / 180.0f ); worldLight.stopdot2 = cos( dynamicLight.m_OuterAngle * M_PI / 180.0f ); } } //----------------------------------------------------------------------------- // Add in dynamic worldlights (lightstyles) //----------------------------------------------------------------------------- static const byte *ComputeLightStyles( lightcache_t* pCache, LightingState_t& lightingState, const Vector& origin, int leaf, const byte* pVis ) { VPROF_INCREMENT_COUNTER( "ComputeLightStyles", 1 ); LightingStateInfo_t info; lightingState.ZeroLightingState(); // Next, add each world light with a lightstyle into the lighting state, // ejecting less relevant local lights + folding them into the ambient cube for ( int i = 0; i < host_state.worldbrush->numworldlights; ++i) { dworldlight_t *wl = &host_state.worldbrush->worldlights[i]; if (wl->style == 0) continue; int byte = wl->style >> 3; int bit = wl->style & 0x7; if( !( pCache->m_pLightstyles[byte] & ( 1 << bit ) ) ) { continue; } // This is an optimization to avoid decompressing Vis twice if (!pVis) { // Figure out the PVS info for this location pVis = CM_ClusterPVS( CM_LeafCluster( leaf ) ); } // Now add that world light into our list of worldlights AddWorldLightToLightingState( wl, NULL, lightingState, info, origin, pVis ); } pCache->m_LastFrameUpdated_LightStyles = r_framecount; return pVis; } //----------------------------------------------------------------------------- // Add in dynamic worldlights (lightstyles) //----------------------------------------------------------------------------- static void AddLightStylesForStaticProp( PropLightcache_t *pcache, LightingState_t& lightingState ) { // Next, add each world light with a lightstyle into the lighting state, // ejecting less relevant local lights + folding them into the ambient cube for( int i = 0; i < pcache->m_LightStyleWorldLights.Count(); ++i ) { Assert( pcache->m_LightStyleWorldLights[i] >= 0 ); Assert( pcache->m_LightStyleWorldLights[i] < host_state.worldbrush->numworldlights ); dworldlight_t *wl = &host_state.worldbrush->worldlights[pcache->m_LightStyleWorldLights[i]]; Assert( wl->style != 0 ); // Now add that world light into our list of worldlights AddWorldLightToLightingState( wl, NULL, lightingState, *pcache, pcache->m_LightingOrigin, NULL, false /*dynamic*/, true /*ignorevis*/ ); } } //----------------------------------------------------------------------------- // Add DLights + ELights to the dynamic lighting //----------------------------------------------------------------------------- static dworldlight_t s_pDynamicLight[MAX_DLIGHTS + MAX_ELIGHTS]; static const byte* AddDLights( LightingStateInfo_t& info, LightingState_t& lightingState, const Vector& origin, int leaf, const byte* pVis ) { if ( !g_bActiveDlights ) return pVis; const bool bIgnoreVis = false; const bool bIgnoreVisTest = true; // Next, add each world light with a lightstyle into the lighting state, // ejecting less relevant local lights + folding them into the ambient cube dlight_t* dl = cl_dlights; for ( int i=0; iflags & (DLIGHT_NO_MODEL_ILLUMINATION | DLIGHT_DISPLACEMENT_MASK)) continue; // Fast reject. If we can reject it here, then we don't have to call WorldLightFromDynamicLight.. bool bReject; int lightCluster = CM_LeafCluster( g_DLightLeafAccessors[i].GetLeaf( dl->origin ) ); pVis = FastRejectLightSource( bIgnoreVis, pVis, origin, emit_point, lightCluster, bReject ); if ( bReject ) continue; // Construct a world light representing the dynamic light // we're making a static list here because the lighting state // contains a set of pointers to dynamic lights WorldLightFromDynamicLight( *dl, s_pDynamicLight[i] ); // Now add that world light into our list of worldlights pVis = AddWorldLightToLightingState( &s_pDynamicLight[i], NULL, lightingState, info, origin, pVis, true, bIgnoreVis, bIgnoreVisTest ); } return pVis; } static const byte* AddELights( LightingStateInfo_t& info, LightingState_t& lightingState, const Vector& origin, int leaf, const byte* pVis ) { if ( !g_bActiveElights ) return pVis; const bool bIgnoreVis = false; const bool bIgnoreVisTest = true; // Next, add each world light with a lightstyle into the lighting state, // ejecting less relevant local lights + folding them into the ambient cube dlight_t* dl = cl_elights; for ( int i=0; iIsRadiusGreaterThanZero() ) continue; // If the light doesn't affect models, then continue if (dl->flags & (DLIGHT_NO_MODEL_ILLUMINATION | DLIGHT_DISPLACEMENT_MASK)) continue; // Fast reject. If we can reject it here, then we don't have to call WorldLightFromDynamicLight.. bool bReject; int lightCluster = CM_LeafCluster( g_ELightLeafAccessors[i].GetLeaf( dl->origin ) ); pVis = FastRejectLightSource( bIgnoreVis, pVis, origin, emit_point, lightCluster, bReject ); if ( bReject ) continue; // Construct a world light representing the dynamic light // we're making a static list here because the lighting state // contains a set of pointers to dynamic lights WorldLightFromDynamicLight( *dl, s_pDynamicLight[i+MAX_DLIGHTS] ); // Now add that world light into our list of worldlights pVis = AddWorldLightToLightingState( &s_pDynamicLight[i+MAX_DLIGHTS], NULL, lightingState, info, origin, pVis, true, bIgnoreVis, bIgnoreVisTest ); } return pVis; } //----------------------------------------------------------------------------- // Given static + dynamic lighting, figure out the total light //----------------------------------------------------------------------------- static const byte *ComputeDynamicLighting( lightcache_t* pCache, LightingState_t& lightingState, const Vector& origin, int leaf, const byte* pVis = 0 ) { if (pCache->m_LastFrameUpdated_DynamicLighting != r_framecount) { VPROF_INCREMENT_COUNTER( "ComputeDynamicLighting", 1 ); // First factor in the cache into the current lighting state.. LightingStateInfo_t info; pCache->m_DynamicLightingState.ZeroLightingState(); // Next, add each dlight one at a time pVis = AddDLights( info, pCache->m_DynamicLightingState, origin, leaf, pVis ); // Finally, add in elights // FIXME: Do we actually use these? pVis = AddELights( info, pCache->m_DynamicLightingState, origin, leaf, pVis ); pCache->m_LastFrameUpdated_DynamicLighting = r_framecount; } Assert( pCache->m_DynamicLightingState.numlights >= 0 && pCache->m_DynamicLightingState.numlights <= MAXLOCALLIGHTS ); memcpy( &lightingState, &pCache->m_DynamicLightingState, sizeof(LightingState_t) ); return pVis; } //----------------------------------------------------------------------------- // Adds a world light to the list of lights //----------------------------------------------------------------------------- static void AddWorldLightToLightingStateForStaticProps( dworldlight_t* pWorldLight, LightingState_t& lightingState, LightingStateInfo_t& info, PropLightcache_t *pCache, bool dynamic = false ) { // FIXME: This is sort of a hack; only one skylight is allowed in the // lighting... if ((pWorldLight->type == emit_skylight) && info.m_LightingStateHasSkylight) return; // Get the lighting ratio float ratio; Vector direction; if (!dynamic) { ratio = LightIntensityAndDirectionAtPoint( pWorldLight, NULL, pCache->m_LightingOrigin, 0, NULL, &direction ); } else { Vector mins, maxs; ComputeLightcacheBounds( pCache->m_LightingOrigin, &mins, &maxs ); ratio = LightIntensityAndDirectionInBox( pWorldLight, NULL, pCache->m_LightingOrigin, pCache->mins, pCache->maxs, LIGHT_NO_OCCLUSION_CHECK, &direction ); } // No light contribution? Get outta here! if ( ratio <= 0.0f ) return; // Check if we've got a skylight if (pWorldLight->type == emit_skylight) info.m_LightingStateHasSkylight = true; // Figure out spotlight attenuation float angularRatio = Engine_WorldLightAngle( pWorldLight, pWorldLight->normal, direction, direction ); // Use standard RGB to gray conversion float illum = ratio * DotProduct( pWorldLight->intensity, s_Grayscale ); // Don't multiply by cone angle? // It can't be a local light if it's too dark // See the comment titled "EMIT_SURFACE LIGHTS" at the top for info. if (pWorldLight->type == emit_surface || illum >= r_worldlightmin.GetFloat()) // FIXME: tune this value { int nWorldLights = min( g_pMaterialSystemHardwareConfig->MaxNumLights(), r_worldlights.GetInt() ); // if remaining slots, add to list if ( lightingState.numlights < nWorldLights ) { // save pointer to world light lightingState.locallight[lightingState.numlights] = pWorldLight; info.m_pIllum[lightingState.numlights] = illum; ++lightingState.numlights; return; } // no remaining slots // find the dimmest existing light and replace int minLightIndex = FindDarkestWorldLight( lightingState.numlights, info.m_pIllum, dynamic ? 100000 : illum ); if (minLightIndex != -1) { // FIXME: We're sorting by ratio here instead of illum cause we either // have to store more memory or do more computations; I'm not // convinced it's any better of a metric though, so ratios for now... // found a light was was dimmer, swap it with the current light V_swap( pWorldLight, lightingState.locallight[minLightIndex] ); V_swap( illum, info.m_pIllum[minLightIndex] ); // FIXME: Avoid these recomputations // But I don't know how to do it without storing a ton of data // per cache entry... yuck! // NOTE: We know the dot product can't be zero or illum would have been 0 to start with! ratio = illum / DotProduct( pWorldLight->intensity, s_Grayscale ); if (pWorldLight->type == emit_skylight) { VectorFill( direction, 0 ); angularRatio = 1.0f; } else { VectorSubtract( pWorldLight->origin, pCache->m_LightingOrigin, direction ); VectorNormalize( direction ); // Recompute the ratios angularRatio = Engine_WorldLightAngle( pWorldLight, pWorldLight->normal, direction, direction ); } } } // Add the low light to the ambient box color AddWorldLightToLightCube( pWorldLight, lightingState.r_boxcolor, direction, ratio * angularRatio ); } static void AddDLightsForStaticProps( LightingStateInfo_t& info, LightingState_t& lightingState, PropLightcache_t *pCache ) { // mask off any dlights that have gone inactive pCache->m_DLightActive &= r_dlightactive; if ( pCache->m_DLightMarkFrame != r_framecount ) { pCache->m_DLightActive = 0; } if ( !pCache->m_DLightActive ) return; // Iterate the relevant dlights and add them to the lighting state dlight_t *dl = cl_dlights; for ( int i=0; im_DLightActive & ( 1 << i ) ) ) continue; // Construct a world light representing the dynamic light // we're making a static list here because the lighting state // contains a set of pointers to dynamic lights WorldLightFromDynamicLight( *dl, s_pDynamicLight[i] ); // Now add that world light into our list of worldlights AddWorldLightToLightingStateForStaticProps( &s_pDynamicLight[i], lightingState, info, pCache, true ); } } //----------------------------------------------------------------------------- // Add static lighting to the lighting state //----------------------------------------------------------------------------- ConVar r_lightcache_zbuffercache( "r_lightcache_zbuffercache", "0", FCVAR_ALLOWED_IN_COMPETITIVE ); static void AddStaticLighting( CBaseLightCache* pCache, const Vector& origin, const byte* pVis, bool bStaticProp, bool bAddedLeafAmbientCube ) { VPROF( "AddStaticLighting" ); // First, blat out the lighting state int i; pCache->m_StaticLightingState.numlights = 0; pCache->m_LightingStateHasSkylight = false; // NOTE: for static props, we mark lightstyles elsewhere (BuildStaticLightingCacheLightStyleInfo) if( !bStaticProp ) { pCache->m_LightingFlags &= ~( HACKLIGHTCACHEFLAGS_HASSWITCHABLELIGHTSTYLE | HACKLIGHTCACHEFLAGS_HASNONSWITCHABLELIGHTSTYLE ); memset( pCache->m_pLightstyles, 0, sizeof( pCache->m_pLightstyles ) ); } // Next, add each static light one at a time into the lighting state, // ejecting less relevant local lights + folding them into the ambient cube // Also, we need to add *all* new lights into the total box color for (i = 0; i < host_state.worldbrush->numworldlights; ++i) { dworldlight_t *wl = &host_state.worldbrush->worldlights[i]; lightzbuffer_t *pZBuf; if ( r_lightcache_zbuffercache.GetInt() ) pZBuf = &host_state.worldbrush->shadowzbuffers[i]; else pZBuf = NULL; // See the comment titled "EMIT_SURFACE LIGHTS" at the top for info. if ( bAddedLeafAmbientCube && (wl->flags & DWL_FLAGS_INAMBIENTCUBE) ) { Assert( wl->type == emit_surface ); continue; } // Don't add lights without lightstyles... we cache static lighting + lightstyles separately from static lighting if (wl->style == 0) { // Now add that world light into our list of worldlights AddWorldLightToLightingState( wl, pZBuf, pCache->m_StaticLightingState, *pCache, origin, pVis, false, false ); } else { // This is a lighstyle (flickering or switchable light) // NOTE: for static props, we mark lightstyles elsewhere. (BuildStaticLightingCacheLightStyleInfo) if( !bStaticProp ) { int byte = wl->style >> 3; int bit = wl->style & 0x7; if( !( pCache->m_pLightstyles[byte] & ( 1 << bit ) ) ) { Vector mins, maxs; Vector dummyDirection; ComputeLightcacheBounds( origin, &mins, &maxs ); float ratio = LightIntensityAndDirectionInBox( wl, NULL, origin, mins, maxs, LIGHT_NO_OCCLUSION_CHECK | LIGHT_IGNORE_LIGHTSTYLE_VALUE, &dummyDirection ); // See if this light has any contribution on this cache entry. if( ratio > 0.0f ) { if( d_lightstylenumframes[wl->style] <= 1 ) { pCache->m_LightingFlags |= HACKLIGHTCACHEFLAGS_HASSWITCHABLELIGHTSTYLE; } else { pCache->m_LightingFlags |= HACKLIGHTCACHEFLAGS_HASNONSWITCHABLELIGHTSTYLE; } pCache->m_pLightstyles[byte] |= (1 << bit); } } } } } } //----------------------------------------------------------------------------- // Checks to see if the lightstyles are valid for this cache entry //----------------------------------------------------------------------------- static bool IsCachedLightStylesValid( CBaseLightCache* pCache ) { if (!pCache->HasLightStyle()) return true; // FIXME: Can this start at 1, or is 0 required? for (int i = 1; i < MAX_LIGHTSTYLES; ++i) { int byte = i >> 3; int bit = i & 0x7; if (pCache->m_pLightstyles[byte] & ( 1 << bit )) { if (d_lightstyleframe[i] > pCache->m_LastFrameUpdated_LightStyles) return false; } } return true; } //----------------------------------------------------------------------------- // Find a lightcache entry within the requested radius from a point //----------------------------------------------------------------------------- #if 0 static int FindRecentCacheEntryWithinRadius( int count, CacheInfo_t* pCache, const Vector& origin, float radius ) { radius *= radius; // Try to find something within the radius of an existing new sample int minIndex = -1; for (int i = 0; i < count; ++i) { Vector delta; ComputeLightcacheOrigin( pCache[i].x, pCache[i].y, pCache[i].z, delta ); delta -= origin; float distSq = delta.LengthSqr(); if (distSq < radius ) { minIndex = i; radius = distSq; } } return minIndex; } #endif //----------------------------------------------------------------------------- // Draw the lightcache box for debugging //----------------------------------------------------------------------------- static void DebugRenderLightcache( Vector &sampleOrigin, LightingState_t& lightingState, bool bDebugModel ) { #ifndef SWDS // draw the cache entry defined by the sampling origin Vector cacheOrigin, cacheMins, cacheMaxs, lightMins, lightMaxs; ComputeLightcacheBounds( sampleOrigin, &cacheMins, &cacheMaxs ); cacheOrigin = ( cacheMins + cacheMaxs ) * 0.5f; cacheMins -= cacheOrigin; cacheMaxs -= cacheOrigin; // For drawing irradiance light probes as shown in [Greger98] if( r_drawlightcache.GetInt() == 5 ) { if ( bDebugModel ) { CDebugOverlay::AddSphereOverlay( sampleOrigin, 2.5f, 32, 32, 255, 255, 255, 255, 0.0f ); // 8 inch solid white sphere for ( int j = 0; j < lightingState.numlights; ++j ) { Vector vLightPosition; int r, g, b; if ( lightingState.locallight[j]->type == emit_skylight ) { vLightPosition = sampleOrigin - lightingState.locallight[j]->normal * 10000.0f; r = 255; g = 50; b = 50; } else { vLightPosition = lightingState.locallight[j]->origin; r = 255; g = 255; b = 255; } CDebugOverlay::AddLineOverlay( sampleOrigin, vLightPosition, r, g, b, 255, true, 0.0f ); } } } else { // draw cache entry CDebugOverlay::AddBoxOverlay( cacheOrigin, cacheMins, cacheMaxs, vec3_angle, 255, 255, 255, 0, 0.0f ); // draw boxes at light ray terminals to visualize endpoints if ( lightingState.numlights > 0 ) { lightMins.Init( -2, -2, -2 ); lightMaxs.Init( 2, 2, 2); CDebugOverlay::AddBoxOverlay( sampleOrigin, lightMins, lightMaxs, vec3_angle, 100, 255, 100, 0, 0.0f ); } int nLineColor[4] = {255, 170, 85, 0}; for (int j = 0; j < lightingState.numlights; ++j) { Vector vLightPosition; int r, g, b; if ( lightingState.locallight[j]->type == emit_skylight ) { vLightPosition = sampleOrigin - lightingState.locallight[j]->normal * 10000.0f; r = 255; g = 50; b = 50; } else { vLightPosition = lightingState.locallight[j]->origin; r = g = b = nLineColor[j]; } // draw lines from sampling point to light CDebugOverlay::AddLineOverlay( sampleOrigin, vLightPosition, r, g, b, 255, true, 0.0f ); CDebugOverlay::AddBoxOverlay( lightingState.locallight[j]->origin, lightMins, lightMaxs, vec3_angle, 255, 255, 100, 0, 0.0f ); } } #endif } //----------------------------------------------------------------------------- // Identify lighting errors //----------------------------------------------------------------------------- bool IdentifyLightingErrors( int leaf, LightingState_t& lightingState ) { if (r_drawlightcache.GetInt() == 3) { if (CM_LeafContents(leaf) == CONTENTS_SOLID) { // Try another choice... lightingState.r_boxcolor[0].Init( 1, 0, 0 ); lightingState.r_boxcolor[1].Init( 1, 0, 0 ); lightingState.r_boxcolor[2].Init( 1, 0, 0 ); lightingState.r_boxcolor[3].Init( 1, 0, 0 ); lightingState.r_boxcolor[4].Init( 1, 0, 0 ); lightingState.r_boxcolor[5].Init( 1, 0, 0 ); lightingState.numlights = 0; return true; } } return false; } //----------------------------------------------------------------------------- // Compute the cache... //----------------------------------------------------------------------------- static const byte* ComputeStaticLightingForCacheEntry( CBaseLightCache *pcache, const Vector& origin, int leaf, bool bStaticProp = false ) { VPROF_INCREMENT_COUNTER( "ComputeStaticLightingForCacheEntry", 1 ); VPROF( "ComputeStaticLightingForCacheEntry" ); // Figure out the PVS info for this location const byte* pVis = CM_ClusterPVS( CM_LeafCluster( leaf ) ); bool bAddedLeafAmbientCube; R_StudioGetAmbientLightForPoint( leaf, origin, pcache->m_StaticLightingState.r_boxcolor, bStaticProp, &bAddedLeafAmbientCube ); // get direct lighting from world light sources (point lights, etc.) if ( !r_ambientlightingonly.GetInt() ) { AddStaticLighting( pcache, origin, pVis, bStaticProp, bAddedLeafAmbientCube ); } return pVis; } static void BuildStaticLightingCacheLightStyleInfo( PropLightcache_t* pcache, const Vector& mins, const Vector& maxs ) { const byte *pVis = NULL; Assert( pcache->m_LightStyleWorldLights.Count() == 0 ); pcache->m_LightingFlags &= ~( HACKLIGHTCACHEFLAGS_HASSWITCHABLELIGHTSTYLE | HACKLIGHTCACHEFLAGS_HASSWITCHABLELIGHTSTYLE ); // clear lightstyles memset( pcache->m_pLightstyles, 0, MAX_LIGHTSTYLE_BYTES ); for ( short i = 0; i < host_state.worldbrush->numworldlights; ++i) { dworldlight_t *wl = &host_state.worldbrush->worldlights[i]; if (wl->style == 0) continue; // This is an optimization to avoid decompressing Vis twice if (!pVis) { // Figure out the PVS info for this static prop pVis = CM_ClusterPVS( CM_LeafCluster( pcache->leaf ) ); } // FIXME: Could do better here if we had access to the list of leaves that this // static prop is in. For now, we use the lighting origin. if( pVis[ wl->cluster >> 3 ] & ( 1 << ( wl->cluster & 7 ) ) ) { // Use the maximum illumination to cull out lights that are far away. dworldlight_t tmpLight = *wl; tmpLight.style = 0; Vector dummyDirection; float ratio = LightIntensityAndDirectionInBox( &tmpLight, NULL, pcache->m_LightingOrigin, mins, maxs, LIGHT_NO_OCCLUSION_CHECK | LIGHT_IGNORE_LIGHTSTYLE_VALUE, &dummyDirection ); // See if this light has any contribution on this cache entry. if( ratio <= 0.0f ) { continue; } { MEM_ALLOC_CREDIT(); pcache->m_LightStyleWorldLights.AddToTail( i ); } int byte = wl->style >> 3; int bit = wl->style & 0x7; pcache->m_pLightstyles[byte] |= ( 1 << bit ); if( d_lightstylenumframes[wl->style] <= 1 ) { pcache->m_LightingFlags |= HACKLIGHTCACHEFLAGS_HASSWITCHABLELIGHTSTYLE; } else { pcache->m_LightingFlags |= HACKLIGHTCACHEFLAGS_HASNONSWITCHABLELIGHTSTYLE; } } } } static ITexture *FindEnvCubemapForPoint( const Vector& origin ) { worldbrushdata_t *pBrushData = host_state.worldbrush; if( pBrushData && pBrushData->m_nCubemapSamples > 0 ) { int smallestIndex = 0; Vector blah = origin - pBrushData->m_pCubemapSamples[0].origin; float smallestDist = DotProduct( blah, blah ); for( int i = 1; i < pBrushData->m_nCubemapSamples; i++ ) { Vector ign = origin - pBrushData->m_pCubemapSamples[i].origin; float dist = DotProduct( ign, ign ); if( dist < smallestDist ) { smallestDist = dist; smallestIndex = i; } } return pBrushData->m_pCubemapSamples[smallestIndex].pTexture; } else { return NULL; } } //----------------------------------------------------------------------------- // Create static light cache entry //----------------------------------------------------------------------------- LightCacheHandle_t CreateStaticLightingCache( const Vector& origin, const Vector& mins, const Vector& maxs ) { PropLightcache_t* pcache = s_PropCache.Alloc(); pcache->m_LightingOrigin = origin; pcache->m_Flags = 0; pcache->mins = mins; pcache->maxs = maxs; // initialize this to point to our current origin pcache->leaf = CM_PointLeafnum(origin); // Add the prop to the list of props pcache->m_pNextPropLightcache = s_pAllStaticProps; s_pAllStaticProps = pcache; pcache->m_Flags = 0; // must set this to zero so that this cache entry will be invalid. pcache->m_pEnvCubemapTexture = FindEnvCubemapForPoint( origin ); BuildStaticLightingCacheLightStyleInfo( pcache, mins, maxs ); return (LightCacheHandle_t)pcache; } bool StaticLightCacheAffectedByDynamicLight( LightCacheHandle_t handle ) { PropLightcache_t *pcache = ( PropLightcache_t *)handle; return pcache->HasDlights(); } bool StaticLightCacheAffectedByAnimatedLightStyle( LightCacheHandle_t handle ) { PropLightcache_t *pcache = ( PropLightcache_t *)handle; if( !pcache->HasLightStyle() ) { return false; } else { for( int i = 0; i < pcache->m_LightStyleWorldLights.Count(); ++i ) { Assert( pcache->m_LightStyleWorldLights[i] >= 0 ); Assert( pcache->m_LightStyleWorldLights[i] < host_state.worldbrush->numworldlights ); dworldlight_t *wl = &host_state.worldbrush->worldlights[pcache->m_LightStyleWorldLights[i]]; Assert( wl->style != 0 ); if( d_lightstylenumframes[wl->style] > 1 ) { return true; } } return false; } } bool StaticLightCacheNeedsSwitchableLightUpdate( LightCacheHandle_t handle ) { PropLightcache_t *pcache = ( PropLightcache_t *)handle; if( !pcache->HasSwitchableLightStyle() ) { return false; } else { for( int i = 0; i < pcache->m_LightStyleWorldLights.Count(); ++i ) { Assert( pcache->m_LightStyleWorldLights[i] >= 0 ); Assert( pcache->m_LightStyleWorldLights[i] < host_state.worldbrush->numworldlights ); dworldlight_t *wl = &host_state.worldbrush->worldlights[pcache->m_LightStyleWorldLights[i]]; Assert( wl->style != 0 ); // Is it a switchable light? if( d_lightstylenumframes[wl->style] <= 1 ) { // Has it changed since the last time we updated our cached static VB version? if( pcache->m_SwitchableLightFrame < d_lightstyleframe[wl->style] ) { pcache->m_SwitchableLightFrame = r_framecount; // return true since our static vb is dirty return true; } } } return false; } } //----------------------------------------------------------------------------- // Clears the prop lighting cache //----------------------------------------------------------------------------- void ClearStaticLightingCache() { s_PropCache.Clear(); s_pAllStaticProps = NULL; } //----------------------------------------------------------------------------- // Recomputes all static prop lighting //----------------------------------------------------------------------------- void InvalidateStaticLightingCache(void) { for ( PropLightcache_t *pCur=s_pAllStaticProps; pCur; pCur=pCur->m_pNextPropLightcache ) { // Compute the static lighting pCur->m_Flags = 0; pCur->m_LightingFlags &=~HACKLIGHTCACHEFLAGS_HASDONESTATICLIGHTING; LightcacheGetStatic( ( LightCacheHandle_t )pCur, NULL, LIGHTCACHEFLAGS_STATIC ); } } //----------------------------------------------------------------------------- // Gets the lightcache entry for a static prop //----------------------------------------------------------------------------- LightingState_t *LightcacheGetStatic( LightCacheHandle_t cache, ITexture **pEnvCubemapTexture, unsigned int flags ) { PropLightcache_t *pcache = ( PropLightcache_t * )cache; Assert( pcache ); // get the cubemap texture if ( pEnvCubemapTexture ) { *pEnvCubemapTexture = pcache->m_pEnvCubemapTexture; } bool bRecalcStaticLighting = false; bool bRecalcLightStyles = (pcache->HasLightStyle() && pcache->m_LastFrameUpdated_LightStyles != r_framecount) && !IsCachedLightStylesValid(pcache); bool bRecalcDLights = pcache->HasDlights() && pcache->m_LastFrameUpdated_DynamicLighting != r_framecount; if ( flags != pcache->m_Flags ) { // This should not happen often, but if the flags change, blow away all of the lighting state. // This cache entry's state must be regenerated. bRecalcStaticLighting = true; bRecalcLightStyles = true; bRecalcDLights = true; pcache->m_Flags = flags; } else if ( !bRecalcDLights && !bRecalcLightStyles ) { // already have expected lighting state // get out of here since we already did this this frame. return &pcache->m_DynamicLightingState; } else { // the dlight cache includes lightstyles // we have to recalc the dlight cache if lightstyles change. if ( bRecalcLightStyles ) { bRecalcDLights = true; } } // must need to recalc, do so LightingState_t accumulatedState; // static lighting state gets preserved because its expensive to generate // it gets re-requested for static props that rebake if ( flags & LIGHTCACHEFLAGS_STATIC ) { // want static lighting data if ( bRecalcStaticLighting && !(pcache->m_LightingFlags & HACKLIGHTCACHEFLAGS_HASDONESTATICLIGHTING) ) { ComputeStaticLightingForCacheEntry( pcache, pcache->m_LightingOrigin, pcache->leaf, true ); pcache->m_LightingFlags |= HACKLIGHTCACHEFLAGS_HASDONESTATICLIGHTING; } // set as start values for accumulation accumulatedState = pcache->m_StaticLightingState; } else { // set as zero for accumulation accumulatedState.ZeroLightingState(); } // lightstyle lighting state gets preserved when there is no lightstyle change if ( flags & LIGHTCACHEFLAGS_LIGHTSTYLE ) { if ( bRecalcLightStyles ) { // accumulate lightstyles AddLightStylesForStaticProp( pcache, accumulatedState ); pcache->m_LightStyleLightingState = accumulatedState; pcache->m_LastFrameUpdated_LightStyles = r_framecount; } else { accumulatedState = pcache->m_LightStyleLightingState; } } if ( flags & LIGHTCACHEFLAGS_DYNAMIC ) { if ( bRecalcDLights ) { // accumulate dynamic lights AddDLightsForStaticProps( *( LightingStateInfo_t *)pcache, accumulatedState, pcache ); pcache->m_DynamicLightingState = accumulatedState; pcache->m_LastFrameUpdated_DynamicLighting = r_framecount; } else { accumulatedState = pcache->m_DynamicLightingState; } } else { // hold the current state pcache->m_DynamicLightingState = accumulatedState; } // caller gets requested data return &pcache->m_DynamicLightingState; } inline const byte *AddLightingState( LightingState_t &dst, const LightingState_t &src, LightingStateInfo_t &info, const Vector& bucketOrigin, const byte *pVis, bool bDynamic, bool bIgnoreVis ) { int i; for( i = 0; i < src.numlights; i++ ) { pVis = AddWorldLightToLightingState( src.locallight[i], NULL, dst, info, bucketOrigin, pVis, bDynamic, bIgnoreVis ); } for( i = 0; i < 6; i++ ) { dst.r_boxcolor[i] += src.r_boxcolor[i]; } return pVis; } //----------------------------------------------------------------------------- // Get or create the lighting information for this point // This is the version for dynamic objects. //----------------------------------------------------------------------------- const byte* PrecalcLightingState( lightcache_t *pCache, const byte *pVis ) { LightingState_t lightingState; lightingState.ZeroLightingState(); pCache->m_StaticPrecalc_LightingStateInfo.Clear(); int i; for( i = 0; i < pCache->m_StaticLightingState.numlights; i++ ) { pVis = AddWorldLightToLightingState( pCache->m_StaticLightingState.locallight[i], NULL, lightingState, pCache->m_StaticPrecalc_LightingStateInfo, pCache->m_LightingOrigin, pVis, true, // bDynamic false // bIgnoreVis ); } for ( i=0; i < 6; i++ ) pCache->m_StaticLightingState.r_boxcolor[i] += lightingState.r_boxcolor[i]; pCache->m_StaticPrecalc_NumLocalLights = lightingState.numlights; for ( i=0; i < pCache->m_StaticPrecalc_NumLocalLights; i++ ) pCache->m_StaticPrecalc_LocalLight[i] = lightingState.locallight[i]; return pVis; } void CopyPrecalcedLightingState( lightcache_t *pCache, LightingState_t &lightingState, LightingStateInfo_t &info ) { info = pCache->m_StaticPrecalc_LightingStateInfo; int i; for ( i=0; i < 6; i++ ) lightingState.r_boxcolor[i] = pCache->m_StaticLightingState.r_boxcolor[i]; lightingState.numlights = pCache->m_StaticPrecalc_NumLocalLights; for ( i=0; i < lightingState.numlights; i++ ) lightingState.locallight[i] = pCache->m_StaticPrecalc_LocalLight[i]; } void AdjustLightCacheOrigin( lightcache_t *pCache, const Vector &origin, int originLeaf ) { Vector cacheMins; Vector cacheMaxs; Vector center; trace_t tr; Ray_t ray; CTraceFilterWorldOnly worldTraceFilter; ITraceFilter *pTraceFilter = &worldTraceFilter; // quiet compiler tr.startsolid = false; tr.fraction = 0; // prefer to use the center of the light cache for all sampling // which helps consistent stable cache entries ComputeLightcacheBounds( origin, &cacheMins, &cacheMaxs ); center = cacheMins + cacheMaxs; center *= 0.5f; bool bTraceToCenter = true; int centerLeaf = CM_PointLeafnum(center); if (centerLeaf != originLeaf) { // preferred center resides in a different leaf if (CM_LeafContents(centerLeaf) & MASK_OPAQUE) { // preferred center is invalid bTraceToCenter = false; } else { // ensure the desired center resides in the leaf that the provided origin is in CM_SnapPointToReferenceLeaf(origin, LIGHTCACHE_SNAP_EPSILON, ¢er); } } if (bTraceToCenter) { // if the center is unavailable, fallback to provided origin ray.Init( origin, center ); g_pEngineTraceClient->TraceRay( ray, MASK_OPAQUE, pTraceFilter, &tr ); } if (bTraceToCenter && tr.startsolid) { // origin is in solid, can't trace anywhere, use bad origin as provided VectorCopy(origin, pCache->m_LightingOrigin); } else if (!bTraceToCenter || tr.fraction < 1) { // center is occluded // trace again, recompute alternate x-y center, substitute z // ensure the desired center resides in the leaf that the provided origin is in center.x = (cacheMins.x + cacheMaxs.x) * 0.5f; center.y = (cacheMins.y + cacheMaxs.y) * 0.5f; center.z = origin.z; CM_SnapPointToReferenceLeaf(origin, LIGHTCACHE_SNAP_EPSILON, ¢er); ray.Init( origin, center ); g_pEngineTraceClient->TraceRay( ray, MASK_OPAQUE, pTraceFilter, &tr ); if (tr.fraction < 1) { // no further fallback, use origin as provided VectorCopy(origin, pCache->m_LightingOrigin); } else { // trace succeeded VectorCopy(center, pCache->m_LightingOrigin); } } else { // trace succeeded VectorCopy(center, pCache->m_LightingOrigin); } } bool AllowFullCacheMiss(int flags) { if ( r_framecount < 60 || r_framecount != g_FrameIndex ) { g_FrameMissCount = 0; g_FrameIndex = r_framecount; } if ( g_FrameMissCount < lightcache_maxmiss.GetInt() ) { g_FrameMissCount++; return true; } if ( flags & LIGHTCACHEFLAGS_ALLOWFAST ) return false; return true; } lightcache_t *FindNearestCache( int x, int y, int z, int leafIndex ) { int bestDist = INT_MAX; lightcache_t *pBest = NULL; short current = GetLightLRUTail().lru_prev; int dx, dy, dz; while ( current != LIGHT_LRU_HEAD_INDEX ) { lightcache_t *pCache = &lightcache[current]; int dist = 0; dx = pCache->x - x; dx = abs(dx); dy = pCache->y - y; dy = abs(dy); dz = pCache->z - z; dz = abs(dz); if ( leafIndex != pCache->leaf ) { dist += 2; } dist = max(dist, dx); dist = max(dist, dy); dist = max(dist, dz); if ( dist < bestDist ) { pBest = pCache; bestDist = dist; if ( dist <= 1 ) break; } current = pCache->lru_prev; } return pBest; } ITexture *LightcacheGetDynamic( const Vector& origin, LightingState_t& lightingState, LightcacheGetDynamic_Stats &stats, unsigned int flags, bool bDebugModel ) { VPROF_BUDGET( "LightcacheGet", VPROF_BUDGETGROUP_LIGHTCACHE ); LightingStateInfo_t info; // generate the hashing vars int originLeaf = CM_PointLeafnum(origin); /* if (IdentifyLightingErrors(leaf, lightingState)) return false; */ int x, y, z; OriginToCacheOrigin( origin, x, y, z ); // convert vars to hash key / bucket id int bucket = LightcacheHashKey( x, y, z, originLeaf ); const byte* pVis = NULL; bool bComputeLightStyles = ( flags & LIGHTCACHEFLAGS_LIGHTSTYLE ) != 0; // See if we've already computed the light in this location lightcache_t *pCache = FindInCache(bucket, x, y, z, originLeaf); if ( pCache ) { // cache hit, move to tail of LRU LightcacheMark( pCache ); if ( bComputeLightStyles && IsCachedLightStylesValid( pCache ) ) { bComputeLightStyles = false; } } else if ( !AllowFullCacheMiss(flags) ) { pCache = FindNearestCache( x, y, z, originLeaf ); originLeaf = pCache->leaf; x = pCache->x; y = pCache->y; z = pCache->z; } if ( !pCache ) { VPROF_INCREMENT_COUNTER( "lightcache miss", 1 ); // cache miss, nothing appropriate from the frame cache, make a new entry pCache = NewLightcacheEntry(bucket); // initialize the cache entry based on provided origin pCache->x = x; pCache->y = y; pCache->z = z; pCache->leaf = originLeaf; if ( r_lightcachecenter.GetBool() ) { AdjustLightCacheOrigin( pCache, origin, originLeaf ); } else { // old behavior, use provided origin VectorCopy(origin, pCache->m_LightingOrigin); } // Figure out which env_cubemap is used for this cache entry. pCache->m_pEnvCubemapTexture = FindEnvCubemapForPoint( pCache->m_LightingOrigin ); // Compute the static portion of the cache pVis = ComputeStaticLightingForCacheEntry( pCache, pCache->m_LightingOrigin, originLeaf ); pVis = PrecalcLightingState( pCache, pVis ); } // NOTE: On a cache miss, this has to be after ComputeStaticLightingForCacheEntry since these flags are computed there. stats.m_bHasNonSwitchableLightStyles = pCache->HasNonSwitchableLightStyle(); stats.m_bHasSwitchableLightStyles = pCache->HasSwitchableLightStyle(); if ( bComputeLightStyles ) { pVis = ComputeLightStyles( pCache, pCache->m_LightStyleLightingState, pCache->m_LightingOrigin, originLeaf, pVis ); stats.m_bNeedsSwitchableLightStyleUpdate = true; } else { stats.m_bNeedsSwitchableLightStyleUpdate = false; } stats.m_bHasDLights = false; if ( flags & LIGHTCACHEFLAGS_DYNAMIC ) { pVis = ComputeDynamicLighting( pCache, pCache->m_DynamicLightingState, pCache->m_LightingOrigin, originLeaf, pVis ); if( pCache->m_DynamicLightingState.numlights > 0 ) { stats.m_bHasDLights = true; } } if ( flags & LIGHTCACHEFLAGS_STATIC ) { CopyPrecalcedLightingState( pCache, lightingState, info ); } else { lightingState.ZeroLightingState(); } if ( flags & LIGHTCACHEFLAGS_LIGHTSTYLE ) { pVis = AddLightingState( lightingState, pCache->m_LightStyleLightingState, info, pCache->m_LightingOrigin, pVis, true /*bDynamic*/, false /*bIgnoreVis*/ ); } if ( flags & LIGHTCACHEFLAGS_DYNAMIC ) { pVis = AddLightingState( lightingState, pCache->m_DynamicLightingState, info, pCache->m_LightingOrigin, pVis, true /*bDynamic*/, false /*bIgnoreVis*/ ); } if ( r_drawlightcache.GetBool() ) { DebugRenderLightcache( pCache->m_LightingOrigin, lightingState, bDebugModel ); } return pCache->m_pEnvCubemapTexture; } //----------------------------------------------------------------------------- // Compute the contribution of D- and E- lights at a point + normal //----------------------------------------------------------------------------- void ComputeDynamicLighting( const Vector& pt, const Vector* pNormal, Vector& color ) { if ( !g_bActiveDlights && !g_bActiveElights ) { VectorFill( color, 0 ); return; } // Next, add each world light with a lightstyle into the lighting state, // ejecting less relevant local lights + folding them into the ambient cube static Vector ambientTerm[6] = { Vector(0,0,0), Vector(0,0,0), Vector(0,0,0), Vector(0,0,0), Vector(0,0,0), Vector(0,0,0) }; int lightCount = 0; LightDesc_t pLightDesc[MAX_DLIGHTS + MAX_ELIGHTS]; int i; dlight_t* dl = cl_dlights; if ( g_bActiveDlights ) { for ( i=0; iflags & (DLIGHT_NO_MODEL_ILLUMINATION | DLIGHT_DISPLACEMENT_MASK)) continue; // Construct a world light representing the dynamic light // we're making a static list here because the lighting state // contains a set of pointers to dynamic lights dworldlight_t worldLight; WorldLightFromDynamicLight( *dl, worldLight ); WorldLightToMaterialLight( &worldLight, pLightDesc[lightCount] ); ++lightCount; } } if ( g_bActiveElights ) { // Next, add each world light with a lightstyle into the lighting state, // ejecting less relevant local lights + folding them into the ambient cube dl = cl_elights; for ( i=0; iIsRadiusGreaterThanZero() ) continue; // If the light doesn't affect models, then continue if (dl->flags & (DLIGHT_NO_MODEL_ILLUMINATION | DLIGHT_DISPLACEMENT_MASK)) continue; // Construct a world light representing the dynamic light // we're making a static list here because the lighting state // contains a set of pointers to dynamic lights dworldlight_t worldLight; WorldLightFromDynamicLight( *dl, worldLight ); WorldLightToMaterialLight( &worldLight, pLightDesc[lightCount] ); ++lightCount; } } if ( lightCount ) { g_pStudioRender->ComputeLighting( ambientTerm, lightCount, pLightDesc, pt, *pNormal, color ); } else { VectorFill( color, 0 ); } } //----------------------------------------------------------------------------- // Is Dynamic Light? //----------------------------------------------------------------------------- static bool IsDynamicLight( dworldlight_t *pWorldLight ) { // NOTE: This only works because we're using some implementation details // that the dynamic lights are stored in a little static array return ( pWorldLight >= s_pDynamicLight && pWorldLight < &s_pDynamicLight[ARRAYSIZE(s_pDynamicLight)] ); } //----------------------------------------------------------------------------- // Computes an average color (of sorts) at a particular point + optional normal //----------------------------------------------------------------------------- void ComputeLighting( const Vector& pt, const Vector* pNormal, bool bClamp, Vector& color, Vector *pBoxColors ) { LightingState_t lightingState; LightcacheGetDynamic_Stats stats; LightcacheGetDynamic( pt, lightingState, stats, LIGHTCACHEFLAGS_STATIC|LIGHTCACHEFLAGS_DYNAMIC|LIGHTCACHEFLAGS_LIGHTSTYLE|LIGHTCACHEFLAGS_ALLOWFAST ); int i; if ( pNormal ) { LightDesc_t* pLightDesc = (LightDesc_t*)stackalloc( lightingState.numlights * sizeof(LightDesc_t) ); for ( i=0; i < lightingState.numlights; ++i ) { // Construct a world light representing the dynamic light // we're making a static list here because the lighting state // contains a set of pointers to dynamic lights WorldLightToMaterialLight( lightingState.locallight[i], pLightDesc[i] ); } g_pStudioRender->ComputeLighting( lightingState.r_boxcolor, lightingState.numlights, pLightDesc, pt, *pNormal, color ); } else { Vector direction; for ( i = 0; i < lightingState.numlights; ++i ) { if ( IsDynamicLight( lightingState.locallight[i] ) ) continue; float ratio = LightIntensityAndDirectionAtPoint( lightingState.locallight[i], NULL, pt, LIGHT_NO_OCCLUSION_CHECK, NULL, &direction ); float angularRatio = Engine_WorldLightAngle( lightingState.locallight[i], lightingState.locallight[i]->normal, direction, direction ); AddWorldLightToLightCube( lightingState.locallight[i], lightingState.r_boxcolor, direction, ratio * angularRatio ); } color.Init( 0, 0, 0 ); for ( i = 0; i < 6; ++i ) { color += lightingState.r_boxcolor[i]; } color /= 6.0f; } // If they want the colors for each box side, give it to them. if ( pBoxColors ) { memcpy( pBoxColors, lightingState.r_boxcolor, sizeof( lightingState.r_boxcolor ) ); } if (bClamp) { if (color.x > 1.0f) color.x = 1.0f; if (color.y > 1.0f) color.y = 1.0f; if (color.z > 1.0f) color.z = 1.0f; } } static const byte *s_pDLightVis = NULL; // All dlights that affect a static prop must mark that static prop every frame. class MarkStaticPropLightsEmumerator : public IPartitionEnumerator { public: void SetLightID( int nLightID ) { m_nLightID = nLightID; } virtual IterationRetval_t EnumElement( IHandleEntity *pHandleEntity ) { Assert( StaticPropMgr()->IsStaticProp( pHandleEntity ) ); PropLightcache_t *pCache = ( PropLightcache_t * )StaticPropMgr()->GetLightCacheHandleForStaticProp( pHandleEntity ); if ( !pCache ) { return ITERATION_CONTINUE; } if( !s_pDLightVis ) { s_pDLightVis = CM_ClusterPVS( CM_LeafCluster( CM_PointLeafnum( cl_dlights[m_nLightID].origin ) ) ); } if( !StaticPropMgr()->IsPropInPVS( pHandleEntity, s_pDLightVis ) ) { return ITERATION_CONTINUE; } if ( !pCache ) { return ITERATION_CONTINUE; } #ifdef _DEBUG if( r_drawlightcache.GetInt() == 4 ) { Vector mins( -5, -5, -5 ); Vector maxs( 5, 5, 5 ); CDebugOverlay::AddLineOverlay( cl_dlights[m_nLightID].origin, pCache->m_LightingOrigin, 0, 0, 255, 255, true, 0.001f ); CDebugOverlay::AddBoxOverlay( pCache->m_LightingOrigin, mins, maxs, vec3_angle, 255, 0, 0, 0, 0.001f ); } #endif pCache->m_DLightActive |= ( 1 << m_nLightID ); pCache->m_DLightMarkFrame = r_framecount; return ITERATION_CONTINUE; } private: int m_nLightID; }; static MarkStaticPropLightsEmumerator s_MarkStaticPropLightsEnumerator; void MarkDLightsOnStaticProps( void ) { if ( !g_bActiveDlights ) return; dlight_t *l = cl_dlights; for (int i=0 ; iflags & (DLIGHT_NO_MODEL_ILLUMINATION | DLIGHT_DISPLACEMENT_MASK)) continue; if (l->die < cl.GetTime() || !l->IsRadiusGreaterThanZero() ) continue; // If the light's not active, then continue if ( (r_dlightactive & (1 << i)) == 0 ) continue; #ifdef _DEBUG if( r_drawlightcache.GetInt() == 4 ) { Vector mins( -5, -5, -5 ); Vector maxs( 5, 5, 5 ); CDebugOverlay::AddBoxOverlay( l->origin, mins, maxs, vec3_angle, 255, 255, 255, 0, 0.001f ); } #endif s_pDLightVis = NULL; s_MarkStaticPropLightsEnumerator.SetLightID( i ); SpatialPartition()->EnumerateElementsInSphere( PARTITION_ENGINE_STATIC_PROPS, l->origin, l->GetRadius(), true, &s_MarkStaticPropLightsEnumerator ); } } float g_flMinLightingValue = 1.0f; void InitDLightGlobals( int nMapVersion ) { if( nMapVersion >= 20 ) { // The light level at which we are close enough to black to treat as black for // culling purposes. g_flMinLightingValue = 1.0f / 256.0f; } else { // This is the broken value from HL2. It is supposed to be // the light level at which we are close enough to black to treat as black for // culling purposes. We leave it at the broken value here for old bsp files // Since HL2 maps were compiled with this bsp version. g_flMinLightingValue = 20.0f / 256.0f; } }