Fitting directional light in view frustum

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

Im trying to optimize my shadow maps by making my directional shadow maps fit in the view frustum, however this is causing me some problems. I tried several things, but all didn't work, or didn't work properly.

Assuming i only have the following information:

vec3 describing direction of the directional light

perspective camera with values (fov, width, height, near plane, far plane) and transform matrix

How can i use this information to create the correct light space matrix to generate and sample my shadow maps?

Any help would be greatly appreciated!

Advertisement
Hi.
Not sure how to fully do this, but just some thoughts:

- the directional light has no range (basically to infinity)
- I would assume you want to cast shadows for objects that are inside the frustum, or better, within a specific range of the viewer
-- there's a margin for objects which bounding volume is outside the frustum but the shadow might be (partially) inside, for this you have to decide what you want
- a potential performance thing could be processing to many objects, watch out for this

Hope this helps a bit

Crealysm game & engine development: http://www.crealysm.com

Looking for a passionate, disciplined and structured producer? PM me

If you want perfect fit for your shadowmap you have to use the depth buffer as information and find the min/max, this method is called SDSM.

Directional light shadow map needs cascade to works on all distance correctly, you can find all informations on internet about it.

If you go for the depth buffer method, don't use a sphere for your shadow map, you will only loose quality.

calculate your view-projection matrix for the main camera, and invert it
transform the 8 corners of the NDC cube through the inverted projection-matrix (with -1 for near-z if opengl, instead of 0 IIRC?)
perspective divide the 8 resulting coordinates

that gives you the 8 corners of your view frustum in worldspace

take the average of all 8, that gives you the midpoint of the frustum

generate an up & right vector perpendicular to your light direction

create a view matrix centred on the origin, using your light direction and these up/right vectors

the projection matrix is an ortho matrix where the left is the minimum of the dot product of all 8 corners and the right vector, the right is the maximum of the dot product of all 8 corners and the right vector, the top is the maximum of the dot product of all 8 corners and the up vector, and you should be able to guess how to derive the other 3 values smile.png

That gives the tightest standard projection to fit the entire frustum.

You generally want to pull the near plane of the ortho matrix back to fit all shadow casters in the scene or "pancake" them at the near plane in the vertex shader.

The shadowmap sampling matrix is the viewprojection for the shadow rendering, but with a scale and bias afterwards as transforming by the matrix will give values between -1 and 1, whereas the texture sampling will need 0 to 1

For cascaded shadows, the process is the same as above, but you split the camera frustum up into multiple z slices, which gives tighter projections closer to the camera.

I'm probably asking for too much here, but would anyone be able to write pseudo code for me? I would've liked to post my current progress here, but my project files are on my desktop, which i won't have access to until after the weekend. Doesn't have to be anything fancy (obviously) like "pancaking" or csm, or anything of the sort. I'm fairly sure i can get that to work myself once i properly understand this.

If anyone could do that for me that would be amazing!

Here the code to compute the 8 corners of the camera frustum in world-space :


void CCameraComponent::ComputeFrustumCornerPoints( CVector3* Points ) const
{
  // Get property values.
  const float NearClipPlane = GetNearClipPlane();
  const float FarClipPlane = GetFarClipPlane();

  // Compute the projection inverse scale factor.
  const float ScaleXInv = 1.0f / m_ProjectionMatrix( 0, 0 );
  const float ScaleYInv = 1.0f / m_ProjectionMatrix( 1, 1 );

  // Compute corners in view space.
  CVector3 CornersVS[ 8 ];
  const float NearX = ScaleXInv * NearClipPlane;
  const float NearY = ScaleYInv * NearClipPlane;
  CornersVS[ 0 ] = CVector3( -NearX, +NearY, NearClipPlane );
  CornersVS[ 1 ] = CVector3( +NearX, +NearY, NearClipPlane );
  CornersVS[ 2 ] = CVector3( +NearX, -NearY, NearClipPlane );
  CornersVS[ 3 ] = CVector3( -NearX, -NearY, NearClipPlane );
  const float FarX = ScaleXInv * FarClipPlane;
  const float FarY = ScaleYInv * FarClipPlane;
  CornersVS[ 4 ] = CVector3( -FarX, +FarY, FarClipPlane );
  CornersVS[ 5 ] = CVector3( +FarX, +FarY, FarClipPlane );
  CornersVS[ 6 ] = CVector3( +FarX, -FarY, FarClipPlane );
  CornersVS[ 7 ] = CVector3( -FarX, -FarY, FarClipPlane );

  // Compute the inverse of the view matrix.
  const CMatrix4 InverseViewMatrix = m_ViewMatrix.Inversed();

  // Compute the world space corners.
  for( UInt32 i = 0; i < 8; ++i )
    Points[ i ] = InverseViewMatrix.TransformPoint( CornersVS[ i ] );
}

