如何计算阴影贴图中的投影纹理坐标
-----作者: 熊 斌 2007.12.26 xiongbincsu@yahoo.com.cn
如需转载,请注明作者
在蓝宝书的第18章中给出了一个阴影贴图的例子,对于其中阴影透视的部分曾经让我百思不得其解,主要疑惑在于
1.一幅平面的纹理怎么样投影到一个三维的场景中去?
2.为什么要对纹理矩阵进行操作?
3.例子中的纹理坐标计算公式: T=M*T
=B*S*Plight*MVlight*MVcamera-1*T 的意义
4.以光源为视点所得到的阴影纹理是如何与以相机为视点的场景中的对象对应起来的.
在查过一些资料及自己思考之后对上面的问题有了自己的答案:
1. 按蓝宝书中的说法: 纹理投影(Project Texture) 是将纹理象胶片一样蒙在投影仪的镜头上,然后将这些纹理投影到一个三维的场景中去. 如何才能使这个三维的场景与这个纹理中的相应点匹配呢? 按上面所说的过程看起来比较复杂,我们还是不知道怎么做.但我们不妨使用逆向思维: 将三维场景反投影到投影仪的镜头处的纹理图片的位置,得到一副位置相同,大小相等的平面图像,拿这个图形中的各点与 纹理图片进行一一对应,那纹理坐标与场景中顶点的对应关系不就很简单了吗? 实际上按我的理解,这个例子中的纹理坐标计算就是按这样一个过程进行的.
2.为什么要对纹理矩阵进行操作呢? 在通常情况下我们不需要对纹理矩阵操作是因为我们通常是使用直接模式指定纹理坐标,而且纹理在映射时只关心四个角点与模型的角点匹配,而不必关心其中心的点与模型中的其他点的匹配情况. 并且模型所贴的场景与当前场景是同一视点. 而我们现在要做的事情是: 将一副光源视点下的场景所形成的纹理贴到相机的视点所成的模型中去. 而且纹理上的每个点与场景中的对象有严格的匹配关系.
为了使得纹理与场景匹配显然我们需要将纹理转换到与当前模型的同一视点下来. 而我们的纹理坐标是根据当前场景中的顶点自动生成的,那么我们就需要根据这个坐标通过对纹理矩阵的操作,使得它刚好与纹理图片中的相应顶点项匹配.
3.4.的答案:
根据1.2所叙述,纹理矩阵操作的目标是: 使得根据在当前(相机)视点中根据顶点坐标自动生成的纹理坐标经过纹理矩阵转换后能够对应到光源视点中的纹理上.(看不懂这句话的话先看红色字体所构成的关键句).. 按我(1)中的说法就是把变换到眼空间的模型,还原到初始场景空间,然后再在光源视点下压扁成二维纹理,与已有阴影贴图对照. 先看第一步:如何把当前眼空间中的纹理坐标还原到最初的模型空间中去.
在书中,当前视点中的纹理坐标是用视觉线性纹理坐标生成模式生成的;
视觉线性纹理坐标的生成模式:
在这个模式下纹理坐标是根据下面的生成函数由顶点的眼坐标(xe,ye,ze,we)作为自变量来生成的:
纹理坐标=p0'*xe+p1'*ye+p2'*ze+p3'*we; (1)
其中:(p0',p1',p2',p3')=(p0,p1,p2,p3)*M-1 (2)
纹理坐标 = (p0,p1,p2,p3)* M-1 (xe,ye,ze,we)' (3)
显然,为了生成纹理坐标,我们需要指定(p0,p1,p2,p3)的值. 这四个数作为平面方程的系数构成了一个平面,
p0*X+p1*Y+p2*Z + p3*D=0 ; M-1是场景空间到眼空间的模型视图矩阵的逆.
在这个例子中(p0,p1,p2,p3)是通过如下方式指定的:
GLfloat sPlan[4] = {1.0,0.0,0.0,0.0} 等四行,
及glTexGenfv(GL_S,GL_EYE_PLANE,sPlan) 等四行一起完成.
根据纹理坐标的计算式(3):
因为sPlan,tPlan,rPlan,qPlan,正好构成了单位矩阵,而M-1与眼坐标的乘积正好将眼坐标转换为场景空间坐标(世界坐标), 所以这样的过程实际上使得顶点的坐标从眼空间返回了最初的场景空间,接下来我们需要做的是(1)中所说的,再将这个三维的场景在光源视点下压扁成为二维的图像,然后我们就可以将其与所得的阴影纹理对比,得到各个点的纹理坐标了.
这个过程就是书中所描述的,先把镜头的设置设得与当初生成阴影贴图的时候一样,然后把镜头位置,朝向摆得跟当初一样,再做一些小小的调整. 这样我们再模拟一下当初进行三维场景投影的过程,就可以得到一副与原来重叠的图形了(当然这些操作是在纹理矩阵上进行的并没有进行渲染), 因为相机位置,模型摆放,都与原来一样,那样我们不就得到一副与原来重合的图像了? 进而两幅图像一样,那么后来投影的这幅图像上的坐标显然与前一副是一一对应的,那么我们所需的纹理坐标不就出来了?
好了,我们再来争对书上的算式进行解释:
T =B*S*Plight*MVlight*MVcamera-1*T (4)
上式中,纹理坐标的生成已经完成了红色的部分了, 我们继续往前, 这就需要你理解OpenGL的渲染过程了,或者你看看书上的那幅从场景空间到裁减空间再到屏幕的渲染过程的图也可以,总之,OpenGL要渲染一副图到屏幕上要按如下的顺序进行:
(1)初始顶点数据先要进行模型视图变换,使得场景空间的坐标(大地坐标)转成眼坐标,
(2)然后再做投影视图变换,裁减变换
(3)设备规范化
(4)视口转换
也就是说4式的左侧部分从右到左的转换就是OpenGL的正常渲染顺序. 既然我们现在通过红色部分得到了初始场景空间的坐标,那么只要调整模型视图矩阵,投影矩阵等,使之与在光源场景下绘制场景的方式一样,那么不就可以达到目的了吗??
确实,例子中也是这样做的! 没看见吗, Plight , MVlight 就是当初在以光源视点绘制阴影贴图时保存下来的.
这样我们得到了与阴影贴图一样的一幅虚拟的场景,这个场景中的各个顶点坐标与阴影贴图中的坐标是一一对应的. 也就是我们所需的阴影贴图的纹理坐标.
注: 以上为本人对纹理坐标计算的个人理解,如有错误欢迎指正