设为首页 加入收藏

TOP

OpenGL超级宝典学习笔记——深度纹理和阴影(一)
2015-02-25 22:42:50 来源: 作者: 【 】 浏览:74
Tags:OpenGL 超级 宝典 学习 笔记 深度 纹理 阴影

之前我们介绍过简单的把物体压平到投影平面来制造阴影。但这种阴影方式有其局限性(如投影平面须是平面)。在OpenGL1.4引入了一种新的方法阴影贴图来产生阴影。


阴影贴图背后的原理是简单的。我们先把光源的位置当作照相机的位置,我们从这个位置观察物体,我们就知道哪些物体的表面是被照射到(被光源看到)的,哪些是没有被照射到(被遮挡住)的(在某个方向上离光源最近的表面是被照射的,后面的表面则没有被照射到)。我们开启深度测试,这样我们就可以得到一个有用的深度缓冲区数据(每一个像素在深度缓冲区中的结果),然后我们从深度缓冲区中读取数据作为一个阴影纹理,投影回场景中,然后我们在使用照相机的视角,来渲染物体。


image


?


首先我们把视角移到光源的位置。我们可以通过glu库的辅助函数:


gluLookAt(lightPos[0], lightPos[1], lightPos[2], 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);


把光源的位置设置为观察的位置。


为了以最佳的方式利用空间来产生阴影贴图。从光源的角度看过去的透视可视区域要适应窗口的比例,且透视的最近平面位置是里光源最近的物体的平面,最远的平面位置是离光源最远的物体的平面。这样我们就可以充分的利用场景的信息来填充深度缓冲区,来制造阴影贴图。我们估计恰好包好整个场景的视野。


?


在上面的代码中,场景的中心位于原点,场景中所有的物体,在以原点为中心,半径为sceneBoundingRadius的圆中。这是我们对场景的粗略估计。大致如下图:


image


因为我们只需要得到像素经过深度测试后,深度缓冲区的结果。所以我们可以去掉一切不必要的的细节,不往颜色缓冲区中写数据因为不需要显示。


?


如果我们可以看到深度缓冲区,深度缓冲区的灰度图大概是这样子的。


image


?


我们需要拷贝深度的数据到纹理中作为阴影贴图。在OpenGL1.4之后,glCopyTexImage2D允许我们从深度缓冲区中拷贝数据。纹理数据多了一种深度纹理的类型,其内部格式包括GL_DEPTH_COMPONENT16,GL_DEPTH_COMPONENT24,GL_DEPTH_COMPONENT32,数字代表每个纹理单元包含的位数。一般情况下,我们希望其内部格式与深度缓冲区的精度相匹配。OpenGL允许你指定通用的GL_DEPTH_COMPONENT格式来匹配你的深度缓冲区。在以光源的视角绘制后,我们把深度缓冲区的数据拷贝出来作为深度纹理:


glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, 0, 0, shadowWidth, shadowHeight, 0);


只有在物体移动或者光源移动时,才需要重新产生深度纹理。如果仅仅是照相机移动,我们并不需要重新产生深度纹理,因为以光源的角度来看,深度纹理没有变化。当窗口的大小改变时,我们也需要产生一个更大的深度纹理。


?


在OpenGL2.0之前,在不支持非二次幂的纹理(GL_ARB_texture_non_power_of_two)的扩展的情况下,我们需要调整深度纹理的大小,使其恰好为二次幂。例如在1024x768的分辨率下,最大的二次幂纹理大小是1024x512.


?


?


如果阴影被定义为完全没有光照的,那么我们不需要绘制它。例如只有单一的聚光灯作为光源,那让阴影是全黑色的就足以满足我们的要求了。如果我们不希望阴影是全黑的,而且需要阴影区域中的一些细节,那么我们需要在场景中模拟一些环境光。同时,我们还添加一些散射光,帮助传递形状的信息。


?


PS:此时我们并不需要交换缓冲区(swapbuffers).


如果显示出来是这样子的。


image


有些OpenGL实现支持一种GL_ARB_shadow_ambient扩展,它可以使我们不必进行第一遍的阴影绘图。


