前面绘制三棱锥时,为了将模型移动到视景体范围内,我们修改了模型的顶点坐标。如果要在场景不同的位置渲染多个模型,直接修改模坐标显然是不合理的。为了解决这个问题,就需要引入模型变换的概念。
一、世界坐标系和模型坐标系
世界坐标系是一种特殊的坐标系,它建立了描述其它坐标系所需的参考框架。也就说要描述其它坐标系,必须依赖世界坐标系,而无法用其它坐标系来描述世界坐标系,因此它也叫全局坐标系。
模型坐标系是和特定模型相关联的坐标系。在定义一个三维模型,或者通过其它3D软件建立三维模型时,一般都需要以模型自身的中心建立坐标系的,然后在这个坐标系下构建三维模型。以模型自身建立的坐标系叫做模型坐标系,模型在模型坐标系下的坐标,叫模型坐标(也叫局部坐标)。
我们可以将模型以任意姿态,任意大小,放到世界坐标系中的任何位置,如下图所示:
将模型放入后世界坐标系,为了使用世界坐标系来描述模型的顶点坐标,就需要将模型的顶点坐标从模型坐标系下变换到世界坐标系下,这个变换过程叫做模型变换。
二、平移变换
平移变换,就是使一个物体沿着一个任意长度,任意方向的向量平移。例如让一个物体沿着向量V平移,如下图所示:
对物体进行平移,需要将物体的每一个顶点P(x,y,z),沿着向量V(vx,vy,vz)平移到新的位置Q(x+vx,y+vy,z+vz)。写成矩阵的运算如下:
那么给定平移向量V(vx,vy,vz),则对应的平移矩阵为:
在上面的平移矩阵推导过程中,我们采用了矩阵右乘的方式进行顶点变换。也可以将顶点写成列向量的形式,采用矩阵左乘的方式进行顶点变换,如下所示:
在矩阵和向量的运算过程中,矩阵置于右侧,还是置于左侧,仅仅只是个人习惯而已。对于计算机图形学而言,此类规则无所谓对与错,我们只需要忠于自己的选择,并对最终的结果负责就可以了。但需要注意的是,在这两种不同的规则下,得到的变换矩阵是不相同的,在数学的意义上它们互为转置。
二、缩放变换
缩放变换的目的是为了放大或者缩小物体的尺寸。如果想用同一个模型来渲染出很多大小不同的物体,或者想按照一定的比例,让模型和现实世界中的尺寸一致。这种情况下,就需要对模型进行缩放变换。
要对模型进行缩放,只需要将模型的每一个顶点P(x,y,z)的各个分量分别乘以x轴,y轴,z轴的缩放因子sx,sy,sz。如果每个轴的缩放因子相同,就是均匀缩放,否则就是非均匀缩放,如下图所示:
同样的方法,也将缩放变换写成矩阵运算的形式,如下:
那么给定缩放因子sx,sy,sz,则对应的缩放矩阵为:
三、组合变换
组合变换,顾名思义就是将上面所介绍的平移变换,缩放变换,包括后续的旋转变换,镜像变换等各种变换相互组合之后产生的变换。
(1)公式推导
假设点P经过多次的矩阵变换,得到新点Q,用数学公式的形式表示:
如果按照上面的公式对模型进行变换,那么模型的每一个顶点都需要和n个矩阵依次相乘,这种做法的效率是比较低的。根据线性代数中,矩阵乘法的相关知识,可以将所有矩阵组合成一个矩阵。这样模型的每一个顶点和组合后的矩阵做一次乘法就可以了。如下所示:
在计算组合矩阵M时,所有变换矩阵从左到右依次相乘。也就说对模型的顶点坐标进行变换时,最初相乘的矩阵必须在最左边(这里最初的矩阵为M0)
这里再次说明,如果采用列向量,矩阵左乘的变换规则,那么组合后的M矩阵应该是:
(2)组合变换效果
为了深入理解变换顺序对变换效果的影响,我们可以设平移矩阵为Mt,缩放矩阵为Ms,并且暂时从三维空间退到二维空间。
第一种变换,先将模型右移1个单位,然后再放大1倍,对应的组合矩阵为:M=Mt Ms,变换效果如下:
第二种变换,先将模型放大1倍,然后再右移1个单位,对应的组合矩阵为:M=Ms Mt ,变换效果如下:
从上面的变换过程可以看到执行顺序不同,应用相同的变换矩阵,最终的效果是不一样的。导致结果不同的根本原因:两次缩放的中心点不一样。第二次缩放的中心点是模型的中心,而第一次缩放的中心点是模型的左边界中点,此时模型的顶点已经变换到了世界坐标系下。从数学角度上来解释,就是因为矩阵的乘法不满足交换律,即A B≠B A。
注意:这里的组合矩阵都是在行向量,矩阵右乘的变换规则下得到的。以后如果没有特殊说明,我们都采用这种规则,包括后续的代码实现。
四、算法实现
首先定义一个4x4的矩阵类,如下
template<typename N> class Matrix4x4 { public: typedef N ValueType; public: //默认初始化一个单位矩阵 Matrix4x4(); //构造函数 Matrix4x4(N f11, N f12, N f13, N f14, N f21, N f22, N f23, N f24, N f31, N f32, N f33, N f34, N f41, N f42, N f43, N f44 ); //拷贝构造函数 Matrix4x4(const Matrix4x4<N>& other); public: union { struct { N m11, m12, m13, m14; N m21, m22, m23, m24; N m31, m32, m33, m34; N m41, m42, m43, m44; }; N m[4][4]; N mat[16]; }; };
//生成平移矩阵 template <typename N> Matrix4x4<N> Matrix4x4<N>::MakeTrans(N tx, N ty, N tz) { Matrix4x4<N> trans; trans.m41 = tx; trans.m42 = ty; trans.m43 = tz; return trans; } //生成缩放矩阵 template <typename N> Matrix4x4<N> Matrix4x4<N>::MakeScale(N sx, N sy, N sz) { Matrix4x4<N> trans; trans.m11 = sx; trans.m22 = sy; trans.m33 = sz; return trans; }
源码下载:TranslateScale