Help with Cascading Shadowmapping

I am trying to do some cascading shadow mapping, and I get this artifact and have no idea why this would be? Anyone have any ideas what would cause this?

Looks like a (near) clipping problem, check your shadow volumes…

Thanks for reply, but I am not sure what you mean by check the shadow volumes? I have moved the near plane to like .1 and didn’t fix anything? If that is what you mean?

If I move my split_weights to like .4 or less it stops happening, but the resolution becomes crap. Also it only happens on my terrain mesh never on the models?

This looks like a self-shadowing issue. Try to increase your depth bias.

what issue?, if u mean the zfighting artifacts on the terrain (cause of the hills geometry)

then u will have to do something like Eosie suggested eg depth bias or PolygonOffset (theres plenty of info on the web about this, most shadowmap papers from a few years ago mention it)

also only render either the front or black faces into the depthbuffer (normally the back, ie the opposite to the normal rendering)

another method is to have your away from light facing normals + the shadowambient color to be similar (like reallife)

Do you use shadowTextureArray on <cascade> ?
What kind of filter do you use ?

Wow thanks all for the help and ideas, I do render the back faces, I am using the PolygonOffset that the tutorial used. I am not using sampler2DArray so no need for GL_COMPARE_R_TO_TEXTURE. I am using GL_LINEAR filtering as does the tutorial on Nvidia this is free PCF.

What I can tell is it happens at the splits mainly the first split. I don’t know if this matters, but the user made the demo using a fps type camera and I am using a 3rd person camera where the camera has the chase view going on. I have tried the tutorial and added in my heightmap and I can’t get it to do what is happening on my terrain. This only happens with the terrain rendering, not with any of the models. I have also removed any culling techniques and brute force rendered all the terrain polygons and that doesn’t matter either. I am going to post my code if anyone wants to look through it.


class ShadowMap
{
public:
	FrustumData f[MAX_SPLITS];
	float shad_cpm[MAX_SPLITS][16];
	float shad_modelview[16];
	float cam_proj[16];
	float cam_modelview[16];
	float cam_inverse_modelview[16];
	float far_bound[MAX_SPLITS];
	float split_weight;
	int depth_size;
	int cur_num_splits;
	GLuint depth_fb;
	GLuint depth_tex_ar;
	GLSL shader;

	ShadowMap()
	{
		split_weight   = .75f;
		depth_size     = 2048;
		cur_num_splits = 4;// MAX_SPLITS is the largest
		depth_tex_ar   = 0;
		depth_fb       = 0;
	}
	~ShadowMap(){}
	void Setup(void);
	void ShowDepthTex(void);
	void CameraInverse(float dst[16], float src[16]);
	void UpdateFrustumPoints(FrustumData& f, vec3& center, vec3& view_dir);
	void UpdateSplitDist(FrustumData f[MAX_SPLITS], float nd, float fd);
	void MakeShadowMap(void);
	void DrawShadowFrustum(void);
	void DrawLightPosition(void);
	float ApplyCropMatrix(FrustumData& f);
};

void ShadowMap::Setup(void)
{
	glGenFramebuffersEXT(1, &depth_fb);
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, depth_fb);
	glDrawBuffer(GL_NONE);
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

	glGenTextures(1, &depth_tex_ar);
	glBindTexture(GL_TEXTURE_2D_ARRAY_EXT, depth_tex_ar);
	glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
	glTexImage3D(GL_TEXTURE_2D_ARRAY_EXT, 0, gameSetupData.enableFPDepthBuffer == false ? GL_DEPTH_COMPONENT24 : GL_DEPTH_COMPONENT32F_NV, 
				 depth_size, depth_size, MAX_SPLITS, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
	CheckFBOStatus();

	double aspect = (double)gameSetupData.screenWidth/(double)gameSetupData.screenHeight;
	for(int i = 0; i < MAX_SPLITS; ++i)
	{
		f[i].fov   = gameSetupData.fov/PI_180_OVER_PI+.2f;
		f[i].ratio = aspect;
	}

	shader.SetupShaders(std::string("shaders/view_vertex.vsp"), std::string("shaders/view_fragment.fsp"));
	shader.textureLocation[0] = glGetUniformLocation(shader.program, "shadowmap");
	glUniform1i(shader.textureLocation[0], 0);
	shader.shaderVariable[0] = glGetUniformLocation(shader.program, "layer");
	glUniform1f(shader.shaderVariable[0], 0.0f);
	shader.UnBindShader();
}