?


?


目前我们有了一个很昏暗的场景,要制造阴影,我需要一个明亮的光照区域,来与阴影区形成对比。如何决定这个接受更强光照的区域是阴影贴图的关键。在这个明亮的区域,我们用两倍于阴影的光照强度进行绘制。


?


?


?


?


?


?


?


我们的目的是需要把阴影贴图投影到场景中(从照相机的位置看)。投影这些代表着光源到被光照射到的第一个物体的距离的深度值。把纹理坐标重定向到正确的坐标空间需要一些数学知识。之前我们解释了把顶点从物体空间变换到视觉空间,再变换到裁剪空间,然后变换到规格化的设备坐标,最后变换到窗口空间的过程。在这里有两组不同的变换矩阵,一组用于变换到照相机的视觉空间,一组用于变换到光源的视觉空间。通过这两组矩阵变换得到两个从不同角度观察的场景。


image


上面的箭头表示了我们需要应用到视觉线性纹理坐标的变换过程。纹理的投影通常是从视觉线性坐标的产生开始的。这个过程是自动产生纹理坐标的。不同于物体线性纹理坐标的生成,视觉线性坐标的生成并不固定到任何几何图形之上。反之,它好像是一台投影仪把纹理投影到场景中,想象一下你在投影仪前走动的时候,屏幕上会出现不规则的身体形状。


投影纹理映射:


image


现在我们获得在照相机的视觉空间下顶点对应的纹理坐标。那我们需要进行一些变换来得到顶点的纹理坐标。当前我们在照相机机的视觉空间,首先我们通过视图矩阵的逆变换回到世界坐标系,然后再变换到光源的视觉空间,然后到光源的裁剪空间。这一系列的变换可以通过下面的矩阵相乘得到:


M = Plight * MVlight * MVcamara-1


裁剪空间规格化后的x,y,z的坐标范围在[-1, 1]之间,然而我们的纹理坐标范围为[0,1],所以我们还需要把[-1,1]变换到[0,1]的范围,这个变换很简单,我们只需要把[-1,1]缩放一半(S),然后偏移0.5就可以得到[0,1]了(B)。


M = B * S * Plight * MVlight * MVcamara-1


所以我们可以得到顶点经过变换后的纹理坐标。T1 = M * T;


图解过程如下:


imageimage


?


imageimage


image


PS: 当前模型视图矩阵的逆矩阵的乘法操作已经包含在了视觉平面方程式中。


即在OpenGL的纹理自动生成模式GL_EYE_LINEAR中,每一个觉平面方程式(eye plane equation)会自动乘以MVcamara-1?


实现上面的步骤一种方式是手动的通过glTranslatef, glScalef, glMultMatrixf 来一步步的实现。另一个方式是在纹理自动生成中,我们可以通过设置一个纹理矩阵来实现上面的变换,把这个纹理矩阵作为视觉线性坐标的视觉平面方程GL_EYE_PLANE即可。


image


M = B * S * Plight * MVlight 大致代码如下:


?


应用到视觉平面中:


?


?


现在我们如何知道从照相机视角看到的点是否在阴影中呢。从上面的那些步骤来看,我们已知顶点的深度纹理坐标,那么这个深度纹理坐标对应的在深度纹理的值我们可以知道即texture[s/q, t/q],这个深度纹理记录了在光的角度看过去离光源最近的点的深度值,我们是设置的深度比较函数是glDepthFunc(GL_LEQUAL);。,同时我们知道(r/q)是顶点在真实光源中深度值,已经通过缩放和偏移变换到了[0,1]的范围。然后我们比较texture[s/q, t/q]和(r/q)如果texture[s/q, t/q] < r/q那么就表示这个点在阴影中。如下图:


image


深度纹理只包含了一个值代表深度。但在纹理环境的纹理查询中,我们需要返回四个成分的值(RGBA)。OpenGL提供了几种方式把这单个深度值扩展到其他

首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇OpenGL超级宝典学习笔记——遮挡.. 下一篇OpenGL超级宝典学习笔记——新的..

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: