透视纹理映射

作者:沈晨


(本文的版权由其作者沈晨保留,如果需要转载或作其他用途,请与本文作者联系)

本文只涉及平面的纹理映射,文章中提到的多边形都是平面多边形.

  透视纹理映射可以根据多边形距离观察者的远近而进行相应的调整从而产生比仿射纹理映射更真实的效果。 我们可以把纹理定义成为一个二维数组,其中每一个元素代表一个颜色值, 把一幅位图定义为纹理是最直观最简便的方法。我们也可用纹理函数来定义纹理, 这样可以产生动态效果,但这不是本文的讨论范围。 我们把纹理所在的二维空间称为纹理空间或u, v空间, 其中可以把u看成是纹理空间的x坐标,v是y坐标。 一般我们定义u, v的范围在0到1之间。虽然u, v可以为任何数, 但一般都将其定义在0到1之间,这可以简化对一些问题的处理。 纹理映射就是把纹理空间的坐标系映射到多边型坐标系。 这里我来解释一下多边形坐标系。两条不共线的向量可以定义一个坐标系, 如果这两个向量不垂直则将使该空间内的图形产生倾斜的效果而当这两个向量长度不相等时, 则会拉伸或压缩该空间内的图形.我们在一个3维空间的多边形上取两个不共线的向量, 则这两个向量可以定义一个多边形坐标系,然后就可以写出这个3维空间的平面的的方程了 (2维或n维空间的平面方程也可同理求得).现在我举例说明. 我们在一个平面上取两点M1和M2可以得到一个向量M=M1-M2, 在取两点N1和N2可以得到另一个向量N=N1-N2, 最后再在该平面上取一个点P1作为该平面空间的原点. 将该点与视见坐标系的原点相减 (如果你的3D引擎没通过视见坐标系变换则与世界坐标系原点相减) 得到一个向量W=P1-(0,0,0).则该平面的方程为Ma+Nb+W,其中a,b为任何实数. 如果用矩阵表示的话如下其中的矩阵我称其为矩阵T)。
(x,y,z) =(a,b,1)× |Mx,My,Mz|
|Nx,Ny,Nz|
|Wx,Wy,Wz|
  如果我们用纹理空间的u,v来代替a,b则可以得到纹理空间到多边形的映射关系, 其中(Mx,My,Mz)对应纹理空间的(1,0)点, (Nx,Ny,Nz)对应纹理空间的(0,1)点,(Wx,Wy,Wz)对应纹理空间的(0,0)点。

(x,y,z)=(u,v,1)×T

  如此便完成了纹理到多边形的映射.看起来很简单,但实际上我们的工作还没完, 因为在大多数情况下我们是在用扫描线法来填充多边形的同时完成纹理映射, 所以我们想知道在多边形经过透视投影后其在屏幕坐标系上与纹理映空间坐标系的映射关系。 其关系可通过如下几步求得.首先视见坐标系或世界坐标系的y 轴方向与屏幕坐标系的y轴方向相反,所以矩阵T应变换为如下形式。
第一步: | Mx, My, Mz|
|-Nx,-Ny,-Nz|
| Wx, Wy, Wz|
第二步: (k*x/z,k*y/z,k)=(k*u/z,k*v/z,k/z)×T (等式1)
  这里将x,y,z乘以k/z是做投影变换,如果需要的话我会写一篇有关的文章。
第三步: (k*x/z,k*y/z,k)×T'=(k*u/z,k*v/z,k/z)×T×T' (等式2)
  其中T'为T的逆矩阵。则第三步的等式可转换为如下形式。
(k*x/z,k*y/z,k)×T'=(k*u/z,k*v/z,k/z) (等式3)
  因为T'=T*/|T|所以等式3可变换为:
(k*x/z,k*y/z,k)×T*=|T|*(k*u/z,k*v/z,k/z) (等式4)
  其中T*为T的伴随矩阵,|T|为T的行列式。上列等式又可转换成如下形式。
(k*x/z,k*y/z,k)×T*=(k*|T|*u/z,k*|T|*v/z,k*|T|/z)
  由此可以看出,求透视纹理映射的关键就是T*矩阵。 在一些Internet上的资料将该矩阵的每一行称为magic value或magic vextor. 现在我给出T*的形式(如果你不知道是如何得出的可参考任何一本线性代数的书)。


        |WyNz-NyWz, WyMz-MyWz, MyNz-NyMz|
        |WzNx-NzWx, MxWz-WxMz, MxNz-NxMz|
        |WxNy-NxWy, WxMy-MxWy, MyNx-NyMx|
  我们用其他符号来表示该矩阵的各个项,使该矩阵看其来更整洁些。

        |Hx,Hy,Hz|
        |Vx,Vy,Vz|
        |Ox,Oy,Oz|
其中:
Hx=WyNz-NyWz, Hy=WyMz-MyWz, Hz=MyNz-NyMz
Vx=WzNx-NzWx, Vy=MxWz-WxMz, Vz=MxNz-NxMz
Ox=WxNy-NxWy, Oy=WxMy-MxWy, Oz=MyNx-NyMx
  如此等式4可变换成下列形式。

              k*x/z*Hx +k*y/z*Vx+k*Ox=k*u*|T|/z
              k*x/z*Hy+k*y/z*Vy+k*Oy=k*v*|T|/z 
              k*x/z*Hz+k*y/z*Vz+k*Oz=k*|T|/z
  上诉等式的两边同时除以k,得到等式:

              x/z*Hx +y/z*Vx+Ox=u*|T|/z
              x/z*Hy+y/z*Vy+Oy=v*|T|/z 
              x/z*Hz+y/z*Vz+Oz=|T|/z
  如此我们便得到了多边形的屏幕坐标与纹理空间的映射关系。 将纹理空间规一化(即除以|T|/z)后得到:

             u= (x/z*Hx +y/z*Vx+Ox)/ (x/z*Hz+y/z*Vz+Oz)
             v= (x/z*Hy+y/z*Vy+Oy)/( x/z*Hz+y/z*Vz+Oz)
  这便是我们最后要求得的等式了。如果你的作为纹理的位图是128×128的大小,指向该位图的指针是p,则对应的该纹理上的点为p[128×u][128×v]。这里还有一种特殊情况需要说明一下,就是又时多边形上的一点对应的纹理空间坐标会大于1,这时我们希望能将这一点对应到纹理空间内来。具体做法就是去掉整数部分,保留小数部分。举个例子。如果多边形上一点对应的纹理空间坐标是(1.1,2.137)则经过处理后其坐标为(0.1,0.137)。这种处理称为u或v wrap。到此平面的纹理映射解释完了,至于曲面的纹理映射则不在本文的介绍范围内了,下面我将介绍一下纹理的反走样处理。由于多边形坐标不一点垂直,也不一定在长度上相等,所以纹理空间的一点可能对应多边形上多个点,或多边形上一点对应纹理空间内的多个点。这会使纹理贴到多边形上后产生失真。解决的方法是纹理空间上一点的颜色值用这一点周围的点的颜色的平均值来表示。

                                B B B
                                B A B
                                B B B
  其中A点的颜色值用周围B点的颜色值的平均值来表示。这样就可以减少失真。