I almost got it working, the shadows are rendered fine, the only problem is that under certain angles shadows get cut off or completely disapear. Obviously something is going wrong, but i don't know what. Here is my code:

Getting world positions of frustum 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);
			}
}

Generating the lightVPMatrix:


glm::mat4 Game::getDirLightVPMatrix(std::vector<glm::vec4> points)
{
	glm::mat4 lvMatrix = glm::lookAt(glm::normalize(lightDir), glm::vec3(0.0f), glm::vec3(1.0f));

	glm::vec4 transf = lvMatrix * points[0];
	float minZ = transf.z;
	float maxZ = transf.z;
	float minX = transf.z;
	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;
}

If someone could help my find the error i made that would be greatly appreciated!

Are the shadows disappearing because they are offscreen? You're fitting the shadow projection's near plane tightly to the frustum, which will cause objects that are offscreen but should still cast shadows into the screen to go missing. Apart from that, the math all looks good :D

its not that the shadow casters are offscreen, ill show some screenshots with the problem, as well with the shadow map generated. These seem to me kinda... off.

st9G7jX.jpg

Everything going allright...

Vp25uSH.jpg

Shadows getting cut off

lD00Z11.jpg

Shadows completely gone besides the character

The vertex shader i use to generate the shadow (no fragment because shadow maps wink.png )

The lightSpaceMatrix is the matrix generated in the prev shown getDirLightVPMatrix function


#version 330 core
layout (location = 0) in vec3 position;

uniform mat4 lightSpaceMatrix;
uniform mat4 model;

void main()
{
    gl_Position = lightSpaceMatrix * model * vec4(position, 1.0f);
} 

I don't see clearly the shadow map but the problem is maybe because your object is not completely rendered in the shadow map which cause to have shadow cutted.

If it's the case, that means you don't have the good size of ortho projection when generating the shadow map.

Here the full code to generate the shadow matrix using cascaded shadow map :


// Set the shadow view matrix.
CMatrix4 ShadowViewMatrix;
ShadowViewMatrix.LookAt( -DirLight, CVector3( 0.0f, 0.0f, 0.0f ) );

// Get the camera projection matrix.
const CMatrix4 ProjectionMatrix = CameraComponent->GetProjectionMatrix();

// Compute the camera view to light view matrix.
const CMatrix4 CameraViewToLight = CameraComponent->GetViewMatrix().Inversed() * ShadowViewMatrix;

// For each cascade find the transformation from shadow to cascade space.
for( UInt32 i = 0; i < 4; ++i )
{
  // Compute the projection inverse scale factor.
  const float ScaleXInv = 1.0f / ProjectionMatrix( 0, 0 );
  const float ScaleYInv = 1.0f / ProjectionMatrix( 1, 1 );

  // Compute corners in view space.
  CVector3 CornersVS[ 8 ];
  const float NearX = ScaleXInv * m_CascadeSplitArray[ i ];
  const float NearY = ScaleYInv * m_CascadeSplitArray[ i ];
  CornersVS[ 0 ] = CVector3( -NearX, +NearY, m_CascadeSplitArray[ i ] );
  CornersVS[ 1 ] = CVector3( +NearX, +NearY, m_CascadeSplitArray[ i ] );
  CornersVS[ 2 ] = CVector3( +NearX, -NearY, m_CascadeSplitArray[ i ] );
  CornersVS[ 3 ] = CVector3( -NearX, -NearY, m_CascadeSplitArray[ i ] );
  const float FarX = ScaleXInv * m_CascadeSplitArray[ i + 1 ];
  const float FarY = ScaleYInv * m_CascadeSplitArray[ i + 1 ];
  CornersVS[ 4 ] = CVector3( -FarX, +FarY, m_CascadeSplitArray[ i + 1 ] );
  CornersVS[ 5 ] = CVector3( +FarX, +FarY, m_CascadeSplitArray[ i + 1 ] );
  CornersVS[ 6 ] = CVector3( +FarX, -FarY, m_CascadeSplitArray[ i + 1 ] );
  CornersVS[ 7 ] = CVector3( -FarX, -FarY, m_CascadeSplitArray[ i + 1 ] );

  // Compute the AABB.
  CAxisAlignedBoundingBox AABB;
  for( UInt32 k = 0; k < 8; ++k )
    AABB.Encapsulate( CameraViewToLight.Transform( CornersVS[ k ] ) );

  // Set the cascade projection matrix.
  CMatrix4 CascadeProjectionMatrix;
  CascadeProjectionMatrix.OrthoOffCenter( AABB.m_Min.x, AABB.m_Max.x, AABB.m_Min.y, AABB.m_Max.y, AABB.m_Min.z, AABB.m_Max.z );

  // Compute the shadow view-proj matrix.
  m_CascadeViewProjArray[ i ] = ShadowViewMatrix * CascadeProjectionMatrix;
}

You can remove the for loop to only have one cascade which is what you actually have.

This topic is closed to new replies.

Advertisement