Jump to content

  • Log In with Google      Sign In   
  • Create Account

[OpenGL] Cascaded shadow mapping - Render shadows to world?


Old topic!

Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.


  • You cannot reply to this topic
27 replies to this topic

#1   Members   -  Reputation: 613

Like
0Likes
Like

Posted 16 August 2014 - 07:17 AM

I'm trying to implement cascaded shadow mapping in my engine, but I'm stuck at the last step.
 
The rendering of the shadows works as it should (From what I can tell):
 
 
The shadow matrix is composed of the cropped projection matrix (Encompassing the scene) and the view (lookAt) matrix pointing straight down.
 
The shadow cascades are stored in a texture array and passed to the main scene shader as a sampler2DArrayShadow. All that's left to do is the shadow texture lookup, but I'm not sure how to transform the vertex shader position into the shadow space.
 
Any nudge in the right direction would be appreciated.


#2   Crossbones+   -  Reputation: 2918

Like
2Likes
Like

Posted 16 August 2014 - 01:04 PM

So to render the shadows you have a orthographic (shadow) matrix which transforms from world space to shadow texture space. 

 

When rendering the scene and applying shadows in the pixel shader, you'll use the pixels position to choose the good cascade.

 

To get a texture coordinate in the shadow texture depends - for example, if the position you have in the pixel shader is in the view space, you'll need to transform the position with the inverse view matrix to get the world position and then use the shadow matrix to get the texture coordinate in the shadow map.

 

If the position is in the clip space, then you'd apply inverse view-projection matrix and then the shadow matrix.

 

Cheers!

 

[edit] you may need to apply some translation and scale to get the correct texture coordinates in the shadow map. Typically you'd calculate the final matrix on the CPU, so in the pixel shader you'll need just one vertex matrix multiplication. 


Edited by kauna, 16 August 2014 - 01:07 PM.


#3   Members   -  Reputation: 613

Like
0Likes
Like

Posted 17 August 2014 - 06:43 AM

The lookup position is calculated in the vertex shader like so:

csmPos1 = csmMatrices[0] *(M *vpos);
csmPos2 = csmMatrices[1] *(M *vpos);
csmPos3 = csmMatrices[2] *(M *vpos);

Where vpos is the vertex position, M is the model matrix to transform it into world space and the respective matrix from csmMatrices is the view-projection matrix I used to render the shadows. This is then passed to the fragment shader, where I select the cascade and use shadow2DArray to grab the shadow value:

float GetShadowCoefficient()
{
	int index = 2;
	vec4 csmPos;
	if(gl_FragCoord.z < far_d.x)
	{
		index = 0;
		csmPos = csmPos1;
	}
	else if(gl_FragCoord.z < far_d.y)
	{
		index = 1;
		csmPos = csmPos2;
	}
	else if(gl_FragCoord.z < far_d.z)
	{
		index = 2;
		csmPos = csmPos3;
	}
	vec4 shadow_coord = csmPos;
	shadow_coord.w = shadow_coord.z;
	shadow_coord.z = float(index);
	return shadow2DArray(csmTextureArray,shadow_coord).x;
}

The code is just for testing purposes and not optimized yet.

Either way, the result is somewhat of a mess:

 

I don't think my shadow matrices are wrong, considering the shadow maps look more or less as they should, so I'm not sure what's going on?



#4   Crossbones+   -  Reputation: 2918

Like
2Likes
Like

Posted 17 August 2014 - 12:34 PM

I think that you'll need to offset and scale your texture coordinates since the projection space is from -1 to 1, but the texture space is from 0 to 1. So, multiply by 0.5 and add 0.5 to your texture coordinate.

 

Cheers!

 

ps. I'm not sure if it will resolve the problem you are having. On the other hand the shadows seem to move more and less correctly. 


Edited by kauna, 17 August 2014 - 12:38 PM.


#5   Members   -  Reputation: 613

Like
0Likes
Like

Posted 17 August 2014 - 01:49 PM

Thanks, that actually did the trick!

 

There are still some issues, but I'm a major step closer now!

 

What puzzles me a bit is how the shadow maps darken the further I look downwards, that doesn't seem right. Any idea what could be causing this?



#6   Crossbones+   -  Reputation: 2918

Like
2Likes
Like

Posted 17 August 2014 - 02:13 PM

Can you show your fragment shader code completely?

 

Cheers!



#7   Members   -  Reputation: 613

Like
0Likes
Like

Posted 17 August 2014 - 02:41 PM

Well, the only CSM-related part in the fragment shader is basically what I already posted before:

in vec4 csmPos[3];

uniform vec4 far_d;
uniform sampler2DArrayShadow csmTextureArray;
uniform mat4 csmMatrices[3];
float GetShadowCoefficient()
{
	int index = 3;
	vec4 shadowCoord;
	for(int i=0;i<4;i++)
	{
		if(gl_FragCoord.z < far_d[i])
		{
			shadowCoord = csmPos[i];
			index = i;
			break;
		}
	}
	shadowCoord.w = shadowCoord.z;
	shadowCoord.z = float(index);
	shadowCoord.x = shadowCoord.x *0.5f +0.5f;
	shadowCoord.y = shadowCoord.y *0.5f +0.5f;
	return shadow2DArray(csmTextureArray,shadowCoord).x;
}

