PDA

View Full Version : Cascaded Shadow Mapping in OpenGL



OpenGL_man
10-29-2014, 07:19 PM
Hello, I'm new on this forum. :)
I'm trying to implement cascaded shadow maps in OpenGL, but it's very hard! :( Can I implement it without frustum splitting? is there any method of cascaded shadow maps implementation that does not involve frustum splitting?

Dark Photon
10-30-2014, 06:31 PM
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.

OpenGL_man
10-31-2014, 04:49 PM
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. :D

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. :)

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]);
}
}

.
.
.

}

DragonForce99
11-04-2014, 04:06 AM
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/

OpenGL_man
11-04-2014, 01:31 PM
Thank you for your reply. :)

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

OpenGL_man
11-04-2014, 01:57 PM
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]);
}
}

.
.
.

}

DragonForce99
11-08-2014, 08:40 AM
frustum splitting is confusing to me i dont knw what dose it mean

DragonForce99
11-08-2014, 09:06 AM
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

DragonForce99
11-08-2014, 09:23 AM
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