void ShadowMap::ShowDepthTex(void)
{
	glPushAttrib(GL_VIEWPORT_BIT | GL_DEPTH_BUFFER_BIT);
	glDisable(GL_DEPTH_TEST);
	glDisable(GL_CULL_FACE);
	shader.BindShader();
	for(int i=0; i<cur_num_splits; i++)
	{
        glViewport(130*i, 0, 128, 128);
		glBindTexture(GL_TEXTURE_2D_ARRAY_EXT, depth_tex_ar);
        glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_COMPARE_MODE, GL_NONE);
		glUniform1f(shader.shaderVariable[0], (float)i);
		glBegin(GL_QUADS);
		glVertex3f(-1.0f, -1.0f, 0.0f);
		glVertex3f( 1.0f, -1.0f, 0.0f);
		glVertex3f( 1.0f,  1.0f, 0.0f);
		glVertex3f(-1.0f,  1.0f, 0.0f);
		glEnd();

	}
	shader.UnBindShader();	
	glViewport(gameSetupData.screenWidth - 129, 0, 128, 128);
	glEnable(GL_DEPTH_TEST);
	glClear(GL_DEPTH_BUFFER_BIT);
	glEnable(GL_CULL_FACE);
	glPopAttrib();
}

void ShadowMap::CameraInverse(float dst[16], float src[16])
{
	dst[0] = src[0];
	dst[1] = src[4];
	dst[2] = src[8];
	dst[3] = 0.0f;
	dst[4] = src[1];
	dst[5] = src[5];
	dst[6]  = src[9];
	dst[7] = 0.0f;
	dst[8] = src[2];
	dst[9] = src[6];
	dst[10] = src[10];
	dst[11] = 0.0f;
	dst[12] = -(src[12] * src[0]) - (src[13] * src[1]) - (src[14] * src[2]);
	dst[13] = -(src[12] * src[4]) - (src[13] * src[5]) - (src[14] * src[6]);
	dst[14] = -(src[12] * src[8]) - (src[13] * src[9]) - (src[14] * src[10]);
	dst[15] = 1.0f;
}

void ShadowMap::UpdateFrustumPoints(FrustumData& f, vec3& center, vec3& view_dir)
{
	vec3 up(0.0f, 1.0f, 0.0f);
	vec3 right = Cross(view_dir, up);

	vec3 fc = center + view_dir*f.fard;
	vec3 nc = center + view_dir*f.neard;

	right.Normalize();
	up = Cross(right, view_dir);
	up.Normalize();

	float near_height = tan(f.fov/2.0f) * f.neard;
	float near_width  = near_height     * f.ratio;
	float far_height  = tan(f.fov/2.0f) * f.fard;
	float far_width   = far_height      * f.ratio;

	f.point[0] = nc - up*near_height - right*near_width;
	f.point[1] = nc + up*near_height - right*near_width;
	f.point[2] = nc + up*near_height + right*near_width;
	f.point[3] = nc - up*near_height + right*near_width;

	f.point[4] = fc - up*far_height - right*far_width;
	f.point[5] = fc + up*far_height - right*far_width;
	f.point[6] = fc + up*far_height + right*far_width;
	f.point[7] = fc - up*far_height + right*far_width;
}

void ShadowMap::UpdateSplitDist(FrustumData f[MAX_SPLITS], float nd, float fd)
{
	float lambda = split_weight;
	float ratio  = fd/nd;
	f[0].neard   = nd;

	for(int i = 1; i < cur_num_splits; ++i)
	{
		float si    = i / (float)cur_num_splits;
		f[i].neard  = lambda*(nd*powf(ratio, si)) + (1-lambda)*(nd + (fd - nd)*si);
		f[i-1].fard = f[i].neard * 1.005f;
	}
	f[cur_num_splits-1].fard = fd;
}

