既然模拟出了光照,那么也少不了阴影,阴影的产生是因为距离光线较近的物体遮挡了距离较远的物体,导致被遮挡的物体接受的光照少于遮挡物的,因此阴影的产生与否与物体到光源的位置有关系,静态物体的阴影可以用光照贴图来模拟,而动态阴影要用阴影锥或者阴影贴图实现,阴影锥会引入许多额外的顶点为管线带来负担,目前比较流行的阴影模拟方法是用阴影贴图,它的好处在于只是用纹理存储物体的深度信息而并不会引入额外顶点.
要实现阴影贴图有以下几个步骤:
首先开辟一块纹理缓存以便之后保存世界的深度信息:
//Create the shadow map texture glGenTextures(1, &shadowMapTextureLeft); glBindTexture(GL_TEXTURE_2D, shadowMapTextureLeft); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE_ARB); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC_ARB, GL_LEQUAL); glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, shadowMapSize, shadowMapSize, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0); glBindTexture(GL_TEXTURE_2D, 0);我这边开了两张深度纹理shadowMapTextureLeft与shadowMapTextureRight.
然后新建渲染目标对象fbo,fbo类似于帧缓冲区,只不过把渲染的片段保存于其他的缓存而不是屏幕上的帧缓冲区:
glGenFramebuffersEXT(1, &fboId); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboId); glDrawBuffer(GL_NONE); glReadBuffer(GL_NONE); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);这边的glDrawBuffer(GL_NONE);与glReadBuffer(GL_NONE);是为了屏蔽颜色的输入输出,因为这个fbo用于写入深度信息.
缓冲区准备工作完成.
接着设置光源:
void changeLightPos(float lx,float ly,float lz) { lightPos.x=lx; lightPos.y=ly; lightPos.z=lz; lightPos.w=0; }
然后设置渲染阴影贴图的投影矩阵:
//Calculate light projection matrix float size=128*2; lightProjectionMatrixLeft = ortho(-size-size,size-size,-size*2,size*2,-size*2,size*2);
这边我用的是方向光源,所以设置的平行投影.
然后设置渲染阴影贴图的视图矩阵,将视点放在光源位置,将方向定位光向量:
lightViewMatrixLeft=lookAt(lightPos.x, lightPos.y, lightPos.z, cx, cy, cz, 0.0f, 1.0f, 0.0f);
好了,现在准备工作都完成了,开始渲染阴影贴图:
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,fboId); glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,GL_TEXTURE_2D, shadowMapTextureLeft, 0); //Use viewport the same size as the shadow map glViewport(0, 0, shadowMapSize, shadowMapSize); //First pass - from light's point of view glMatrixMode(GL_PROJECTION); glLoadMatrixf(lightProjectionMatrixLeft); glMatrixMode(GL_MODELVIEW); glLoadMatrixf(lightViewMatrixLeft); //Draw back faces into the shadow map glCullFace(GL_FRONT); glClear(GL_DEPTH_BUFFER_BIT); //Draw the scene callback();给fbo挂上深度纹理,接下来渲染的内容都将保存到深度纹理当中,把遮挡物的正面剔除,因为遮挡物反面也会产生正确的阴影.
然后还原渲染目标,清除一下帧缓冲区内容
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,0); //restore states glCullFace(GL_BACK); glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);
接着进行一遍普通渲染,重置投影与视图矩阵:
glViewport(0, 0, windowWidth, windowHeight); glMatrixMode(GL_PROJECTION); glLoadMatrixf(cameraProjectionMatrix); glMatrixMode(GL_MODELVIEW); glLoadMatrixf(cameraViewMatrix);
glActiveTextureARB(GL_TEXTURE1_ARB); glBindTexture(GL_TEXTURE_2D, shadowMapTextureLeft); glActiveTextureARB(GL_TEXTURE2_ARB); glBindTexture(GL_TEXTURE_2D, shadowMapTextureRight); callback(); glActiveTextureARB(GL_TEXTURE1_ARB); glBindTexture(GL_TEXTURE_2D, 0); glActiveTextureARB(GL_TEXTURE2_ARB); glBindTexture(GL_TEXTURE_2D, 0);
有一点要说明一下,阴影贴图中的片段深度信息都是在纹理坐标系内的点,那么进行正常渲染的时候我们传入渲染阴影贴图时候用的视图矩阵与投影矩阵,然后进行以下变换:
模型空间->世界空间->阴影视图空间->阴影投影空间->规范化设备空间->阴影贴图纹理空间
那么接下来向着色器传递以下矩阵:
//Calculate texture matrix for projection //This matrix takes us from eye space to the light's clip space //It is postmultiplied by the inverse of the current view matrix when specifying texgen static MATRIX4X4 biasMatrix(0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f); //bias from [-1, 1] to [0, 1] shadowMatrix=biasMatrix*lightProjectionMatrixLeft*lightViewMatrixLeft;
所以顶点着色器可以这么写:
uniform mat4 viewMatrix,modelMatrix,normalMatrix; uniform mat4 shadowLeftMatrix,shadowRightMatrix; varying vec4 shadowVertLeft,shadowVertRight; ...... ...... shadowVertLeft = shadowLeftMatrix * modelMatrix * gl_Vertex; shadowVertRight = shadowRightMatrix * modelMatrix * gl_Vertex; viewVertex = vec3(viewMatrix * modelMatrix * gl_Vertex); gl_Position = gl_ProjectionMatrix * viewMatrix * modelMatrix * gl_Vertex;
片段着色器这么写:
uniform sampler2DShadow texShadowLeft,texShadowRight; varying vec4 shadowVertLeft,shadowVertRight; ...... ...... vec4 texcoordOffset = shadowVertLeft; if(texcoordOffset.x >= 0.0 && texcoordOffset.y >= 0.0 && texcoordOffset.x <= 1.0 && texcoordOffset.y <= 1.0 ) { texcoordOffset.z+=bias; float depth = shadow2DProj(texShadowLeft, texcoordOffset).z; if(depth<1.0 && factor>=0.2) factor-= 0.1; if(factor<0.4) factor=0.4; }
最后返回的factor值即为阴影值,把它与环境颜色相乘就能够输出片段了.
最终效果如下:
还能够在渲染阴影贴图的那一步使用着色器对深度纹理进行过滤产生半影区,这边我使用了pcf法对阴影进行处理.
原文地址:http://blog.csdn.net/zxx43/article/details/43925333