You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
708 lines
19 KiB
708 lines
19 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//=============================================================================// |
|
|
|
#include "vrad.h" |
|
#include "leaf_ambient_lighting.h" |
|
#include "bsplib.h" |
|
#include "vraddetailprops.h" |
|
#include "mathlib/anorms.h" |
|
#include "pacifier.h" |
|
#include "coordsize.h" |
|
#include "vstdlib/random.h" |
|
#include "bsptreedata.h" |
|
#include "messbuf.h" |
|
#include "vmpi.h" |
|
#include "vmpi_distribute_work.h" |
|
|
|
static TableVector g_BoxDirections[6] = |
|
{ |
|
{ 1, 0, 0 }, |
|
{ -1, 0, 0 }, |
|
{ 0, 1, 0 }, |
|
{ 0, -1, 0 }, |
|
{ 0, 0, 1 }, |
|
{ 0, 0, -1 }, |
|
}; |
|
|
|
|
|
|
|
static void ComputeAmbientFromSurface( dface_t *surfID, dworldlight_t* pSkylight, |
|
Vector& radcolor ) |
|
{ |
|
if ( !surfID ) |
|
return; |
|
|
|
texinfo_t *pTexInfo = &texinfo[surfID->texinfo]; |
|
|
|
// If we hit the sky, use the sky ambient |
|
if ( pTexInfo->flags & SURF_SKY ) |
|
{ |
|
if ( pSkylight ) |
|
{ |
|
// add in sky ambient |
|
VectorCopy( pSkylight->intensity, radcolor ); |
|
} |
|
} |
|
else |
|
{ |
|
Vector reflectivity = dtexdata[pTexInfo->texdata].reflectivity; |
|
VectorMultiply( radcolor, reflectivity, radcolor ); |
|
} |
|
} |
|
|
|
|
|
// TODO: it's CRAZY how much lighting code we share with the engine. It should all be shared code. |
|
float Engine_WorldLightAngle( const dworldlight_t *wl, const Vector& lnormal, const Vector& snormal, const Vector& delta ) |
|
{ |
|
float dot, dot2; |
|
|
|
Assert( wl->type == emit_surface ); |
|
|
|
dot = DotProduct( snormal, delta ); |
|
if (dot < 0) |
|
return 0; |
|
|
|
dot2 = -DotProduct (delta, lnormal); |
|
if (dot2 <= ON_EPSILON/10) |
|
return 0; // behind light surface |
|
|
|
return dot * dot2; |
|
} |
|
|
|
|
|
// TODO: it's CRAZY how much lighting code we share with the engine. It should all be shared code. |
|
float Engine_WorldLightDistanceFalloff( const dworldlight_t *wl, const Vector& delta ) |
|
{ |
|
Assert( wl->type == emit_surface ); |
|
|
|
// Cull out stuff that's too far |
|
if (wl->radius != 0) |
|
{ |
|
if ( DotProduct( delta, delta ) > (wl->radius * wl->radius)) |
|
return 0.0f; |
|
} |
|
|
|
return InvRSquared(delta); |
|
} |
|
|
|
|
|
void AddEmitSurfaceLights( const Vector &vStart, Vector lightBoxColor[6] ) |
|
{ |
|
fltx4 fractionVisible; |
|
|
|
FourVectors vStart4, wlOrigin4; |
|
vStart4.DuplicateVector ( vStart ); |
|
|
|
for ( int iLight=0; iLight < *pNumworldlights; iLight++ ) |
|
{ |
|
dworldlight_t *wl = &dworldlights[iLight]; |
|
|
|
// Should this light even go in the ambient cubes? |
|
if ( !( wl->flags & DWL_FLAGS_INAMBIENTCUBE ) ) |
|
continue; |
|
|
|
Assert( wl->type == emit_surface ); |
|
|
|
// Can this light see the point? |
|
wlOrigin4.DuplicateVector ( wl->origin ); |
|
TestLine ( vStart4, wlOrigin4, &fractionVisible ); |
|
if ( !TestSignSIMD ( CmpGtSIMD ( fractionVisible, Four_Zeros ) ) ) |
|
continue; |
|
|
|
// Add this light's contribution. |
|
Vector vDelta = wl->origin - vStart; |
|
float flDistanceScale = Engine_WorldLightDistanceFalloff( wl, vDelta ); |
|
|
|
Vector vDeltaNorm = vDelta; |
|
VectorNormalize( vDeltaNorm ); |
|
float flAngleScale = Engine_WorldLightAngle( wl, wl->normal, vDeltaNorm, vDeltaNorm ); |
|
|
|
float ratio = flDistanceScale * flAngleScale * SubFloat ( fractionVisible, 0 ); |
|
if ( ratio == 0 ) |
|
continue; |
|
|
|
for ( int i=0; i < 6; i++ ) |
|
{ |
|
float t = DotProduct( g_BoxDirections[i], vDeltaNorm ); |
|
if ( t > 0 ) |
|
{ |
|
lightBoxColor[i] += wl->intensity * (t * ratio); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
void ComputeAmbientFromSphericalSamples( int iThread, const Vector &vStart, Vector lightBoxColor[6] ) |
|
{ |
|
// Figure out the color that rays hit when shot out from this position. |
|
Vector radcolor[NUMVERTEXNORMALS]; |
|
float tanTheta = tan(VERTEXNORMAL_CONE_INNER_ANGLE); |
|
|
|
for ( int i = 0; i < NUMVERTEXNORMALS; i++ ) |
|
{ |
|
Vector vEnd = vStart + g_anorms[i] * (COORD_EXTENT * 1.74); |
|
|
|
// Now that we've got a ray, see what surface we've hit |
|
Vector lightStyleColors[MAX_LIGHTSTYLES]; |
|
lightStyleColors[0].Init(); // We only care about light style 0 here. |
|
CalcRayAmbientLighting( iThread, vStart, vEnd, tanTheta, lightStyleColors ); |
|
|
|
radcolor[i] = lightStyleColors[0]; |
|
} |
|
|
|
// accumulate samples into radiant box |
|
for ( int j = 6; --j >= 0; ) |
|
{ |
|
float t = 0; |
|
|
|
lightBoxColor[j].Init(); |
|
|
|
for (int i = 0; i < NUMVERTEXNORMALS; i++) |
|
{ |
|
float c = DotProduct( g_anorms[i], g_BoxDirections[j] ); |
|
if (c > 0) |
|
{ |
|
t += c; |
|
lightBoxColor[j] += radcolor[i] * c; |
|
} |
|
} |
|
|
|
lightBoxColor[j] *= 1/t; |
|
} |
|
|
|
// Now add direct light from the emit_surface lights. These 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. |
|
AddEmitSurfaceLights( vStart, lightBoxColor ); |
|
} |
|
|
|
|
|
bool IsLeafAmbientSurfaceLight( dworldlight_t *wl ) |
|
{ |
|
static const float g_flWorldLightMinEmitSurface = 0.005f; |
|
static const float g_flWorldLightMinEmitSurfaceDistanceRatio = ( InvRSquared( Vector( 0, 0, 512 ) ) ); |
|
|
|
if ( wl->type != emit_surface ) |
|
return false; |
|
|
|
if ( wl->style != 0 ) |
|
return false; |
|
|
|
float intensity = max( wl->intensity[0], wl->intensity[1] ); |
|
intensity = max( intensity, wl->intensity[2] ); |
|
|
|
return (intensity * g_flWorldLightMinEmitSurfaceDistanceRatio) < g_flWorldLightMinEmitSurface; |
|
} |
|
|
|
|
|
class CLeafSampler |
|
{ |
|
public: |
|
CLeafSampler( int iThread ) : m_iThread(iThread) {} |
|
|
|
// Generate a random point in the leaf's bounding volume |
|
// reject any points that aren't actually in the leaf |
|
// do a couple of tracing heuristics to eliminate points that are inside detail brushes |
|
// or underneath displacement surfaces in the leaf |
|
// return once we have a valid point, use the center if one can't be computed quickly |
|
void GenerateLeafSamplePosition( int leafIndex, const CUtlVector<dplane_t> &leafPlanes, Vector &samplePosition ) |
|
{ |
|
dleaf_t *pLeaf = dleafs + leafIndex; |
|
|
|
float dx = pLeaf->maxs[0] - pLeaf->mins[0]; |
|
float dy = pLeaf->maxs[1] - pLeaf->mins[1]; |
|
float dz = pLeaf->maxs[2] - pLeaf->mins[2]; |
|
bool bValid = false; |
|
for ( int i = 0; i < 1000 && !bValid; i++ ) |
|
{ |
|
samplePosition.x = pLeaf->mins[0] + m_random.RandomFloat(0, dx); |
|
samplePosition.y = pLeaf->mins[1] + m_random.RandomFloat(0, dy); |
|
samplePosition.z = pLeaf->mins[2] + m_random.RandomFloat(0, dz); |
|
bValid = true; |
|
|
|
for ( int j = leafPlanes.Count(); --j >= 0 && bValid; ) |
|
{ |
|
float d = DotProduct(leafPlanes[j].normal, samplePosition) - leafPlanes[j].dist; |
|
if ( d < DIST_EPSILON ) |
|
{ |
|
// not inside the leaf, try again |
|
bValid = false; |
|
break; |
|
} |
|
} |
|
if ( !bValid ) |
|
continue; |
|
|
|
for ( int j = 0; j < 6; j++ ) |
|
{ |
|
Vector start = samplePosition; |
|
int axis = j%3; |
|
start[axis] = (j<3) ? pLeaf->mins[axis] : pLeaf->maxs[axis]; |
|
float t; |
|
Vector normal; |
|
CastRayInLeaf( m_iThread, samplePosition, start, leafIndex, &t, &normal ); |
|
if ( t == 0.0f ) |
|
{ |
|
// inside a func_detail, try again. |
|
bValid = false; |
|
break; |
|
} |
|
if ( t != 1.0f ) |
|
{ |
|
Vector delta = start - samplePosition; |
|
if ( DotProduct(delta, normal) > 0 ) |
|
{ |
|
// hit backside of displacement, try again. |
|
bValid = false; |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
if ( !bValid ) |
|
{ |
|
// didn't generate a valid sample point, just use the center of the leaf bbox |
|
samplePosition = ( Vector( pLeaf->mins[0], pLeaf->mins[1], pLeaf->mins[2] ) + Vector( pLeaf->maxs[0], pLeaf->maxs[1], pLeaf->maxs[2] ) ) * 0.5f; |
|
} |
|
} |
|
|
|
private: |
|
int m_iThread; |
|
CUniformRandomStream m_random; |
|
}; |
|
|
|
// gets a list of the planes pointing into a leaf |
|
void GetLeafBoundaryPlanes( CUtlVector<dplane_t> &list, int leafIndex ) |
|
{ |
|
list.RemoveAll(); |
|
int nodeIndex = leafparents[leafIndex]; |
|
int child = -(leafIndex + 1); |
|
while ( nodeIndex >= 0 ) |
|
{ |
|
dnode_t *pNode = dnodes + nodeIndex; |
|
dplane_t *pNodePlane = dplanes + pNode->planenum; |
|
if ( pNode->children[0] == child ) |
|
{ |
|
// front side |
|
list.AddToTail( *pNodePlane ); |
|
} |
|
else |
|
{ |
|
// back side |
|
int plane = list.AddToTail(); |
|
list[plane].dist = -pNodePlane->dist; |
|
list[plane].normal = -pNodePlane->normal; |
|
list[plane].type = pNodePlane->type; |
|
} |
|
child = nodeIndex; |
|
nodeIndex = nodeparents[child]; |
|
} |
|
} |
|
|
|
// this stores each sample of the ambient lighting |
|
struct ambientsample_t |
|
{ |
|
Vector pos; |
|
Vector cube[6]; |
|
}; |
|
|
|
// add the sample to the list. If we exceed the maximum number of samples, the worst sample will |
|
// be discarded. This has the effect of converging on the best samples when enough are added. |
|
void AddSampleToList( CUtlVector<ambientsample_t> &list, const Vector &samplePosition, Vector *pCube ) |
|
{ |
|
const int MAX_SAMPLES = 16; |
|
|
|
int index = list.AddToTail(); |
|
list[index].pos = samplePosition; |
|
for ( int i = 0; i < 6; i++ ) |
|
{ |
|
list[index].cube[i] = pCube[i]; |
|
} |
|
|
|
if ( list.Count() <= MAX_SAMPLES ) |
|
return; |
|
|
|
int nearestNeighborIndex = 0; |
|
float nearestNeighborDist = FLT_MAX; |
|
float nearestNeighborTotal = 0; |
|
for ( int i = 0; i < list.Count(); i++ ) |
|
{ |
|
int closestIndex = 0; |
|
float closestDist = FLT_MAX; |
|
float totalDC = 0; |
|
for ( int j = 0; j < list.Count(); j++ ) |
|
{ |
|
if ( j == i ) |
|
continue; |
|
float dist = (list[i].pos - list[j].pos).Length(); |
|
float maxDC = 0; |
|
for ( int k = 0; k < 6; k++ ) |
|
{ |
|
// color delta is computed per-component, per cube side |
|
for (int s = 0; s < 3; s++ ) |
|
{ |
|
float dc = fabs(list[i].cube[k][s] - list[j].cube[k][s]); |
|
maxDC = max(maxDC,dc); |
|
} |
|
totalDC += maxDC; |
|
} |
|
// need a measurable difference in color or we'll just rely on position |
|
if ( maxDC < 1e-4f ) |
|
{ |
|
maxDC = 0; |
|
} |
|
else if ( maxDC > 1.0f ) |
|
{ |
|
maxDC = 1.0f; |
|
} |
|
// selection criteria is 10% distance, 90% color difference |
|
// choose samples that fill the space (large distance from each other) |
|
// and have largest color variation |
|
float distanceFactor = 0.1f + (maxDC * 0.9f); |
|
dist *= distanceFactor; |
|
|
|
// find the "closest" sample to this one |
|
if ( dist < closestDist ) |
|
{ |
|
closestDist = dist; |
|
closestIndex = j; |
|
} |
|
} |
|
// the sample with the "closest" neighbor is rejected |
|
if ( closestDist < nearestNeighborDist || (closestDist == nearestNeighborDist && totalDC < nearestNeighborTotal) ) |
|
{ |
|
nearestNeighborDist = closestDist; |
|
nearestNeighborIndex = i; |
|
} |
|
} |
|
list.FastRemove( nearestNeighborIndex ); |
|
} |
|
|
|
// max number of units in gamma space of per-side delta |
|
int CubeDeltaGammaSpace( Vector *pCube0, Vector *pCube1 ) |
|
{ |
|
int maxDelta = 0; |
|
// do this comparison in gamma space to try and get a perceptual basis for the compare |
|
for ( int i = 0; i < 6; i++ ) |
|
{ |
|
for ( int j = 0; j < 3; j++ ) |
|
{ |
|
int val0 = LinearToScreenGamma( pCube0[i][j] ); |
|
int val1 = LinearToScreenGamma( pCube1[i][j] ); |
|
int delta = abs(val0-val1); |
|
if ( delta > maxDelta ) |
|
maxDelta = delta; |
|
} |
|
} |
|
return maxDelta; |
|
} |
|
// reconstruct the ambient lighting for a leaf at the given position in worldspace |
|
// optionally skip one of the entries in the list |
|
void Mod_LeafAmbientColorAtPos( Vector *pOut, const Vector &pos, const CUtlVector<ambientsample_t> &list, int skipIndex ) |
|
{ |
|
for ( int i = 0; i < 6; i++ ) |
|
{ |
|
pOut[i].Init(); |
|
} |
|
float totalFactor = 0; |
|
for ( int i = 0; i < list.Count(); i++ ) |
|
{ |
|
if ( i == skipIndex ) |
|
continue; |
|
// do an inverse squared distance weighted average of the samples to reconstruct |
|
// the original function |
|
float dist = (list[i].pos - pos).LengthSqr(); |
|
float factor = 1.0f / (dist + 1.0f); |
|
totalFactor += factor; |
|
for ( int j = 0; j < 6; j++ ) |
|
{ |
|
pOut[j] += list[i].cube[j] * factor; |
|
} |
|
} |
|
for ( int i = 0; i < 6; i++ ) |
|
{ |
|
pOut[i] *= (1.0f / totalFactor); |
|
} |
|
} |
|
|
|
// this samples the lighting at each sample and removes any unnecessary samples |
|
void CompressAmbientSampleList( CUtlVector<ambientsample_t> &list ) |
|
{ |
|
Vector testCube[6]; |
|
for ( int i = 0; i < list.Count(); i++ ) |
|
{ |
|
if ( list.Count() > 1 ) |
|
{ |
|
Mod_LeafAmbientColorAtPos( testCube, list[i].pos, list, i ); |
|
if ( CubeDeltaGammaSpace(testCube, list[i].cube) < 3 ) |
|
{ |
|
list.FastRemove(i); |
|
i--; |
|
} |
|
} |
|
} |
|
} |
|
|
|
// basically this is an intersection routine that returns a distance between the boxes |
|
float AABBDistance( const Vector &mins0, const Vector &maxs0, const Vector &mins1, const Vector &maxs1 ) |
|
{ |
|
Vector delta; |
|
for ( int i = 0; i < 3; i++ ) |
|
{ |
|
float greatestMin = max(mins0[i], mins1[i]); |
|
float leastMax = min(maxs0[i], maxs1[i]); |
|
delta[i] = (greatestMin < leastMax) ? 0 : (leastMax - greatestMin); |
|
} |
|
return delta.Length(); |
|
} |
|
|
|
// build a list of leaves from a query |
|
class CLeafList : public ISpatialLeafEnumerator |
|
{ |
|
public: |
|
virtual bool EnumerateLeaf( int leaf, intp context ) |
|
{ |
|
m_list.AddToTail(leaf); |
|
return true; |
|
} |
|
|
|
CUtlVector<int> m_list; |
|
}; |
|
|
|
// conver short[3] to vector |
|
static void LeafBounds( int leafIndex, Vector &mins, Vector &maxs ) |
|
{ |
|
for ( int i = 0; i < 3; i++ ) |
|
{ |
|
mins[i] = dleafs[leafIndex].mins[i]; |
|
maxs[i] = dleafs[leafIndex].maxs[i]; |
|
} |
|
} |
|
|
|
// returns the index of the nearest leaf with ambient samples |
|
int NearestNeighborWithLight(int leafID) |
|
{ |
|
Vector mins, maxs; |
|
LeafBounds( leafID, mins, maxs ); |
|
Vector size = maxs - mins; |
|
CLeafList leafList; |
|
ToolBSPTree()->EnumerateLeavesInBox( mins-size, maxs+size, &leafList, 0 ); |
|
float bestDist = FLT_MAX; |
|
int bestIndex = leafID; |
|
for ( int i = 0; i < leafList.m_list.Count(); i++ ) |
|
{ |
|
int testIndex = leafList.m_list[i]; |
|
if ( !g_pLeafAmbientIndex->Element(testIndex).ambientSampleCount ) |
|
continue; |
|
|
|
Vector testMins, testMaxs; |
|
LeafBounds( testIndex, testMins, testMaxs ); |
|
float dist = AABBDistance( mins, maxs, testMins, testMaxs ); |
|
if ( dist < bestDist ) |
|
{ |
|
bestDist = dist; |
|
bestIndex = testIndex; |
|
} |
|
} |
|
return bestIndex; |
|
} |
|
|
|
// maps a float to a byte fraction between min & max |
|
static byte Fixed8Fraction( float t, float tMin, float tMax ) |
|
{ |
|
if ( tMax <= tMin ) |
|
return 0; |
|
|
|
float frac = RemapValClamped( t, tMin, tMax, 0.0f, 255.0f ); |
|
return byte(frac+0.5f); |
|
} |
|
|
|
CUtlVector< CUtlVector<ambientsample_t> > g_LeafAmbientSamples; |
|
|
|
void ComputeAmbientForLeaf( int iThread, int leafID, CUtlVector<ambientsample_t> &list ) |
|
{ |
|
CUtlVector<dplane_t> leafPlanes; |
|
CLeafSampler sampler( iThread ); |
|
|
|
GetLeafBoundaryPlanes( leafPlanes, leafID ); |
|
list.RemoveAll(); |
|
// this heuristic tries to generate at least one sample per volume (chosen to be similar to the size of a player) in the space |
|
int xSize = (dleafs[leafID].maxs[0] - dleafs[leafID].mins[0]) / 32; |
|
int ySize = (dleafs[leafID].maxs[1] - dleafs[leafID].mins[1]) / 32; |
|
int zSize = (dleafs[leafID].maxs[2] - dleafs[leafID].mins[2]) / 64; |
|
xSize = max(xSize,1); |
|
ySize = max(xSize,1); |
|
zSize = max(xSize,1); |
|
// generate update 128 candidate samples, always at least one sample |
|
int volumeCount = xSize * ySize * zSize; |
|
if ( g_bFastAmbient ) |
|
{ |
|
// save compute time, only do one sample |
|
volumeCount = 1; |
|
} |
|
int sampleCount = clamp( volumeCount, 1, 128 ); |
|
if ( dleafs[leafID].contents & CONTENTS_SOLID ) |
|
{ |
|
// don't generate any samples in solid leaves |
|
// NOTE: We copy the nearest non-solid leaf sample pointers into this leaf at the end |
|
return; |
|
} |
|
Vector cube[6]; |
|
for ( int i = 0; i < sampleCount; i++ ) |
|
{ |
|
// compute each candidate sample and add to the list |
|
Vector samplePosition; |
|
sampler.GenerateLeafSamplePosition( leafID, leafPlanes, samplePosition ); |
|
ComputeAmbientFromSphericalSamples( iThread, samplePosition, cube ); |
|
// note this will remove the least valuable sample once the limit is reached |
|
AddSampleToList( list, samplePosition, cube ); |
|
} |
|
|
|
// remove any samples that can be reconstructed with the remaining data |
|
CompressAmbientSampleList( list ); |
|
} |
|
|
|
static void ThreadComputeLeafAmbient( int iThread, void *pUserData ) |
|
{ |
|
CUtlVector<ambientsample_t> list; |
|
while (1) |
|
{ |
|
int leafID = GetThreadWork (); |
|
if (leafID == -1) |
|
break; |
|
list.RemoveAll(); |
|
ComputeAmbientForLeaf(iThread, leafID, list); |
|
// copy to the output array |
|
g_LeafAmbientSamples[leafID].SetCount( list.Count() ); |
|
for ( int i = 0; i < list.Count(); i++ ) |
|
{ |
|
g_LeafAmbientSamples[leafID].Element(i) = list.Element(i); |
|
} |
|
} |
|
} |
|
|
|
void VMPI_ProcessLeafAmbient( int iThread, uint64 iLeaf, MessageBuffer *pBuf ) |
|
{ |
|
CUtlVector<ambientsample_t> list; |
|
ComputeAmbientForLeaf(iThread, (int)iLeaf, list); |
|
|
|
VMPI_SetCurrentStage( "EncodeLeafAmbientResults" ); |
|
|
|
// Encode the results. |
|
int nSamples = list.Count(); |
|
pBuf->write( &nSamples, sizeof( nSamples ) ); |
|
if ( nSamples ) |
|
{ |
|
pBuf->write( list.Base(), list.Count() * sizeof( ambientsample_t ) ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Called on the master when a worker finishes processing a static prop. |
|
//----------------------------------------------------------------------------- |
|
void VMPI_ReceiveLeafAmbientResults( uint64 leafID, MessageBuffer *pBuf, int iWorker ) |
|
{ |
|
// Decode the results. |
|
int nSamples; |
|
pBuf->read( &nSamples, sizeof( nSamples ) ); |
|
|
|
g_LeafAmbientSamples[leafID].SetCount( nSamples ); |
|
if ( nSamples ) |
|
{ |
|
pBuf->read(g_LeafAmbientSamples[leafID].Base(), nSamples * sizeof(ambientsample_t) ); |
|
} |
|
} |
|
|
|
|
|
void ComputePerLeafAmbientLighting() |
|
{ |
|
// Figure out which lights should go in the per-leaf ambient cubes. |
|
int nInAmbientCube = 0; |
|
int nSurfaceLights = 0; |
|
for ( int i=0; i < *pNumworldlights; i++ ) |
|
{ |
|
dworldlight_t *wl = &dworldlights[i]; |
|
|
|
if ( IsLeafAmbientSurfaceLight( wl ) ) |
|
wl->flags |= DWL_FLAGS_INAMBIENTCUBE; |
|
else |
|
wl->flags &= ~DWL_FLAGS_INAMBIENTCUBE; |
|
|
|
if ( wl->type == emit_surface ) |
|
++nSurfaceLights; |
|
|
|
if ( wl->flags & DWL_FLAGS_INAMBIENTCUBE ) |
|
++nInAmbientCube; |
|
} |
|
|
|
Msg( "%d of %d (%d%% of) surface lights went in leaf ambient cubes.\n", nInAmbientCube, nSurfaceLights, nSurfaceLights ? ((nInAmbientCube*100) / nSurfaceLights) : 0 ); |
|
|
|
g_LeafAmbientSamples.SetCount(numleafs); |
|
|
|
if ( g_bUseMPI ) |
|
{ |
|
// Distribute the work among the workers. |
|
VMPI_SetCurrentStage( "ComputeLeafAmbientLighting" ); |
|
DistributeWork( numleafs, VMPI_DISTRIBUTEWORK_PACKETID, VMPI_ProcessLeafAmbient, VMPI_ReceiveLeafAmbientResults ); |
|
} |
|
else |
|
{ |
|
RunThreadsOn(numleafs, true, ThreadComputeLeafAmbient); |
|
} |
|
|
|
// now write out the data |
|
Msg("Writing leaf ambient..."); |
|
g_pLeafAmbientIndex->RemoveAll(); |
|
g_pLeafAmbientLighting->RemoveAll(); |
|
g_pLeafAmbientIndex->SetCount( numleafs ); |
|
g_pLeafAmbientLighting->EnsureCapacity( numleafs*4 ); |
|
for ( int leafID = 0; leafID < numleafs; leafID++ ) |
|
{ |
|
const CUtlVector<ambientsample_t> &list = g_LeafAmbientSamples[leafID]; |
|
g_pLeafAmbientIndex->Element(leafID).ambientSampleCount = list.Count(); |
|
if ( !list.Count() ) |
|
{ |
|
g_pLeafAmbientIndex->Element(leafID).firstAmbientSample = 0; |
|
} |
|
else |
|
{ |
|
g_pLeafAmbientIndex->Element(leafID).firstAmbientSample = g_pLeafAmbientLighting->Count(); |
|
// compute the samples in disk format. Encode the positions in 8-bits using leaf bounds fractions |
|
for ( int i = 0; i < list.Count(); i++ ) |
|
{ |
|
int outIndex = g_pLeafAmbientLighting->AddToTail(); |
|
dleafambientlighting_t &light = g_pLeafAmbientLighting->Element(outIndex); |
|
|
|
light.x = Fixed8Fraction( list[i].pos.x, dleafs[leafID].mins[0], dleafs[leafID].maxs[0] ); |
|
light.y = Fixed8Fraction( list[i].pos.y, dleafs[leafID].mins[1], dleafs[leafID].maxs[1] ); |
|
light.z = Fixed8Fraction( list[i].pos.z, dleafs[leafID].mins[2], dleafs[leafID].maxs[2] ); |
|
light.pad = 0; |
|
for ( int side = 0; side < 6; side++ ) |
|
{ |
|
VectorToColorRGBExp32( list[i].cube[side], light.cube.m_Color[side] ); |
|
} |
|
} |
|
} |
|
} |
|
for ( int i = 0; i < numleafs; i++ ) |
|
{ |
|
// UNDONE: Do this dynamically in the engine instead. This will allow us to sample across leaf |
|
// boundaries always which should improve the quality of lighting in general |
|
if ( g_pLeafAmbientIndex->Element(i).ambientSampleCount == 0 ) |
|
{ |
|
if ( !(dleafs[i].contents & CONTENTS_SOLID) ) |
|
{ |
|
Msg("Bad leaf ambient for leaf %d\n", i ); |
|
} |
|
|
|
int refLeaf = NearestNeighborWithLight(i); |
|
g_pLeafAmbientIndex->Element(i).ambientSampleCount = 0; |
|
g_pLeafAmbientIndex->Element(i).firstAmbientSample = refLeaf; |
|
} |
|
} |
|
Msg("done\n"); |
|
} |
|
|
|
|