float ShadowMap::ApplyCropMatrix(FrustumData& f)
{
	float shad_proj[16] = {0.0f};
	float shad_crop[16] = {0.0f};
	float shad_mvp[16]  = {0.0f};
	float maxX = -1000.0f;
    float maxY = -1000.0f;
	float maxZ = 0.0f;
    float minX = 1000.0f;
    float minY = 1000.0f;
	float minZ = 0.0f;

	Matrix4x4 nv_mvp;
	vec4 transf;	
	
	glGetFloatv(GL_MODELVIEW_MATRIX, shad_modelview);
	nv_mvp = shad_modelview;
	transf = nv_mvp*vec4(f.point[0], 1.0f);
	minZ = transf.z;
	maxZ = transf.z;
	for(int i = 1; i < 8; ++i)
	{
		transf = nv_mvp*vec4(f.point[i], 1.0f);
		if(transf.z > maxZ) maxZ = transf.z;
		if(transf.z < minZ) minZ = transf.z;
	}
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(-1.0, 1.0, -1.0, 1.0, -maxZ, -minZ);
	glGetFloatv(GL_PROJECTION_MATRIX, shad_proj);
	glPushMatrix();
	glMultMatrixf(shad_modelview);
	glGetFloatv(GL_PROJECTION_MATRIX, shad_mvp);
	glPopMatrix();
	nv_mvp = shad_mvp;
	for(int i = 0; i < 8; ++i)
	{
		transf    = nv_mvp*vec4(f.point[i], 1.0f);
		transf.x /=transf.w;
		transf.y /=transf.w;
		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;
	}

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

	nv_mvp.Identity();
	nv_mvp.matrix[0] = scaleX;
	nv_mvp.matrix[5] = scaleY;
	nv_mvp.matrix[3] = offsetX;
	nv_mvp.matrix[7] = offsetY;
	nv_mvp = nv_mvp.Transpose();
	memcpy(shad_crop, nv_mvp.matrix, sizeof(float) * 16);
	glLoadMatrixf(shad_crop);
	glMultMatrixf(shad_proj);

	return minZ;
}

void ShadowMap::MakeShadowMap(void)
{
	glDisable(GL_TEXTURE_2D);
	glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();
	gluLookAt(0.0, 0.0, 0.0, -mapData.lightVec.vec[0], -mapData.lightVec.vec[1], -mapData.lightVec.vec[2], -1.0, 0.0, 0.0);
	glGetFloatv(GL_MODELVIEW_MATRIX, shad_modelview);

	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, depth_fb);
	glPushAttrib(GL_VIEWPORT_BIT);
	glViewport(0, 0, depth_size, depth_size);
	glPolygonOffset(1.0f, 4096.0f);
	glEnable(GL_POLYGON_OFFSET_FILL);
	// draw all faces since our terrain is not closed.
	glDisable(GL_CULL_FACE);

	UpdateSplitDist(f, gameSetupData.nearPlane, gameSetupData.farPlane);
	for(int i = 0; i < cur_num_splits; ++i)
	{
		UpdateFrustumPoints(f[i], camera.pos, camera.facing);
		float minZ = ApplyCropMatrix(f[i]);
		glFramebufferTextureLayerEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, depth_tex_ar, 0, i);
		glClear(GL_DEPTH_BUFFER_BIT);
		glMatrixMode(GL_MODELVIEW);
		
		// draw the scene
		physx->Draw(true);
		
		glMatrixMode(GL_PROJECTION);
		glMultMatrixf(shad_modelview);
		glGetFloatv(GL_PROJECTION_MATRIX, shad_cpm[i]);
	}
	// revert to normal back face culling as used for rendering
	glEnable(GL_CULL_FACE);
	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
	glDisable(GL_POLYGON_OFFSET_FILL);
	glPopAttrib();
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();
}

