Web 技术研究所

我一直坚信着,Web 将会成为未来应用程序的主流

投射矩阵(Projection Martrix)的推导

  3D编程最基础的问题就是,如何把一个三维的物体呈现到只有二维屏幕上来?这个问题看似复杂,其实只需要简单数学的知识就可以解决了。但是我们必须标准化这个过程,于是需要使用矩阵来做这个变换。所以我们需要先推导出投射矩阵(Projection Martrix)。
  投射矩阵的作用就是把一个视椎体变换到一个规范化设备坐标上。那么,什么是视椎体呢?视椎体就是我们能看到的区域,从眼睛或者摄像机的焦距一直到能看到的最远距离其中的空间。由于越远的东西看上去越小,可以看到的范围也就越大。这样,从焦距到可以看到的最远距离的可见区域增大,使得整个可见区域变成一个椎体,这就是视椎体。不过,我们的眼睛可没有什么最远距离的限制,无论多远的东西只要它足够亮我们都能看到。但是这放入计算机中计算显然不现实,所以视椎体需要一个最远可见距离。原点到焦距的距离我们称为近平面距离,用字母n表示;原点到最远可见距离我们称为远平面距离,用字母f表示。这两个平面的距离就是视椎体的“高(z轴)”用字母d表示。现在只知道d显然无法确定一个椎体,我还需要知道这个椎体的顶点角度。这个角度就是我们常说的视角,这个角度越宽视野就越开阔。但是电脑屏幕是矩形的,这个椎体不是圆锥,而是一个四棱锥,因此存在垂直视角和水平视角的问题。这个问题可以放到最后再来处理,为了方面计算,我们会把这个视椎体当作一个正四棱椎来计算,因此它们的视角相等,用字母a表示。

  那么,规范化设备坐标又是什么呢?它是一个立方体,在WebGL中,它的坐标是从(-1,-1,-1)到(1,1,1)。在这个区域中的东西就被正交投影到屏幕上。而我们要做的工作就是构造一个投射矩阵把视椎体的东西变换到规范化设备坐标上。这个看似很容易吧?只要把视椎体使劲捏成立方体就可以了。由于我们上面规定了它是个正四棱锥,所以x方向和y方向的处理是完全一样的。
  首先,我们应该把这个视椎的x和y坐标都捏到规范设备区域坐标的范围内。完成这个步骤我们通常就可以得到一个细长的长方体。

  我们知道,越远的地方这个视椎体发散的就越厉害,视角越大这个视椎体发散的就越快。也就是说,我们需要压缩的力度是和z坐标与视角a有关的。这个计算用简单的三角函数可以搞定我就不说了。
  由于计算时是用a/2的,所以后面的a表示半视角大小,我就不多写过除以2了。
某点的坐标为:(x,y,z)
压缩后坐标为:(x₀,y₀,z)
x₀=x*cot(a)/z
y₀=y*cot(a)/z
xy压缩式:(x*cot(a)/z,y*cot(a)/z,z)
  xy方向的压缩这么简单就完了。接着是z方向的,这个和xy方向的做法就不同了。因为xy坐标的计算都是与z有关的,xy变换时最后都需要除以z的。为了写成矩阵,我们需要把这个z从他们上面搞掉,也就是所有项同时乘以z,所以需要引入齐次坐标w。这样,上面的“xy压缩式”就可以变成“(x*cot(a),y*cot(a),z²,z)”。可以看出,这个齐次坐标也会影响到原来的z变换。要是还按照原来的思路使用这个z,我们就无法把它写成矩阵,因为z在这里已经变成非线性的了。于是,我们需要对这个z²重新处理。这个处理是暴力的,是把二次函数的曲线适当拉直,使其变为一次的。因此也会损失精确度,毕竟二次函数无缘无故被降为一次的,它总是会多少有一些委屈。不过不用担心,因为z坐标的精确度本身不重要,转换成规范化设备坐标以后,显卡对它的操作是正交投影。正交投影中z坐标的功能只是投影的先后顺序而已,也就是颜色的遮盖问题而已。这只关系到函数的单调性,和精确度没关系。实际上z坐标从n变化到f是单调递增,所以这就打消了所有因为精度损失造成的疑虑。
  那么,z坐标应该怎么变呢?其实这个方法很多的,不过比较简单也比较标准的是使用二元一次方程组的做法。由于z在近平面上时变换后得到-1,z在远平面上时变换后得到1。我们就可以把这两个已知项做成二元一次方程组来求出一次函数的A,B两个参数。 z₀=(A*z+B)/z
#A*z+B是一次函数,由于写成矩阵需要一次函数的格式
#外面的除以z是这整个一次函数还受齐次项的影响
带入已知项,得出二元一次方程组:
1=(A*f+B)/f
-1=(A*n+B)/n
解得:
A=(f+n)/(f-n)
B=-2*f*n/(f-n)
所以:
z₀=(z*(f+n)/(f-n)-2*f*n/(f-n))/z
处理齐次项:
z₀=z*(f+n)/(f-n)-2*f*n/(f-n)
  现在,我们得到了变换后的坐标(x₀,y₀,z₀,w₀)我们就可以把它写成矩阵。
{ cot(a)000 }
0cot(a)00
00(f+n)/(f-n)1
00-2*f*n/(f-n)0

  但是还没完,有两个地方还需要处理。首先,我们一开始就把视锥体当作正四棱椎。但是屏幕或者说绘图区域,未必是正方形的。因此,我们需要稍微调整一下xy。这就很简单,只要取得绘图区域的长宽比“r=width/height”,再在x变换成x₀的时候多除以一个r就行。还有一个坐标系的问题,由于视椎体的坐标系(观察坐标系)是右手坐标系,而规范化设备坐标是左手坐标系。这个差异需要在计算时把z变为-z。所以矩阵中w列的1需要变为-1。另外,由于上面的二元一次方程组在计算时,无论z是正是负,B的符号都不会变,只有A的符号会变。所以,z变为-z是我们也要把A变为-A。调整这些参数后可以得到最终的矩阵。
{ cot(a)/r000 }
0cot(a)00
00-(f+n)/(f-n)-1
00-2*f*n/(f-n)0

网名:
3.80.32.*
电子邮箱:
仅用于接收通知
提交 悄悄的告诉你,Ctrl+Enter 可以提交哦
神奇海螺
[查看全部主题]
各类Web技术问题讨论区
发起新主题
本模块采用即时聊天邮件通知的模式
让钛合金F5成为历史吧!
次碳酸钴的技术博客,文章原创,转载请保留原文链接 ^_^