I've been trying to get a single directional shadows orthogonal matrix to fit around the view frustum before I move on to splitting it up and calculating a tighter bound for it. However I can't seem to get the shimmering effect to go away. I've been following microsofts example https://msdn.microsoft.com/en-us/library/windows/desktop/ee416324(v=vs.85).aspx like everyone else but I can't get the shimmering to go away, unless I'm missing something here. And yeah, don't mind the large texels from the shadows, the scene/view frustum is pretty big because I wanted to test it properly. Here's the effect:
And the code:
GLfloat far = -INFINITY;
GLfloat near = INFINITY;
//Multiply all the world space frustum corners with the view matrix of the light
Frustum cameraFrustum = CameraMan.getActiveCamera()->mFrustum;
lightViewMatrix = glm::lookAt((cameraFrustum.frustumCenter - glm::vec3(-0.447213620f, -0.89442790f, 0.0f)), cameraFrustum.frustumCenter, glm::vec3(0.0f, 0.0f, 1.0f));
glm::vec3 arr[8];
for (unsigned int i = 0; i < 8; ++i)
arr[i] = glm::vec3(lightViewMatrix * glm::vec4(cameraFrustum.frustumCorners[i], 1.0f));
glm::vec3 minO = glm::vec3(INFINITY, INFINITY, INFINITY);
glm::vec3 maxO = glm::vec3(-INFINITY, -INFINITY, -INFINITY);
for (auto& vec : arr)
{
minO = glm::min(minO, vec);
maxO = glm::max(maxO, vec);
}
far = maxO.z;
near = minO.z;
//Get the longest diagonal of the frustum, this along with texel sized increments is used to keep the shadows from shimmering
//far top right - near bottom left
glm::vec3 longestDiagonal = cameraFrustum.frustumCorners[0] - cameraFrustum.frustumCorners[6];
GLfloat lengthOfDiagonal = glm::length(longestDiagonal);
longestDiagonal = glm::vec3(lengthOfDiagonal);
glm::vec3 borderOffset = (longestDiagonal - (maxO - minO)) * glm::vec3(0.5f, 0.5f, 0.5f);
borderOffset *= glm::vec3(1.0f, 1.0f, 0.0f);
maxO += borderOffset;
minO -= borderOffset;
GLfloat worldUnitsPerTexel = lengthOfDiagonal / 1024.0f;
glm::vec3 vWorldUnitsPerTexel = glm::vec3(worldUnitsPerTexel, worldUnitsPerTexel, 0.0f);
minO /= vWorldUnitsPerTexel;
minO = glm::floor(minO);
minO *= vWorldUnitsPerTexel;
maxO /= vWorldUnitsPerTexel;
maxO = glm::floor(maxO);
maxO *= vWorldUnitsPerTexel;
lightOrthoMatrix = glm::ortho(minO.x, maxO.x, minO.y, maxO.y, near, far);
//For more accurate near and far planes, clip the scenes AABB with the orthographic frustum
//calculateNearAndFar();
At first I thought I was simply using a too big scene for such a small texture, but even with smaller near/far values I get pixel shimmering. I based the texel increment/longest diagonal solution on the code that goes along with that article but i can't quite get it right
EDIT: this is how the frustum is calculated each frame, and how the shadow textures are setup
void Camera::updateFrustum()
{
//Just to visualise it http://www.panohelp.com/lensfov.html
float nearHeight = 2 * tan(mFOV / 2) * mNear;
float nearWidth = nearHeight * mRatio;
float farHeight = 2 * tan(mFOV / 2) * mFar;
float farWidth = farHeight * mRatio;
glm::vec3 fc = mPos + mFront * mFar;
glm::vec3 nc = mPos + mFront * mNear;
mFrustum.frustumCorners[0] = fc + (mUp * farHeight / 2.0f) - (mRight * farWidth / 2.0f);
mFrustum.frustumCorners[1] = fc + (mUp * farHeight / 2.0f) + (mRight * farWidth / 2.0f);
mFrustum.frustumCorners[2] = fc - (mUp * farHeight / 2.0f) - (mRight * farWidth / 2.0f);
mFrustum.frustumCorners[3] = fc - (mUp * farHeight / 2.0f) + (mRight * farWidth / 2.0f);
mFrustum.frustumCorners[4] = nc + (mUp * nearHeight / 2.0f) - (mRight * nearWidth / 2.0f);
mFrustum.frustumCorners[5] = nc + (mUp * nearHeight / 2.0f) + (mRight * nearWidth / 2.0f);
mFrustum.frustumCorners[6] = nc - (mUp * nearHeight / 2.0f) - (mRight * nearWidth / 2.0f);
mFrustum.frustumCorners[7] = nc - (mUp * nearHeight / 2.0f) + (mRight * nearWidth / 2.0f);
mFrustum.frustumCenter = mPos + mFront * ((mFar - mNear) / 2.0f);
}
mShadowMapWidth = 1024;
mShadowMapHeight = 1024;
// Directional light shadow map buffer
glGenFramebuffers(1, &mDirectionalShadowFBO);
glBindFramebuffer(GL_FRAMEBUFFER, mDirectionalShadowFBO);
glGenTextures(1, &mDirectionalShadowDepthTexture);
glBindTexture(GL_TEXTURE_2D, mDirectionalShadowDepthTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, mShadowMapWidth, mShadowMapHeight, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, mDirectionalShadowDepthTexture, 0);
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);
// This code removes the shimmering effect along the edges of shadows due to
// the light changing to fit the camera.
if( m_eSelectedCascadesFit == FIT_TO_SCENE )
{
// Fit the ortho projection to the cascades far plane and a near plane of zero.
// Pad the projection to be the size of the diagonal of the Frustum partition.
//
// To do this, we pad the ortho transform so that it is always big enough to cover
// the entire camera view frustum.
XMVECTOR vDiagonal = vFrustumPoints[0] - vFrustumPoints[6];
vDiagonal = XMVector3Length( vDiagonal );
// The bound is the length of the diagonal of the frustum interval.
FLOAT fCascadeBound = XMVectorGetX( vDiagonal );
// The offset calculated will pad the ortho projection so that it is always the same size
// and big enough to cover the entire cascade interval.
XMVECTOR vBoarderOffset = ( vDiagonal -
( vLightCameraOrthographicMax - vLightCameraOrthographicMin ) )
* g_vHalfVector;
// Set the Z and W components to zero.
vBoarderOffset *= g_vMultiplySetzwToZero;
// Add the offsets to the projection.
vLightCameraOrthographicMax += vBoarderOffset;
vLightCameraOrthographicMin -= vBoarderOffset;
// The world units per texel are used to snap the shadow the orthographic projection
// to texel sized increments. This keeps the edges of the shadows from shimmering.
FLOAT fWorldUnitsPerTexel = fCascadeBound / (float)m_CopyOfCascadeConfig.m_iBufferSize;
vWorldUnitsPerTexel = XMVectorSet( fWorldUnitsPerTexel, fWorldUnitsPerTexel, 0.0f, 0.0f );
}
And this code above is the one in the microsoft example