Cascaded Shadow Mapping in OpenGL

Hello, I’m new on this forum. :slight_smile:
I’m trying to implement cascaded shadow maps in OpenGL, but it’s very hard! :frowning: Can I implement it without frustum splitting? is there any method of cascaded shadow maps implementation that does not involve frustum splitting?

Without frustum splitting? That’s the “cascaded” part of cascaded shadow mapping.

You might want to do more reading on how basic shadow mapping works, what problems it has, which leads into when you’d use cascaded shadow mapping. Then you can decide if you can drop the “cascaded” part. If you’re not comfortable with all of OpenGL’s coordinate spaces, how coordinate frame transformations work, and how the depth buffer works, you need to study up on that first.

Hey, thank you very much for your reply. :smiley:

But, today I got it!!! I implemented a “cascaded shadow map” in OpenGL!.. Or, like you say, maybe not! :confused: Because it has no frustum splitting! If this is not a cascaded shadow map, then what is it? I’m new in shadow mapping. Happy Haloween. :slight_smile:

There is the source code:

C++ code:

void MakeShadowMaps(MD5Model* modelPtr, int model, MD5Model* weaponPtr)
{
float units;
int i = -1, j;
double boundX, boundZ;

// Light's point of view
viewMatrix = Mat4::LookAt(Vec::Vec3(light.position), 
	                      Vec::Vec3(0, 0, 0), 
						  Vec::Vec3(0, 1, 0));

// Render on the whole framebuffer, 
// complete from the lower left corner to the upper right
glViewport(0, 0, WIDTH, HEIGHT); // WIDTH = 1024, HEIGHT = 1024

while (++i < 4)
{
	// Render to our framebuffer
	glBindFramebuffer(GL_FRAMEBUFFER, FramebufferName[i]);

	if (i == 0)
	{
		units  = 2.0f;     // units of glPolygonOffset()
		boundX = boundZ = BOUND0; // 300
	}
	else if (i == 1)
	{
		units  = 4.0f;
		boundX = boundZ = BOUND1; // 600
	}
	else if (i == 2)
	{
		units  = 8.0f;
		boundX = boundZ = BOUND2; // 1200
	}
	else
	{
		glViewport(0, 0, WIDTH2, HEIGHT2); // WIDTH = 2048, HEIGHT = 2048
		units  = 10.0f;
		boundX = boundZ = BOUND3; // 3000
	}

	// Clear the screen
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	point[0].x = (float)-boundX;
	point[0].y = (float)-boundZ;
	point[0].z = 0;
	point[0].w = 1;

	point[1].x = (float)-boundX;
	point[1].y = (float)boundZ;
	point[1].z = 0;
	point[1].w = 1;

	point[2].x = (float)boundX;
	point[2].y = (float)-boundZ;
	point[2].z = 0;
	point[2].w = 1;

	point[3].x = (float)boundX;
	point[3].y = (float)boundZ;
	point[3].z = 0;
	point[3].w = 1;

	// Compute the MVP matrix from the light's point of view
	projectionMatrix = Mat4::Ortho(-1, 1,-1, 1, Z_NEAR, Z_FAR); // ZNEAR = -3000, ZFAR = 6000
    camera = eyePosition + eyeDirection * (float)boundX;
    ApplyCropMatrix((float)boundX);

	matrix.LoadIdentity();
	CalculateTerrainDepthMatrix(i);

	if (abs(light.position.x) < 100.0f || 
		abs(light.position.z) < 100.0f)
		glPolygonOffset(1.0f, units * 100);
	else
		glPolygonOffset(1.0f, units * 5);

	animation = false;
	matrix.LoadIdentity();
	UpdateShadersUniforms(WALLS, -1, i);
	walls.NoAnimation();
	walls.DrawShadow();

	glPolygonOffset(1.0f, units);
	matrix.LoadIdentity();
	UpdateShadersUniforms(GRAVES, -1, i);
	graves.NoAnimation();
	graves.DrawShadow();
	animation = true;
		
	matrix.LoadIdentity();
	matrix.Translatev(modelTranslatev = Vec::Vec3(mx, 0, mz));
	UpdateShadersUniforms(model, -1, i);
	modelPtr->DrawShadow();
	if (model == DARK_KNIGHT)
		weaponPtr->DrawShadow();

	j = -1;
	while(++j < NUM_OF_TREES)
	{
		matrix.LoadIdentity();
		matrix.Translatev(treesCoord[j]);
		UpdateShadersUniforms(TREE, j, i);
		treev[j].NoAnimation();
		treev[j].DrawShadow();
	}

	glPolygonOffset(1.0f, units * 2);
	matrix.LoadIdentity();
	matrix.Translatev(churchCoord);
	UpdateShadersUniforms(CHURCH, -1, i);
	church.NoAnimation();
	church.DrawShadow();
	animation = true;
}

}