The result from this function is then multiplied by the output color, that's about it. As for the vertex shader:

layout(std140) uniform ViewProjection
{
	mat4 M; // Model Matrix
	mat4 V; // View Matrix
	mat4 P; // Projection Matrix
	mat4 MVP;
};

layout(location = 0) in vec4 vertPos;

// CSM Test
uniform mat4 csmMatrices[3];
out vec4 csmPos[3];

[...] Inside the main function:
	csmPos[0] = csmMatrices[0] *(M *vertPos);
	csmPos[1] = csmMatrices[1] *(M *vertPos);
	csmPos[2] = csmMatrices[2] *(M *vertPos);

These are the only parts that use the CSM-related data.



#8   Crossbones+   -  Reputation: 2918

Like
2Likes
Like

Posted 17 August 2014 - 03:01 PM

I'd like to see the whole fragment shader. Are you performing a comparison between the fragments position and the shadowmap value somewhere?

 

Cheers!



#9   Crossbones+   -  Reputation: 24451

Like
2Likes
Like

Posted 17 August 2014 - 07:41 PM

When generating the shadow maps you are enclosing only the “visible” area (created by the view frustum).  This doesn’t make sense, as things above the player, even if not directly visible to the player, can still cast shadows visible to the player.

 

Lock the near plane of the shadow-map projections to include any objects above the player that can cast shadows visible to the player.

 

 

L. Spiro



#10   Members   -  Reputation: 613

Like
0Likes
Like

Posted 18 August 2014 - 02:51 PM

When generating the shadow maps you are enclosing only the “visible” area (created by the view frustum).  This doesn’t make sense, as things above the player, even if not directly visible to the player, can still cast shadows visible to the player.

 

Lock the near plane of the shadow-map projections to include any objects above the player that can cast shadows visible to the player.

 

Right, thanks. I've changed the bounds for each cascade to be large enough to encompass the entire scene, just for testing purposes, and that does give me better results:

904f0cd679.jpg

 

 

The other problems (self-shadowing and different intensity) are directly related with the other problem I believe:

 

I'd like to see the whole fragment shader. Are you performing a comparison between the fragments position and the shadowmap value somewhere?

 

The return value from GetShadowCoefficient is literally just multiplied with the diffuse color. I've tried a comparison like so:

	[...]
	shadowCoord.w = shadowCoord.z;
	shadowCoord.z = float(index);
	shadowCoord.x = shadowCoord.x *0.5f +0.5f;
	shadowCoord.y = shadowCoord.y *0.5f +0.5f;
	float z = shadow2DArray(csmTextureArray,shadowCoord).x;
	if(z < shadowCoord.w)
		return 0.25f;
	return 1;

However that only gives me proper results for the first cascade, after that it's just cut off:

3e87c730b9.jpg



#11   Members   -  Reputation: 613

Like
0Likes
Like

Posted 23 September 2014 - 02:58 PM

Still haven't figured this out. I've went through the Nvidia CSM Tutorial again, which I based my code mostly on, but they are doing pretty much exactly what I am. Their sample program results in similar cascades as my code does, so I'm pretty sure it's the final shader (For the main scene) which is messing things up.

 

Their shader looks like this:

uniform sampler2D tex; // terrain texture
uniform vec4 far_d; // far distances of
 // every split
varying vec4 vPos; // fragment’s position in 
// view space
uniform sampler2DArrayShadow stex; // depth textures
float shadowCoef() 
{ 
    int index = 3; 
    // find the appropriate depth map to look up in 
    // based on the depth of this fragment 
    if(gl_FragCoord.z < far_d.x) 
        index = 0; 
    else if(gl_FragCoord.z < far_d.y) 
        index = 1; 
    else if(gl_FragCoord.z < far_d.z) 
        index = 2; 
 
    // transform this fragment's position from view space to 
    // scaled light clip space such that the xy coordinates 
    // lie in [0;1]. Note that there is no need to divide by w 
    // for othogonal light sources 
    vec4 shadow_coord = gl_TextureMatrix[index]*vPos; 
    // set the current depth to compare with
    shadow_coord.w = shadow_coord.z; 
 
    // tell glsl in which layer to do the look up 
    shadow_coord.z = float(index); 
 
    // let the hardware do the comparison for us 
   return shadow2DArray(stex, shadow_coord).x; 
} 
void main() 
{ 
    vec4 color_tex = texture2D(tex, gl_TexCoord[0].st); 
    float shadow_coef = shadowCoef(); 
    float fog_coef = clamp(gl_Fog.scale*(gl_Fog.end + vPos.z), 0.0, 1.0); 
    gl_FragColor = mix(gl_Fog.color, (0.9 * shadow_coef * gl_Color * color_tex + 0.1), fog_coef); 
} 

