图形学笔记(四) 数学变换
数学变换
点和坐标
什么是点?
注意区分图形学中的点与图论中的点:图论中的点重视点与点的拓扑结构,而不关心点的坐标,而图形学中的点没有严格限制拓扑结构,且及其关注点的坐标。
在几何学中,点是最简单的形,是几何图形最基本的组成部分,在空间中作为1个一维的对象。在欧氏几何中,点是空间中只有位置,没有大小的图形。
在解析几何中,点可以以坐标的形式表示。在计算机图形学中,坐标是点的本质属性。
什么是笛卡尔坐标系?
笛卡尔坐标系是我们一直以来接触的,也是计算机科学中最常用的坐标系类型,它有一个更通俗的名称——直角坐标系。笛卡尔坐标系也分为二维笛卡尔坐标系和三维笛卡尔坐标系,它们也可以分别被称作平面直角坐标系和空间直角坐标系。
一个二维笛卡尔坐标系由两部分组成:原点和两条相互垂直的基向量。基向量也被广泛的称为x轴和y轴,在图形学中有时也被称为u轴和v轴,在线性代数中它们被称作标准正交基。使用垂直而非其它夹角是因为直角符合人类哲学,并且能最简洁的表达位置。
三维笛卡尔坐标系也由两部分组成:原点和三条相互垂直的基向量,这三条基向量也被广泛的称为x轴、y轴和z轴。
同一空间中任何一个顶点都可以找到相对于坐标系的方向和距离。同时,坐标系也用于标志同一空间中向量的方向。没有坐标系,坐标和方向都没有意义。在图形学中,明确坐标和方向位于哪个坐标系至关重要。
什么是左手坐标系和右手坐标系?
二维笛卡尔坐标系实际上只有两种:y轴在x轴逆时针方向90°的坐标系,和y轴在x轴顺时针方向90°的坐标系。因为任意二维笛卡尔坐标系经过二维平面内的旋转、平移和缩放后都只能归为这二者之一。
三维笛卡尔坐标系类似的,也有两种:从z轴负方向向z轴正方向看去,y轴在x轴逆时针方向90°的坐标系,以及从z轴负方向向z轴正方向看去,y轴在x轴顺时针方向90°的坐标系。这样的描述太过冗余,所以我们将前者称为左手坐标系,而右者称为右手坐标系。任意三维笛卡尔坐标系经过三维空间内的旋转、平移和缩放后也只能归为这二者之一。
在数学上,二维笛卡尔坐标系实际上只有一种,因为在数学中二维平面是不分正反的,两种坐标系只需要进行翻转就可以重合。但在计算机领域我们一般认为二维平面以面向屏幕的方向为正面,所以这两种坐标系事实上有必要进行区分。
左右手坐标系同时定义了旋转的方向。在左手坐标系中,绕某轴旋转的正方向由左手法则定义,即从该轴正方向向负方向看去的顺时针方向;而右手坐标系中,绕某轴旋转的正方向由右手法则定义,即从该轴正方向向负方向看去的拟时针方向。判断叉积结果方向时也会用到左手或右手定则。
几何变换
什么是变换?
变换是按照某种规则,将顶点或向量进行有针对性的修改的过程。
在图形学中,常见的变换包括了平移、缩放、旋转、错切、镜像、正交投影等。这些变换可以改变顶点的位置,也可以改变向量的位置,还可以改变坐标系的位置。
变换分为线性变换和非线性变换,其中线性变换是满足以下公式的变换:
f(x)+f(y)=f(x+y)f(x)+f(y)=f(x+y) f(x)+f(y)=f(x+y)
kf(x)=f(kx)kf(x)=f(kx) kf(x)=f(kx)
在上面提到的几种变换中,除了平移和正交投影外,都属于线性变换。
在线性代数中我们知道,n×n的矩阵是可以用于实现n维线性变换的。
什么是齐次坐标空间?
由于平移不属于线性变换,所以我们没办法用3×3的矩阵来处理平移。
于是我们引入了仿射变换(affine transform),通过将三维顶点坐标引入四维空间,我们就可以通过4×4的矩阵来解决平移问题了。而为了实现仿射变换而引入的四维空间,就被称为齐次坐标空间(homogeneous space)。
在齐次坐标空间中,坐标的第四个分量w被用于实现平移,设为常量1。而向量是不需要平移的,所以分量w被设为常量0。
如何构造一个变换矩阵?
我们已经知道要同4×4的矩阵来表示平移、旋转和缩放。我们把表示纯平移、纯旋转和纯缩放的矩阵叫做基础变换矩阵。我们可以把一个基础变换矩阵分为4部分:
[M3×3t3×101×31]\begin{bmatrix} M_{3×3}&t_{3×1}\\ 0_{1×3}&1 \end{bmatrix} [M3×301×3t3×11]
其中,M3×3被用于旋转和缩放,t3×1被用于平移,01×3是零矩阵,右下角是标量1。
平移矩阵定义:
MT=[100tx010ty001tz0001]M_T=\begin{bmatrix} 1&0&0&t_x\\0&1&0&t_y\\0&0&1&t_z\\0&0&0&1 \end{bmatrix} MT=⎣⎢⎢⎡100001000010txtytz1⎦⎥⎥⎤
其中tx、ty、tz分别表示在x、y、z轴上平移的长度。
平移矩阵作用在顶点(w分量为1)上时:
[100tx010ty001tz0001][xyz1]=[x+txy+tyz+tz1]\begin{bmatrix} 1&0&0&t_x\\0&1&0&t_y\\0&0&1&t_z\\0&0&0&1 \end{bmatrix} \begin{bmatrix} x\\y\\z\\1 \end{bmatrix}= \begin{bmatrix} x+t_x\\y+t_y\\z+t_z\\1 \end{bmatrix} ⎣⎢⎢⎡100001000010txtytz1⎦⎥⎥⎤⎣⎢⎢⎡xyz1⎦⎥⎥⎤=⎣⎢⎢⎡x+txy+tyz+tz1⎦⎥⎥⎤
平移矩阵作用在向量(w分量为0)上时:
[100tx010ty001tz0001][xyz0]=[xyz0]\begin{bmatrix} 1&0&0&t_x\\0&1&0&t_y\\0&0&1&t_z\\0&0&0&1 \end{bmatrix} \begin{bmatrix} x\\y\\z\\0 \end{bmatrix}= \begin{bmatrix} x\\y\\z\\0 \end{bmatrix} ⎣⎢⎢⎡100001000010txtytz1⎦⎥⎥⎤⎣⎢⎢⎡xyz0⎦⎥⎥⎤=⎣⎢⎢⎡xyz0⎦⎥⎥⎤
缩放矩阵定义:
MS=[kx0000ky0000kz00001]M_S=\begin{bmatrix}k_x&0&0&0\\0&k_y&0&0\\0&0&k_z&0\\0&0&0&1 \end{bmatrix} MS=⎣⎢⎢⎡kx0000ky0000kz00001⎦⎥⎥⎤
其中kx、ky、kz分别表示在x、y、z轴上缩放的比例。
缩放矩阵作用在顶点上时:
[kx0000ky0000kz00001][xyz1]=[kxxkyykzz1]\begin{bmatrix}k_x&0&0&0\\0&k_y&0&0\\0&0&k_z&0\\0&0&0&1 \end{bmatrix}\begin{bmatrix}x\\y\\z\\1 \end{bmatrix}=\begin{bmatrix}k_xx\\k_yy\\k_zz\\1 \end{bmatrix} ⎣⎢⎢⎡kx0000ky0000kz00001⎦⎥⎥⎤⎣⎢⎢⎡xyz1⎦⎥⎥⎤=⎣⎢⎢⎡kxxkyykzz1⎦⎥⎥⎤
绕过点(a,b,c)方向为(x,y,z)的轴旋转角度θ的旋转矩阵定义如下:
MR=[x2+(y2+z2)cosθxy(1−cosθ)−zsinθxz(1−cosθ)+ysinθ(a(y2+z2)−x(by+cz))(1−cosθ)+(bz−cy)sinθxy(1−cosθ)+zsinθy2+(x2+z2)cosθyz(1−cosθ)−xsinθ(b(x2+z2)−y(ax+cz))(1−cosθ)+(cx−az)sinθxz(1−cosθ)−ysinθyz(1−cosθ)+xsinθz2+(x2+y2)cosθ(c(x2+y2)−z(ax+by))(1−cosθ)+(ay−bx)sinθ0001]M_R=\begin{bmatrix} x^2+(y^2+z^2)cos\theta&xy(1-cos\theta)-zsin\theta& xz(1-cos\theta)+ysin\theta&(a(y^2+z^2)-x(by+cz))(1-cos\theta)+(bz-cy)sin\theta\\ xy(1-cos\theta)+zsin\theta&y^2+(x^2+z^2)cos\theta& yz(1-cos\theta)-xsin\theta&(b(x^2+z^2)-y(ax+cz))(1-cos\theta)+(cx-az)sin\theta\\ xz(1-cos\theta)-ysin\theta&yz(1-cos\theta)+xsin\theta& z^2+(x^2+y^2)cos\theta&(c(x^2+y^2)-z(ax+by))(1-cos\theta)+(ay-bx)sin\theta\\ 0&0&0&1 \end{bmatrix} MR=⎣⎢⎢⎡x2+(y2+z2)cosθxy(1−cosθ)+zsinθxz(1−cosθ)−ysinθ0xy(1−cosθ)−zsinθy2+(x2+z2)cosθyz(1−cosθ)+xsinθ0xz(1−cosθ)+ysinθyz(1−cosθ)−xsinθz2+(x2+y2)cosθ0(a(y2+z2)−x(by+cz))(1−cosθ)+(bz−cy)sinθ(b(x2+z2)−y(ax+cz))(1−cosθ)+(cx−az)sinθ(c(x2+y2)−z(ax+by))(1−cosθ)+(ay−bx)sinθ1⎦⎥⎥⎤
这个矩阵很难记忆,但几乎每个图形学相关的类库中都会含有这个矩阵。推导过程略过。
作为旋转矩阵的特例,绕x、y、z轴旋转角度θ的旋转矩阵分别是:
MRx=[10000cosθ−sinθ00sinθcosθ00001]M_{Rx}=\begin{bmatrix} 1&0&0&0\\0&cos\theta&-sin\theta&0\\0&sin\theta&cos\theta&0\\0&0&0&1 \end{bmatrix} MRx=⎣⎢⎢⎡10000cosθsinθ00−sinθcosθ00001⎦⎥⎥⎤
MRy=[cosθ0sinθ00100−sinθ0cosθ00001]M_{Ry}=\begin{bmatrix} cos\theta&0&sin\theta&0\\0&1&0&0\\-sin\theta&0&cos\theta&0\\0&0&0&1 \end{bmatrix} MRy=⎣⎢⎢⎡cosθ0−sinθ00100sinθ0cosθ00001⎦⎥⎥⎤
MRz=[cosθ−sinθ00sinθcosθ0000100001]M_{Rz}=\begin{bmatrix} cos\theta&-sin\theta&0&0\\sin\theta&cos\theta&0&0\\0&0&1&0\\0&0&0&1 \end{bmatrix} MRz=⎣⎢⎢⎡cosθsinθ00−sinθcosθ0000100001⎦⎥⎥⎤
这三个矩阵常用来进行欧拉变换。若给定一个欧拉变换(α,β,γ),其旋转矩阵如下:
MEular(α,β,γ)=MRz(γ)MRx(α)MRy(β)M_{Eular}(\alpha,\beta,\gamma)=M_{Rz}(\gamma)M_{Rx}(\alpha)M_{Ry}(\beta) MEular(α,β,γ)=MRz(γ)MRx(α)MRy(β)
MEular也被称为欧拉旋转矩阵。欧拉旋转矩阵中的顺序是可以变化的,一般由平台决定。这里给出的Z->X->Y的顺序是Unity的欧拉变换标准。
如何进行复合变换?
复合变换实际上就是一次进行多个变换。
复合变换的顺序是:先缩放、再旋转、最后平移。这个顺序属于行业约定,不能打乱。
由于我们使用的是列矩阵,所以阅读顺序是从右向左,如下:
Pnew=MTMRMSPoldP_{new}=M_TM_RM_SP_{old} Pnew=MTMRMSPold
如何表达旋转?
我们知道,向量可以表达方向,但不能表达旋转,因为仅通过向量我们无法确定物体绕向量方向旋转的多少。
一个向量不足以表达旋转,我们需要一对向量,用一对向量表达旋转被称为双向量法(Double Vector)。通过确定一个物体的上方和前方向量,我们可以唯一的确定一个物体的旋转。双向量表示法的问题有三个:
使用两个三维向量,需要六个浮点数的储存空间。
我们没有办法保证这两个向量一定是垂直的,在遇到非法输入时需要额外的处理程序。
双向量进行插值计算不一定得到合法的结果,不适用于插值计算在图形学领域是一个极其致命的问题。
为了解决双向量法的问题,我们可以改用**欧拉角(Eular Angle)**表示法。
欧拉角是一个直观的利用三维向量表达旋转的方法,它源于刚体动能计算,后来广泛用于航空业,随后被计算机领域引入描述旋转。欧拉角中的三个维度记录了绕x轴旋转x度,绕y轴旋转y度,绕z轴旋转z度,通过这三个数据我们可以得到一个欧拉变换矩阵。如果我们把物体前方朝向z轴正方向,上方朝向y轴正方向的状态作为欧拉角中的零向量,那么欧拉角就可以表达一个旋转状态。
在这里我们使用比物理专业术语更易懂的航空术语进行解释。如果我们将物体模型坐标的y轴作为物体的上方,z轴作为前方,在左手坐标系中x轴作为右方,右手坐标系中x轴作为左方,我们将欧拉角中的x分量称为俯仰(Pitch),因为绕x轴旋转会使物体的面向方向朝上或下旋转;将欧拉角中的y分量称为偏航(Yaw),因为绕y轴旋转会使物体的面向方向朝左或右旋转;将欧拉角中的z分量称为桶滚(Roll),因为绕z轴旋转不会改变物体面向的方向,而是绕面向的方向滚动。在不同的坐标体系中,欧拉角的定义可能完全不同,比如在某些三维软件中将z轴作为物体的上方,x轴作为物体的前方。
在刚体物理学中,将以质心为原点,以世界坐标系的x/y/z轴为基向量的坐标系记为OXYZ,其中世界坐标系z轴朝上;将以质心为原点,以刚体上方为z轴,刚体正方为x轴,侧方为y轴的坐标系记作Oxyz。则称∠zOZ为章动角θ,称平面zOZ的垂线ON为节线,称∠XON为进动角ψ,称∠xON为自转角φ。这套欧拉角表达式可以用于描述在陀螺自转问题中陀螺的三种运动状态。
欧拉角分为静态欧拉角和动态欧拉角,静态欧拉角沿世界坐标的三个轴旋转,而动态欧拉角沿模型坐标的三个轴旋转。航空领域的欧拉角使用动态欧拉角,物理专业领域常用静态欧拉角,而在计算机领域我们既可以使用静态欧拉角也可以使用动态欧拉角。如在Unity中,就存在选项在这两种欧拉变换中切换。
欧拉角使用和三维向量相同的储存方式,它占用的空间比双向量小了一半,而且不会遇到非法输入。
但欧拉角也有三个主要的问题:
- 欧拉角没有统一的行业标准。同一个欧拉角在不同平台表达完全不同的旋转。因为欧拉变换的结果与欧拉变换矩阵的形式以及坐标系的设定有关,甚至有些平台会使用刚体物理中对欧拉角的定义,这使得欧拉角的跨平台性非常差。
- 欧拉角的三个分量并不是正交的,非正交性导致动态欧拉变换存在俗称万向节死锁的问题,这使得欧拉变换的结果具有不可预见性,程序员很难准确的预测通过某个欧拉变换后物体的新朝向。
- 欧拉角仍然不适用于插值计算,对两个欧拉角插值得到的并不是两个旋转的中间量。
于是我们引入一个新的概念,它被称为四元数(Quaternion)。
什么是四元数?
四元数是一种超复数,它有一个实部和三个虚部:
q=w+xi+yj+zk=s+v⃗q=w+xi+yj+zk=s+\vec{v} q=w+xi+yj+zk=s+v
将四元数所在的数域称为H。
四元数具有以下特性:
i2=j2=k2=−1i^2=j^2=k^2=-1 i2=j2=k2=−1
ij=−ji=kij=-ji=k ij=−ji=k
jk=−kj=ijk=-kj=i jk=−kj=i
ki=−ik=jki=-ik=j ki=−ik=j
对空间几何敏感的话可以注意到,ijk的乘法运算实际上和三维空间中三个互相垂直的向量的叉乘逻辑一致。
设q1=s1+v1⃗,q2=s2+v2⃗,则设q_1=s_1+\vec{v_1},q_2=s_2+\vec{v_2},则 设q1=s1+v1,q2=s2+v2,则
q1q2=s1s2−v1⃗⋅v2⃗+s1v2⃗+s2v1⃗+v1⃗×v2⃗(1)q_1q_2=s_1s_2-\vec{v_1}\cdot\vec{v_2}+s_1\vec{v_2}+s_2\vec{v_1}+\vec{v_1}\times\vec{v_2} \tag{1} q1q2=s1s2−v1⋅v2+s1v2+s2v1+v1×v2(1)
四元数的模:
∣q⃗∣=w2+x2+y2+z2∣q⃗1q⃗2∣=∣q⃗1∣∗∣q⃗2∣\mid\vec q\mid =\sqrt{w^2+x^2+y^2+z^2}\\\mid\vec q_1\vec q_2\mid=\mid \vec q_1\mid*\mid\vec q_2\mid ∣q∣=w2+x2+y2+z2∣q1q2∣=∣q1∣∗∣q2∣
共轭四元数:
q=s+v⃗,q‾=s−v⃗qq‾=q‾q=q⋅q‾=∣q∣2=q2q = s+\vec{v},\, \overline{q} = s - \vec{v}\\ q\overline{q} = \overline{q}q=q\cdot\overline{q}=\mid q\mid^2=q^2 q=s+v,q=s−vqq=qq=q⋅q=∣q∣2=q2
四元数的逆:
q−1=q‾q2qq−1=q−1q=1(q−1)−1=qq^{-1}=\frac{\overline{q}}{q^2}\\ qq^{-1}=q^{-1}q=1\\(q^{-1})^{-1}=q q−1=q2qqq−1=q−1q=1(q−1)−1=q
四元数作为四维向量形式的点积:
cosθ=p⋅q=pxqx+pyqy+pzqz+pwqw\cos\theta=p\cdot q=p_xq_x+p_yq_y+p_zq_z+p_wq_w cosθ=p⋅q=pxqx+pyqy+pzqz+pwqw
旋转可以被理解为一个函数Φ:R3->R3(从R3到R3的映射)。P,P1,**P2**∈R3,Φ要想表达一个旋转,必须在旋转过程中保证向量长度、向量夹角和手性不变,即:
∣Φ(P⃗)∣=∣P⃗∣(2)\mid \Phi(\vec{P})\mid = \mid\vec{P}\mid\tag{2} ∣Φ(P)∣=∣P∣(2)
Φ(P1⃗)⋅Φ(P2⃗)=P1⃗⋅P2⃗(3)\Phi(\vec{P_1})\cdot\Phi(\vec{P_2})=\vec{P_1}\cdot \vec{P_2}\tag{3} Φ(P1)⋅Φ(P2)=P1⋅P2(3)
Φ(P1⃗)×Φ(P2⃗)=Φ(P1⃗×P2⃗)(4)\Phi(\vec{P_1})\times \Phi(\vec{P_2})=\Phi(\vec{P_1}\times \vec{P_2})\tag{4} Φ(P1)×Φ(P2)=Φ(P1×P2)(4)
扩展Φ为一个H->H的映射,要求Φ(s + V) = s + Φ(V),于是我们可以重写公式(3):
Φ(P1⃗)⋅Φ(P2⃗)=Φ(P1⃗⋅P2⃗)(5)\Phi(\vec{P_1})\cdot \Phi(\vec{P_2})=\Phi(\vec{P_1}\cdot \vec{P_2})\tag{5} Φ(P1)⋅Φ(P2)=Φ(P1⋅P2)(5)
把P1和P2当作标量部分为0的四元数,使P1,P2,Φ(P1),Φ(P2)∈H,将s1=s2=0代入公式(1)得:
Φ(P1⃗)Φ(P2⃗)=−Φ(P1⃗)⋅Φ(P2⃗)+Φ(P1⃗)×Φ(P2⃗)P1⃗P2⃗=−P1⃗⋅P2⃗+P1⃗×P2⃗\Phi(\vec{P_1})\Phi(\vec{P_2})=-\Phi(\vec{P_1})\cdot \Phi(\vec{P_2})+\Phi(\vec{P_1})\times \Phi(\vec{P_2})\\ \vec{P_1}\vec{P_2}=-\vec{P_1}\cdot \vec{P_2}+\vec{P_1}\times \vec{P_2} Φ(P1)Φ(P2)=−Φ(P1)⋅Φ(P2)+Φ(P1)×Φ(P2)P1P2=−P1⋅P2+P1×P2
因此我们可以(4)和(5)合成成新的公式:
KaTeX parse error: No such environment: split at position 8: \begin{̲s̲p̲l̲i̲t̲}̲ \Phi(\vec{P_1}…
我们给出一个满足(2)和(6)的函数:
KaTeX parse error: Got function '\vec' with no arguments as subscript at position 7: \Phi_\̲v̲e̲c̲{q}(\vec{P})=\v…
先证明(7)满足(2):
KaTeX parse error: Got function '\vec' with no arguments as subscript at position 11: \mid\Phi_\̲v̲e̲c̲{q}(\vec{P})\mi…
再证明(7)满足(6):
KaTeX parse error: Got function '\vec' with no arguments as subscript at position 7: \Phi_\̲v̲e̲c̲{q}(\vec{P_1})\…
易证明对任意a∈R(a≠0),Φaq=Φq,所以不妨设q为单位四元数,则:
q⃗−1=q⃗‾=s−v⃗(8)\vec{q}^{-1} =\overline{\vec{q}}=s-\vec{v}\tag{8} q−1=q=s−v(8)
在此我们补充一个定理,对于任意P,Q∈R3,有:
P⃗×(Q⃗×P⃗)=P⃗×Q⃗×P⃗=P⃗2Q⃗−(P⃗⋅Q⃗)P⃗(9)\vec{P}\times (\vec{Q} \times \vec{P}) = \vec{P}\times \vec{Q}\times \vec{P}=\vec{P}^2\vec{Q}-(\vec{P}\cdot \vec{Q})\vec{P}\tag 9 P×(Q×P)=P×Q×P=P2Q−(P⋅Q)P(9)
于是我们得到:
KaTeX parse error: No such environment: split at position 8: \begin{̲s̲p̲l̲i̲t̲}̲\vec{q}\vec{P}\…
设v=tA,其中t是非0常数,A是旋转轴所在的单位向量,重新(10)为:
q⃗P⃗q⃗−1=(s2−t2)P⃗+2stA⃗×P⃗+2t2(A⃗⋅P⃗)A⃗(11)\vec{q}\vec{P}\vec{q}^{-1}=(s^2-t^2)\vec{P}+2st\vec A\times \vec{P}+2t^2(\vec A\cdot \vec{P})\vec A\tag{11} qPq−1=(s2−t2)P+2stA×P+2t2(A⋅P)A(11)
我们引入向量P绕轴A旋转θ度的公式:
P′⃗=P⃗cosθ+(A⃗×P⃗)sinθ+A⃗(A⃗⋅P⃗)(1−cosθ)(12)\vec{P'} =\vec{P}cos\theta+(\vec{A}\times\vec{P})sin\theta+\vec{A}(\vec{A}\cdot\vec{P})(1-cos\theta)\tag{12} P′=Pcosθ+(A×P)sinθ+A(A⋅P)(1−cosθ)(12)
比较(11)与(12)可以得到:
{s2−t2=cosθ2st=sinθ2t2=1−cosθ(13)\left \{ \begin{array}{c} s^2-t^2=cos\theta\\2st=sin\theta\\2t^2 =1-cos\theta \end{array}\right.\tag{13} ⎩⎨⎧s2−t2=cosθ2st=sinθ2t2=1−cosθ(13)
解方程组得:
{t=sinθ2s=cosθ2(14)\left \{ \begin{array}{c} t=sin\frac{\theta}{2}\\s=cos\frac{\theta}{2} \end{array}\right.\tag{14} {t=sin2θs=cos2θ(14)
于是我们解得:
KaTeX parse error: No such environment: split at position 8: \begin{̲s̲p̲l̲i̲t̲}̲ \vec{q}&=s+\ve…
运用四元数实现旋转比旋转矩阵更便捷,两个四元数相乘只需要16次乘加,而两个4×4的矩阵需要64次乘加。
接下来求解四元数q=w+xi+bj+ck对应的旋转矩阵,首先将(11)用矩阵形式表现:
q⃗P⃗q⃗−1=[s2−t2000s2−t2000s2−t2]P⃗+[0−2stAz2stAy2stAz0−2stAx−2stAy2stAx0]P⃗+[2t2Ax22t2AxAy2t2AxAz2t2AxAy2t2Ay22t2AyAz2t2AxAz2t2AyAz2t2Az2]P⃗\vec q\vec P\vec {q}^{-1}=\begin{bmatrix}s^2-t^2&0&0\\0&s^2-t^2&0\\0&0&s^2-t^2 \end{bmatrix}\vec P+\begin{bmatrix}0&-2stA_z&2stA_y\\2stA_z&0&-2stA_x\\-2stA_y&2stA_x&0 \end{bmatrix}\vec P\\+\begin{bmatrix}2t^2A_x^2&2t^2A_xA_y&2t^2A_xA_z\\2t^2A_xA_y&2t^2A_y^2&2t^2A_yA_z\\2t^2A_xA_z&2t^2A_yA_z&2t^2A_z^2 \end{bmatrix}\vec P qPq−1=⎣⎡s2−t2000s2−t2000s2−t2⎦⎤P+⎣⎡02stAz−2stAy−2stAz02stAx2stAy−2stAx0⎦⎤P+⎣⎡2t2Ax22t2AxAy2t2AxAz2t2AxAy2t2Ay22t2AyAz2t2AxAz2t2AyAz2t2Az2⎦⎤P
用x、y、z、w来重现上面的矩阵:
q⃗P⃗q⃗−1=[w2−x2−y2−z2000w2−x2−y2−z2000w2−x2−y2−z2]P⃗+[0−2wz2wy2wz0−2wx−2wy2wx0]P⃗+[2x22xy2xz2xy2y22yz2xz2yz2z2]P⃗\vec q\vec P\vec {q}^{-1}=\begin{bmatrix}w^2-x^2-y^2-z^2&0&0\\0&w^2-x^2-y^2-z^2&0\\0&0&w^2-x^2-y^2-z^2 \end{bmatrix}\vec P\\ +\begin{bmatrix}0&-2wz&2wy\\2wz&0&-2wx\\-2wy&2wx&0 \end{bmatrix}\vec P+\begin{bmatrix}2x^2&2xy&2xz\\2xy&2y^2&2yz\\2xz&2yz&2z^2 \end{bmatrix}\vec P qPq−1=⎣⎡w2−x2−y2−z2000w2−x2−y2−z2000w2−x2−y2−z2⎦⎤P+⎣⎡02wz−2wy−2wz02wx2wy−2wx0⎦⎤P+⎣⎡2x22xy2xz2xy2y22yz2xz2yz2z2⎦⎤P
由于q是单位四元数,可得:
w2−x2−y2−z2=(1−x2−y2−z2)−x2−y2−z2=1−2x2−2y2−2z2w^2-x^2-y^2-z^2=(1-x^2-y^2-z^2)-x^2-y^2-z^2=1-2x^2-2y^2-2z^2 w2−x2−y2−z2=(1−x2−y2−z2)−x2−y2−z2=1−2x2−2y2−2z2
得到q对应的旋转矩阵Rq为:
Rq=[1−2y2−2z22xy−2wz2xz+2wy2xy+2wz1−2x2−2z22yz−2wx2xz−2wy2yz+2wx1−2x2−2y2]R_q=\begin{bmatrix}1-2y^2-2z^2&2xy-2wz&2xz+2wy\\2xy+2wz&1-2x^2-2z^2&2yz-2wx\\ 2xz-2wy&2yz+2wx&1-2x^2-2y^2 \end{bmatrix} Rq=⎣⎡1−2y2−2z22xy+2wz2xz−2wy2xy−2wz1−2x2−2z22yz+2wx2xz+2wy2yz−2wx1−2x2−2y2⎦⎤
如何对旋转进行插值?
利用四元数可以对旋转进行线性插值。
四元数的插值方法有两种,分别记作Lerp(线性插值)和SLerp(球面线性插值)。
Lerp是对四元数套用四元矢量的线性插值方法。给定两个分别代表旋转A和旋转B的四元数qA和qB,可以找出自旋转A至旋转B之间β百分比的中间旋转qLerp:
qLerp=Lerp(qA,qb,β)=(1−β)qA+βqB∣(1−β)qA+βqB∣q_{Lerp}=Lerp(q_A,q_b,\beta)=\frac{(1-\beta)q_A+\beta q_B}{|(1-\beta)q_A+\beta q_B|} qLerp=Lerp(qA,qb,β)=∣(1−β)qA+βqB∣(1−β)qA+βqB
Lerp的问题在于,他没有考虑四元数作为四维超球面(hypershpere)的点的特性。Lerp是沿超球的弦插值的,而不是在球面上插值,这样会导致当β以恒定速度改变时,旋转动画不能以恒定角度进行。旋转在两端看起来较慢,而在动画中间就会较快。
解决此问题的方法是,使用正弦和余弦在四维超球面的球上进行插值,即SLerp:
为了避免对弦插值,我们使用新的权值ωp和ωq取代单个权值β:
qSLerp=SLerp(p,q,b)=ωpp+ωqqq_{SLerp} = SLerp(p,q,b)=\omega_pp+\omega_qq qSLerp=SLerp(p,q,b)=ωpp+ωqq
其中ωp和ωq由对角度进行的插值得到:
ωp=sin((1−β)θ)sinθωq=sinβθsinθ\omega_p=\frac{\sin((1-\beta)\theta)}{\sin\theta}\\ \omega_q=\frac{\sin{\beta\theta}}{\sin\theta} ωp=sinθsin((1−β)θ)ωq=sinθsinβθ
前面我们提到了四元数作为向量点积的意义,所以我们可以利用这个性质求得θ:
θ=arccos(p⋅q)\theta = \arccos(p\cdot q) θ=arccos(p⋅q)
空间变换
什么是世界坐标空间?
某点或某向量基于某坐标空间,就等于某点或某向量基于某坐标系。
图形学中会存在大量不同的坐标空间,它们各自有各自的作用。在不同的处理中,我们往往需要运用不同的坐标系。虽然对计算机而言,额外的坐标系确实增加了性能消耗,但通过转换坐标空间使顶点等图形学信息能更容易的被开发者理解,减少开发者的脑力负担。
首先,图形学中一般会存在一个世界坐标空间(World Coordinate)或根坐标空间(Root Coordinate)。坐标系之间形成了一个树形结构,如果A坐标系是通过计算在B坐标系中的坐标确定位置与旋转的,那么B坐标系就是A坐标系的父坐标系,而世界坐标就是这个坐标树的根节点。
什么是坐标空间变换?
顶点所在的坐标系一般被称为模型坐标空间(Model Coordinate)或本地坐标空间(Local Coordinate)。在渲染过程中,流水线部件并不能识别模型坐标,我们必须计算出顶点在指定坐标空间(也就是NDC)的坐标才能将顶点交付给流水线使用。为了完成这个过程,我们要先不断的求出顶点在父坐标系下的坐标,直到求出它在世界坐标空间下的坐标,然后再将它向目标坐标空间转换。为此,我们必须直到坐标是如何在父子坐标空间中互相转化的。
将问题简化,假设有父坐标P和子坐标C,我们已知C在P中的坐标PC,C在P中的旋转RC(Rx,Ry,Rz)(欧拉角),C在P中的缩放SC(Sx,Sy,Sz),试求C中坐标AC在P中的坐标AP,以及P中坐标BP在C中的坐标BC。我们知道,矩阵具有变换的功能,所以我们列出下列的式子:
Ap=Mc−pAcA_p=M_{c-p}A_c Ap=Mc−pAc
Bc=Mp−cBpB_c=M_{p-c}B_p Bc=Mp−cBp
由于父子坐标空间变换是一对反向变换,所以显然矩阵Mc-p和矩阵Mp-c是一对逆矩阵,这样我们只需要解出其中一个矩阵即可,现在我们来求解Mc-p。我们设Ac=(a,b,c)。
在C坐标系中,C的原点坐标为O(0,0,0),C的三条基向量分别为Vcx、Vcy、Vcz,则:
AC⃗=O⃗+aVcx⃗+bVcy⃗+cVcz⃗\vec{A_C}=\vec{O}+a\vec{V_{cx}}+\vec{bV_{cy}}+\vec{cV_{cz}} AC=O+aVcx+bVcy+cVcz
将其中的所有坐标和矢量转换到父坐标P下,设在P坐标系中C的三条基向量分别为Vpx、Vpy、Vpz,则:
AP⃗=Pc⃗+aVpx⃗+bVpy⃗+cVpz⃗\vec{A_P} = \vec{P_c}+a\vec{V_{px}}+b\vec{V_{py}}+c\vec{V_{pz}} AP=Pc+aVpx+bVpy+cVpz
将这个算式写成矩阵的格式,就会变成这样:
AP=Pc⃗+[∣∣∣Vpx⃗Vpy⃗Vpz⃗∣∣∣][abc]A_P=\vec{P_c} + \begin{bmatrix}\mid&\mid&\mid\\ \vec{V_{px}}&\vec{V_{py}}&\vec{V_{pz}}\\ \mid&\mid&\mid\end{bmatrix}\begin{bmatrix}a\\b\\c\end{bmatrix} AP=Pc+⎣⎡∣Vpx∣∣Vpy∣∣Vpz∣⎦⎤⎣⎡abc⎦⎤
将这个算式扩展到齐次坐标空间中,就会变成:
AP=[∣∣∣∣Vpx⃗Vpy⃗Vpz⃗Pc⃗∣∣∣∣0001][abc1]A_P=\begin{bmatrix}\mid&\mid&\mid&\mid\\ \vec{V_{px}}&\vec{V_{py}}&\vec{V_{pz}}&\vec{P_c}\\ \mid&\mid&\mid&\mid\\0&0&0&1 \end{bmatrix}\begin{bmatrix}a\\b\\c\\1\end{bmatrix} AP=⎣⎢⎢⎡∣Vpx∣0∣Vpy∣0∣Vpz∣0∣Pc∣1⎦⎥⎥⎤⎣⎢⎢⎡abc1⎦⎥⎥⎤
接下来我们考虑Vpx、Vpy、**Vpz的求法:我们已经知道C在P中的旋转RC(欧拉角)和C在P中的缩放SC,对C的基向量Vcx在P中的表示Vpx**来说,**Vpx**相当于P的x轴基向量先经过缩放Sx再经过旋转RC的结果,以此类推的分析另外两个C的基向量,可以得到:
Ap=[100∣010Pc⃗001∣0001]MEular(RC)MS(SC)[1000010000100001][abc1]A_p=\begin{bmatrix}1&0&0&\mid\\0&1&0&\vec{P_c}\\0&0&1&\mid\\0&0&0&1\end{bmatrix}M_{Eular}(R_C)M_S(S_C)\begin{bmatrix} 1&0&0&0\\0&1&0&0\\0&0&1&0\\0&0&0&1 \end{bmatrix}\begin{bmatrix}a\\b\\c\\1\end{bmatrix} Ap=⎣⎢⎢⎡100001000010∣Pc∣1⎦⎥⎥⎤MEular(RC)MS(SC)⎣⎢⎢⎡1000010000100001⎦⎥⎥⎤⎣⎢⎢⎡abc1⎦⎥⎥⎤
Ap=[100∣010Pc⃗001∣0001]MEular(RC)[Sx0000Sy0000Sz00001][abc1]A_p=\begin{bmatrix}1&0&0&\mid\\0&1&0&\vec{P_c}\\0&0&1&\mid\\0&0&0&1\end{bmatrix}M_{Eular}(R_C) \begin{bmatrix} S_x&0&0&0\\0&S_y&0&0\\0&0&S_z&0\\0&0&0&1 \end{bmatrix}\begin{bmatrix}a\\b\\c\\1\end{bmatrix} Ap=⎣⎢⎢⎡100001000010∣Pc∣1⎦⎥⎥⎤MEular(RC)⎣⎢⎢⎡Sx0000Sy0000Sz00001⎦⎥⎥⎤⎣⎢⎢⎡abc1⎦⎥⎥⎤
所以:
Mc−p=[100∣010Pc⃗001∣0001]MEular(RC)[Sx0000Sy0000Sz00001]M_{c-p}=\begin{bmatrix}1&0&0&\mid\\0&1&0&\vec{P_c}\\0&0&1&\mid\\0&0&0&1\end{bmatrix}M_{Eular}(R_C) \begin{bmatrix} S_x&0&0&0\\0&S_y&0&0\\0&0&S_z&0\\0&0&0&1 \end{bmatrix} Mc−p=⎣⎢⎢⎡100001000010∣Pc∣1⎦⎥⎥⎤MEular(RC)⎣⎢⎢⎡Sx0000Sy0000Sz00001⎦⎥⎥⎤
其中MEular(RC)是RC的欧拉变换矩阵。
什么是观察空间变换?
观察空间变换将顶点的世界坐标转换到摄像机的模型坐标空间,摄像机的模型坐标空间又被称为观察空间(View Space)。根据前面的公式我们已经得到了Mc-p,现在我们只需要摄像机的坐标、旋转和缩放代入Mc-p,即可得到从像机模型坐标到世界坐标的变换矩阵(假设摄像机没有父坐标系),然后再求它的逆矩阵,就可以得到Mp-c,即世界坐标到像机模型坐标的变换矩阵。
在线性代数中我们知道:
(M1M2)−1=M2−1M1−1(M_1M_2)^{-1}=M_2^{-1}M_1^{-1} (M1M2)−1=M2−1M1−1
所以我们得到了通常版本的Mp-c:
Mp−c=Mc−p−1=[1Sx00001Sy00001Sz00001]MEular(−RC)[100∣010−Pc⃗001∣0001]M_{p-c}=M_{c-p}^{-1}=\begin{bmatrix}\frac1{S_x}&0&0&0\\0&\frac1{S_y}&0&0\\0&0&\frac1{S_z}&0\\0&0&0&1\end{bmatrix}M_{Eular}(-R_C)\begin{bmatrix}1&0&0&\mid\\0&1&0&\vec{-P_c}\\0&0&1&\mid\\0&0&0&1\end{bmatrix} Mp−c=Mc−p−1=⎣⎢⎢⎡Sx10000Sy10000Sz100001⎦⎥⎥⎤MEular(−RC)⎣⎢⎢⎡100001000010∣−Pc∣1⎦⎥⎥⎤
在Unity中,像机模型坐标空间是右手坐标系,而世界坐标空间和其它模型坐标空间是左手坐标系,于是我们需要对手性取反,所以在Unity中,在观察空间变化时的Mp-c改为:
Mp−c=Mc−p−1=[1Sx00001Sy00001Sz00001]MEular(−RC)[100∣010−Pc⃗00−1∣0001]M_{p-c}=M_{c-p}^{-1}=\begin{bmatrix}\frac1{S_x}&0&0&0\\0&\frac1{S_y}&0&0\\0&0&\frac1{S_z}&0\\0&0&0&1\end{bmatrix}M_{Eular}(-R_C)\begin{bmatrix}1&0&0&\mid\\0&1&0&\vec{-P_c}\\0&0&-1&\mid\\0&0&0&1\end{bmatrix} Mp−c=Mc−p−1=⎣⎢⎢⎡Sx10000Sy10000Sz100001⎦⎥⎥⎤MEular(−RC)⎣⎢⎢⎡1000010000−10∣−Pc∣1⎦⎥⎥⎤
什么是裁剪空间变换?
我们已经求得了顶点在观察空间中的坐标,接下来我们要将它们转换到裁剪空间(Clip Space)中以方便裁剪和归一。位于**裁剪空间(Clip Space)**内的图元会被保留,而位于裁剪空间外的图元会被剔除。
裁剪空间是由**视锥体(view frustum)决定的,视锥体由六个裁剪平面(Clip Planes)**决定。在3D渲染中最常使用的是两种视锥体:**正交投影(Orthographic Projection)视锥体和Z-透视投影(Z-Perspective Projection)**视锥体。
裁剪空间变换对所有顶点的x、y、z分量进行了缩放,并对z分量进行了平移。
正交投影视锥体本身是一个立方体,这个立方体有两个面与摄像机的法线方向垂直,我们将这两个面中靠近摄像机的称为近裁剪平面(Near Clip Plane),它到摄像机的距离记为Near,远离摄像机的称为远裁剪平面(Far Clip Plane),它到摄像机的距离记为Far,Far-Near记为Depth。视锥体中在视线上方和下方的两个平面称为上裁剪平面和下裁剪平面,它们到摄像机法线的距离相等,记为Size。视锥体中在实现左方和右方的两个平面称为左裁剪平面和右裁剪平面,它们到摄像机法线的距离相等,将摄像机的横纵比Aspect记为Aspect。
Aspect由屏幕分辨率和摄像机决定,而视锥体宽度由Aspect*Size决定。
根据上面的参数得到正交裁剪矩阵:
Mortho=[1Aspect∗Size00001Size0000−2Depth−Far+NearDepth0001]M_{ortho}=\begin{bmatrix}\frac1{Aspect*Size}&0&0&0\\0&\frac1{Size}&0&0\\ 0&0&-\frac2{Depth}&-\frac{Far+Near}{Depth}\\0&0&0&1\end{bmatrix} Mortho=⎣⎢⎢⎡Aspect∗Size10000Size10000−Depth2000−DepthFar+Near1⎦⎥⎥⎤
Z-透视投影视锥体是一个四棱台,将四棱台补全为四棱锥,则四棱锥的侧楞交于观察空间原点(摄像机的坐标),视锥体上裁剪平面和下裁剪平面的夹角记位FOV(Field of Vied,视野)。摄像机的四棱台相互平行的两个面与摄像机法线方向垂直,我们将这两个面中靠近摄像机的称为近裁剪平面(Near Clip Plane),它到摄像机的距离记为Near,远离摄像机的称为远裁剪平面(Far Clip Plane),它到摄像机的距离记为Far,Far-Near记为Depth。将摄像机的横纵比Aspect记为Aspect。根据上面的参数得到透视裁剪矩阵:
Mfrustum=[cotFOV2Aspect0000cotFOV20000−Far+NearDepth−2∗Near∗FarDepth00−10]M_{frustum}=\begin{bmatrix}\frac{cot\frac{FOV}{2}}{Aspect}&0&0&0\\ 0&cot\frac{FOV}2&0&0\\0&0&-\frac{Far+Near}{Depth}&-\frac{2*Near*Far}{Depth}\\0&0&-1&0\end{bmatrix} Mfrustum=⎣⎢⎢⎢⎡Aspectcot2FOV0000cot2FOV0000−DepthFar+Near−100−Depth2∗Near∗Far0⎦⎥⎥⎥⎤
注意,在对顶点左乘Z-透视裁剪矩阵后顶点的w分量不再是1:
Pclip=MfrustumPview=[xcotFOV2AspectycotFOV2−zFar+NearDepth−2∗Near∗FarDepth−z]P_{clip}=M_{frustum}P_{view}=\begin{bmatrix}x\frac{cot\frac{FOV}2}{Aspect}\\ycot\frac{FOV}2\\-z\frac{Far+Near}{Depth}-\frac{2*Near*Far}{Depth}\\-z\end{bmatrix} Pclip=MfrustumPview=⎣⎢⎢⎢⎡xAspectcot2FOVycot2FOV−zDepthFar+Near−Depth2∗Near∗Far−z⎦⎥⎥⎥⎤
这是为了方便将裁剪空间坐标转换为NDC。
这里得到的两个裁剪空间坐标是基于Unity的,在其它平台上对裁剪空间坐标的定义不同,就会得到不同的裁剪变换矩阵。
我们注意到,Z-透视裁剪矩阵在z相等时将x和y缩放相同的倍数,这使得该矩阵只能实现针对点到摄像机平面(z深度)的近大远小效果,而不能实现针对点到摄像机距离(r深度)的近大远小效果,后者被称为R-透视(R-Perspective Projection)。例如,当一个具有网格纹理的平面平行于观察平面放置时,离摄像机较远的网格应对比离摄像机较近的网格小,而在Z-透视中忽略了这样的变化。
为了将Z-透视更新为R-透视,我们考虑适合R-透视的球壳形视锥体。在R-透视中上、下、左、右裁剪平面和Z-透视中类似,是四棱台的四个斜面。R-透视中的近、远裁剪平面改为以原点为球心,半径分别为Near和Far的球面的一部分。在R-透视中,到摄像机的距离相等的每个点应该具有相同的深度。为此我们对所有观察空间中的顶点进行一个深度变换:
P′=∣P∣PzP=Px2+Py2+Pz2PzPP'=\frac{\mid P\mid}{P_z}P=\frac{\sqrt{P_x^2+P_y^2+P_z^2}}{P_z}P P′=Pz∣P∣P=PzPx2+Py2+Pz2P
通过这个变换,我们将球壳形视锥体延展成了四棱台形,变换前到摄像机距离相同的点在新坐标下具有相同的深度值。并且由于坐标的三个分量进行了等量的缩放,顶点在球坐标系中的φ和θ都不会改变,这证明深度变换不会改变顶点在视野中的位置。然后我们再对新的顶点坐标使用Z-透视裁剪矩阵:
Pclip=MfrustumPview’P_{clip}=M_{frustum}P^{’}_{view} Pclip=MfrustumPview’
用Z-透视代替R-透视是值得的,因为深度变换不是线性变换,所以在R-透视中对于每一个点P都要进行一次求模运算和一次除法来求得P‘,相比Z-透视性能消耗高不少。而Z-透视的误差通常出现在镜头的边缘,这些误差大部分情况下都会被忽略,只有在及其追求真实感的场景中,才会对部分重要的网格使用R-透视。
什么是屏幕空间变换?
我们需要将视锥体投影到**屏幕空间(Screen Space)**中并获得像素坐标。
首先,我们要进行齐次除法(Homogeneous Division),就是用齐次坐标系的w分量去除x、y、z分量,这样得到的坐标在OpenGL中被称为归一化设备坐标(Normalized Device Coordinates,NDC)。按照OpenGL的传统,NDC的范围是[-1,1]。而在DirectX中,z的范围是[0,1]。为了匹配这样的规定,不同的硬件会使用不同的映射手段对z进行映射。
在NDC中,像素坐标的z值就是深度值,将被用于深度剔除。而x和y将被转换为像素坐标:
screenx=clipx∗pixelWidth2∗clipw+pixelWidth2screeny=clipy∗pixelHeight2∗clipw+pixelHeight2screen_x=\frac{clip_x*pixelWidth}{2*clip_w}+\frac{pixelWidth}2\\ screen_y=\frac{clip_y*pixelHeight}{2*clip_w}+\frac{pixelHeight}2 screenx=2∗clipwclipx∗pixelWidth+2pixelWidthscreeny=2∗clipwclipy∗pixelHeight+2pixelHeight
什么是法线变换?
一般来说,点和绝大部分方向矢量都可以使用同一个4×4或3×3的变换矩阵MA->B把其从坐标空间A变换到坐标空间B中。但在变换法线的时候(非统一缩放时),如果使用同一个变换矩阵,可能就无法确保法线的垂直性。
由于切线是由两个顶点之间的插值计算得到的,因此我们可以直接使用用于变换顶点的变换矩阵来变换切线,也就是MA->B:
TB⃗=MA−>BTA⃗\vec {T_B}=M_{A->B}\vec{T_A} TB=MA−>BTA
其中TA和TB分别表示在坐标空间A和B中的顶点切线方向。
我们知道,切线和法线应当垂直,即TA·NA=0。给定变换矩阵MA->B,我们已经知道TB=MA->BTA,现求矩阵G来变换法线NA使其满足TBNB=0:
TB⃗NB⃗=(MA−>BTA)⋅(GNA)=0\vec{T_B}\vec{N_B}=(M_{A->B}T_A)\cdot(GN_A)=0 TBNB=(MA−>BTA)⋅(GNA)=0
对上式进行推导后得:
(MA−>BTA)⋅(GNA)=(MA−>BTA)T(GNA)=TATMA−>BTGNA=TAT(MA−>BTG)NA=0(M_{A->B}T_A)\cdot(GN_A)=(M_{A->B}T_A)^T(GN_A)=T_A^TM_{A->B}^TGN_A=T_A^T(M_{A->B}^TG)N_A=0 (MA−>BTA)⋅(GNA)=(MA−>BTA)T(GNA)=TATMA−>BTGNA=TAT(MA−>BTG)NA=0
由于TA·NA=0,因此如果MTA->B=E,那么上式即可成立。也就是说:
G=(MA−>BT)−1=(MA−>B−1)TG=(M_{A->B}^T)^-1=(M_{A->B}^{-1})^T G=(MA−>BT)−1=(MA−>B−1)T
值得注意的是,如果原变换矩阵MA->B是正交矩阵,那么G=MA->B。如果变换只包含旋转,那么这个矩阵就是正交矩阵。若只包含旋转和统一缩放,统一缩放系数为k,可以得到:
G=1kMA−>BG=\frac1kM_{A->B} G=k1MA−>B
图形学笔记(四) 数学变换相关推荐
- 计算机图形学笔记(观测变换、模型变换、视图变换、投影变换、视口变换)
计算机图形学笔记(观测变换.模型变换.视图变换.投影变换.视口变换) 目录 计算机图形学笔记(观测变换.模型变换.视图变换.投影变换.视口变换) 一.简介 1.模型变换(Model transform ...
- 图形学笔记(四)变换——三维变换(三维旋转与欧拉角)、MVP变换、视图变换、投影变换(正交投影与透视投影)
图形学笔记(三)变换--缩放.镜像.切变 图形学笔记(五)光栅化--屏幕.像素.屏幕空间.视口变换.基础图元与三角形.采样.包围盒.锯齿或走样 文章目录 1 三维空间中的变换 1.1 三维空间中的齐次 ...
- 图形学笔记(五)光栅化——屏幕、像素、屏幕空间、视口变换、基础图元与三角形、采样、包围盒、锯齿或走样
图形学笔记(四)变换--三维变换(三维旋转与欧拉角).MVP变换.视图变换.投影变换(正交投影与透视投影) 图形学笔记(六)光栅化2 -- Artifacts.时域与频域.滤波.卷积定理.超采样.MS ...
- 【计算机图形学】小白谈计算机图形学(四)二维三维图形变换—1
小白谈计算机图形学(四)二维三维图形变换-1 窗口与视图 二维图形的几何变换 平移变换 比例变换 旋转变换 二维图形变换的矩阵表示 三种变换 齐次坐标变换 原二维线性变换 齐次坐标法 复合变换 例题: ...
- 理解计算机3D图形学中的坐标系变换
要谈坐标系变换,那么坐标系有哪些呢?依次有:物体坐标系,世界坐标系,相机坐标系,投影坐标系以及屏幕坐标系.我要讨论的就是这些坐标系间的转换. 这些坐标系不是凭空而来,他们都是为了完成计算机3 ...
- 【Visual C++】游戏开发笔记四十三 浅墨DirectX教程十一 为三维世界添彩:纹理映射技术(二)...
本系列文章由zhmxy555(毛星云)编写,转载请注明出处. 作者:毛星云(浅墨) 邮箱: happylifemxy@163.com 本篇文章里,我们首先对Direct3D之中固定功能流水线中的 ...
- 【Visual C++】游戏开发笔记四十六 浅墨DirectX教程十四 模板测试与镜面特效专场
本系列文章由zhmxy555(毛星云)编写,转载请注明出处. 文章链接: http://blog.csdn.net/zhmxy555/article/details/8632184 作者:毛星云( ...
- 计算机图形学基础1——MVP变换
参考链接: 线性变换 计算机图形学入门教程 视图变换 图形学随笔:MVP变换-视图变换 计算机图形学笔记-专栏 View/Camera Transformation视图变换 MVP变换: 我们知道我们 ...
- 图形学笔记(三)—— Harris角点检测器
图形学笔记(三)-- Harris角点检测器 前言 CSDN不支持我的公式,大家可以到我的博客:wang-sy.github.io去看 从现在开始学习的是书中的第二章:局部图像描述子.这里主要是寻找图 ...
最新文章
- paramiko的使用
- 漫画:骚操作系列(灯泡开关的经典面试题)
- .net中日至框架log4net.dll如何使用
- Solr-4.10.2安装
- Linux 下使用Java连接 mysql
- echarts 弹出放大_Echarts图标增加全屏/放大功能
- Spring Cloud连载(2)搭建开发环境
- java基础8 构造函数和构造代码块
- PHP 操作ini文件,读取及写入操作(代码)
- 电信云服务器装系统,天翼云主机重装系统的详细操作步骤
- 任强-京东智能云服务平台
- lighttpd 之九 配置信息加载
- Java学多久可以接项目_自学Java,多久可以找到工作?
- vscode replace with a newline
- 【免费】Linux命令行与Shell脚本编程大全 第3版 PDF全本 21MB 百度网盘下载
- 打造前端 Deepin Linux 工作环境——安装最新版本的火狐firefox浏览器
- SAP ABAP 调用 BAPI_GOODSMVT_CREATE 没有执行 MIGO/MB0A 相同检查的问题
- python实现凤凰新闻监控
- 自动驾驶系统入门(八)- 自动驾驶仿真技术
- 从50分到90分,网站性能优化实践
热门文章
- 二方外包和三方外包是什么?
- 【CXY】JAVA基础 之 Set
- linux下文件损坏怎么删除 No such file or directory
- 几道特别难搞的数据库面试题
- 计算机控制系统的输入输出信号,工业控制系统的输入与输出信号
- Using GDB To Trace Into a Parallel Worker Spawned By Postmaster During a Large Query
- 用新浪SAE免费搭建自己的应用
- maven打包报错 Failed to execute goal org.apache.maven.plugins:maven-jar-plugin:3.0.2:jar
- OS知识点汇总(考研用)——第二章:进程管理(下)
- 用有数据的单元格内容向下填充空白单元格