变换是对例如点和向量这类实体进行转换的某种操作,对于计算机图形学的实践者来说这是极为重要的一项内容。通过变换,程序员可以对对象移动位置、放缩尺寸,对模型光源相机附加动画效果。
线性变换(linear transform) 是一种完成向量加法和数乘的操作,设操作表示为f ,其性质如下公式所示。
典型的线性变换包括旋转和尺寸放缩,对于三维空间中的线性变换,我们可以使用3*3的矩阵与向量乘法运算获得变换结果。对于平移,我们使用一个固定的向量与原始向量相加获得平移结果,例如完成向(7,3,2)平移我们需要使用如下的变换实现。
按之前的定义,平移变换f显然不是线性变换,无法用3×3矩阵表示,为了统一变换的形式,我们将平移和线性变换统一为仿射变换(affine transform),仿射变换使用4×4矩阵来表示。为了与仿射变换对应,我们将点和向量扩展为4维,定义点的第四维为1、向量的第4维为0(如此满足点的差为两点间向量,点与向量和为点)。下面介绍的内容中我们使用列向量(其他使用行向量的图形库对应的变换矩阵恰好都是本篇给出的矩阵的转置),那么向量扩展后写为:点 v=(vx,vy,vz,1)T 向量 v=(vx,vy,vz,0)T。所有的平移、旋转、放缩镜像、错切都是仿射变换的一种。仿射变换最显著的特征就是变换结果会保持原始的平行性质(即之前平行的两条线,在变换后一定仍然平行)。
对于图形学程序员,完成变换往往是通过提供的接口函数实现。但了解函数背后的变换矩阵如何工作进而可以提升代码质量(比如有些变换是由正交矩阵完成,正交阵的逆可以直接通过求转置完成),所以这一部分内容的学习有着重要意义。
这一节介绍最为基础的变换,是后续小节和本书后续内容的必要背景知识。
这一小节将会深入旋转的方法,首先根据上一节给出的三种基础旋转方式,通过组合形成任意一个旋转;之后再给出围绕任意一个轴进行旋转的方法。
欧拉角是由围绕三个轴的旋转组合而成,我们先对各轴方向以及对应旋转的名称进行定义。在本书中,设定默认视角方向为-z,head(就是yaw)旋转围绕y轴进行,命名设定为上图所示。
可行的欧拉变换顺序一共有24种(使用三个轴的是全排列6个,用两个轴的是6个,一共12个,又分为场景坐标系为中心外旋和以物体为中心的内旋,一共12x2=24种),书中选取最为常见的旋转顺序:
欧拉变换简单容易被描述,但是它不容易完成插值任务,对于两个不同的欧拉变换矩阵,它们表示的变换可能是同一个旋转,这时插值过程应该是保持静止的,可显然不同的矩阵进行简单的插值一定导致插值过程中角度是变化的。
最后欧拉角还存在万向锁(gimbal lock) ,万向锁是在某种旋转方式导致一个旋转自由度丢失的情况。举个最简单的例子,当我们使用xyz的旋转顺序,先绕x旋转若干角度,再绕y轴旋转90度,这个时候x和z轴处于重合状态,这意味着开始时对x轴的变化已经体现在z轴的变化中,此时z轴的变化可以被舍弃掉。
之前提到的都是绕x、y、z轴进行旋转的例子。如果需要描述绕任意轴进行旋转,是一定能够通过欧拉角表示的,但表示方法显然不够直观,下面给出一种较为直观的方法。
假设我们要绕r(设r为单位长度向量)旋转α度。第一步要将r旋转到x正向,这一步的变换矩阵设为M;第二步使用绕x旋转的矩阵 Rx 将模型旋转α;第三步使用M的逆还原回r所在位置。流程如图所示:
在图中可以看到我们又根据r的方向设置了s、t两个轴,这是我们创建矩阵M的步骤之一。创建M的过程十分简单,首先第一步根据r设置方向s(s、t是等价的先设置s只是语义上的区别)。显然和r垂直的向量不是唯一的,我们要选取精度最大的一个,避免选择的s坐标存在接近0而不是0的数值。
公式的三种情况对应于r的坐标最小值是xyz的三种情形,我们要让s坐标中出现的是r中最大的两个坐标,随后再对s进行单位化。第二步,叉积计算t,注意这里由于s、r都设置为单位化向量,t的计算结果长度是1。
计算出rst后,M矩阵写为(使用基准向量拼接一个想要的变换矩阵是一种常见的技巧):这样我们的公式可以写作:
四元数由一个由虚数组成的三维向量和一个实数组成,对应的一个实数称为实部,虚数向量称为虚部。在粗体字体上加一个^,四元数和向量粗体进行区分。
四元数的乘法使用满足分配律的计算方法,但是四元数乘法是不可交换的。
四元数除了乘法还包含了多种运算,下面介绍与旋转表示相关的运算定义。
我们变换操作使用的四元数是四元数集合的子集,是长度为一的单位四元数(unit quaternions)。首先我们简单的将点或向量写成4维的形式p=(px,py,pz,pw);接着设一个单位四元数表示为
其中uq 为单位向量,最后通过公式
来表示绕轴uq旋转角度2×Ф的结果。
我们注意到对于单位四元数,由于模为1,推出逆和共轭是相同的。
感性思考:四元数是四维空间运算,一次四元数乘法得到的结果不在向量或点最初的三维空间中(w不为1或0),所以只进行一次四元数乘法完成旋转是不可行的,只能通过取旋转角度的一半,进行两次旋转才能得到正确结果
之前提到了对欧拉角进行插值是不可行的,四元数最出色的特点就是它可以简单的完成插值任务,在应用中实现动画效果。slerp是球面线性插值(spherical linear interpolation) 的缩写,他表示插值过程将会在球面大圆(great circle) 中的一段(称为大圆弧great arc)完成。
公式中t在[0,1]之间,t=0时插值结果为q,t=1时插值结果为r。直觉上不使用的正弦函数直接使用(1-t)和t也可以完成插值,不过这样获得的结果是在球的弦上进行的线性插值,这会导致旋转过程先变快再变慢。
实际应用中我们会使用四元数表示将空间中一个向量旋转到另一个向量的位置的变换。假设起始向量为s目的向量为t,旋转角度2×Ф。首先第一步要对s、t进行单位化;二者叉乘可以表示旋转轴,用二者内积可以表示夹角2×Ф的余弦值;
最后通过半角公式推出公式:
注意到当e为-1,也就是s和t方向恰好相反为180度时,分母为零,此时是没有定义的。