some enchancements for shadowmapping

new /= operator in point4f
_calcClipSpaceAABB in pssm no longer uses radius just transform points into light space and do min max
_roundProjection matrix also optimized.
This commit is contained in:
marauder2k7 2024-12-09 17:54:48 +00:00
parent 03a348deb7
commit 80bd8f8117
3 changed files with 66 additions and 63 deletions

View file

@ -135,65 +135,68 @@ void PSSMLightShadowMap::_calcSplitPos(const Frustum& currFrustum)
Box3F PSSMLightShadowMap::_calcClipSpaceAABB(const Frustum& f, const MatrixF& transform, F32 farDist) Box3F PSSMLightShadowMap::_calcClipSpaceAABB(const Frustum& f, const MatrixF& transform, F32 farDist)
{ {
// Calculate frustum center PROFILE_SCOPE(PSSMLightShadowMap_calcClipSpaceAABB);
Point3F center(0,0,0);
for (U32 i = 0; i < 8; i++) // Transform frustum corners to light space.
{ Point3F transformedPoints[8];
const Point3F& pt = f.getPoints()[i]; const Point3F* frustumPoints = f.getPoints();
center += pt; for (U32 i = 0; i < 8; i++) {
transformedPoints[i] = frustumPoints[i];
transform.mulP(transformedPoints[i]);
} }
center /= 8;
// Calculate frustum bounding sphere radius // Compute the AABB for the transformed points.
F32 radius = 0.0f;
for (U32 i = 0; i < 8; i++)
radius = getMax(radius, (f.getPoints()[i] - center).lenSquared());
radius = mFloor( mSqrt(radius) );
// Now build box for sphere
Box3F result; Box3F result;
Point3F radiusBox(radius, radius, radius); result.minExtents.set(F32_MAX, F32_MAX, F32_MAX);
result.minExtents = center - radiusBox; result.maxExtents.set(-F32_MAX, -F32_MAX, -F32_MAX);
result.maxExtents = center + radiusBox;
// Transform to light projection space for (U32 i = 0; i < 8; i++) {
transform.mul(result); result.minExtents.setMin(transformedPoints[i]);
result.maxExtents.setMax(transformedPoints[i]);
return result; }
// Clamp Z to within near and far distances to avoid over-extension.
result.minExtents.z = getMax(result.minExtents.z, 0.0f); // Z must be non-negative in light space.
result.maxExtents.z = getMin(result.maxExtents.z, farDist);
return result;
} }
// This "rounds" the projection matrix to remove subtexel movement during shadow map // This "rounds" the projection matrix to remove subtexel movement during shadow map
// rasterization. This is here to reduce shadow shimmering. // rasterization. This is here to reduce shadow shimmering.
void PSSMLightShadowMap::_roundProjection(const MatrixF& lightMat, const MatrixF& cropMatrix, Point3F &offset, U32 splitNum) void PSSMLightShadowMap::_roundProjection(const MatrixF& lightMat, const MatrixF& cropMatrix, Point3F &offset, U32 splitNum)
{ {
// Round to the nearest shadowmap texel, this helps reduce shimmering // Combine the matrices to transform into light projection space.
MatrixF currentProj = GFX->getProjectionMatrix(); MatrixF lightProjection = cropMatrix * lightMat;
currentProj.reverseProjection();
currentProj = cropMatrix * currentProj * lightMat;
// Project origin to screen. // Project origin to screen space.
Point4F originShadow4F(0,0,0,1); Point4F origin(0, 0, 0, 1);
currentProj.mul(originShadow4F); lightProjection.mul(origin);
Point2F originShadow(originShadow4F.x / originShadow4F.w, originShadow4F.y / originShadow4F.w); origin /= origin.w;
// Convert to texture space (0..shadowMapSize) // Convert to texture space (based on shadow map resolution).
F32 t = mNumSplits < 4 ? mShadowMapTex->getWidth() / mNumSplits : mShadowMapTex->getWidth() / 2; F32 texelWidth = mShadowMapTex->getWidth() / (mNumSplits < 4 ? mNumSplits : 2);
Point2F texelsToTexture(t / 2.0f, mShadowMapTex->getHeight() / 2.0f); Point2F texelScale(texelWidth * 0.5f, mShadowMapTex->getHeight() * 0.5f);
if (mNumSplits >= 4) texelsToTexture.y *= 0.5f;
originShadow.convolve(texelsToTexture);
// Clamp to texel boundary // Adjust origin to align to nearest texel.
Point2F originRounded; Point2F originTexelSpace(origin.x * texelScale.x, origin.y * texelScale.y);
originRounded.x = mFloor(originShadow.x + 0.5f); Point2F roundedOriginTexelSpace(mFloor(originTexelSpace.x + 0.5f), mFloor(originTexelSpace.y + 0.5f));
originRounded.y = mFloor(originShadow.y + 0.5f); Point2F texelOffset = (roundedOriginTexelSpace - originTexelSpace) / texelScale;
// Subtract origin to get an offset to recenter everything on texel boundaries // Apply the offset back to the projection matrix.
originRounded -= originShadow; offset.x += texelOffset.x;
offset.y += texelOffset.y;
}
// Convert back to texels (0..1) and offset void PSSMLightShadowMap::_adjustScaleAndOffset(Box3F& clipAABB, Point3F& scale, Point3F& offset) {
originRounded.convolveInverse(texelsToTexture); scale.x = 2.0f / (clipAABB.maxExtents.x - clipAABB.minExtents.x);
offset.x += originRounded.x; scale.y = 2.0f / (clipAABB.maxExtents.y - clipAABB.minExtents.y);
offset.y += originRounded.y; scale.z = 1.0f;
// Center the offset to tightly align the projection.
offset.x = -0.5f * (clipAABB.maxExtents.x + clipAABB.minExtents.x) * scale.x;
offset.y = -0.5f * (clipAABB.maxExtents.y + clipAABB.minExtents.y) * scale.y;
offset.z = 0.0f;
} }
void PSSMLightShadowMap::_render( RenderPassManager* renderPass, void PSSMLightShadowMap::_render( RenderPassManager* renderPass,
@ -271,24 +274,11 @@ void PSSMLightShadowMap::_render( RenderPassManager* renderPass,
Box3F clipAABB = _calcClipSpaceAABB(subFrustum, lightViewProj, fullFrustum.getFarDist()); Box3F clipAABB = _calcClipSpaceAABB(subFrustum, lightViewProj, fullFrustum.getFarDist());
// Calculate our crop matrix // Calculate our crop matrix
Point3F scale(2.0f / (clipAABB.maxExtents.x - clipAABB.minExtents.x), Point3F scale;
2.0f / (clipAABB.maxExtents.y - clipAABB.minExtents.y),
1.0f);
// TODO: This seems to produce less "pops" of the Point3F offset;
// shadow resolution as the camera spins around and
// it should produce pixels that are closer to being
// square.
//
// Still is it the right thing to do?
//
scale.y = scale.x = ( getMin( scale.x, scale.y ) );
//scale.x = mFloor(scale.x);
//scale.y = mFloor(scale.y);
Point3F offset( -0.5f * (clipAABB.maxExtents.x + clipAABB.minExtents.x) * scale.x, _adjustScaleAndOffset(clipAABB, scale, offset);
-0.5f * (clipAABB.maxExtents.y + clipAABB.minExtents.y) * scale.y,
0.0f );
MatrixF cropMatrix(true); MatrixF cropMatrix(true);
cropMatrix.scale(scale); cropMatrix.scale(scale);
@ -323,9 +313,7 @@ void PSSMLightShadowMap::_render( RenderPassManager* renderPass,
// Crop matrix multiply needs to be post-projection. // Crop matrix multiply needs to be post-projection.
MatrixF alightProj = GFX->getProjectionMatrix(); MatrixF alightProj = GFX->getProjectionMatrix();
alightProj.reverseProjection();
alightProj = cropMatrix * alightProj; alightProj = cropMatrix * alightProj;
alightProj.reverseProjection();
// Set our new projection // Set our new projection
GFX->setProjectionMatrix(alightProj); GFX->setProjectionMatrix(alightProj);

View file

@ -58,7 +58,7 @@ protected:
Box3F _calcClipSpaceAABB(const Frustum& f, const MatrixF& transform, F32 farDist); Box3F _calcClipSpaceAABB(const Frustum& f, const MatrixF& transform, F32 farDist);
void _calcPlanesCullForShadowCasters(Vector< Vector<PlaneF> > &out, const Frustum &viewFrustum, const Point3F &_ligthDir); void _calcPlanesCullForShadowCasters(Vector< Vector<PlaneF> > &out, const Frustum &viewFrustum, const Point3F &_ligthDir);
void _roundProjection(const MatrixF& lightMat, const MatrixF& cropMatrix, Point3F &offset, U32 splitNum); void _roundProjection(const MatrixF& lightMat, const MatrixF& cropMatrix, Point3F &offset, U32 splitNum);
void _adjustScaleAndOffset(Box3F& clipAABB, Point3F& scale, Point3F& offset);
static const S32 MAX_SPLITS = 4; static const S32 MAX_SPLITS = 4;
U32 mNumSplits; U32 mNumSplits;
F32 mSplitDist[MAX_SPLITS+1]; // +1 because we store a cap F32 mSplitDist[MAX_SPLITS+1]; // +1 because we store a cap

View file

@ -104,6 +104,7 @@ class Point4F
Point4F& operator*=(const Point4F&); Point4F& operator*=(const Point4F&);
Point4F& operator=(const Point3F&); Point4F& operator=(const Point3F&);
Point4F& operator=(const Point4F&); Point4F& operator=(const Point4F&);
Point4F& operator/=(F32);
Point3F asPoint3F() const { return Point3F(x,y,z); } Point3F asPoint3F() const { return Point3F(x,y,z); }
@ -186,6 +187,20 @@ inline Point4F& Point4F::operator=(const Point4F &_vec)
return *this; return *this;
} }
inline Point4F& Point4F::operator/=(F32 scalar)
{
// Prevent division by zero
if (mIsZero(scalar))
return *this;
x /= scalar;
y /= scalar;
z /= scalar;
w /= scalar;
return *this;
}
inline Point4F Point4F::operator+(const Point4F& _add) const inline Point4F Point4F::operator+(const Point4F& _add) const
{ {
return Point4F( x + _add.x, y + _add.y, z + _add.z, w + _add.w ); return Point4F( x + _add.x, y + _add.y, z + _add.z, w + _add.w );