Which is essentially the same as my shader above.

 

I'm at a complete loss...



#12   Members   -  Reputation: 1066

Like
2Likes
Like

Posted 23 September 2014 - 03:22 PM

Are you sure you properly set uniform values for gl_TextureMatrix[1] and [2] ? I would rather use explicitly declared matrixes for this.



#13   Members   -  Reputation: 613

Like
0Likes
Like

Posted 23 September 2014 - 11:44 PM

Are you sure you properly set uniform values for gl_TextureMatrix[1] and [2] ? I would rather use explicitly declared matrixes for this.

 

I'm using my own uniforms, in my case "csmMatrices" represents the "gl_TextureMatrix" that's being used in the Nvidia shader.

// Extract from my vertex shader
layout(std140) uniform ViewProjection
{
	mat4 M; // Model Matrix
	mat4 V; // View Matrix
	mat4 P; // Projection Matrix
	mat4 MVP;
};

layout(location = 0) in vec4 vertPos;

// CSM Test
uniform mat4 csmMatrices[3];
out vec4 csmPos[3];

[...] Inside the main function:
	csmPos[0] = csmMatrices[0] *(M *vertPos);
	csmPos[1] = csmMatrices[1] *(M *vertPos);
	csmPos[2] = csmMatrices[2] *(M *vertPos);

"csmPos" is equivalent to the "shadow_coord" from Nvidias shader, I'm just calculating it on the vertex shader instead of the fragment shader.


Edited by Silverlan, 23 September 2014 - 11:45 PM.


#14   Members   -  Reputation: 1066

Like
2Likes
Like

Posted 24 September 2014 - 08:40 AM

I think the issue is here :

 


The return value from GetShadowCoefficient is literally just multiplied with the diffuse color. I've tried a comparison like so:
[...]
shadowCoord.w = shadowCoord.z;
shadowCoord.z = float(index);
shadowCoord.x = shadowCoord.x *0.5f +0.5f;
shadowCoord.y = shadowCoord.y *0.5f +0.5f;
float z = shadow2DArray(csmTextureArray,shadowCoord).x;
if(z < shadowCoord.w)
return 0.25f;
return 1;
However that only gives me proper results for the first cascade, after that it's just cut off:

 

Assuming the sampler mode is GL_NEAREST, shadow2DArray returns either 0. ("false") if the comparaison test fails or 1. ("true") if the comparaison test succeed.

If the sampler mode is GL_LINEAR then the 0./1. from neighbour texels are interpolated linearly.

 

I don't understand the "if(z < shadowCoord.w)" statement since you're comparing a "bool" (the result of a comparison) with a depth value.

I think you should use "return z;" directly.



#15   Members   -  Reputation: 613

Like
0Likes
Like

Posted 24 September 2014 - 09:43 AM

Assuming the sampler mode is GL_NEAREST, shadow2DArray returns either 0. ("false") if the comparaison test fails or 1. ("true") if the comparaison test succeed.

If the sampler mode is GL_LINEAR then the 0./1. from neighbour texels are interpolated linearly.

 

I don't understand the "if(z < shadowCoord.w)" statement since you're comparing a "bool" (the result of a comparison) with a depth value.

I think you should use "return z;" directly.

 

That's what I did before, which essentially had the same results:

ede72cd73d.jpg

 

 

The texture parameters for the cascades are:

glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_COMPARE_FUNC,GL_LEQUAL);
glReadBuffer(GL_NONE);
glDrawBuffer(GL_NONE);


#16   Members   -  Reputation: 1066

Like
2Likes
Like

Posted 24 September 2014 - 10:00 AM

What is wrong with this picture ?

 

To have better result with self shadowing you need to use a bias or polygon offset.



#17   Members   -  Reputation: 613

Like
0Likes
Like

Posted 24 September 2014 - 10:13 AM

What is wrong with this picture ?

 

To have better result with self shadowing you need to use a bias or polygon offset.

 

The different 'intensity' of the shadow between the cascades is what I'm trying to get rid of. You can see that the shadows on each cascade are darker than in the previous one.



#18   Members   -  Reputation: 1066

Like
2Likes
Like

Posted 24 September 2014 - 10:33 AM

I suspect there is some z fighting here.
What happens if you add a small offset to shadowcoord.w before sampling ? 



#19   Members   -  Reputation: 613

Like
0Likes
Like

Posted 24 September 2014 - 11:02 AM

I suspect there is some z fighting here.
What happens if you add a small offset to shadowcoord.w before sampling ? 

 

That's... odd. I can change the 'w' component to anything, 1, 0, any value inbetween, negative values - It literally changes nothing, the result is always the same as in the screenshot I posted.

I assume that means something's wrong with the actual shadow map textures?



#20   Members   -  Reputation: 1066

Like
2Likes
Like

Posted 24 September 2014 - 01:56 PM

Did you set GL_TEXTURE_COMPARE_MODE too ? eg    

 

glTexParameterf(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE)






Old topic!

Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.




PARTNERS