//main rendering function

Matrix4x4 nm;
	shadowMap->MakeShadowMap();
	
	double aspect = double(gameSetupData.screenWidth) / double(gameSetupData.screenHeight);
	float sky_color[4] = {0.8f, mapData.lightVec.vec[1]*0.1f + 0.7f, mapData.lightVec.vec[1]*0.4f + 0.5f, 1.0f};
	glClearColor(sky_color[0], sky_color[1], sky_color[2], sky_color[3]);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
	camera.LookAt();

	glGetFloatv(GL_MODELVIEW_MATRIX, shadowMap->cam_modelview);
	shadowMap->CameraInverse(shadowMap->cam_inverse_modelview, shadowMap->cam_modelview);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(gameSetupData.fov, aspect, shadowMap->f[0].neard, shadowMap->f[shadowMap->cur_num_splits-1].fard);
	glGetFloatv(GL_PROJECTION_MATRIX, shadowMap->cam_proj);
	
	for(int i=shadowMap->cur_num_splits; i<MAX_SPLITS; i++)
		shadowMap->far_bound[i] = 0;
	for(int i = 0; i < shadowMap->cur_num_splits; ++i)
	{
		shadowMap->far_bound[i] = 0.5f*(-shadowMap->f[i].fard*shadowMap->cam_proj[10]+shadowMap->cam_proj[14])/shadowMap->f[i].fard+0.5f;
		glActiveTexture(GL_TEXTURE0+(GLenum)i);
		glMatrixMode(GL_TEXTURE);
		glLoadMatrixf(MATRIX_BIAS);
		glMultMatrixf(shadowMap->shad_cpm[i]);
		glMultMatrixf(shadowMap->cam_inverse_modelview);
		glGetFloatv(GL_TEXTURE_MATRIX, nm.matrix);
		nm.Inverse();
		nm.Transpose();
		glActiveTexture(GL_TEXTURE0+(GLenum)(i+4));
		glMatrixMode(GL_TEXTURE);
		glLoadMatrixf(nm.matrix);		
	}

Anyone? I am at my sanity’s end. I have tried everything I can think of, I have used Nvidia’s math lib instead of my own, and still I get the chopped off effect. I have put my camera class in their demo, and my camera works fine with there demo. Argh…

delete

theres really no way to fix it easily.

have u tried making the shading away from the sun darker (more like reality), ie both areas arent recieving direct sunlight thats only indirect light thats why theyre darker

this should lessen its appearence.

see this video here
http://www.gametrailers.com/player/usermovies/242790.html
the same precision errors are there also but theyre very difficult to spot. (true its pretty dark, but even in full sunlight theyre lesser)

another way to lessen it is to do some shadowmap softening eg a blur (perhaps screenspace) or PCF or VSM

If you look an the shadowmap images you will see that top of that hill appears to be culled by near plane of the camera used during the shadowmap rendering (this is what oc2k1 mentioned). You need to move the camera used for shadowmap rendering to bigger height so it can see entire terain which should be rendered into that shadowmap.

oh I see what u mean now, I thought he was talking about the shadow acne eg behind the car,
in that case yes its just a case of the shadow casters being clipped.
either cause the zclip regions of the glFrustum call arent big enuf(or to big) or the cameras in the wrong place

Hey guys, yeah you beat me to it, I just figured it out, and was going to post the solution! Thanks for the help.

what u should have is a debugging function

glMultCurrentMatrixf( inverse_modelview_matrix );
glMultCurrentMatrixf( inverse_projection_matrix );

draw a cube (-1,-1,-1) -> (1,1,1)

do this with the shadowmap’s frustum, also perhaps u wanna draw the cameras frustum as well.
have a toogle state so u can freeze both of them onscreen but still fly around with the camera.
this is very useful cause it lets u see onscreen exactly what area the shadowmap is encompassing

Yeah I did have that for my other shadowmapping code, but I don’t have it updated to work with the splits and such with the CSM code. But yes, I totally agree it’s useful beyond description. BTW why doesn’t this forum spit out email notifications?