diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /utils/vrad/leaf_ambient_lighting.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'utils/vrad/leaf_ambient_lighting.cpp')
| -rw-r--r-- | utils/vrad/leaf_ambient_lighting.cpp | 708 |
1 files changed, 708 insertions, 0 deletions
diff --git a/utils/vrad/leaf_ambient_lighting.cpp b/utils/vrad/leaf_ambient_lighting.cpp new file mode 100644 index 0000000..3836592 --- /dev/null +++ b/utils/vrad/leaf_ambient_lighting.cpp @@ -0,0 +1,708 @@ +//========= 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, int 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"); +} + |