Fitting directional light in view frustum

Started by
12 comments, last by Merx 8 years, 6 months ago

This is what i made of that code


glm::mat4 Game::getDirLightVPMatrix()
{
	glm::mat4 lvMatrix = glm::lookAt(-glm::normalize(lightDir), glm::vec3(0.0f), glm::vec3(1.0f));
	glm::mat4 CameraViewToLight = glm::inverse(view) * lvMatrix;

	const float ScaleXInv = 1.0f / projection[0][0];
	const float ScaleYInv = 1.0f / projection[1][1];

	std::vector<glm::vec3> CornersVS;
	CornersVS.reserve(8);

	const float NearX = ScaleXInv * near;
	const float NearY = ScaleYInv * near;
	CornersVS.push_back(glm::vec3(-NearX, +NearY, near));
	CornersVS.push_back(glm::vec3(+NearX, +NearY, near));
	CornersVS.push_back(glm::vec3(+NearX, -NearY, near));
	CornersVS.push_back(glm::vec3(-NearX, -NearY, near));
	const float FarX = ScaleXInv * far;
	const float FarY = ScaleYInv * far;
	CornersVS.push_back(glm::vec3(-FarX, +FarY, far));
	CornersVS.push_back(glm::vec3(+FarX, +FarY, far));
	CornersVS.push_back(glm::vec3(+FarX, -FarY, far));
	CornersVS.push_back(glm::vec3(-FarX, -FarY, far));

	glm::vec4 transf = CameraViewToLight * glm::vec4(CornersVS[0], 1.0);
	float minZ = transf.z;
	float maxZ = transf.z;
	float minX = transf.x;
	float maxX = transf.x;
	float minY = transf.y;
	float maxY = transf.y;

	for (unsigned int i = 1; i < 8; i++)
	{
		transf = CameraViewToLight * glm::vec4(CornersVS[i], 1.0f);

		if (transf.z > maxZ) maxZ = transf.z;
		if (transf.z < minZ) minZ = transf.z;
		if (transf.x > maxX) maxX = transf.x;
		if (transf.x < minX) minX = transf.x;
		if (transf.y > maxY) maxY = transf.y;
		if (transf.y < minY) minY = transf.y;
	}

	glm::mat4 lpMatrix = glm::ortho(minX, maxX, minY, maxY, minZ, maxZ);

	return lpMatrix * lvMatrix;
}

it does work, and it does generate a different shadow map, but it suffers from very similar problem

Advertisement
This does not actually work properly, the shadows move slighly when the camera moves

This is a normal problem, you have to use a bounding sphere to avoid this problem, since the projection change when the camera rotate, you have the problem.

Bounding Sphere is a common method to avoid this problem, it's called "Stable Cascaded Shadow Map".

In other words, you have to make the projection rotation invariant and use a way to stabilize the translation too.

Using SDSM, it's not needed because the projection is very efficient, it only takes the distance needed based on the depth buffer, the problem is not visible.

Here a link from MSDN which speaks about common problems and solutions : https://msdn.microsoft.com/en-us/library/windows/desktop/ee416324(v=vs.85).aspx

It has a part about "Shimmering shadow edges" which is the camera translation issue.

thnx, but it was another issue, the shadows moving where i was talking about was just a typo on my part :P

ok, found the problems with my code. The reason why my shadows disapeared under certain angles was a really embarrasing typo:


float minX = transf.z;

The second problem, that shadows got cut off was just a simple logic problem while creating the lightViewMatrix, where


glm::mat4 lvMatrix = glm::lookAt(glm::vec3(0.0f), -glm::normalize(lightPos), glm::vec3(0.0f, 1.0f, 0.0f));

had to be


glm::vec4 frustumCenter = points[0];
for (unsigned int i = 1; i < 8; i++)
{
	frustumCenter += points[i];
}
frustumCenter /= 8;

glm::mat4 lvMatrix = glm::lookAt(glm::vec3(frustumCenter) + glm::normalize(lightPos), glm::vec3(frustumCenter), glm::vec3(0.0f, 1.0f, 0.0f));

If anyone who has a similar problem happens to run across this, this is my complete code:


//function where you get the lightViewProjectionMatrix;
std::vector<glm::vec4> corners;
corners.reserve(8);

getFrustumWorldSpace(corners);
lightVPMatrix = getDirLightVPMatrix(corners);

void Game::getFrustumWorldSpace(std::vector<glm::vec4>& points)
{
	glm::mat4 inverseProjectionMatrix = glm::inverse(projection * view);

	for (unsigned int x = 0; x < 2; x++)
		for (unsigned int y = 0; y < 2; y++)
			for (unsigned int z = 0; z < 2; z++)
			{
				glm::vec4 projClipSpacePosition(x*2.0f - 1.0f, y*2.0f - 1.0f, z*2.0f - 1.0f, 1.0f);
				glm::vec4 projWorldSpacePosition = inverseProjectionMatrix * projClipSpacePosition;
				points.push_back(projWorldSpacePosition / projWorldSpacePosition.w);
			}
}

glm::mat4 Game::getDirLightVPMatrix(std::vector<glm::vec4> points)
{
	glm::vec4 frustumCenter = points[0];
	for (unsigned int i = 1; i < 8; i++)
	{
		frustumCenter += points[i];
	}
	frustumCenter /= 8;

	glm::mat4 lvMatrix = glm::lookAt(glm::vec3(frustumCenter) + glm::normalize(lightPos), glm::vec3(frustumCenter), glm::vec3(0.0f, 1.0f, 0.0f));

	glm::vec4 transf = lvMatrix * points[0];
	float minZ = transf.z;
	float maxZ = transf.z;
	float minX = transf.x;
	float maxX = transf.x;
	float minY = transf.y;
	float maxY = transf.y;

	for (unsigned int i = 1; i < 8; i++)
	{
		transf = lvMatrix * points[i];

		if (transf.z > maxZ) maxZ = transf.z;
		if (transf.z < minZ) minZ = transf.z;
		if (transf.x > maxX) maxX = transf.x;
		if (transf.x < minX) minX = transf.x;
		if (transf.y > maxY) maxY = transf.y;
		if (transf.y < minY) minY = transf.y;
	}

	glm::mat4 lpMatrix = glm::ortho(-1.0f, 1.0f, -1.0f, 1.0f, minZ, maxZ);

	const float scaleX = 2.0f / (maxX - minX);
	const float scaleY = 2.0f / (maxY - minY);
	const float offsetX = -0.5f * (minX + maxX) * scaleX;
	const float offsetY = -0.5f * (minY + maxY) * scaleY;

	glm::mat4 cropMatrix(1.0f);
	cropMatrix[0][0] = scaleX;
	cropMatrix[1][1] = scaleY;
	cropMatrix[3][0] = offsetX;
	cropMatrix[3][1] = offsetY;

	return cropMatrix * lpMatrix * lvMatrix;
}

This topic is closed to new replies.

Advertisement