opengl 教程(23) shadow mapping (1) - 迈克老狼2012 - 博客园
文章推薦指數: 80 %
在计算机图形学中,有很多种技术可以产生阴影,本篇教程中,我们学习一种最常用的阴影技术—shadow mapping。
对于OpenGL程序中的阴影问题,可以归结 ...
首页
新闻
博问
专区
闪存
班级
我的博客
我的园子
账号设置
简洁模式...
退出登录
注册
登录
迈克老狼2012
opengl教程(23)shadowmapping(1)
原帖地址:http://ogldev.atspace.co.uk/www/tutorial23/tutorial23.html 当光投射到物体上时,会在地面或者墙壁等物体上产生阴影。
在计算机图形学中,有很多种技术可以产生阴影,本篇教程中,我们学习一种最常用的阴影技术—shadowmapping。
对于OpenGL程序中的阴影问题,可以归结为:如何判定一个像素是否在阴影区域。
简单的说,我们可以把像素的位置和光源的位置连接起来(如下图所示),如果连接线通过物体(假设该物体不透明),则该像素可能在阴影内,否则不在阴影内,如下图中A像素和物体交于C点,所以它应该在阴影内,而B像素和物体没有交点,所以不在阴影内。
我们可以把摄像机放在光源的位置,则在阴影区域的点深度测试会失败,而不在阴影区域的点则不会,shadowmapping就是基于这种思路。
上面我们得到结论,深度测试可以帮我们判定一个像素点是否在阴影区域,但前提条件是光源和摄像机位置相同,在大多数情况下,光源和摄像机都不在一个位置,此时该怎么做呢? 解决方法就是我们渲染场景两次:第一次渲染时候,我们把摄像机放在光源的位置,此时我们并不写颜色缓冲,只是输出深度缓冲,通常是输出到一个纹理缓冲中。
第二次渲染时候,摄像机在其原始的位置,在第二次渲染的片元shader中,我们会读入第一次渲染的深度缓冲(通常是一个纹理)。
对于第二次渲染的每个像素,我们会把这个像素的深度值转化到光源作为视点的空间坐标中[就是通过公式计算得到光源到这点个像素点的距离],然后和第一次渲染保存的深度值进行比较,如果这两个深度是一样的,则表示该像素不在阴影之内,我们输入其正常的颜色即可,如果深度缓冲不同,则表示从光源位置看时,其它的像素遮盖住了它,该像素应该在阴影之内,此时,我们可以输入一个阴影的颜色,比如灰色,作为该像素的输出颜色。
注意深度比较判断等于和不等于时候,要考虑到精度的问题,如果两个浮点表示的深度值足够接近,就认为它们相等。
我们的场景由两个物体组成,立方体和地面,光源位于左上角,指向立方体。
第一次渲染中,摄像机在光源的位置,此时B被渲染,它的深度值进入深度缓冲,而A点和C点在同一条线上,此时C的深度更小,所以C的深度被写入深度缓冲。
在第二次渲染中,摄像机在其原始的位置,此时对于B点来说,它的光源视点深度和第一次渲染的深度是一样的(注意,可能不是完全一致,有浮点精度问题存在),所以它不在阴影内,而对于A点,现在的光源视点深度值和第一次渲染的深度不一样,所以A在阴影内。
我们第一次渲染生成的深度图就称作:shadowmap,对于基于shadowmap的阴影算法,我们分两篇教程来学习,本篇教程中,我们只学习如何生成shadowmap,就是通过纹理映射技术,把第一次渲染的深度图输出到一张纹理中去,最后我们会在屏幕上显示生成的shadowmap图,这也是一个很好的调试方法,可以观察到shadowmap是否正确,有时候阴影不正确,就是因为shadowmap图不对。
在源代码中,包括一个简单的四边形,该四边形用来显示shadowmap。
该四边形由2个三角形组成,纹理坐标设置成(0,0),(1,0),(0,1)(1,1),以便使得它覆盖整个纹理空间。
主要源代码:shadow_map_fbo.hclassShadowMapFBO{ public: ShadowMapFBO(); ~ShadowMapFBO(); boolInit(unsignedintWindowWidth,unsignedintWindowHeight); voidBindForWriting(); voidBindForReading(GLenumTextureUnit); private: GLuintm_fbo; GLuintm_shadowMap;}; 在OpengGL中,3D管线最终的输出缓冲称作framebuffer对象或者说FBO,FBO的概念涉及颜色缓冲,深度缓冲以及其它的一些缓冲,比如模板缓冲等等。
当函数glutInitDisplayMode()被调用时候,会创建一个缺省的framebuffer对象,这个framebuffer对象由窗口系统管理,OpenGL不能删除它,除了缺省的framebuffer,每个应用程序还能创建自己的FBO,这些对象由应用程序控制,可以用来实现一些特效。
本篇教程中的ShadowMapFBO类提供一种很方便管理FBO的方法,该类包含2个OpenGL句柄,句柄m_fbo表示真实的FBO(输出到屏幕上的FBO),句柄m_shadowMap表示深度缓冲。
注意:只有缺省的framebuffer才能显示在屏幕上,应用程序创建的FBO只能用来离线渲染,比如把该FBO保存在文件中。
framebuffer本身只是一个句柄,我们需要把它和纹理关联起来,纹理中的数据才是framebuffer中真正的内容。
下面是OpenGL中纹理和FBO关联的一些设置:COLOR_ATTACHMENTi-片元shader的输出图像将放到该纹理中。
后缀i意味着可能有多个纹理和颜色缓冲相关联,在片元shader中,我们可以同时渲染多个颜色缓冲。
DEPTH_ATTACHMENT-纹理和深度缓冲相关联。
STENCIL_ATTACHMENT-纹理和模板缓冲相关联。
DEPTH_STENCIL_ATTACHMENT-纹理和深度模板缓冲相关联。
在shadowmapping阴影实现中,我们只需要深度缓冲,m_shadowMap就是和深度缓冲相关联的纹理句柄。
ShadowMapFBO类也提供了一些在maincpp中调用的函数,比如渲染shadowmap前要调用BindForWriting(),在第二次渲染前要调用BindForReading()。
shadow_map_fbo.cppglGenFramebuffers(1,&m_fbo);我们开始创建FBO,创建方法和纹理类似,首先我们指定一个GLuints数组地址和大小,数组中为fbo句柄。
glGenTextures(1,&m_shadowMap);glBindTexture(GL_TEXTURE_2D,m_shadowMap);glTexImage2D(GL_TEXTURE_2D,0,GL_DEPTH_COMPONENT,WindowWidth,WindowHeight,0,GL_DEPTH_COMPONENT,GL_FLOAT,NULL);glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);接下来,我们创建shadowmap纹理,它是一个标准的2D纹理。
纹理的内部格式是GL_DEPTH_COMPONENT,这和普通的纹理设置不同,通常的纹理一般是GL_RGB,GL_DEPTH_COMPONENT表示纹理数据是一个单浮点格式,该浮点数表示归一化的深度值。
最后一个参数glTexImage2D是空的,这意味着我们不提供任何数据来初始化该缓冲。
GL_CLAMP使得纹理坐标限制在[0,1]内。
glBindFramebuffer(GL_DRAW_FRAMEBUFFER,m_fbo); 上面的代码把纹理对象和FBO关联起来。
GL_DRAW_FRAMEBUFFER表示写framebuffer,而GL_READ_FRAMEBUFFER表示读framebuffer,此时我们可以用glReadPixels得到framebuffer的内容。
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,GL_TEXTURE_2D,m_shadowMap,0); 我们把shadowmap纹理和深度FBO关联起来,最后一个参数是mipmap层,因为我们没有使用mipmap层,所以这儿为0,第四个参数为纹理句柄,如果为0的话,则会取消深度FBO的纹理关联。
glDrawBuffer(GL_NONE);由于第一次渲染并不输出color缓冲,所以我们使用GL_NONE参数。
缺省情况下,颜色缓冲target是GL_COLOR_ATTACHMENT0。
有效的参数包括:GL_NONE以及GL_COLOR_ATTACHMENT0到GL_COLOR_ATTACHMENTm,这儿m是GL_MAX_COLOR_ATTACHMENTS-1,注意,这些参数仅对FBO有效。
GLenumStatus=glCheckFramebufferStatus(GL_FRAMEBUFFER);if(Status!=GL_FRAMEBUFFER_COMPLETE){ printf("FBerror,status:0x%x\n",Status); returnfalse;}FBO配置完成,我们需要验证它是否有效,保证程序不会出错。
voidShadowMapFBO::BindForWriting(){ glBindFramebuffer(GL_DRAW_FRAMEBUFFER,m_fbo);}voidShadowMapFBO::BindForReading(GLenumTextureUnit){ glActiveTexture(TextureUnit); glBindTexture(GL_TEXTURE_2D,m_shadowMap);} 我们会在shadowmap和缺省的framebuffer之间进行切换,第一次输出到shadowmap,第二次输出到framebuffer,上面的两个函数就是执行该功能。
下面是第一趟渲染时候,也就是渲染shadowmap时的vs和fs(ps)的代码:shadowmap.vs#version400 layout(location=0)invec3Position; layout(location=1)invec2TexCoord; layout(location=2)invec3Normal; uniformmat4gWVP; outvec2TexCoordOut; voidmain() { gl_Position=gWVP*vec4(Position,1.0); TexCoordOut=TexCoord; }。
shadowmap.ps#version400invec2TexCoordOut;uniformsampler2DgShadowMap;outvec4FragColor;voidmain(){ floatDepth=texture(gShadowMap,TexCoordOut).x; Depth=1.0-(1.0-Depth)*25.0; FragColor=vec4(Depth);} 在第二次执行渲染中,我们会执行片元shader,输出shadowmap纹理。
由于shadowmap创建时候使用了GL_DEPTH_COMPONENTU做为格式,它是单浮点数,并不是颜色,所以我们用纹理坐标的x分量,来采样纹理值。
透视投影有个问题,它把一个顶点向量z值归一化时候,它会保留更多接近视点的位置,靠近视点位置的的深度比较小,用图像显示出来,可能不太清晰,我们用一个变化显示深度,并把深度扩展为vec4表示的颜色。
tutorial23.cppvirtualvoidRenderSceneCB(){ m_pGameCamera->OnRender(); m_scale+=0.05f; ShadowMapPass(); RenderPass(); glutSwapBuffers();}主函数的渲染很简单,它调用两个渲染函数,先渲染shadowmap,第二趟渲染把shadowmap输出到屏幕上。
virtualvoidShadowMapPass(){ m_shadowMapFBO.BindForWriting(); glClear(GL_DEPTH_BUFFER_BIT); Pipelinep; p.Scale(0.1f,0.1f,0.1f); p.Rotate(0.0f,m_scale,0.0f); p.WorldPos(0.0f,0.0f,5.0f); p.SetCamera(m_spotLight.Position,m_spotLight.Direction,Vector3f(0.0f,1.0f,0.0f)); p.SetPerspectiveProj(20.0f,WINDOW_WIDTH,WINDOW_HEIGHT,1.0f,50.0f); m_pShadowMapTech->SetWVP(p.GetWVPTrans()); m_pMesh->Render(); glBindFramebuffer(GL_FRAMEBUFFER,0);}第一次渲染中,会把摄像机放在光源的位置,用来得到shadowmap。
virtualvoidRenderPass(){ glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); m_pShadowMapTech->SetTextureUnit(0); m_shadowMapFBO.BindForReading(GL_TEXTURE0); Pipelinep; p.Scale(5.0f,5.0f,5.0f); p.WorldPos(0.0f,0.0f,10.0f); p.SetCamera(m_pGameCamera->GetPos(),m_pGameCamera->GetTarget(),m_pGameCamera->GetUp()); p.SetPerspectiveProj(30.0f,WINDOW_WIDTH,WINDOW_HEIGHT,1.0f,50.0f); m_pShadowMapTech->SetWVP(p.GetWVPTrans()); m_pQuad->Render();}第二次渲染中,把输入的纹理shadowmap,渲染在一个quad中,注意:需要装入quad模型quad.obj。
程序执行后效果如下:
postedon
2013-03-1120:15
迈克老狼2012
阅读(2711)
评论(0)
编辑
收藏
举报
刷新评论刷新页面返回顶部
导航
Poweredby:
博客园
Copyright©2022迈克老狼2012
Poweredby.NET6onKubernetes
延伸文章資訊
- 1阴影映射 - LearnOpenGL-CN
阴影映射(Shadow Mapping)背后的思路非常简单:我们以光的位置为视角进行渲染, ... 然而帧缓冲对象不是完全不包含颜色缓冲的,所以我们需要显式告诉OpenGL我们不适用 ...
- 2Tutorial 16 : Shadow mapping
As such, rendering the shadow map is done with an orthographic projection matrix. An orthographic...
- 3opengl 教程(23) shadow mapping (1) - 迈克老狼2012 - 博客园
在计算机图形学中,有很多种技术可以产生阴影,本篇教程中,我们学习一种最常用的阴影技术—shadow mapping。 对于OpenGL程序中的阴影问题,可以归结 ...
- 4Shadow Mapping OpenGL shadow not always drawing, and ...
Is there an easy way to get shadows in OpenGL? - Stack ...
- 5Shadow Mapping - OpenGL ES SDK for Android - GitHub Pages
Shadow Mapping. Yellow cube represents the spot light source. The application displays two cubes ...