.
.
.

void ApplyCropMatrix(float bound)
{
matrix4x4_t shadMvp = projectionMatrix *
(viewMatrix * Mat4::Translatev3(camera));
float maxX = Z_NEAR, minX = Z_FAR,
maxY = Z_NEAR, minY = Z_FAR;

int i = -1;
while (++i < 4)
{
	transf = shadMvp * point[i];

	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 absMaxXminX         = abs(maxX - minX),
	  absMaxYminY         = abs(maxY - minY),
	  absBoundAbsMaxXminX = abs((bound *= 2) - absMaxXminX) * 0.5f,
	  absBoundAbsMaxYminY = abs(bound        - absMaxYminY) * 0.5f;

if (absMaxXminX < bound)
{
	maxX += absBoundAbsMaxXminX;
	minX -= absBoundAbsMaxXminX;
}
else if (absMaxXminX > bound)
{
	maxX -= absBoundAbsMaxXminX;
	minX += absBoundAbsMaxXminX;
}
if (absMaxYminY < bound)
{
	maxY += absBoundAbsMaxYminY;
	minY -= absBoundAbsMaxYminY;
}
else if (absMaxYminY > bound)
{
	maxY -= absBoundAbsMaxYminY;
	minY += absBoundAbsMaxYminY;
}

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

shadMvp = Mat4::Identity();

shadMvp.m(0, 0) = scaleX;
shadMvp.m(1, 1) = scaleY;
shadMvp.m(0, 3) = offsetX;
shadMvp.m(1, 3) = offsetY;

projectionMatrix *= shadMvp;

}

.
.
.

GLSL code:

Vertex shader:

#version 120

#ifndef MAX_BONES
#define MAX_BONES 128
#endif

// Input vertex data, different for all executions of this shader.
attribute vec3 vertexPosition_modelspace;
attribute vec3 vertexUV;
attribute vec3 vertexNormal_modelspace;
attribute vec4 boneIndex;
attribute vec3 weight;

// Output data ; will be interpolated for each fragment.
varying vec2 UV;

varying vec3 lightDirection;
varying vec3 eyeDirection;

varying vec4 fragCoord;
varying vec4 ShadowCoord[4];

// Values that stay constant for the whole mesh.
uniform mat4 boneMatrix[MAX_BONES];
uniform mat3 normalMatrix;
uniform mat4 M;
uniform mat4 MV;
uniform mat4 MVP;
uniform mat4 DepthBiasMVP[4];

uniform vec3 viewSpaceLightPosition;
uniform vec3 eyePosition;

uniform bool animation;

void main()
{
float w = sign(vertexUV.z);
vec4 vertexPosition = vec4(vertexPosition_modelspace, 1);
vec3 vertexView = (MV * vertexPosition).xyz,
vertexNormal = vertexNormal_modelspace,
vertexTangent = w * vertexUV.z * vec3(1.0/256.0, 1.0, 256.0),
t, b, n,
tempLightPos, tempEyePos;

vertexTangent = vertexTangent - floor(vertexTangent);
vertexTangent = (vertexTangent - vec3(0.5, 0.5, 0.5)) * 2.0;

if (animation)
{
	mat4 matTransform = boneMatrix[int(boneIndex.x)] * weight.x +
			            boneMatrix[int(boneIndex.y)] * weight.y +
			            boneMatrix[int(boneIndex.z)] * weight.z +
	                    boneMatrix[int(boneIndex.w)] * 
			            (1 - (weight.x + weight.y + weight.z));

	vertexPosition = matTransform * vertexPosition;
	vertexNormal   = normalize(vec3(matTransform * 
		                            vec4(vertexNormal, 0)));
	vertexTangent  = vec3(matTransform * vec4(vertexTangent, 0));
}
	   
// Output position of the vertex, in clip space : MVP * position	
fragCoord = gl_Position = MVP * vertexPosition;
	
ShadowCoord[0] = DepthBiasMVP[0] * vertexPosition;
ShadowCoord[1] = DepthBiasMVP[1] * vertexPosition;
ShadowCoord[2] = DepthBiasMVP[2] * vertexPosition;
ShadowCoord[3] = DepthBiasMVP[3] * vertexPosition;

n = normalize(normalMatrix * vertexNormal);
t = normalize(normalMatrix * vertexTangent);
b = normalize(normalMatrix * (w * cross(vertexNormal, vertexTangent)));
tempLightPos = viewSpaceLightPosition - vertexView;
tempEyePos   = -vertexView;

lightDirection.x = dot(tempLightPos, t);
lightDirection.y = dot(tempLightPos, b);
lightDirection.z = dot(tempLightPos, n);

eyeDirection.x = dot(tempEyePos, t);
eyeDirection.y = dot(tempEyePos, b);
eyeDirection.z = dot(tempEyePos, n);

UV = vertexUV.xy;

}

Fragment shader:
#version 120
#extension GL_EXT_gpu_shader4 : enable

struct material_t
{
vec4 Ka;
vec4 Ke;
vec4 Kd;
float shininess;
};

// Interpolated values from the vertex shaders
varying vec2 UV;

varying vec3 lightDirection;
varying vec3 eyeDirection;

varying vec4 fragCoord;
varying vec4 ShadowCoord[4];

// Values that stay constant for the whole mesh.
uniform sampler2D myTextureSampler;
uniform sampler2D normalMap;
uniform sampler2D specularSampler;
uniform sampler2DShadow shadowMap[4];
uniform vec3 farBound;

uniform vec4 globalAmbient;
uniform vec3 lightColor;
uniform material_t material;

uniform bool shadow;
uniform bool showSplit;

float Shadow(sampler2DShadow sMap, vec4 sCoord)
{
// Use PCF to improve shadow quality at the shadow edges.
// Gaussian 3x3 filter
float coeff = shadow2D(sMap, vec3(sCoord)).x * 0.25;
coeff += shadow2DOffset(sMap, vec3(sCoord), ivec2(-1,-1)).x * 0.0625;
coeff += shadow2DOffset(sMap, vec3(sCoord), ivec2(-1, 0)).x * 0.125;
coeff += shadow2DOffset(sMap, vec3(sCoord), ivec2(-1, 1)).x * 0.0625;
coeff += shadow2DOffset(sMap, vec3(sCoord), ivec2( 0,-1)).x * 0.125;
coeff += shadow2DOffset(sMap, vec3(sCoord), ivec2( 0, 1)).x * 0.125;
coeff += shadow2DOffset(sMap, vec3(sCoord), ivec2( 1,-1)).x * 0.0625;
coeff += shadow2DOffset(sMap, vec3(sCoord), ivec2( 1, 0)).x * 0.125;
return coeff +=
shadow2DOffset(sMap, vec3(sCoord), ivec2(1, 1)).x * 0.0625;
}

void main()
{
// Calculate ambient term
vec3 ambient = material.Ka.rgb * globalAmbient.rgb,
// Compute the diffuse lighting
N = normalize(texture2D(normalMap, UV).rgb * 2.0 - 1.0),
L = normalize(lightDirection),
diffuse, specular, splitColor = vec3(0, 0, 0);

float cosTheta = clamp(dot(N, L ), 0, 1),
      // Compute the specular lighting
	  cosAlpha = clamp(dot(normalize(eyeDirection), reflect(-L, N)), 
					   0, 1),
      // Variables for shadows
      alpha       = alphaShadow,
	  shadowCoeff = 1;

// Modulate diffuse and specular by material color
diffuse  = material.Kd.rgb * lightColor * cosTheta;
specular = texture2D(specularSampler, UV).rgb * 
	       lightColor * pow(cosAlpha, material.shininess);

if (shadow)
{
	if (showSplit)
	{
		if (fragCoord.z < farBound.x)
		{
			shadowCoeff = shadow2D(shadowMap[0], vec3(ShadowCoord[0])).x;
			splitColor  = vec3(0.5, 0.5, 1.0);
		}
		else if (fragCoord.z < farBound.y)
		{
			shadowCoeff = shadow2D(shadowMap[1], vec3(ShadowCoord[1])).x;
			splitColor  = vec3(0.5, 1.0, 0.5);
		}
		else if (fragCoord.z < farBound.z)
		{
			shadowCoeff = shadow2D(shadowMap[2], vec3(ShadowCoord[2])).x;
			splitColor  = vec3(1.0, 0.5, 0.5);
		}
		else
		{
			shadowCoeff = shadow2D(shadowMap[3], vec3(ShadowCoord[3])).x;
			splitColor  = vec3(1.0, 1.0, 1.0);
		}
	}
	else
	{
		if (fragCoord.z < farBound.x)
			shadowCoeff = Shadow(shadowMap[0], ShadowCoord[0]);
		else if (fragCoord.z < farBound.y)
			shadowCoeff = Shadow(shadowMap[1], ShadowCoord[1]);
		else if (fragCoord.z < farBound.z)
			shadowCoeff = Shadow(shadowMap[2], ShadowCoord[2]);
		else
			shadowCoeff = Shadow(shadowMap[3], ShadowCoord[3]);
	}
}
	
gl_FragColor  = vec4(material.Ke.rgb + ambient + shadowCoeff * 
					 ((shadow && showSplit) ? splitColor : 
					                          diffuse + specular), 
					 1);
gl_FragColor *= texture2D(myTextureSampler, UV);

if (gl_FragColor.a < 0.666667)
	gl_FragColor = vec4(0, 0, 0, 0);

}

[QUOTE=Dark Photon;1262312]Without frustum splitting? That’s the “cascaded” part of cascaded shadow mapping.

You might want to do more reading on how basic shadow mapping works, what problems it has, which leads into when you’d use cascaded shadow mapping. Then you can decide if you can drop the “cascaded” part. If you’re not comfortable with all of OpenGL’s coordinate spaces, how coordinate frame transformations work, and how the depth buffer works, you need to study up on that first.[/QUOTE]

Hey, thank you very much for your reply. :smiley:

But, today I got it!!! I implemented a “cascaded shadow map” in OpenGL!.. Or, like you say, maybe not! :confused: Because it has no frustum splitting! If this is not a cascaded shadow map, then what is it? I’m new in shadow mapping. Happy Haloween. :slight_smile:

There is the source code:

C++ code:

void MakeShadowMaps(MD5Model* modelPtr, int model, MD5Model* weaponPtr)
{
	float  units;
	int    i = -1, j;
	double boundX, boundZ;

    // Light's point of view
	viewMatrix = Mat4::LookAt(Vec::Vec3(light.position), 
		                      Vec::Vec3(0, 0, 0), 
							  Vec::Vec3(0, 1, 0));

	// Render on the whole framebuffer, 
	// complete from the lower left corner to the upper right
	glViewport(0, 0, WIDTH, HEIGHT); // WIDTH = 1024, HEIGHT = 1024

	while (++i < 4)
	{
		// Render to our framebuffer
		glBindFramebuffer(GL_FRAMEBUFFER, FramebufferName[i]);

		if (i == 0)
		{
			units  = 2.0f;     // units of glPolygonOffset()
			boundX = boundZ = BOUND0; // 300
		}
		else if (i == 1)
		{
			units  = 4.0f;
			boundX = boundZ = BOUND1; // 600
		}
		else if (i == 2)
		{
			units  = 8.0f;
			boundX = boundZ = BOUND2; // 1200
		}
		else
		{
			glViewport(0, 0, WIDTH2, HEIGHT2); // WIDTH = 2048, HEIGHT = 2048
			units  = 10.0f;
			boundX = boundZ = BOUND3; // 3000
		}

		// Clear the screen
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		point[0].x = (float)-boundX;
		point[0].y = (float)-boundZ;
		point[0].z = 0;
		point[0].w = 1;

		point[1].x = (float)-boundX;
		point[1].y = (float)boundZ;
		point[1].z = 0;
		point[1].w = 1;

		point[2].x = (float)boundX;
		point[2].y = (float)-boundZ;
		point[2].z = 0;
		point[2].w = 1;

		point[3].x = (float)boundX;
		point[3].y = (float)boundZ;
		point[3].z = 0;
		point[3].w = 1;

		// Compute the MVP matrix from the light's point of view
		projectionMatrix = Mat4::Ortho(-1, 1,-1, 1, Z_NEAR, Z_FAR); // ZNEAR = -3000, ZFAR = 6000
	    camera = eyePosition + eyeDirection * (float)boundX;
	    ApplyCropMatrix((float)boundX);

		matrix.LoadIdentity();
		CalculateTerrainDepthMatrix(i);
	
		if (abs(light.position.x) < 100.0f || 
			abs(light.position.z) < 100.0f)
			glPolygonOffset(1.0f, units * 100);
		else
			glPolygonOffset(1.0f, units * 5);

		animation = false;
		matrix.LoadIdentity();
		UpdateShadersUniforms(WALLS, -1, i);
		walls.NoAnimation();
		walls.DrawShadow();

		glPolygonOffset(1.0f, units);
		matrix.LoadIdentity();
		UpdateShadersUniforms(GRAVES, -1, i);
		graves.NoAnimation();
		graves.DrawShadow();
		animation = true;
			
		matrix.LoadIdentity();
		matrix.Translatev(modelTranslatev = Vec::Vec3(mx, 0, mz));
		UpdateShadersUniforms(model, -1, i);
		modelPtr->DrawShadow();
		if (model == DARK_KNIGHT)
			weaponPtr->DrawShadow();

		j = -1;
		while(++j < NUM_OF_TREES)
		{
			matrix.LoadIdentity();
			matrix.Translatev(treesCoord[j]);
			UpdateShadersUniforms(TREE, j, i);
			treev[j].NoAnimation();
			treev[j].DrawShadow();
		}

		glPolygonOffset(1.0f, units * 2);
		matrix.LoadIdentity();
		matrix.Translatev(churchCoord);
		UpdateShadersUniforms(CHURCH, -1, i);
		church.NoAnimation();
		church.DrawShadow();
		animation = true;
	}
}

.
.
.

void ApplyCropMatrix(float bound)
{	
	matrix4x4_t shadMvp = projectionMatrix * 
		                  (viewMatrix * Mat4::Translatev3(camera));
	float       maxX = Z_NEAR, minX = Z_FAR, 
		        maxY = Z_NEAR, minY = Z_FAR;

	int i = -1;
	while (++i < 4)
	{
		transf = shadMvp * point[i];

		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 absMaxXminX         = abs(maxX - minX),
		  absMaxYminY         = abs(maxY - minY),
		  absBoundAbsMaxXminX = abs((bound *= 2) - absMaxXminX) * 0.5f,
		  absBoundAbsMaxYminY = abs(bound        - absMaxYminY) * 0.5f;

	if (absMaxXminX < bound)
	{
		maxX += absBoundAbsMaxXminX;
		minX -= absBoundAbsMaxXminX;
	}
	else if (absMaxXminX > bound)
	{
		maxX -= absBoundAbsMaxXminX;
		minX += absBoundAbsMaxXminX;
	}
	if (absMaxYminY < bound)
	{
		maxY += absBoundAbsMaxYminY;
		minY -= absBoundAbsMaxYminY;
	}
	else if (absMaxYminY > bound)
	{
		maxY -= absBoundAbsMaxYminY;
		minY += absBoundAbsMaxYminY;
	}

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

	shadMvp.m(0, 0) = scaleX;
	shadMvp.m(1, 1) = scaleY;
	shadMvp.m(0, 3) = offsetX;
	shadMvp.m(1, 3) = offsetY;

	projectionMatrix *= shadMvp;
}

.
.
.

GLSL code:

Vertex shader:

#version 120

.
.
.

// Input vertex data, different for all executions of this shader.
attribute vec3 vertexPosition_modelspace;
.
.
.

// Output data ; will be interpolated for each fragment.

.
.
.

varying vec4 fragCoord;
varying vec4 ShadowCoord[4];

// Values that stay constant for the whole mesh.

.
.
.

uniform mat4 MVP;
uniform mat4 DepthBiasMVP[4];

.
.
.

void main()
{
	vec4  vertexPosition = vec4(vertexPosition_modelspace, 1);
        
        .
        .
        .
		   
	// Output position of the vertex, in clip space : MVP * position	
	fragCoord = gl_Position = MVP * vertexPosition;
		
	ShadowCoord[0] = DepthBiasMVP[0] * vertexPosition;
	ShadowCoord[1] = DepthBiasMVP[1] * vertexPosition;
	ShadowCoord[2] = DepthBiasMVP[2] * vertexPosition;
	ShadowCoord[3] = DepthBiasMVP[3] * vertexPosition;

        .
        .
        .
}

Fragment shader:

#version 120
#extension GL_EXT_gpu_shader4 : enable

.
.
.

// Interpolated values from the vertex shaders

.
.
.

varying vec4 fragCoord;
varying vec4 ShadowCoord[4];

// Values that stay constant for the whole mesh.

.
.
.

uniform sampler2DShadow shadowMap[4];
uniform vec3            farBound;

.
.
.

uniform bool  shadow;
uniform bool  showSplit;

float Shadow(sampler2DShadow sMap, vec4 sCoord)
{
	// Use PCF to improve shadow quality at the shadow edges.
	// Gaussian 3x3 filter
	float coeff = shadow2D(sMap, vec3(sCoord)).x * 0.25;
	coeff += shadow2DOffset(sMap, vec3(sCoord), ivec2(-1,-1)).x * 0.0625;
	coeff += shadow2DOffset(sMap, vec3(sCoord), ivec2(-1, 0)).x * 0.125;
	coeff += shadow2DOffset(sMap, vec3(sCoord), ivec2(-1, 1)).x * 0.0625;
	coeff += shadow2DOffset(sMap, vec3(sCoord), ivec2( 0,-1)).x * 0.125;
	coeff += shadow2DOffset(sMap, vec3(sCoord), ivec2( 0, 1)).x * 0.125;
	coeff += shadow2DOffset(sMap, vec3(sCoord), ivec2( 1,-1)).x * 0.0625;
	coeff += shadow2DOffset(sMap, vec3(sCoord), ivec2( 1, 0)).x * 0.125;
	return coeff += 
		   shadow2DOffset(sMap, vec3(sCoord), ivec2(1, 1)).x * 0.0625;
}

void main()
{

        .
        .
        .

	if (shadow)
	{
		if (showSplit)
		{
			if (fragCoord.z < farBound.x)
			{
				shadowCoeff = shadow2D(shadowMap[0], vec3(ShadowCoord[0])).x;
				splitColor  = vec3(0.5, 0.5, 1.0);
			}
			else if (fragCoord.z < farBound.y)
			{
				shadowCoeff = shadow2D(shadowMap[1], vec3(ShadowCoord[1])).x;
				splitColor  = vec3(0.5, 1.0, 0.5);
			}
			else if (fragCoord.z < farBound.z)
			{
				shadowCoeff = shadow2D(shadowMap[2], vec3(ShadowCoord[2])).x;
				splitColor  = vec3(1.0, 0.5, 0.5);
			}
			else
			{
				shadowCoeff = shadow2D(shadowMap[3], vec3(ShadowCoord[3])).x;
				splitColor  = vec3(1.0, 1.0, 1.0);
			}
		}
		else
		{
			if (fragCoord.z < farBound.x)
				shadowCoeff = Shadow(shadowMap[0], ShadowCoord[0]);
			else if (fragCoord.z < farBound.y)
				shadowCoeff = Shadow(shadowMap[1], ShadowCoord[1]);
			else if (fragCoord.z < farBound.z)
				shadowCoeff = Shadow(shadowMap[2], ShadowCoord[2]);
			else
				shadowCoeff = Shadow(shadowMap[3], ShadowCoord[3]);
		}
	}

        .
        .
        .

}

Can I implement it without frustum splitting?

your question is strange looks like you need to read about shado map

1-read about FBO (Frame Buffer Object) or render to texture

2-read about Render Depth Buffer

ok i will explane to in short

what is FBO (frame Buffer Object)

when you call draw function in opengl opengl dosn’t draw it on your screen or window
but it’s store it as FBO in Graphic Card’s memory then if you need to show it you have to call function to draw it like glutSwapBuffers (if you are using glut)

you can render to another target(another frame buffer) without effect on your orginal buffer

the shadow is render to texture but it is rendering the depth of your pixel

you need to learn longer about them

take a look

render to texture :http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-14-render-to-texture/

shadow map :http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-16-shadow-mapping/

Thank you for your reply. :slight_smile:

Yes, I’m new in shadow mapping, but I know the basis on this mather. The code above (by the way, how can I modify it?) is from one implementation made by myself, It’s not regular cascade shadow mapping (there are no frustum splitting), but it does exactly the same thing! Take a look: http://www.youtube.com/watch?v=ofm_LwR5RDI

Sorry, but I have to change some lines:

C++ code:
 
void MakeShadowMaps(MD5Model* modelPtr, int model, MD5Model* weaponPtr)
{
    float  units;
    int    i = -1, j;
    double boundX, boundZ, fovRatio = FRUSTUM_FOV / 45.0;
 
    // Light's point of view
    viewMatrix = Mat4::LookAt(Vec::Vec3(light.position), 
                              Vec::Vec3(0, 0, 0), 
                              Vec::Vec3(0, 1, 0));
 
    // Render on the whole framebuffer, 
    // complete from the lower left corner to the upper right
    glViewport(0, 0, WIDTH, HEIGHT); // WIDTH = 1024, HEIGHT = 1024
 
    while (++i < 4)
    {
        // Render to our framebuffer
        glBindFramebuffer(GL_FRAMEBUFFER, FramebufferName[i]);
 
        if (i == 0)
        {
            units  = 2.0f;     // units of glPolygonOffset()
            boundX = boundZ = BOUND0 * fovRatio; // 300
        }
        else if (i == 1)
        {
            units  = 4.0f;
            boundX = boundZ = BOUND1 * fovRatio; // 600
        }
        else if (i == 2)
        {
            units  = 8.0f;
            boundX = boundZ = BOUND2 * fovRatio; // 1200
        }
        else
        {
            glViewport(0, 0, WIDTH2, HEIGHT2); // WIDTH = 2048, HEIGHT = 2048
            units  = 10.0f;
            boundX = boundZ = BOUND3 * fovRatio; // 3000
        }
 
        // Clear the screen
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
        point[0].x = (float)-boundX;
        point[0].y = (float)-boundZ;
        point[0].z = 0;
        point[0].w = 1;
 
        point[1].x = (float)-boundX;
        point[1].y = (float)boundZ;
        point[1].z = 0;
        point[1].w = 1;
 
        point[2].x = (float)boundX;
        point[2].y = (float)-boundZ;
        point[2].z = 0;
        point[2].w = 1;
 
        point[3].x = (float)boundX;
        point[3].y = (float)boundZ;
        point[3].z = 0;
        point[3].w = 1;
 
        // Compute the MVP matrix from the light's point of view
        projectionMatrix = Mat4::Ortho(-1, 1,-1, 1, Z_NEAR, Z_FAR); // ZNEAR = -3000, ZFAR = 6000
        camera = eyePosition + eyeDirection * (float)boundX;
        ApplyCropMatrix((float)boundX);
 
        matrix.LoadIdentity();
        CalculateTerrainDepthMatrix(i);
 
        if (abs(light.position.x) < 100.0f || 
            abs(light.position.z) < 100.0f)
            glPolygonOffset(1.0f, units * 100);
        else
            glPolygonOffset(1.0f, units * 5);
 
        animation = false;
        matrix.LoadIdentity();
        UpdateShadersUniforms(WALLS, -1, i);
        walls.NoAnimation();
        walls.DrawShadow();
 
        glPolygonOffset(1.0f, units);
        matrix.LoadIdentity();
        UpdateShadersUniforms(GRAVES, -1, i);
        graves.NoAnimation();
        graves.DrawShadow();
        animation = true;
 
        matrix.LoadIdentity();
        matrix.Translatev(modelTranslatev = Vec::Vec3(mx, 0, mz));
        UpdateShadersUniforms(model, -1, i);
        modelPtr->DrawShadow();
        if (model == DARK_KNIGHT)
            weaponPtr->DrawShadow();
 
        j = -1;
        while(++j < NUM_OF_TREES)
        {
            matrix.LoadIdentity();
            matrix.Translatev(treesCoord[j]);
            UpdateShadersUniforms(TREE, j, i);
            treev[j].NoAnimation();
            treev[j].DrawShadow();
        }
 
        glPolygonOffset(1.0f, units * 2);
        matrix.LoadIdentity();
        matrix.Translatev(churchCoord);
        UpdateShadersUniforms(CHURCH, -1, i);
        church.NoAnimation();
        church.DrawShadow();
        animation = true;
    }
}
 
.
.
.
 
void ApplyCropMatrix(float bound)
{    
    matrix4x4_t shadMvp = projectionMatrix * 
                          (viewMatrix * Mat4::Translatev3(camera));
    float       maxX = Z_NEAR, minX = Z_FAR, 
                maxY = Z_NEAR, minY = Z_FAR;
 
    int i = -1;
    while (++i < 4)
    {
        transf = shadMvp * point[i];
 
        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 absMaxXminX         = abs(maxX - minX),
          absMaxYminY         = abs(maxY - minY),
          absBoundAbsMaxXminX = abs((bound *= 2) - absMaxXminX) * 0.5f,
          absBoundAbsMaxYminY = abs(bound        - absMaxYminY) * 0.5f;
 
    if (absMaxXminX < bound)
    {
        maxX += absBoundAbsMaxXminX;
        minX -= absBoundAbsMaxXminX;
    }
    else if (absMaxXminX > bound)
    {
        maxX -= absBoundAbsMaxXminX;
        minX += absBoundAbsMaxXminX;
    }
    if (absMaxYminY < bound)
    {
        maxY += absBoundAbsMaxYminY;
        minY -= absBoundAbsMaxYminY;
    }
    else if (absMaxYminY > bound)
    {
        maxY -= absBoundAbsMaxYminY;
        minY += absBoundAbsMaxYminY;
    }
 
    float scaleX  =  2.0f / (maxX - minX),
          scaleY  =  2.0f / (maxY - minY),
          offsetX = -0.5f * (maxX + minX) * scaleX,
          offsetY = -0.5f * (maxY + minY) * scaleY;
 
    shadMvp = Mat4::Identity();
 
    shadMvp.m(0, 0) = scaleX;
    shadMvp.m(1, 1) = scaleY;
    shadMvp.m(0, 3) = offsetX;
    shadMvp.m(1, 3) = offsetY;
 
    projectionMatrix *= shadMvp;
}
 
.
.
.
 
GLSL code:
 
Vertex shader:
 
#version 120
 
.
.
.
 
// Input vertex data, different for all executions of this shader.
attribute vec3 vertexPosition_modelspace;
.
.
.
 
// Output data ; will be interpolated for each fragment.
 
.
.
.
 
varying vec4 fragCoord;
varying vec4 ShadowCoord[4];
 
// Values that stay constant for the whole mesh.
 
.
.
.
 
uniform mat4 MVP;
uniform mat4 DepthBiasMVP[4];
 
.
.
.
 
void main()
{
    vec4  vertexPosition = vec4(vertexPosition_modelspace, 1);
 
        .
        .
        .
 
    // Output position of the vertex, in clip space : MVP * position    
    fragCoord = gl_Position = MVP * vertexPosition;
 
    ShadowCoord[0] = DepthBiasMVP[0] * vertexPosition;
    ShadowCoord[1] = DepthBiasMVP[1] * vertexPosition;
    ShadowCoord[2] = DepthBiasMVP[2] * vertexPosition;
    ShadowCoord[3] = DepthBiasMVP[3] * vertexPosition;
 
        .
        .
        .
}
 
Fragment shader:
 
#version 120
#extension GL_EXT_gpu_shader4 : enable
 
.
.
.
 
// Interpolated values from the vertex shaders
 
.
.
.
 
varying vec4 fragCoord;
varying vec4 ShadowCoord[4];
 
// Values that stay constant for the whole mesh.
 
.
.
.
 
uniform sampler2DShadow shadowMap[4];
uniform vec3            farBound;
 
.
.
.
 
uniform bool  shadow;
uniform bool  showSplit;
 
float Shadow(sampler2DShadow sMap, vec4 sCoord)
{
    // Use PCF to improve shadow quality at the shadow edges.
    // Gaussian 3x3 filter
    float coeff = shadow2D(sMap, vec3(sCoord)).x * 0.25;
    coeff += shadow2DOffset(sMap, vec3(sCoord), ivec2(-1,-1)).x * 0.0625;
    coeff += shadow2DOffset(sMap, vec3(sCoord), ivec2(-1, 0)).x * 0.125;
    coeff += shadow2DOffset(sMap, vec3(sCoord), ivec2(-1, 1)).x * 0.0625;
    coeff += shadow2DOffset(sMap, vec3(sCoord), ivec2( 0,-1)).x * 0.125;
    coeff += shadow2DOffset(sMap, vec3(sCoord), ivec2( 0, 1)).x * 0.125;
    coeff += shadow2DOffset(sMap, vec3(sCoord), ivec2( 1,-1)).x * 0.0625;
    coeff += shadow2DOffset(sMap, vec3(sCoord), ivec2( 1, 0)).x * 0.125;
    return coeff += 
           shadow2DOffset(sMap, vec3(sCoord), ivec2(1, 1)).x * 0.0625;
}
 
void main()
{
 
        .
        .
        .
 
    if (shadow)
    {
        if (showSplit)
        {
            if (fragCoord.z < farBound.x)
            {
                shadowCoeff = shadow2D(shadowMap[0], vec3(ShadowCoord[0])).x;
                splitColor  = vec3(0.5, 0.5, 1.0);
            }
            else if (fragCoord.z < farBound.y)
            {
                shadowCoeff = shadow2D(shadowMap[1], vec3(ShadowCoord[1])).x;
                splitColor  = vec3(0.5, 1.0, 0.5);
            }
            else if (fragCoord.z < farBound.z)
            {
                shadowCoeff = shadow2D(shadowMap[2], vec3(ShadowCoord[2])).x;
                splitColor  = vec3(1.0, 0.5, 0.5);
            }
            else
            {
                shadowCoeff = shadow2D(shadowMap[3], vec3(ShadowCoord[3])).x;
                splitColor  = vec3(1.0, 1.0, 1.0);
            }
        }
        else
        {
            if (fragCoord.z < farBound.x)
                shadowCoeff = Shadow(shadowMap[0], ShadowCoord[0]);
            else if (fragCoord.z < farBound.y)
                shadowCoeff = Shadow(shadowMap[1], ShadowCoord[1]);
            else if (fragCoord.z < farBound.z)
                shadowCoeff = Shadow(shadowMap[2], ShadowCoord[2]);
            else
                shadowCoeff = Shadow(shadowMap[3], ShadowCoord[3]);
        }
    }
 
        .
        .
        .
 
}

frustum splitting is confusing to me i dont knw what dose it mean

i see i understand what you are talking about you can finde my anser above
in your code you are using shadow map
you only need one shadow

learn FBO render to texture vary well

make shader program to calculat your shadow

gl_FragColor = vec4(distance(LightPosition,Vertex)/MaxLightDistance,0,0,Alpha);

and you get depth result texture each pixel contents the distance betwwen vertex and light position (range [0-1])

to calculate shadow use this programe while rendering depth case and swith to your orginal programe and compare the distance betwwn light distance
the pixel in your texture

if you distance between light and vertex = pixel is value means there is no shadow here
else vertex’s color = black

this is basics about shadow but you need to learn how to render to texture to understand these lines