第 5 章 二维变换和二维观察 .. 5.1 图形变换基本知识 为方便读者更好地理解图形变换,本节对图形变换的基本知识做简单介绍。 5.1.1 矢量和矩阵 1.矢量的相关概念 (1)矢量的定义。 U = ux uy uz é . êêêê ù . úúúú V = vx vy vz é . êêêê ù . úúúú (2)矢量的和。 U +V = ux +vx uy +vy uz +vz é . êêêê ù . úúúú (3)矢量的数乘。 k·U = kux kuy kuz é . êêêê ù . úúúú (4)矢量的点积。 U·V =uxvx +uyvy +uzvz (5)矢量的长度。 U = U·U = u2x +u2y +u2z 图5-1 矢量的夹角 (6)矢量的性质。 U·V =V·U U·V =0.U ⊥V U·U =0.U =0 (7)矢量的夹角。 矢量的夹角如图5-1所示。 cosθ = U·V U · V 84 计算机图形学原理与实现 或 U·V = U · V cosθ (8)矢量的叉积。 U ×V = i j k ux uy uz vx vy vz =(uyvz -uzvy )i+ (uzvx -uxvz)j+ (uxvy -uyvx )k 或 U ×V = U · V sinθ·I 其中,I 表示U×V 的单位向量。 2.矩阵的相关概念 (1)矩阵的定义。 矩阵是由m ×n 个数按照一定位置排列的一个整体,简称m ×n 矩阵。 a11 a12 … a1n a21 a22 … a2n . . . . am1 am2 … amn é . êêêêê ù . úúúúú (2)矩阵的加法。 设A、B 为两个具有相同行和列元素的矩阵,则: A +B = a11 +b11 a12 +b12 … a1n +b1n a21 +b21 a22 +b22 … a2n +b2n . . . . am1 +bm1 am2 +bm2 … amn +bmn é . êêêêê ù . úúúúú (3)矩阵的数乘。 k·A =[kaij] i=1,…,m ,j=1,…,n (4)矩阵的乘法。 设A 为2×3矩阵,B 为3×2矩阵,则 C =A·B = a11 a12 a13 a21 a22 a23 é . êê ù . úú · b11 b12 b21 b22 b31 b32 é . êêêê ù . úúúú = a11b11 +a12b21 +a13b31 a11b12 +a12b22 +a13b32 a21b11 +a22b21 +a23b31 a21b12 +a22b22 +a23b32 é . êê ù . úú (5)单位矩阵。 在一个矩阵中,其主对角线上各元素aij =1,而其余元素都为0,这种矩阵称为单位矩 阵。n 阶单位矩阵通常记作In 。 (6)逆矩阵。 若矩阵A 存在A·A-1=A-1·A=I,则称A-1为A 的逆矩阵。 (7)矩阵的转置。 把矩阵A=(aij)m ×n 的行和列互换而得到的n×m 矩阵称为A 的转置矩阵,记为AT。 第5章 二维变换和二维观察 85 (AT)T =A,(A +B)T =AT +BT,(aA)T =aAT,(A·B)T =BT·AT 若A 为n 阶矩阵,且A=AT,则A 是对称矩阵。 (8)矩阵运算的基本性质。 ① 矩阵的交换律与结合律。 A +B =B +A A + (B +C)=(A +B)+C ② 矩阵数乘的分配律及结合律。 a(A +B)=aA +aB a(A·B)=(aA)·B =A·(aB) (a +b)A =aA +bA a(bA)=(ab)A ③ 矩阵乘法的结合律及分配律。 A(B·C)=(A·B)C (A +B)·C =A·C +B·C C·(A +B)=C·A +C·B 注意:矩阵的乘法不适合交换律。 5.1.2 齐次坐标 所谓齐次坐标表示法,就是由n+1维向量表示一个n 维向量。例如将n 维向量(p1, p2,…,pn)表示为(hp1,hp2,…,hpn ,h),其中h 称为哑坐标。普通坐标和齐次坐标有如下 关系。 (1)h 可以取不同的值,所以同一点的齐次坐标不是唯一的。例如普通坐标系下的点 (2,3)变换为齐次坐标可以是(1,1.5,0.5),(4,6,2),(6,9,3)等。 (2)普通坐标与齐次坐标是“一对多”的关系。由普通坐标×h 可转换为齐次坐标,由 齐次坐标÷h 可以得到普通坐标。 (3)当h=1时,产生的齐次坐标称为“规格化坐标”,也称为“规范化齐次坐标”,因此前 n 个坐标就是普通坐标系下的n 维坐标。 (x,y)点对应的齐次坐标为(xh ,yh ,h),其中xh =hx,yh =hy,h≠0。(x,y)点对应的 齐次坐标为三维空间的一条直线,即: xh =hx yh =hy zh =h ì . í .. .. (5-1) 将h=1时的坐标称为规范化齐次坐标,齐次坐标有如下作用。 (1)将各种变换用阶数统一的矩阵表示。提供了用矩阵运算把二维、三维甚至高维空 间上的一个点从一个坐标系变换到另一个坐标系的有效方法。 (2)便于表示无穷远点。例如:(x×h,y×h,h),令h=0。 (3)采用齐次坐标形式表示的变换矩阵可以将直线变换成直线段,将平面变换成平面, 将多边形变换成多边形,将多面体变换成多面体。 (4)齐次坐标变换具有统一的表示形式,便于变换合成以及硬件实现。 86 计算机图形学原理与实现 .. 5.2 基本二维变换 图形变换是将图形的几何信息经过几何变换后产生新的图形的过程,是计算机图形学 的基础内容之一。图形变换可以把用户坐标系与设备坐标系联系起来,可以由简单图形生 成复杂图形,可以用二维图形表示三维形体等。图形变换有两种形式,一种是图形不变,坐 标系改变,另一种是图形改变而坐标系不变,本节是针对坐标系的改变而言的,这种变换又 称为图形的几何变换。基本二维图形的几何变换(简称基本二维变换)是改变二维图形坐标 描述的变换,例如改变二维图形的方向、尺寸和形状等。具体来说,图形变换包括平移、旋 转、缩放等,下面分别进行介绍。 5.2.1 平移变换 所谓平移变换是图形对象沿直线运动产生的变换。如图5-2所示,点P (x,y)平移到 P'(x',y'),其平移向量为(tx ,ty ),平移公式可以表示为: x'=x +tx y'=y +ty (5-2) 图5-3给出了平移变换的例子,如果用矩阵表示平移变换,过程如下所示。 P = x y é . êê ù . úú , P'= x' y' é . êê ù . úú , T = tx ty' é . êê ù . úú , P'=P +T (5-3) 图5-2 二维平移变换 图5-3 二维平移变换实例 5.2.2 旋转变换 所谓旋转变换是图形对象沿圆弧路径运动产生的变换,如图5-4所示。旋转变换有3 个输入参数:①基准点,即绕哪一点旋转,可以是坐标原点或任意一点;②旋转角度θ; ③旋转方向,本书约定逆时针方向为正方向。 接下来推导旋转变换的变换矩阵。为方便起见,此处讨论绕原点的旋转,如图5-5所 示。已知二维平面上的P(x,y)点绕坐标原点逆时针旋转θ 角到P'(x',y'),那么P'的坐 标是多少呢? 假设P(x,y)点与x 轴夹角为.,P (x,y)点到坐标原点的距离为r,那么 P'(x',y')的坐标推导公式如下: x'=rcos(θ+.)=r(cos.cosθ-sin.sinθ)=rcos.cosθ-rsin.sinθ =xcosθ-ysinθ y'=rsin(θ+.)=r(cos.sinθ+sin.cosθ)=rcos.sinθ+rsin.cosθ =xsinθ+ycosθ (5-4) 第5章 二维变换和二维观察 87 图5-4 绕原点和任意点的二维旋转变换 图5-5 二维旋转变换 绕二维空间任意点的旋转变换坐标应该如何表示呢? 可以先将空间任意点平移到坐标 原点,再进行旋转变换,最后做反向平移即可。和平移变换一样,旋转变换也是一种不产生 变形而移动对象的刚体变换。用矩阵表示绕原点的旋转变换如式(5-5)所示。 P = x y é . êê ù . úú , P'= x' y' é . êê ù . úú , R = cosθ -sinθ sinθ cosθ é . êê ù . úú , P'=R·P (5-5) 5.2.3 缩放变换 缩放变换又称为变比变换,是改变图形对象大小的变换。缩放变换的输入参数包括: ①变比因子(sx ,sy );②基准点;③变比方向。基准点为坐标原点的缩放变换可表示如下: x'=xsx y'=ysy (5-6) 基准点为固定参考点(xf ,yf )的缩放变换公式为: x'=xf + (x -xf )sx y'=yf + (y -yf )sy (5-7) 如果用矩阵形式表示基准点为坐标原点的缩放变换,如式(5-8)所示。 P = x y é . êê ù . úú , P'= x' y' é . êê ù . úú , S = sx 0 0 sy é . êê ù . úú , P'=S·P (5-8) 图5-6是以一个基准点为坐标原点的缩放变换的例子,在x 轴方向上放大2倍,在y 轴方向上不变,变比方向分别沿x 轴和y 轴的正方向。 图5-6 以基准点为坐标原点的缩放变换示例 对于二维缩放变换,有如下结论: (1)如果sx 或sy 大于1,则表示图形在x 轴方向或y 轴方向放大; (2)如果sx 或sy 小于1,则表示图形在x 轴方向或y 轴方向缩小; (3)如果sx = sy ,则表示均匀缩放; (4)如果sx ≠ sy ,则表示差值缩放; 88 计算机图形学原理与实现 (5)如果sx 或sy 等于1,则表示图形在x 轴方向或y 轴方向不变; (6)如果sx 或sy 小于零,则表示图形在x 轴方向或y 轴方向做镜面变换。 5.2.4 基本二维变换的矩阵表示 在图形系统中,矩阵是实现变换的标准方法。在上述讨论中,平移变换、旋转变换和缩 放变换的矩阵表示形式分别如下。 (1)平移变换:P'=P+T。 (2)旋转变换:P'=R·P。 (3)缩放变换:P'=S·P。 对于平移变换、旋转变换和缩放变换,可以表示为普通矩阵形式: P'=M1·P +M2 (5-9) 能否进一步将公式(5-9)表示为P'=M ·P 的形式呢? 通过引入规范化齐次坐标可以 实现上述矩阵的表示形式,即二维空间的点用齐次坐标表示: P(x,y)→ (xh ,yh ,h)→ (x,y,1)T 假设点P(x,y)为xOy 平面上二维图形变换前的一点坐标,变换后该点的坐标为 P'(x',y')。引入规范化齐次坐标后,点P 可以用一个矩阵来表示,这个矩阵既可以是行向 量矩阵也可以是列向量矩阵,即 [x y 1] 或者 x y1 é . êêêê ù . úúúú 引入齐次坐标后,平移变换、旋转变换和缩放变换的矩阵表示形式如下所示。 (1)平移变换。 P'=T(tx ,ty )·P , 即 x' y' 1 é . êêêê ù . úúúú = 1 0 tx 0 1 ty 0 0 1 é . êêêê ù . úúúú · x y1 é . êêêê ù . úúúú (5-10) (2)旋转变换。 P'=R(θ)·P, 即 x' y' 1 é . êêêê ù . úúúú = cosθ -sinθ 0 sinθ cosθ 0 0 0 1 é . êêêê ù . úúúú · x y1 é . êêêê ù . úúúú (5-11) (3)缩放变换(变比变换)。 P'=S(sx ,sy )·P, 即 x' y' 1 é . êêêê ù . úúúú = sx 0 0 0 sy 0 0 0 1 é . êêêê ù . úúúú · x y1 é . êêêê ù . úúúú (5-12) 这3种变换都是针对坐标原点和x 或y 轴方向的。需要说明的是,上面的矩阵表示采 用的是列向量矩阵,也可以采用行向量矩阵,在上述矩阵的等式两边做转置即可。本书后续 将不再强调图形变换采用何种矩阵,请读者自行判断。 引入齐次坐标后,二维图形变换都可以统一表示成如下形式: P'最终坐标=M 变换矩阵·P原坐标(5-13) 其中,M 变换矩阵可以表示成3×3的方阵,这不但可以提高矩阵运算的速度,而且便于硬件实 第5章 二维变换和二维观察 89 现。下面编程实现基本二维平移变换。 【源程序5-1】 绘制二维直角坐标系。 /*画二维直角坐标系(Canvas 结构体), 坐标原点在(300,300) */ void drawCCS(Canvas _canvas) { //创建颜色变量_color(白色) RGBA _color; _color.m_r = 255; _color.m_g = 255; _color.m_b = 255; _color.m_a = 0; //定义坐标点数组pt Point2D pt[]= { {200, 300}, {500, 300}, {495, 295}, {495, 305}, {300, 500}, {300, 20}, {305, 25}, {295, 25} }; //绘制坐标系的线段,使用DDA 算法 drawLineDDA(pt[0], pt[1], _color, _canvas); drawLineDDA(pt[2], pt[1], _color, _canvas); drawLineDDA(pt[1], pt[3], _color, _canvas); drawLineDDA(pt[4], pt[5], _color, _canvas); drawLineDDA(pt[5], pt[6], _color, _canvas); drawLineDDA(pt[5], pt[7], _color, _canvas); } 绘制二维直角坐标系时用到了DDA 画线算法,代码如源程序4-2所示。 【源程序5-2】 绘制单个三角形。 void drawTriangleSingle(Point2D shape[3], Canvas _canvas) { //创建颜色变量_color RGBA _color; _color.m_r = 255; _color.m_g = 255; _color.m_b = 255; _color.m_a = 0; //创建存储三角形顶点的数组points,并复制shape 中的3 个坐标 Point2D points[3]= {0}; for(int i = 0; i < 3; i++) { points[i]= shape[i]; } 90 计算机图形学原理与实现 //将三角形的坐标系移动到画布中心并翻转 for(int i = 0; i < 3; i++) { points[i].x = 300 + points[i].x; points[i].y = 300 - points[i].y; } //创建表示三角形的四边形spt1,设RGB 颜色为_color,用drawLineDDA 函数绘制直线填充三角形 Point2D spt1[]= { {points[0].x, points[0].y}, {points[1].x, points[1].y}, {points[2].x, points[2].y}, {points[0].x, points[0].y}, }; //用drawLineDDA 函数填充spt1 数组的两点之间的线段 drawLineDDA(spt1[0], spt1[1], _color, _canvas); drawLineDDA(spt1[1], spt1[2], _color, _canvas); drawLineDDA(spt1[2], spt1[0], _color, _canvas); } 【源程序5-3】 3×3矩阵相乘函数。 /* 3*3 矩阵相乘(数组1,数组2,数组3,参与矩阵计算的点数)*/ void multiplyMatrix2D(float a[3][3], float b[3][3], float c[3][3], int npt) { int i, j, k; float sum; //循环遍历所有点 for(i = 0; i < npt; i++) { //循环遍历第一个矩阵中的元素 for(k = 0; k < 3; k++) { sum = 0; //循环遍历第二个矩阵中的元素 for(j = 0; j < 3; j++) { //对应位置的两个元素相乘并累加到sum 中 sum += a[i][j]* b[j][k]; //将计算结果存储在新矩阵c 的对应位置中 c[i][k]= sum; } } } } 【源程序5-4】 二维平移变换。 /* 2D 三角形的平移(三角形各顶点的坐标数组,数组长度,x 轴平移距离,y 轴平移距离,Canvas 结构体) */ void translate2D(Point2D ptArray[], int arrayLength, float xs, float ys, Canvas _canvas) 第5章 二维变换和二维观察 91 { //创建一个空间大小为arrayLength 的新数组Rshape Point2D *Rshape = (Point2D *) malloc(arrayLength * sizeof(Point2D)); //定义一个3*3 的单位矩阵 float trans[3][3]= { 1, 0, 0, 0, 1, 0, 0, 0, 1 }; //将平移矩阵中的最后一列修改为(xs, ys, 1) trans[2][0]= xs; trans[2][1]= ys; //创建1*3 的点向量和结果向量,初始化为零 float pnt[1][3]= {0, 0, 1}; float result[1][3]= {0}; for(int i = 0; i < arrayLength; i++) { //遍历原始顶点数组,在点向量中设置当前元素坐标值 pnt[0][0]= ptArray[i].x; pnt[0][1]= ptArray[i].y; //将点向量和转移矩阵相乘,将结果存储在结果向量中 multiplyMatrix2D(pnt, trans, result, 1); //将计算出的新坐标值设置到仿射变换后的顶点数组中 Rshape[i].x = result[0][0]; Rshape[i].y = result[0][1]; } //将Rshape 数组中的值复制到原始二维坐标数组ptArray 中 for(int i = 0; i < arrayLength; i++) { ptArray[i]= Rshape[i]; } //释放占用的内存空间 free(Rshape); } 二维平移变换中用到了源程序5-3对二维数据进行处理。 在main.c文件的Render()指定位置填写源程序5-5进行二维平移变换,源程序5-5用 到了源程序5-1绘制直角坐标系,也用到了源程序5-2绘制单个三角形,且用到了源程序5-4 进行二维平移变换,运行界面如图5-7所示。 图5-7 二维平移程序运行界面 92 计算机图形学原理与实现 【源程序5-5】 通过确定x 轴和y 轴的平移量,实现对三角形的平移。 floatPoint2D ptArray2D[]= { {40, 40}, {40, 80}, {80, 40} }; drawCCS(_canvas); drawTriangleSingle(ptArray2D, _canvas); translate2D(ptArray2D, 3, 50, 50, _canvas); //分别向x 轴和y 轴方向平移50 drawTriangleSingle(ptArray2D, _canvas); 【源程序5-6】 二维旋转变换。 /* 2D 三角形的旋转(三角形各顶点的坐标数组,数组长度,角度,Canvas 结构体) */ void rotate2D(Point2D ptArray[], int arrayLength, float jd, Canvas _canvas) { //用动态内存分配的方式声明两个Point2D 类型的指针数组: shape 和Rshape,并使其大小 //为arrayLength Point2D *shape = (Point2D *) malloc(arrayLength * sizeof(Point2D)); Point2D *Rshape = (Point2D *) malloc(arrayLength * sizeof(Point2D)); //将ptArray 中的点复制到shape 数组中 for(int i = 0; i < arrayLength; i++) { shape[i]= ptArray[i]; } //将角度转换为弧度制 jd = jd * DEC; //创建3*3 的变换矩阵 float rotate[3][3]= { 0, 0, 0, 0, 0, 0, 0, 0, 1 }; //计算旋转矩阵中的数值 rotate[0][0]= rotate[1][1]= cos(jd); rotate[0][1]= sin(jd); rotate[1][0]= -rotate[0][1]; //创建1*3 的点向量和结果向量,初始化为零 float pnt[1][3]= {0, 0, 1}; float result[1][3]= {0}; for(int i = 0; i < arrayLength; i++) { //遍历原始顶点数组,在点向量中设置当前元素坐标值 pnt[0][0]= shape[i].x; pnt[0][1]= shape[i].y; //将点向量和转移矩阵相乘,将结果存储在结果向量中 multiplyMatrix2D(pnt, rotate, result, 1);