一个物体之所以会处在阴影当中,是由于在它和光源之间存在着遮蔽物,或者说遮蔽物离光源的距离比物体要近,这就是 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; }