Skip to content

Commit

Permalink
[Fwd Clustered] Considerably improve perf when spotlight angle > 90°
Browse files Browse the repository at this point in the history
The original algorithm was generating a very large cone since tan(45)
starts growing really fast.

Now spotlights are much better culled if their cone is > 90°
  • Loading branch information
darksylinc committed Dec 3, 2024
1 parent 97c8a98 commit eaaa764
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 10 deletions.
2 changes: 2 additions & 0 deletions OgreMain/include/OgreLight.h
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ namespace Ogre
const Radian &getSpotlightOuterAngle() const { return mSpotOuter; }

Real getSpotlightTanHalfAngle() const { return mTanHalfAngle; }
Real getSpotlightSinHalfAngle() const { return mSinHalfAngle; }

/** Returns the falloff between the inner and outer cones of the spotlight.
*/
Expand Down Expand Up @@ -621,6 +622,7 @@ namespace Ogre
Radian mSpotInner;

Real mTanHalfAngle; // = tan( mSpotOuter * 0.5f );
Real mSinHalfAngle; // = sin( mSpotOuter * 0.5f );
Real mSpotFalloff;
Real mSpotNearClip;
Real mRange;
Expand Down
155 changes: 148 additions & 7 deletions OgreMain/src/OgreForwardClustered.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ namespace Ogre
}
}
}
else
else if( ( *itLight )->getSpotlightTanHalfAngle() <= 1.0f )
{
// Spotlight. Do pyramid vs frustum intersection. This pyramid
// has 5 sides and encloses the spotlight's cone.
Expand All @@ -492,13 +492,13 @@ namespace Ogre
const Real lightRange = ( *itLight )->getAttenuationRange();
const Real lenOpposite = ( *itLight )->getSpotlightTanHalfAngle() * lightRange;

Vector3 leftCorner =
lightNode->_getDerivedOrientation() * Vector3( -lenOpposite, lenOpposite, 0 );
Vector3 rightCorner =
lightNode->_getDerivedOrientation() * Vector3( lenOpposite, lenOpposite, 0 );
const Quaternion lightRot = lightNode->_getDerivedOrientation();

Vector3 scalarLightPos = ( *itLight )->getParentNode()->_getDerivedPosition();
Vector3 scalarLightDir = ( *itLight )->getDerivedDirection() * lightRange;
const Vector3 leftCorner = lightRot * Vector3( -lenOpposite, lenOpposite, 0 );
const Vector3 rightCorner = lightRot * Vector3( lenOpposite, lenOpposite, 0 );

const Vector3 scalarLightPos = ( *itLight )->getParentNode()->_getDerivedPosition();
const Vector3 scalarLightDir = ( *itLight )->getDerivedDirection() * lightRange;

Plane scalarPlane[6];

Expand Down Expand Up @@ -618,6 +618,147 @@ namespace Ogre
}
}
}
else
{
// Spotlight with outer angle > 90°. tan(45°) starts growing too large very quickly,
// yet the spotlight's light reach is limited by its radius, causing the "false"
// positives to blow up (they're not technically false positives because light is
// infinite, but for practical purposes, they are).
// Just doing the OBB that encloses the spotlight is much more conservative.

Node *lightNode = ( *itLight )->getParentNode();

// Generate the 6 OBB vertices
const Real lightRange = ( *itLight )->getAttenuationRange();
const Real lenOpposite = ( *itLight )->getSpotlightSinHalfAngle() * lightRange;

const Quaternion lightRot = lightNode->_getDerivedOrientation();

const Vector3 bottomLeftCorner = lightRot * Vector3( -lenOpposite, -lenOpposite, 0 );
const Vector3 topRightCorner = -bottomLeftCorner;

const Vector3 scalarLightPos = ( *itLight )->getParentNode()->_getDerivedPosition();
const Vector3 scalarLightDir = ( *itLight )->getDerivedDirection() * lightRange;

Plane scalarPlane[6];

scalarPlane[FRUSTUM_PLANE_FAR] =
Plane( lightRot.zAxis(), scalarLightPos + scalarLightDir );
scalarPlane[FRUSTUM_PLANE_NEAR] =
Plane( -scalarPlane[FRUSTUM_PLANE_FAR].normal, scalarLightPos );

scalarPlane[FRUSTUM_PLANE_LEFT] =
Plane( lightRot.xAxis(), scalarLightPos + bottomLeftCorner );
scalarPlane[FRUSTUM_PLANE_RIGHT] =
Plane( -scalarPlane[FRUSTUM_PLANE_LEFT].normal, scalarLightPos + topRightCorner );

scalarPlane[FRUSTUM_PLANE_TOP] =
Plane( -lightRot.yAxis(), scalarLightPos + topRightCorner );
scalarPlane[FRUSTUM_PLANE_BOTTOM] =
Plane( -scalarPlane[FRUSTUM_PLANE_TOP].normal, scalarLightPos + bottomLeftCorner );

ArrayPlane obbPlane[6];
obbPlane[0].normal.setAll( scalarPlane[0].normal );
obbPlane[0].negD = Mathlib::SetAll( -scalarPlane[0].d );
obbPlane[1].normal.setAll( scalarPlane[1].normal );
obbPlane[1].negD = Mathlib::SetAll( -scalarPlane[1].d );
obbPlane[2].normal.setAll( scalarPlane[2].normal );
obbPlane[2].negD = Mathlib::SetAll( -scalarPlane[2].d );
obbPlane[3].normal.setAll( scalarPlane[3].normal );
obbPlane[3].negD = Mathlib::SetAll( -scalarPlane[3].d );
obbPlane[4].normal.setAll( scalarPlane[4].normal );
obbPlane[4].negD = Mathlib::SetAll( -scalarPlane[4].d );
obbPlane[5].normal.setAll( scalarPlane[5].normal );
obbPlane[5].negD = Mathlib::SetAll( -scalarPlane[5].d );

const Vector3 topLeftCorner = lightRot * Vector3( -lenOpposite, lenOpposite, 0 );
const Vector3 bottomRightCorner = -topLeftCorner;

ArrayVector3 obbVertex[8];

obbVertex[0].setAll( scalarLightPos + bottomLeftCorner );
obbVertex[0].setAll( scalarLightPos + topRightCorner );
obbVertex[1].setAll( scalarLightPos + scalarLightDir + bottomLeftCorner );
obbVertex[2].setAll( scalarLightPos + scalarLightDir + topRightCorner );

obbVertex[3].setAll( scalarLightPos + topLeftCorner );
obbVertex[4].setAll( scalarLightPos + bottomRightCorner );
obbVertex[5].setAll( scalarLightPos + scalarLightDir + topLeftCorner );
obbVertex[6].setAll( scalarLightPos + scalarLightDir + bottomRightCorner );

for( size_t j = 0; j < numPackedFrustumsPerSlice; ++j )
{
const FrustumRegion *RESTRICT_ALIAS frustumRegion =
mFrustumRegions.get() + frustumStartIdx + j;

ArrayReal dotResult;
ArrayMaskR mask;

mask = BooleanMask4::getAllSetMask();

// There is no intersection if for at least one of the 12 planes
//(6+6) all the vertices (8+8 verts.) are on the negative side.

// Test all 8 obb vertices against each of the 6 frustum planes.
for( int k = 0; k < 6; ++k )
{
ArrayMaskR vertexMask = ARRAY_MASK_ZERO;

for( int l = 0; l < 8; ++l )
{
dotResult = frustumRegion->plane[k].normal.dotProduct( obbVertex[l] ) -
frustumRegion->plane[k].negD;
vertexMask = Mathlib::Or(
vertexMask, Mathlib::CompareGreater( dotResult, ARRAY_REAL_ZERO ) );
}

mask = Mathlib::And( mask, vertexMask );
}

if( BooleanMask4::getScalarMask( mask ) != 0 )
{
// Test all 8 frustum corners against each of the 6 obb planes.
for( int k = 0; k < 6; ++k )
{
ArrayMaskR vertexMask = ARRAY_MASK_ZERO;

for( int l = 0; l < 8; ++l )
{
dotResult = obbPlane[k].normal.dotProduct( frustumRegion->corners[l] ) -
obbPlane[k].negD;
vertexMask = Mathlib::Or(
vertexMask, Mathlib::CompareGreater( dotResult, ARRAY_REAL_ZERO ) );
}

mask = Mathlib::And( mask, vertexMask );
}
}

const uint32 scalarMask = BooleanMask4::getScalarMask( mask );

for( size_t k = 0; k < ARRAY_PACKED_REALS; ++k )
{
if( IS_BIT_SET( k, scalarMask ) )
{
const size_t idx = ( frustumStartIdx + j ) * ARRAY_PACKED_REALS + k;
FastArray<LightCount>::iterator numLightsInCell =
mLightCountInCell.begin() + idx;

// assert( numLightsInCell < mLightCountInCell.end() );

if( numLightsInCell->lightCount[0] < mLightsPerCell )
{
uint16 *RESTRICT_ALIAS cellElem =
mGridBuffer + idx * mObjsPerCell +
( numLightsInCell->lightCount[0] + c_reservedLightSlotsPerCell );
*cellElem = static_cast<uint16>( i * c_ForwardPlusNumFloat4PerLight );
++numLightsInCell->lightCount[0];
++numLightsInCell->lightCount[lightType];
}
}
}
}
}

