Shadow mapping 的原理:
一个物体之所以会处在阴影当中,是由于在它和光源之间存在着遮蔽物,或者说遮蔽物离光源的距离比物体要近,这就是 shadow mapping 算法的基本原理.
Pass1: 以光源为视点,或者说在光源坐标系下面对整个场景进行渲染,目的是要得到一副所有物体相对于光源的 depth map (也就是我们所说的shadow map),也就是这副图像中每个象素的值代表着场景里面离光源最近的 fragment 的深度值。由于这个pass中我们感兴趣的只是象素的深度值,所以可以把所有的光照计算关掉,打开 z-test 和 z-write 的 render state 。
Pass2: 将视点恢复到原来的正常位置,渲染整个场景,对每个象素计算它和光源的距离,然后将这个值和 depth map中相应的值比较,以确定这个象素点是否处在阴影当中。然后根据比较的结果,对 shadowed fragment 和 lighted fragment 分别进行不同的光照计算,这样就可以得到阴影的效果了。
实现一:固定管线
纹理:
在实现过程中,需要指定纹理坐标,而opengl中有两种为顶点指定坐标的方法:
第一种是人工为每个顶点指定坐标,可以通过函数glTexCord*()来完成
第二种是自动生成纹理坐标,由函数glTexGen*()来完成。
//Set up texture coordinate generation.
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGenfv(GL_S, GL_EYE_PLANE, textureMatrix.GetRow(0));
glEnable(GL_TEXTURE_GEN_S);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGenfv(GL_T, GL_EYE_PLANE, textureMatrix.GetRow(1));
glEnable(GL_TEXTURE_GEN_T);
glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGenfv(GL_R, GL_EYE_PLANE, textureMatrix.GetRow(2));
glEnable(GL_TEXTURE_GEN_R);
glTexGeni(GL_Q, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGenfv(GL_Q, GL_EYE_PLANE, textureMatrix.GetRow(3));
glEnable(GL_TEXTURE_GEN_Q);
实现二:可编程管线
void display()
{
//1.render to framebuffer with fixed pipeline
glBindFramebuffer(GL_FRAMEBUFFER,fboId);
//Use viewport the same size as the shadow map
glViewport(0, 0, windowWidth, windowHeight);
// Clear previous frame values
glClear(GL_DEPTH_BUFFER_BIT);
//Disable color rendering, we only want to write to the Z-Buffer
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
// Culling switching, rendering only backface, this is done to avoid self-shadowing
glCullFace(GL_FRONT);
glMatrixMode(GL_PROJECTION);
glLoadMatrixf(&lightProjectionMatrix[0][0]);
glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(&lightViewMatrix[0][0]);
drawScene();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
//2.render to screen with programmable pipeline
glViewport(0, 0, windowWidth, windowHeight);
//Enabling color write (previously disabled for light POV z-buffer rendering)
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
// Clear the screen
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glCullFace(GL_BACK); // Cull back-facing triangles -> draw only front-facing triangles
glMatrixMode(GL_PROJECTION);
glLoadMatrixf(&cameraProjectionMatrix[0][0]);
glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(&cameraViewMatrix[0][0]);
glm::mat4 biasMatrix(
0.5, 0.0, 0.0, 0.0,
0.0, 0.5, 0.0, 0.0,
0.0, 0.0, 0.5, 0.0,
0.5, 0.5, 0.5, 1.0
);
glm::mat4 depthBiasMVP = biasMatrix*cameraProjectionMatrix*cameraViewMatrix;
// Use shader
glUseProgram(shadowProgramID);
glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &depthBiasMVP[0][0]);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, shadowMapTexture);
glUniform1i(shadowMapUniform, 0);
drawScene();
// DEBUG only. this piece of code draw the depth buffer onscreen
glUseProgram(0);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-windowWidth/2,windowWidth/2,-windowHeight/2,windowHeight/2,1,20);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glColor4f(1,1,1,1);
glActiveTextureARB(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D,shadowMapTexture);
glEnable(GL_TEXTURE_2D);
glTranslated(0,0,-1);
glBegin(GL_QUADS);
glTexCoord2d(1,1);glVertex3f(-1,-1,0);
glTexCoord2d(0,1);glVertex3f(-windowWidth/2,-1,0);
glTexCoord2d(0,0);glVertex3f(-windowWidth/2,-windowHeight/2,0);
glTexCoord2d(1,0);glVertex3f(-1,-windowHeight/2,0);
glEnd();
glDisable(GL_TEXTURE_2D);
glutSwapBuffers();
}
//shadowmap.vert
#version 120
uniform mat4 DepthBiasMVP;
varying vec4 ShadowCoord;
void main()
{
gl_Position = ftransform();
gl_FrontColor = gl_Color;
ShadowCoord = DepthBiasMVP * gl_Vertex;
}
//shadowmap.frag
#version 120
uniform sampler2D ShadowMap;
varying vec4 ShadowCoord;
void main()
{
vec4 shadowCoordinateWdivide = ShadowCoord / ShadowCoord.w;
shadowCoordinateWdivide.z += 0.0005;
float distanceFromLight = texture2D(ShadowMap, shadowCoordinateWdivide.st).z;
float shadow = 1.0;
if(ShadowCoord.w > 0.0)
shadow = distanceFromLight < shadowCoordinateWdivide.z ? 0.5 : 0.99;
gl_FragColor = shadow * gl_Color;
}