++itLight;
}
Expand Down
25 changes: 22 additions & 3 deletions OgreMain/src/OgreLight.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ namespace Ogre
mSpotOuter( Degree( 40.0f ) ),
mSpotInner( Degree( 30.0f ) ),
mTanHalfAngle( Math::Tan( mSpotOuter * 0.5f ) ),
mSinHalfAngle( Math::Sin( mSpotOuter * 0.5f ) ),
mSpotFalloff( 1.0f ),
mSpotNearClip( 0.0f ),
mRange( 23.0f ),
Expand Down Expand Up @@ -156,6 +157,7 @@ namespace Ogre
mSpotFalloff = falloff;

mTanHalfAngle = Math::Tan( mSpotOuter * 0.5f );
mSinHalfAngle = Math::Sin( mSpotOuter * 0.5f );

if( boundsChanged && mLightType == LT_SPOTLIGHT )
updateLightBounds();
Expand All @@ -168,6 +170,7 @@ namespace Ogre
bool boundsChanged = mSpotOuter != val;
mSpotOuter = val;
mTanHalfAngle = Math::Tan( mSpotOuter * 0.5f );
mSinHalfAngle = Math::Sin( mSpotOuter * 0.5f );
if( boundsChanged && mLightType == LT_SPOTLIGHT )
updateLightBounds();
}
Expand Down Expand Up @@ -277,9 +280,25 @@ namespace Ogre
{
// In local space, lights are centered at origin, facing towards +Z
Aabb aabb;
Real lenOpposite = mTanHalfAngle * mRange;
aabb.mCenter = Vector3( 0, 0, -mRange * 0.5f );
aabb.mHalfSize = Vector3( lenOpposite, lenOpposite, mRange * 0.5f );
if( mTanHalfAngle <= 1.0f )
{
const Real lenOpposite = mTanHalfAngle * mRange;
aabb.mCenter = Vector3( 0, 0, -mRange * 0.5f );
aabb.mHalfSize = Vector3( lenOpposite, lenOpposite, mRange * 0.5f );
}
else
{
// Spotlight with outer angle > 90°. tan(45°) starts growing too large very quickly,
// yet the spotlight's light reach is limited by its radius, causing the "false"
// positives to blow up (they're not technically false positives because light is
// infinite, but for practical purposes, they are).
// Just doing the OBB that encloses the spotlight is much more conservative.
// This might cause issues with shadow mapping though (if the range is too small for
// the light attentuation and the outer angle is not too large yet).
const Real lenOpposite = mSinHalfAngle * mRange;
aabb.mCenter = Vector3( 0, 0, -mRange * 0.5f );
aabb.mHalfSize = Vector3( lenOpposite, lenOpposite, mRange * 0.5f );
}
mObjectData.mLocalRadius[mObjectData.mIndex] = aabb.getRadius();
mObjectData.mLocalAabb->setFromAabb( aabb, mObjectData.mIndex );
}
Expand Down

0 comments on commit eaaa764

Please sign in to comment.