精华内容
下载资源
问答
  • 【3D计算机图形学】变换矩阵、欧拉角、四元数(示例代码)
    2021-08-01 02:57:12

    【3D计算机图形学】变换矩阵、欧拉角、四元数

    旋转矩阵、欧拉角、四元数主要用于:向量的旋转、坐标系之间的转换、角位移计算、方位的平滑插值计算。

    一、变换矩阵:

    首先要区分旋转矩阵和变换矩阵:

    旋转矩阵:向量绕某一个轴旋转,用3x3的矩阵表示。

    变换矩阵:向量的移动、旋转、缩放,用4x4的矩阵表示。

    这里额外补充一个知识,就是三维坐标变换是用4x4矩阵(采用齐次坐标)而不是3x3矩阵的原因是:统一平移和缩放(本来是向量加法来描述)为矩阵乘法的形式来计算。所以旋转矩阵也扩展为4x4矩阵,这样一来,平移矩阵、缩放矩阵、旋转矩阵可以相乘最后结果为一个唯一的变换矩阵。

    可以参考下面这篇文章,解释的很清楚:

    旋转矩阵的推导过程网上有很多,这里不再赘述。可以参考如下文章:

    二、欧拉角:

    欧拉角的基本思想是将角位移分解为绕三个互相垂直轴的三个旋转组成的序列。所以,欧拉旋转的三个角,可以对应于三个旋转矩阵。

    Yaw(偏航):欧拉角向量的y轴

    Pitch(俯仰):欧拉角向量的x轴

    Roll(翻滚): 欧拉角向量的z轴

    Unity3D中,欧拉旋转是按照ZYX的顺序旋转的。(不同的旋转顺序最终得到的结果是不一样的,要引起重视)

    三、四元数:

    四元数的定义:

    一个四元数可以表示为q = w + xi + yj + zk,现在就来回答这样一个简单的式子是怎么和三维旋转结合在一起的。为了方便,我们下面使用q = ((x, y, z),w) = (v, w),其中v是向量,w是实数,这样的式子来表示一个四元数。

    我们先来看问题的答案。我们可以使用一个四元数q=((x,y,z)sinθ2, cosθ2) 来执行一个旋转。具体来说,如果我们想要把空间的一个点P绕着单位向量轴u

    = (x, y, z)表示的旋转轴旋转θ角度,我们首先把点P扩展到四元数空间,即四元数p = (P,

    0)。那么,旋转后新的点对应的四元数(当然这个计算而得的四元数的实部为0,虚部系数就是新的坐标)为:

    p′=qpq−1

    其中,q=(cosθ2, (x,y,z)sinθ2) ,q−1=q∗N(q)

    当然,四元数不仅可以用来方便的进行向量旋转计算,还能够用于平滑插值计算等。

    四元数更多的介绍参看下面的链接;

    四、旋转矩阵、欧拉角、四元数比较:

    任务/性质

    旋转矩阵

    欧拉角

    四元数

    在坐标系间(物体和惯性)旋转点

    不能(必须转换到矩阵)

    不能(必须转换到矩阵)

    连接或增量旋转

    能,但经常比四元数慢,小心矩阵蠕变的情况

    不能

    能,比矩阵快

    插值

    基本上不能

    能,但可能遭遇万向锁或其他问题

    Slerp提供了平滑插值

    易用程度

    在内存或文件中存储

    9个数

    3个数

    4个数

    对给定方位的表达方式是否唯一

    不是,对同一方位有无数多种方法

    不是,有两种方法,它们互相为互

    可能导致非法

    矩阵蠕变

    任意三个数都能构成合法的欧拉角

    可能会出现误差积累,从而产生非法的四元数

    不同的方位表示方法适用于不同的情况。下面是我们对合理选择格式的一些建议:

    欧拉角最容易使用。当需要为世界中的物体指定方位时,欧拉角能大大的简化人机交互,

    包括直接的键盘输入方位、在代码中指定方位(如为渲染设定摄像机)、在调试中测试。这个优点不应该被忽视,不要以”优化”为名义而牺牲易用性,除非你去顶这种优化的确有效果。

    如果需要在坐标系之间转换响亮,那么就选择矩阵形式。当然,这并不意味着你就不能用其他格式来保存方位,并在需要的时候转换到矩阵格式。另一种方法是用欧拉角作为方位的”主拷贝”但同时维护一个旋转矩阵,当欧拉角发生改变时矩阵也要同时进行更新。

    当需要大量保存方位数据(如:动画)时,就使用欧拉角或四元数。欧拉角将少占用25%的内存,但它在转换到矩阵时要稍微慢一些。如果动画数据需要嵌套坐标系之间的连接,四元数可能是最好的选择。

    平滑的插值只能用四元数完成。如果你用其他形式,也可以先转换到四元数然后再插值,插值完毕后再转换回原来的形式。

    五、欧拉角与四元数的转换:

    参看下面一篇文章,讲的比较通俗易懂:

    六、Unity3D中变换矩阵(平移、缩放、旋转)、欧拉角、四元数有关的类和相互之间的转换:

    Quaternion:四元数类。

    Matrix4x4:变换矩阵类。

    Vector3:欧拉角用此表示就可以了。

    四元数转欧拉角:

    public Quaternion rotation = Quaternion.identity;

    print(rotation.eulerAngles.x);

    print(rotation.eulerAngles.y);

    print(rotation.eulerAngles.z);

    欧拉角转四元数:

    public Quaternion rotation = Quaternion.Euler(0, 30, 0);//Euler方法即通过一个Vector3的欧拉角返回一个四元数

    四元数转变换矩阵:

    Quaternion q = Quaternion.LookRotation(new Vector3(0,0.5,1));

    Matrix4x4 rot = new Matrix4x4();

    rot.SetTRS(new Vector3(0,0,0),q,new Vector3(1,1,1));

    变换矩阵转四元数:

    Matrix4x4 rot = new Matrix4x4();

    rot.SetTRS(new Vector3(0,0,0),q,new Vector3(1,1,1));

    Vector4 vy = rot.GetColumn(1);

    Vector4 vz = rot.GetColumn(2);

    Quaternion newQ = Quaternion.LookRotation(new Vector3(vz.x,vz.y,vz.z),new Vector3(vy.x,vy.y,vy.z));

    更多相关内容
  • 图形学 二维图形的几何变换 对称平移缩放旋转 矩阵实现 C++
  • 基本图形变换:平移、旋转、变比、对称。符合变换:相对非原点的变比,绕非原点旋转。 关键字:二维图形变换,平移,旋转,变比
  • MFC写的实现立方体旋转功能,使用矩阵相乘方式,没有调用openGL旋转函数。
  • 计算机图形学(第三版)二维复合矩阵编程示例代码代码运行软件版本(Visual Studio 2015)【软件安装教程百度“VS2015安装+OpenGL环境配置及测试”】 参考书本代码203~207页代码,稍作修改,实现二维图形平移,...
  • 4.程序先绘制一个长方体,然后隔0.1秒又绘制出另一个围绕z轴旋转i角度的长方体, 循环中每次(每个顶点)都被旋转矩阵变换成新的顶点。把变换矩阵改成围绕x轴的旋转矩阵、y轴旋转的 变换矩阵, 5. 透视投影和平行...
  • 以下均为MFC工程代码代码一:简单实现void CGeotranView::Onyuantu(){// 原图,画三角形CDC *pDC=GetDC();pDC->MoveTo(100,100);pDC->LineTo(200,50);pDC->LineTo(200,150);pDC->LineTo(100,100);...

    以下均为MFC工程代码

    代码一:简单实现

    void CGeotranView::Onyuantu()

    {

    // 原图,画三角形

    CDC *pDC=GetDC();

    pDC->MoveTo(100,100);

    pDC->LineTo(200,50);

    pDC->LineTo(200,150);

    pDC->LineTo(100,100);

    ReleaseDC(pDC);

    }

    void CGeotranView::Ontranslation()

    {

    // 平移 tx=50,ty=60

    CDC *pDC=GetDC();

    int tx=50,ty=60;

    pDC->MoveTo(100+tx,100+ty);

    pDC->LineTo(200+tx,50+ty);

    pDC->LineTo(200+tx,150+ty);

    pDC->LineTo(100+tx,100+ty);

    ReleaseDC(pDC);

    }

    void CGeotranView::Onrotation()

    {

    // 旋转, 角度sita=30度

    CDC *pDC=GetDC();

    double sita=30;

    double hudu=sita*3.14159/180;

    pDC->MoveTo(int(100*cos(hudu)-100*sin(hudu)),int(100*sin(hudu)+100*cos(hudu)));

    pDC->LineTo(int(200*cos(hudu)-50*sin(hudu)),int(200*sin(hudu)+50*cos(hudu)));

    pDC->LineTo(int(200*cos(hudu)-150*sin(hudu)),int(200*sin(hudu)+150*cos(hudu)));

    pDC->LineTo(int(100*cos(hudu)-100*sin(hudu)),int(100*sin(hudu)+100*cos(hudu)));

    ReleaseDC(pDC);

    }

    void CGeotranView::Onscaling()

    {

    // 缩放 sx=2,sy=3

    int sx=2,sy=3;

    CDC *pDC=GetDC();

    pDC->MoveTo(100*sx,100*sy);

    pDC->LineTo(200*sx,50*sy);

    pDC->LineTo(200*sx,150*sy);

    pDC->LineTo(100*sx,100*sy);

    ReleaseDC(pDC);

    }

    代码二:新的点的坐标计算通过函数实现

    POINT translationPoint(POINT point,int tx,int ty)

    {

    POINT newpoint;

    newpoint.x=point.x+tx;

    newpoint.y=point.y+ty;

    return newpoint;

    }

    POINT rotationPoint(POINT point,double sita)

    {

    POINT newpoint;

    double hudu=sita*3.14159/180;

    newpoint.x=(int)(point.x*cos(hudu)-point.y*sin(hudu));

    newpoint.y=(int)(point.x*sin(hudu)+point.y*cos(hudu));

    return newpoint;

    }

    POINT scalingPoint(POINT point,int sx,int sy)

    {

    POINT newpoint;

    newpoint.x=point.x*sx;

    newpoint.y=point.y*sy;

    return newpoint;

    }

    void CGeotranView::Onyuantu2()

    {

    POINT point1,point2,point3;

    point1.x=200;point1.y=200;

    point2.x=300;point3.y=150;

    point3.x=300;point2.y=250;

    // 原图,画三角形

    CDC *pDC=GetDC();

    pDC->MoveTo(point1);

    pDC->LineTo(point2);

    pDC->LineTo(point3);

    pDC->LineTo(point1);

    ReleaseDC(pDC);

    }

    void CGeotranView::Ontranslation2()

    {

    POINT point1,point2,point3;

    point1.x=200;point1.y=200;

    point2.x=300;point3.y=150;

    point3.x=300;point2.y=250;

    // 平移 tx=50,ty=60

    CDC *pDC=GetDC();

    int tx=50,ty=60;

    pDC->MoveTo(translationPoint(point1,tx,ty));

    pDC->LineTo(translationPoint(point2,tx,ty));

    pDC->LineTo(translationPoint(point3,tx,ty));

    pDC->LineTo(translationPoint(point1,tx,ty));

    ReleaseDC(pDC);

    }

    void CGeotranView::Onrotation2()

    {

    POINT point1,point2,point3;

    point1.x=200;point1.y=200;

    point2.x=300;point3.y=150;

    point3.x=300;point2.y=250;

    // 旋转, 角度sita=30度

    CDC *pDC=GetDC();

    double sita=30;

    pDC->MoveTo(rotationPoint(point1,sita));

    pDC->LineTo(rotationPoint(point2,sita));

    pDC->LineTo(rotationPoint(point3,sita));

    pDC->LineTo(rotationPoint(point1,sita));

    ReleaseDC(pDC);

    }

    void CGeotranView::Onscaling2()

    {

    POINT point1,point2,point3;

    point1.x=200;point1.y=200;

    point2.x=300;point3.y=150;

    point3.x=300;point2.y=250;

    // 缩放 sx=2,sy=2

    int sx=2,sy=2;

    CDC *pDC=GetDC();

    pDC->MoveTo(scalingPoint(point1,sx,sy));

    pDC->LineTo(scalingPoint(point2,sx,sy));

    pDC->LineTo(scalingPoint(point3,sx,sy));

    pDC->LineTo(scalingPoint(point1,sx,sy));

    ReleaseDC(pDC);

    }

    展开全文
  • 计算机图形学实验代码(包括图形旋转、图形平移、图形缩放) 内含C++源代码和测试图片
  • 图形学中的矩阵变换

    千次阅读 2020-08-25 18:35:36
    图形学因为要处理三维中的物体,所以经常要用到矩阵变换,包括基础的模型变换(旋转、平移、缩放变换),以及投影变换、视口变换。这其中有很多有意思的数学知识。网络上虽然有很多介绍矩阵变换的博客,但是很多只...

    摘要

    图形学因为要处理三维中的物体,所以经常要用到矩阵变换,包括基础的模型变换(旋转、平移、缩放变换),以及投影变换、视口变换。这其中有很多有意思的数学知识。网络上虽然有很多介绍矩阵变换的博客,但是很多只介绍了基本的矩阵运算,没有深入介绍四元数等高级用法。因此本文将从模型变换入手,介绍图形学中的插值操作,包括四元数等内容。

    模型变换

    模型变换是将一个点(模型)变换到另一个点(模型)的操作。一般我们会把三维点用齐次坐标(homogeneous notation)表示,所以变换矩阵是 4 × 4 4\times4 4×4的。另一个需要注意的点是,矩阵乘法运算一般情况下不满足交换律,所以变换的顺序很重要,一个直观的例子是原点处的物体先平移再绕原点旋转的结果与先绕原点旋转再平移的结果是不一样的。这里我们约定,平移变换 T T T、缩放变换 S S S、旋转变换 R R R的顺序依次是先缩放再旋转最后平移,表示为:
    M = T R S M=TRS M=TRS

    平移变换

    平移变换比较自然的一种表示形式是向量——这也是向量的物理意义。假设一个平移操作 t = ( t x , t y , t x ) {\bf{t}}=(t_x, t_y, t_x) t=(tx,ty,tx),写为矩阵是:
    T ( t ) = T ( t x , t y , t z ) = ( 1 0 0 t x 0 1 0 t y 0 0 1 t x 0 0 0 1 ) {\bf{T}}({\bf{t}}) = T(t_x, t_y, t_z) = \begin{pmatrix} 1 & 0 & 0 & t_x \\ 0 & 1 & 0 & t_y \\ 0 & 0 & 1 & t_x \\ 0 & 0 & 0 & 1 \\ \end{pmatrix} T(t)=T(tx,ty,tz)=100001000010txtytx1
    平移矩阵的逆矩阵也很容易求,将 t {\bf{t}} t取负即可: T − 1 ( t ) = T ( − t ) {\bf{T}}^{-1}({\bf{t}})={\bf{T}}(-{\bf{t}}) T1(t)=T(t)。逻辑上比较容易理解。

    缩放变换

    缩放变换用于将 x x x y y y z z z轴依次按系数 ( s x , s y , s y ) (s_x, s_y, s_y) (sx,sy,sy)进行缩放,缩放矩阵表示为:
    S ( s ) = ( s x 0 0 0 0 s y 0 0 0 0 s z 0 0 0 0 1 ) {\bf{S}}({\bf{s}}) = \begin{pmatrix} s_x & 0 & 0 & 0 \\ 0 & s_y & 0 & 0 \\ 0 & 0 & s_z & 0 \\ 0 & 0 & 0 & 1 \\ \end{pmatrix} S(s)=sx0000sy0000sz00001
    其逆矩阵 S − 1 ( s ) = S ( 1 / s x , 1 / s y , 1 / s z ) {\bf{S}}^{-1}({\bf{s}})={\bf{S}}(1/s_x, 1/s_y, 1/s_z) S1(s)=S(1/sx,1/sy,1/sz)

    旋转变换

    旋转变换相较于前两种变换要复杂一些。我们先看简单的形式,假设旋转轴是 x x x y y y z z z轴,旋转角度为 ϕ \phi ϕ,那么旋转矩阵可以写为:
    R x ( ϕ ) = ( 1 0 0 0 0 cos ⁡ ϕ − sin ⁡ ϕ 0 0 sin ⁡ ϕ cos ⁡ ϕ 0 0 0 0 1 ) R y ( ϕ ) = ( cos ⁡ ϕ 0 sin ⁡ ϕ 0 0 1 0 0 − sin ⁡ ϕ 0 cos ⁡ ϕ 0 0 0 0 1 ) R z ( ϕ ) = ( cos ⁡ ϕ − sin ⁡ ϕ 0 0 sin ⁡ ϕ cos ⁡ ϕ 0 0 0 0 1 0 0 0 0 1 ) {\bf{R}}_x({\bf{\phi}}) = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & \cos\phi & -\sin\phi & 0 \\ 0 & \sin\phi & \cos\phi & 0 \\ 0 & 0 & 0 & 1 \\ \end{pmatrix} \\ {\bf{R}}_y({\bf{\phi}}) = \begin{pmatrix} \cos\phi & 0 & \sin\phi & 0 \\ 0 & 1 & 0 & 0 \\ -\sin\phi & 0 & \cos\phi & 0 \\ 0 & 0 & 0 & 1 \\ \end{pmatrix} \\ {\bf{R}}_z({\bf{\phi}}) = \begin{pmatrix} \cos\phi & -\sin\phi & 0 & 0 \\ \sin\phi & \cos\phi & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \\ \end{pmatrix} Rx(ϕ)=10000cosϕsinϕ00sinϕcosϕ00001Ry(ϕ)=cosϕ0sinϕ00100sinϕ0cosϕ00001Rz(ϕ)=cosϕsinϕ00sinϕcosϕ0000100001
    其逆矩阵
    R i − 1 ( ϕ ) = R i ( − ϕ ) = R i T ( ϕ ) {\bf{R}}^{-1}_i(\phi)={\bf{R}}_i(-\phi)={\bf{R}}^{T}_i(\phi) Ri1(ϕ)=Ri(ϕ)=RiT(ϕ)
    也即:
    R i T ( ϕ ) R i ( ϕ ) = I {\bf{R}}^{T}_i(\phi){\bf{R}}_i(\phi) = {\bf{I}} RiT(ϕ)Ri(ϕ)=I

    其他一些有趣的性质包括:
    det ( R ( ϕ ) ) = 1 R i ( 0 ) = I R i ( ϕ 1 ) R i ( ϕ 2 ) = R i ( ϕ 1 + ϕ 2 ) R i ( ϕ 1 ) R i ( ϕ 2 ) = R i ( ϕ 2 ) R i ( ϕ 1 ) \begin{aligned} \text{det}({\bf{R}}(\phi)) & = 1 \\ {\bf{R}}_i(0) & = {\bf{I}} \\ {\bf{R}}_i(\phi_1){\bf{R}}_i(\phi_2) & = {\bf{R}}_i(\phi_1 + \phi_2) \\ {\bf{R}}_i(\phi_1){\bf{R}}_i(\phi_2) & = {\bf{R}}_i(\phi_2){\bf{R}}_i(\phi_1) \end{aligned} det(R(ϕ))Ri(0)Ri(ϕ1)Ri(ϕ2)Ri(ϕ1)Ri(ϕ2)=1=I=Ri(ϕ1+ϕ2)=Ri(ϕ2)Ri(ϕ1)

    绕任意轴的旋转

    绕任意轴的旋转看起来会比较复杂,但是仔细分析一下其实并不困难。推导过程主要分两步,

    1. 求旋转前后两个向量的关系;
    2. 将这个关系转换为矩阵;

    假设我们需要将向量 v {\bf{v}} v绕着轴 a {\bf{a}} a旋转角度 θ \theta θ,如下图所示。
    rotate_arbitrary_axis
    v c {\bf{v}}_c vc表示 v {\bf{v}} v在轴 a {\bf{a}} a上的投影,即:
    v c = a ∣ ∣ v ∣ ∣ cos ⁡ α = a ( v ⋅ a ) {\bf{v}}_c = {\bf{a}}||{\bf{v}}||\cos\alpha={\bf{a}}({\bf{v}}\cdot{\bf{a}}) vc=avcosα=a(va)
    并且令:
    v 1 = v − v c v 2 = v 1 × a {\bf{v}}_1={\bf{v}}-{\bf{v}}_c \\ {\bf{v}}_2={\bf{v}}_1\times{\bf{a}} v1=vvcv2=v1×a
    则根据 v 1 {\bf{v}}_1 v1 v 1 ′ {\bf{v}}_1' v1 v 2 {\bf{v}}_2 v2 v 2 ′ {\bf{v}}_2' v2的关系,可以求得:
    v ′ = v c + v 1 cos ⁡ θ + v 2 sin ⁡ θ {\bf{v}}'={\bf{v}}_c+{\bf{v}}_1\cos\theta+{\bf{v}}_2\sin\theta v=vc+v1cosθ+v2sinθ
    这样就得到了 v ′ {\bf{v}}' v v {\bf{v}} v的关系。

    接下来,考虑如何将这个向量关系转换为矩阵关系。思路就是,旋转矩阵的每一行相当于对相应坐标轴 ( 1 , 0 , 0 ) (1,0,0) (1,0,0) ( 0 , 1 , 0 ) (0,1,0) (0,1,0) ( 0 , 0 , 1 ) (0,0,1) (0,0,1)的旋转操作,我们可以分别计算三个坐标轴基底旋转之后的向量,也即旋转矩阵的对应行。
    R x ( ϕ ) = ( cos ⁡ θ + ( 1 − cos ⁡ θ ) a x 2 ( 1 − cos ⁡ θ ) a x a y − a z sin ⁡ θ ( 1 − cos ⁡ θ ) a x a z + a y sin ⁡ θ 0 ( 1 − cos ⁡ θ ) a x a y + a z sin ⁡ θ cos ⁡ θ + ( 1 − cos ⁡ θ ) a y 2 ( 1 − cos ⁡ θ ) a y a z − a x sin ⁡ θ 0 ( 1 − cos ⁡ θ ) a x a z − a y sin ⁡ θ ( 1 − cos ⁡ θ ) a y a z + a x sin ⁡ θ cos ⁡ θ + ( 1 − cos ⁡ θ ) a z 2 0 0 0 0 1 ) {\bf{R}}_x({\bf{\phi}}) = \begin{pmatrix} \cos\theta + (1-\cos\theta)a_x^2 & (1-\cos\theta)a_xa_y-a_z\sin\theta & (1-\cos\theta)a_xa_z+a_y\sin\theta & 0 \\ (1-\cos\theta)a_xa_y+a_z\sin\theta & \cos\theta+(1-\cos\theta)a_y^2 & (1-\cos\theta)a_ya_z-a_x\sin\theta & 0 \\ (1-\cos\theta)a_xa_z-a_y\sin\theta & (1-\cos\theta)a_ya_z+a_x\sin\theta & \cos\theta+(1-\cos\theta)a_z^2 & 0 \\ 0 & 0 & 0 & 1 \\ \end{pmatrix} Rx(ϕ)=cosθ+(1cosθ)ax2(1cosθ)axay+azsinθ(1cosθ)axazaysinθ0(1cosθ)axayazsinθcosθ+(1cosθ)ay2(1cosθ)ayaz+axsinθ0(1cosθ)axaz+aysinθ(1cosθ)ayazaxsinθcosθ+(1cosθ)az200001
    具体的代码实现如下。

    Matrix4x4 Rotate(float theta, const Vector3f &axis) {
        Vector3f a = Normalize(axis);
        float sinTheta = std::sin(Radians(theta));
        float cosTheta = std::cos(Radians(theta));
        Matrix4x4 m = Matrix4x4::Identity();
        // Compute rotation of first basis vector
        m.m[0][0] = a.x * a.x + (1 - a.x * a.x) * cosTheta;
        m.m[0][1] = a.x * a.y * (1 - cosTheta) - a.z * sinTheta;
        m.m[0][2] = a.x * a.z * (1 - cosTheta) + a.y * sinTheta;
        m.m[0][3] = 0;
        // Compute rotations of second basis vector
        m.m[1][0] = a.x * a.y * (1 - cosTheta) + a.z * sinTheta;
        m.m[1][1] = a.y * a.y + (1 - a.y * a.y) * cosTheta;
        m.m[1][2] = a.y * a.z * (1 - cosTheta) - a.x * sinTheta;
        m.m[1][3] = 0;
        // Compute rotations of third basis vector
        m.m[2][0] = a.x * a.z * (1 - cosTheta) - a.y * sinTheta;
        m.m[2][1] = a.y * a.z * (1 - cosTheta) + a.x * sinTheta;
        m.m[2][2] = a.z * a.z + (1 - a.z * a.z) * cosTheta;
        m.m[2][3] = 0;
        return m;
    }
    

    欧拉变换

    由于矩阵乘法不满足交换律,因此规定旋转的先后顺序很重要。这里我们介绍比较常用的欧拉变换(Euler Transform)。

    如下图所示,假定视角方向是z轴方向,物体的放置为面对z轴负方向,上方向为y轴方向。定义沿x轴正方向的旋转为pitch,沿y轴正方向的旋转为head,z轴正方向的旋转为roll,那么这三个旋转操作可以以此对应到三个旋转矩阵 R x ( p ) {\bf{R}}_x(p) Rx(p) R y ( h ) {\bf{R}}_y(h) Ry(h) R z ( r ) {\bf{R}}_z(r) Rz(r)。:
    Euler_transform
    欧拉变换定义了这三个旋转的操作顺序为
    R ( h , p , r ) = R z ( r ) R x ( p ) R y ( h ) R ( h , p , r ) = ( cos ⁡ r cos ⁡ h − sin ⁡ r sin ⁡ p sin ⁡ h − sin ⁡ r cos ⁡ p cos ⁡ r sin ⁡ h + sin ⁡ r sin ⁡ p cos ⁡ h 0 sin ⁡ r cos ⁡ h + cos ⁡ r sin ⁡ p sin ⁡ h cos ⁡ r cos ⁡ p sin ⁡ r sin ⁡ h − cos ⁡ r sin ⁡ p cos ⁡ h 0 − cos ⁡ p sin ⁡ h sin ⁡ p cos ⁡ p cos ⁡ h 0 0 0 0 1 ) \begin{aligned} {\bf{R}}(h,p,r) & ={\bf{R}}_z(r){\bf{R}}_x(p){\bf{R}}_y(h) \\ {\bf{R}}(h,p,r) & = \begin{pmatrix} \cos{r}\cos{h}-\sin{r}\sin{p}\sin{h} & -\sin{r}\cos{p} & \cos{r}\sin{h}+\sin{r}\sin{p}\cos{h} & 0 \\ \sin{r}\cos{h}+\cos{r}\sin{p}\sin{h} & \cos{r}\cos{p} & \sin{r}\sin{h}-\cos{r}\sin{p}\cos{h} & 0 \\ -\cos{p}\sin{h} & \sin{p} & \cos{p}\cos{h} & 0 \\ 0 & 0 & 0 & 1 \\ \end{pmatrix} \\ \end{aligned} R(h,p,r)R(h,p,r)=Rz(r)Rx(p)Ry(h)=cosrcoshsinrsinpsinhsinrcosh+cosrsinpsinhcospsinh0sinrcospcosrcospsinp0cosrsinh+sinrsinpcoshsinrsinhcosrsinpcoshcospcosh00001
    有了这个约定,就可以顺利的从旋转矩阵提取旋转角度,或者从旋转角度恢复旋转矩阵了。
    h = arctan ⁡ ( − r 20 , r 22 ) p = arcsin ⁡ ( r 21 ) r = arctan ⁡ ( − r 01 , r 11 ) \begin{aligned} h & = \arctan(-r_{20}, r_{22}) \\ p & = \arcsin(r_{21}) \\ r & = \arctan(-r_{01}, r_{11}) \end{aligned} hpr=arctan(r20,r22)=arcsin(r21)=arctan(r01,r11)
    其中, arctan ⁡ ( y , x ) \arctan(y,x) arctan(y,x)表示 y / x y/x y/x的反正切函数,其值域是 [ 0 , 2 π ) [0,2\pi) [0,2π)。注意,这里与传统的值域为 ( − π / 2 , π / 2 ) (-\pi/2,\pi/2) (π/2,π/2)的反正切函数不一样。因为我们定义的旋转角度是 2 π 2\pi 2π的范围,传统的反正切函数是无法满足的。而且,考虑 x x x y y y同号/异号的情况下, y / x y/x y/x的符号始终为正/负,是无法区分该角度所在的象限的。因此传入两个参数求反正切函数是有必要的。Real-Time Rendering将这个改进的反正切函数记为 a t a n 2 ( y , x ) \rm{atan2}(y,x) atan2(y,x)。同时,C++的标准库也内置了这个函数。

    代码实现:

    Matrix4x4 Rotate(float h, float p, float r){
    	Matrix4x4 rotate;
    	float sinh = std::sin(h);
    	float cosh = std::cos(h);
    	float sinp = std::sin(p);
    	float cosp = std::cos(p);
    	float sinr = std::sin(r);
    	float cosr = std::cos(r);
    	rotate(0,0) = cosr*cosh-sinr*sinp*sinh;
    	rotate(0,1) = -sinr*cosp;
    	rotate(0,2) = cosr*sinh+sinr*sinp*cosh;
    	rotate(0,3) = 0;
    	rotate(1,0) = sinr*cosh+cosr*sinp*sinh;
    	rotate(1,1) = cosr*cosp;
    	rotate(1,2) = sinr*sinh-cosr*sinp*cosh;
    	rotate(1,3) = 0;
    	rotate(2,0) = -cosp*sinh;
    	rotate(2,1) = sinp;
    	rotate(2,2) = cosp*cosh;
    	rotate(2,3) = 0;
    	rotate(3,0) = 0;
    	rotate(3,1) = 0;
    	rotate(3,2) = 0;
    	rotate(3,3) = 1;
    	return rotate;
    }
    void RotateDecompose(Matrix4x4 rotate, float& h, float& p, float& r){
    	h = std::atan2(-rotate(2,0), rotate(2,2));
    	p = std::asin(rotate(2,1));
    	r = std::atan2(-rotate(0,1), rotate(1,1));
    }
    

    矩阵分解

    前面介绍的基本上是如何生成模型变换矩阵,有时候我们也需要将给定的模型变换矩阵分解为平移、缩放、旋转矩阵。简便起见,我们假设给定的变换矩阵 M M M仅包含平移 T T T、缩放 S S S和旋转 R R R三个操作。

    分离平移操作

    平移操作是最容易分解出来的,因为矩阵的第四列的前三项代表的就是平移项。提取出来后将对应的位置置零,得到的矩阵 M ′ {\bf{M}}' M就是仅包含缩放和旋转的矩阵。
    T = ( M 03 , M 13 , M 23 ) T = (M_{03}, M_{13}, M_{23}) T=(M03,M13,M23)

    分离旋转操作

    旋转操作的分解要相对麻烦一些。但是也有很多数值分析的方法,例如QR分解,奇异值分解(SVD),极分解(Polar Decomposition)。对于我们这个应用场景来说,我们希望获得一个正交矩阵R和一个对角线矩阵,极分解是最好的方法。

    极分解的问题描述是这样的,给定一个矩阵 M {\bf{M}} M,计算正交系数 R {\bf{R}} R使得 R T R = I {\bf{R}}^T{\bf{R}}={\bf{I}} RTR=I,且 ∣ ∣ R − M ∣ ∣ F 2 = ∑ i , j ( R i j − M i j ) 2 ||{\bf{R}}-{\bf{M}}||_F^2=\sum_{i,j}(R_{ij}-M_{ij})^2 RMF2=i,j(RijMij)2最小。

    具体的推导过程在这里就不展开了,有兴趣的读者可以参考[9]的附录部分。这里直接给出近似的数值解:

    • R 0 = M {\bf{R}}_0={\bf{M}} R0=M
    • 计算 R i + 1 = 1 2 ( R + R − T ) {\bf{R}}_{i+1}=\frac{1}{2}({\bf{R}}+{\bf{R}}^{-T}) Ri+1=21(R+RT)
    • 迭代,直到 R i + 1 − R i ≈ 0 {\bf{R}}_{i+1}-{\bf{R}}_i\approx{\bf{0}} Ri+1Ri0

    得到的 R {\bf{R}} R就是我们要求的旋转矩阵。

    需要注意的一点是,用极分解的前提是待分解的矩阵仅包含旋转操作和缩放操作。

    分离缩放矩阵

    有了前面两步,缩放矩阵只需要用 M ′ {\bf{M}}' M左乘 R − 1 {\bf{R}}^{-1} R1即可:
    S = R − 1 M ′ {\bf{S}}={\bf{R}}^{-1}{\bf{M}}' S=R1M

    代码实现

    完整的矩阵分解代码实现(参考PBRT):

    void Decompose(const Matrix4x4 &m, Vector3f *T, Matrix4x4 *Rout, Matrix4x4 *S) {
        // Extract translation _T_ from transformation matrix
        T->x = m.m[0][3];
        T->y = m.m[1][3];
        T->z = m.m[2][3];
    
        // Compute new transformation matrix _M_ without translation
        Matrix4x4 M = m;
        for (int i = 0; i < 3; ++i) 
            M.m[i][3] = M.m[3][i] = 0.f;
        M.m[3][3] = 1.f;
    
        // Extract rotation _R_ from transformation matrix
        Float norm;
        int count = 0;
        Matrix4x4 R = M;
        do {
            // Compute next matrix _Rnext_ in series
            Matrix4x4 Rnext;
            Matrix4x4 Rit = Inverse(Transpose(R));
            for (int i = 0; i < 4; ++i)
                for (int j = 0; j < 4; ++j)
                    Rnext.m[i][j] = 0.5f * (R.m[i][j] + Rit.m[i][j]);
    
            // Compute norm of difference between _R_ and _Rnext_
            norm = 0;
            for (int i = 0; i < 3; ++i) {
                Float n = std::abs(R.m[i][0] - Rnext.m[i][0]) +
                          std::abs(R.m[i][1] - Rnext.m[i][1]) +
                          std::abs(R.m[i][2] - Rnext.m[i][2]);
                norm = std::max(norm, n);
            }
            R = Rnext;
        } while (++count < 100 && norm > .0001);
        *Rout = R;
    
        // Compute scale _S_ using rotation and original matrix
        *S = Matrix4x4::Mul(Inverse(R), M);
    }
    
    

    插值

    了解了基本的几种变换,我们再来讨论一下常用的插值操作。

    插值(lerp)可以理解为,求两个状态的中间状态。给定状态A和状态B,以及介于0到1的系数 t = a / ( a + b ) t=a/(a+b) t=a/(a+b),求 t t t时刻的状态

    C = lerp(A, B, t)
    

    比如对位置插值就是求两个点中间某个点。对平移、缩放、旋转做插值就是在平移、缩放、旋转空间求两个状态的中间状态。
    lerp
    线性插值就是用线性刻度(linear scale)度量 x 1 x_1 x1 x 2 x_2 x2

    x 2 − x x − x 1 = b a = 1 − t t x = t x 2 + ( 1 − t ) x 1 \begin{aligned} \frac{x_2-x}{x-x_1} & = \frac{b}{a}=\frac{1-t}{t} \\ x & = tx_2 + (1-t) x_1 \end{aligned} xx1x2xx=ab=t1t=tx2+(1t)x1

    对数插值就是用对数刻度(logarithmic scale)度量 x 1 x_1 x1 x 2 x_2 x2

    log ⁡ x 2 − log ⁡ x log ⁡ x − log ⁡ x 1 = b a = 1 − t t x = x 2 t x 1 1 − t \begin{aligned} \frac{\log{x_2}-\log{x}}{\log{x}-\log{x_1}} & =\frac{b}{a}=\frac{1-t}{t} \\ x & = x_2^tx_1^{1-t} \end{aligned} logxlogx1logx2logxx=ab=t1t=x2tx11t

    这两种插值方式都是常用的插值方式,后面会用到。

    对平移和缩放操作做插值比较直接,一般的线性插值就足够,如果觉得线性插值过渡不够平滑,可以采用样条曲线或者贝塞尔曲线的方式插值,这个话题超出了本文的范围,因此此处略过。这里主要讨论一下对旋转操作的插值。理想情况下,对旋转操作做插值应该具有以下三种性质。

    1. 力矩最小(torque minimization);
    2. 角速度固定(constant angular velocity);
    3. 满足交换律(commutivity);

    力矩最小指的是,中间状态划过的轨迹在对应的旋转空间是最短的。这个性质可以类比对点的插值,我们知道两点之间线段最短,因此对两个点的插值轨迹就是连接这两个点的线段。如果把旋转空间投影到平面上,那么中间状态的轨迹也应该是一条线段。

    角速度固定指的是如果 t t t的变化速度是固定的,那么旋转的速度也应该是固定的。

    满足交换律指的是,如果对角度进行连续插值,比如从 A A A B B B再到 C C C,那么交换插值的顺序(先从 C C C B B B再到 A A A)不影响插值结果,前提是插值系数 t t t要保持一致。

    注:按照上面的描述[2],感觉说的更像是结合律。不确定为什么这里被称为交换律。个人理解交换律应该是从 A A A B B B的插值结果等于从 B B B A A A的结果。

    直观来讲,力矩最小保证了旋转操作走的是最近路线,角速度固定保证了旋转速度不变,交换律能保证连续插值时的稳定性。不幸的是,目前还不存在同时满足这三个性质的对旋转操作的插值方法。

    那么可能的对旋转变换的插值方法有哪些呢?

    1. 欧拉角线性插值
    2. 旋转矩阵线性插值
    3. 四元数线性插值
    4. 四元数球面插值
    5. 四元数对数插值

    前两种方法用到了前面介绍的旋转矩阵和欧拉变换,比较直接,下面给出了具体的实现代码。注意,这里输入的矩阵均是旋转矩阵,从完整的模型矩阵分解得到旋转矩阵可以参考Decompose函数。

    Matrix4x4 EulerLerp(Matrix4x4 start, Matrix4x4 end, float t){
        float starth, startp, startr, endh, endp, endr;
        RotateDecompose(start, starth, startp, startr);
        RotateDecompose(end, endh, endp, endr);
        float h = starth + (endh - starth) * ratio;
        float p = startp + (endp - startp) * ratio;
        float r = startr + (endr - startr) * ratio;
        return Rotate(h, p, r);
    }
    
    Matrix4x4 MatLerp(Matrix4x4 start, Matrix4x4 end, float t){
        return start + (end - start) * t;
    }
    

    后三种涉及到四元数,我们先看一下四元数是什么。

    四元数法

    四元数(quaternions)早在1843年就被提出,最早是作为一种对复数的拓展。之后1985年被引入图形学,用来表示旋转或描述朝向。四元数可以与旋转的矩阵描述、欧拉角描述相互转换,最重要的是,用四元数做旋转插值有着其他的两种描述方式不具有的优良特性,后面会具体讲到。

    四元数 q ^ {\bf{\hat{q}}} q^定义为:
    q ^ = ( q v , q w ) = i q x + j q y + k q z + q w i 2 = j 2 = k 2 = − 1 , j k = − k j = i , k i = − i k = j , i j = − j i = k {\bf{\hat{q}}} = ({\bf{q}}_v, q_w)=iq_x+jq_y+kq_z+q_w \\ i^2=j^2=k^2=-1, jk=-kj=i, ki=-ik=j, ij=-ji=k q^=(qv,qw)=iqx+jqy+kqz+qwi2=j2=k2=1,jk=kj=i,ki=ik=j,ij=ji=k
    其中, q w q_w qw表示四元数的实部, q v {\bf{q}}_v qv表示四元数的虚部, i i i j j j k k k均为虚数单位。

    四元数的数学性质

    对四元数的加减乘操作与向量和虚数的操作法则类似:
    q ^ + r ^ = ( q v , q w ) + ( r v , r w ) = ( q v + r v , q w + r w ) s q ^ = q ^ s = ( 0 , s ) ( q v , q w ) = ( s q v , s q w ) q ^ r ^ = ( i q x + j q y + k q z + q w ) ( i r x + j r y + k r z + r w ) = i ( q y r z − q z r y + r w q x + q w r x ) + j ( q z r x − q x r z + r w q y + q w r y ) + k ( q x r y − q y r x + r w q z + q w r z ) + q w r w − q x r x − q y r y − q z r z = ( q v × r v + r w q v + q w r v , q w r w − q v ⋅ r v ) \begin{aligned} {\bf{\hat{q}}}+{\bf{\hat{r}}}& = ({\bf{q}}_v, q_w)+({\bf{r}}_v, r_w) = ({\bf{q}}_v+{\bf{r}}_v, q_w+r_w)\\ s{\bf{\hat{q}}} & = {\bf{\hat{q}}}s \\ & = ({\bf{0}}, s)({\bf{q}}_v, q_w) = (s{\bf{q}}_v, sq_w)\\ {\bf{\hat{q}}}{\bf{\hat{r}}} & = (iq_x+jq_y+kq_z+q_w)(ir_x+jr_y+kr_z+r_w) \\ & = i(q_yr_z-q_zr_y+r_wq_x+q_wr_x) \\ & + j(q_zr_x-q_xr_z+r_wq_y+q_wr_y) \\ & + k(q_xr_y-q_yr_x+r_wq_z+q_wr_z) \\ & + q_wr_w - q_xr_x - q_yr_y - q_zr_z \\ & = ({\bf{q}}_v\times{\bf{r}}_v+r_w{\bf{q}}_v+q_w{\bf{r}}_v, q_wr_w-{\bf{q}}_v\cdot{\bf{r}}_v) \end{aligned} q^+r^sq^q^r^=(qv,qw)+(rv,rw)=(qv+rv,qw+rw)=q^s=(0,s)(qv,qw)=(sqv,sqw)=(iqx+jqy+kqz+qw)(irx+jry+krz+rw)=i(qyrzqzry+rwqx+qwrx)+j(qzrxqxrz+rwqy+qwry)+k(qxryqyrx+rwqz+qwrz)+qwrwqxrxqyryqzrz=(qv×rv+rwqv+qwrv,qwrwqvrv)
    注意,四元数与标量的乘法满足交换律,但是四元数之间的乘法不满足交换律。这主要是因为向量的叉乘不满足交换律。而四元数之间的乘法是满足结合律的。

    共轭操作:
    q ^ ∗ = ( q v , q w ) ∗ = ( − q v , q w ) {\bf{\hat{q}}}^*=({\bf{q}}_v, q_w)^*=(-{\bf{q}}_v, q_w) q^=(qv,qw)=(qv,qw)
    取模操作:
    ∣ ∣ q ^ ∣ ∣ = n ( q ^ ) = q ^ q ^ ∗ = q ^ ∗ q ^ = q v ⋅ q v + q w 2 = q x 2 + q y 2 + q z 2 + q w 2 \begin{aligned} ||{\bf{\hat{q}}}||=n({\bf{\hat{q}}}) & =\sqrt{{\bf{\hat{q}}}{\bf{\hat{q}}}^*}=\sqrt{{\bf{\hat{q}}}^*{\bf{\hat{q}}}} = \sqrt{{\bf{q}}_v\cdot{\bf{q}}_v+q_w^2} \\ & = \sqrt{q_x^2+q_y^2+q_z^2+q_w^2} \end{aligned} q^=n(q^)=q^q^ =q^q^ =qvqv+qw2 =qx2+qy2+qz2+qw2
    单位四元数:
    i ^ = ( 0 , 1 ) {\bf{\hat{i}}}=({\bf{0}}, 1) i^=(0,1)
    逆运算:
    q ^ − 1 = 1 n ( q ^ ) 2 q ^ ∗ {\bf{\hat{q}}}^{-1}=\frac{1}{n({\bf{\hat{q}}})^2}{\bf{\hat{q}}}^* q^1=n(q^)21q^

    四元数与旋转操作

    单位四元数,即满足 n ( q ^ ) = 1 n({\bf{\hat{q}}})=1 n(q^)=1的四元数 q ^ {\bf{\hat{q}}} q^,可以写为:
    q ^ = ( sin ⁡ ϕ u q , cos ⁡ ϕ ) = sin ⁡ ϕ u q + cos ⁡ ϕ {\bf{\hat{q}}}=(\sin\phi{\bf{u}}_q,\cos\phi)=\sin\phi{\bf{u}}_q+\cos\phi q^=(sinϕuq,cosϕ)=sinϕuq+cosϕ
    其中 u q {\bf{u}}_q uq满足 ∣ ∣ u q ∣ ∣ 2 = 1 ||{\bf{u}}_q||^2=1 uq2=1

    这样的单位四元数的物理意义是,围绕轴 u q {\bf{u}}_q uq旋转 2 ϕ 2\phi 2ϕ角度的旋转操作。
    quaternion_rotation
    单位四元数也可以利用公式 cos ⁡ ϕ + i sin ⁡ ϕ = e i ϕ \cos\phi+i\sin\phi=e^{i\phi} cosϕ+isinϕ=eiϕ改写为:
    q ^ = sin ⁡ ϕ u q + cos ⁡ ϕ = e ϕ u q {\bf{\hat{q}}}=\sin\phi{\bf{u}}_q+\cos\phi=e^{\phi{\bf{u}}_q} q^=sinϕuq+cosϕ=eϕuq
    这样我们可以对单位四元数做一些指数、对数操作:
    log ⁡ q ^ = log ⁡ e ϕ u q = ϕ u q q ^ t = ( sin ⁡ ϕ u q + cos ⁡ ϕ ) t = e ϕ t u q = sin ⁡ ( ϕ t ) u q + cos ⁡ ϕ t \begin{aligned} \log{{\bf{\hat{q}}}} & =\log{e^{\phi{\bf{u}}_q}}=\phi{\bf{u}}_q \\ {\bf{\hat{q}}}^t & =(\sin\phi{\bf{u}}_q+\cos\phi)^t \\ & =e^{\phi t {\bf{u}}_q} \\ & =\sin{(\phi t)}{\bf{u}}_q+\cos{\phi t} \end{aligned} logq^q^t=logeϕuq=ϕuq=(sinϕuq+cosϕ)t=eϕtuq=sin(ϕt)uq+cosϕt
    单位四元数的一个性质是,它的逆等于共轭:
    q ^ − 1 = q ^ ∗ {\bf{\hat{q}}}^{-1}={\bf{\hat{q}}}^* q^1=q^
    这个性质对于计算逆来说方便了很多。

    对于一个齐次坐标表示的点 p = ( p x , p y , p z , p w ) T {\bf{p}}=(p_x, p_y, p_z, p_w)^T p=(px,py,pz,pw)T,给定单位四元数 q ^ = sin ⁡ ϕ u q + cos ⁡ ϕ {\bf{\hat{q}}}=\sin\phi{\bf{u}}_q+\cos\phi q^=sinϕuq+cosϕ,那么围绕 u {\bf{u}} u轴旋转 2 ϕ 2\phi 2ϕ角度的旋转操作可以记为:
    q ^ p ^ q ^ − 1 {\bf{\hat{q}}}{\bf{\hat{p}}}{\bf{\hat{q}}}^{-1} q^p^q^1
    其中, p ^ {\bf{\hat{p}}} p^表示向量 p {\bf{p}} p的四元数形式。

    从旋转的角度来说, q ^ {\bf{\hat{q}}} q^ − q ^ -{\bf{\hat{q}}} q^的物理意义是一致的,都代表同一个旋转过程。

    四元数与矩阵的转换

    至此,我们知道了单位四元数所代表的旋转操作的意义,也知道怎么用矩阵表示绕任意轴的旋转操作,因此可以写出单位四元数 q ^ = ( q x , q y , q z , q w ) {\bf{\hat{q}}}=(q_x, q_y, q_z, q_w) q^=(qx,qy,qz,qw)与旋转矩阵 M q M^q Mq的相互转换公式:
    M q = ( 1 − 2 ( q y 2 + q z 2 ) 2 ( q x q y − q w q z ) 2 ( q x q z + q w q y ) 0 2 ( q x q y + q w q z ) 1 − 2 ( q x 2 + q z 2 ) 2 ( q y q z − q w q x ) 0 2 ( q x q z − q w q y ) 2 ( q y q z + q w q x ) 1 − 2 ( q x 2 + q y 2 ) 0 0 0 0 1 ) M^q= \begin{pmatrix} 1-2(q_y^2+q_z^2) & 2(q_xq_y-q_wq_z) & 2(q_xq_z+q_wq_y) & 0 \\ 2(q_xq_y+q_wq_z) & 1-2(q_x^2+q_z^2) & 2(q_yq_z-q_wq_x) & 0 \\ 2(q_xq_z-q_wq_y) & 2(q_yq_z+q_wq_x) & 1-2(q_x^2+q_y^2) & 0 \\ 0 & 0 & 0 & 1 \\ \end{pmatrix} \\ Mq=12(qy2+qz2)2(qxqy+qwqz)2(qxqzqwqy)02(qxqyqwqz)12(qx2+qz2)2(qyqz+qwqx)02(qxqz+qwqy)2(qyqzqwqx)12(qx2+qy2)00001
    从旋转矩阵到四元数:
    tr ( M q ) = 4 − 2 ( q x 2 + q y 2 + q z 2 ) = 4 ( 1 − q x 2 + q y 2 + q z 2 q x 2 + q y 2 + q z 2 + q w 2 ) = 4 q w 2 q x 2 + q y 2 + q z 2 + q w 2 = 4 q w 2 q w = 1 2 tr ( M q ) q x = m 21 − m 12 4 q w q y = m 02 − m 20 4 q w q z = m 10 − m 01 4 q w \begin{aligned} \text{tr}(M^q) & =4-2(q_x^2+q_y^2+q_z^2) = 4\left(1-\frac{q_x^2+q_y^2+q_z^2}{q_x^2+q_y^2+q_z^2+q_w^2}\right)\\ & = \frac{4q_w^2}{q_x^2+q_y^2+q_z^2+q_w^2} = 4q_w^2 \\ q_w & = \frac{1}{2}\sqrt{\text{tr}(M^q)} \\ q_x & = \frac{m_{21}-m_{12}}{4q_w} \\ q_y & = \frac{m_{02}-m_{20}}{4q_w} \\ q_z & = \frac{m_{10}-m_{01}}{4q_w} \\ \end{aligned} tr(Mq)qwqxqyqz=42(qx2+qy2+qz2)=4(1qx2+qy2+qz2+qw2qx2+qy2+qz2)=qx2+qy2+qz2+qw24qw2=4qw2=21tr(Mq) =4qwm21m12=4qwm02m20=4qwm10m01

    4 q w 2 = tr ( M q ) 4 q x 2 = m 00 − m 11 − m 22 + m 33 4 q y 2 = − m 00 + m 11 − m 22 + m 33 4 q z 2 = − m 00 − m 11 + m 22 + m 33 \begin{aligned} 4q_w^2 & = \text{tr}(M^q) \\ 4q_x^2 & = m_{00}-m_{11}-m_{22}+m_{33} \\ 4q_y^2 & = -m_{00}+m_{11}-m_{22}+m_{33} \\ 4q_z^2 & = -m_{00}-m_{11}+m_{22}+m_{33} \\ \end{aligned} 4qw24qx24qy24qz2=tr(Mq)=m00m11m22+m33=m00+m11m22+m33=m00m11+m22+m33

    四元数的插值方法

    对四元数进行插值可以有三种方法,线性插值(记为lerp,如果插值后做了归一化操作,则记为nlerp),球面插值(记为slerp),和对数插值(记为llerp)。
    lerp ( q ^ , r ^ , t ) = ( 1 − t ) q ^ + t r ^ slerp ( q ^ , r ^ , t ) = sin ⁡ ( ϕ ( 1 − t ) ) sin ⁡ ϕ q ^ + sin ⁡ ( ϕ t ) sin ⁡ ϕ r ^ cos ⁡ ϕ = q ^ ⋅ r ^ llerp ( q ^ , r ^ , t ) = q ^ 1 − t r ^ t = sin ⁡ ( ϕ q ( 1 − t ) ) u q + cos ⁡ ϕ q ( 1 − t ) + sin ⁡ ( ϕ r t ) u r + cos ⁡ ϕ r t \begin{aligned} \text{lerp}({\bf{\hat{q}}},{\bf{\hat{r}}}, t) & =(1-t){\bf{\hat{q}}}+t{\bf{\hat{r}}} \\ \text{slerp}({\bf{\hat{q}}},{\bf{\hat{r}}}, t) & =\frac{\sin{(\phi(1-t))}}{\sin\phi}{\bf{\hat{q}}}+\frac{\sin{(\phi t)}}{\sin\phi}{\bf{\hat{r}}} \\ \cos\phi & = {\bf{\hat{q}}}\cdot{\bf{\hat{r}}} \\ \text{llerp}({\bf{\hat{q}}},{\bf{\hat{r}}}, t) & = {\bf{\hat{q}}}^{1-t}{\bf{\hat{r}}}^t\\ & = \sin{(\phi_q (1-t))}{\bf{u}}_q+\cos{\phi_q (1-t)} + \sin{(\phi_r t)}{\bf{u}}_r+\cos{\phi_r t} \end{aligned} lerp(q^,r^,t)slerp(q^,r^,t)cosϕllerp(q^,r^,t)=(1t)q^+tr^=sinϕsin(ϕ(1t))q^+sinϕsin(ϕt)r^=q^r^=q^1tr^t=sin(ϕq(1t))uq+cosϕq(1t)+sin(ϕrt)ur+cosϕrt
    几个需要注意的点:

    1. 单位四元数进行线性插值以后会变成非单位的四元数,如果继续用它来表示旋转操作的话,需要先归一化。否则插值出来的旋转操作会带有额外的缩放操作。
    2. 球面插值看起来跟对数插值有点类似,但是并不是一回事。球面插值的物理意义其实是对角度进行插值,系数 t t t正比于插值结果和 q ^ {\bf{\hat{q}}} q^的夹角。插值公式可以从二维的插值推广得来,具体的推导可以参考[2]。
    3. 球面插值的物理意义是计算球面上的测地距离,相比于其他两种方式更“平滑”。下图[4]展示了线性插值和球面插值的区别。图a表示插值的两个端点,图b表示线性插值的四等分位置,平分的是两点连线的线段;图c表示球面插值的四等分位置,平分的是两点连成的圆弧。
      lerp_slerp
      具体实现:
    Quaternion lerp(const Quaternion &q1, const Quaternion &q2, float t) {
    	float scale0 = 1.0f - t;
    	float scale1 = ( dot(q1, q2) >= 0.0f ) ? t : -t;
    	return scale0 * q1 + scale1 * q2;
    }
    
    Quaternion nlerp(const Quaternion &q1, const Quaternion &q2, float t) {
    	float scale0 = 1.0f - t;
    	float scale1 = ( dot(q1, q2) >= 0.0f ) ? t : -t;
    	return normalize(scale0 * q1 + scale1 * q2);
    }
    
    Quaternion slerp(const Quaternion &q1, const Quaternion &_q2, float t) {
    	Quaternion q2(_q2);
    
    	T cosTheta = dot(q1, q2);
    	if (cosTheta < 0) {
    		/* Take the short way! */
    		q2 = -q2;
    		cosTheta = -cosTheta;
    	}
    	if (cosTheta > .9995f) {
    		// Revert to plain linear interpolation
    		return normalize(q1 * (1.0f - t) +  q2 * t);
    	} else {
    		float theta = math::safe_acos(math::clamp(cosTheta, (float) -1.0f, (float) 1.0f));
    		float thetap = theta * t;
    		Quaternion qperp = normalize(q2 - q1 * cosTheta);
    		return q1 * std::cos(thetap) + qperp * std::sin(thetap);
    	}
    }
    
    Quaternion llerp(const Quaternion &q1, const Quaternion &q2, float t) {
    	return (q1.log() * (1.0f - t) + q2.log() * t).exp();
    }
    

    几种插值方法的比较

    了解了四元数插值方法,我们再回头讨论一下为什么用四元数对旋转操作插值会更好.

    下图展示了五种插值方式的实际效果。

    • 左上:欧拉角线性插值
    • 右上:旋转矩阵线性插值
    • 左中:lerp,四元数线性插值(未归一化)
    • 右中:nlerp,四元数线性插值
    • 左下:slerp,四元数球面插值
    • 右下:llerp,四元数对数插值
      bathroom

    第一种欧拉角线性插值方法,很明显角速度不是匀速的,这是因为在这个例子中,三个欧拉角都有变化,如果将旋转矩阵拆分为欧拉角形式,再做插值,虽然在三个轴上角速度都是匀速的,但是组合成旋转矩阵它的速度就不一定是匀速的了。

    第二种矩阵线性插值方法,可以看出旋转过程中有微弱的缩放效果(窗户被拉伸了)。如果对插值的中间结果做矩阵分解,可以看出始终为单位矩阵的缩放矩阵被改变了!这个副作用很明显是我们所不希望的。PBRT中举的例子是说用旋转矩阵线性插值会导致旋转的物体“变模糊”,这个模糊效果就是缩放矩阵被改变导致的。如下图所示,左侧是静止状态,中间是四元数球面插值的效果,右侧是旋转矩阵线性插值的效果。注意右侧的旋转球在极点位置是模糊的,而且整个球体的边缘由于模糊而显得更大。
    spinning_sphere
    第三种和第四种四元数线性插值,未归一化和归一化。最大的问题是角速度也不是固定的。而且未归一化的情形略有缩放感,窗户的高度先被压缩后被拉伸。

    第五种四元数球面插值,可以说是这五种方法中最“平稳”的一种了。但是它也不是那么完美——不满足交换律。因为球面插值与两个四元数的夹角有关系,因此是无法满足交换律的。不过对于绝大部分场景而言,交换律并不是必须的。比如在这个简单的示例中其实看不出来它的缺陷。

    第六种四元数对数插值,不清楚实现的对不对,总感觉镜头往反方向转似乎某处的符号写反了。找到的资料非常有限,也不确定这个方法有什么实际用途。

    下表展示了三种四元数方法所具有的性质[2]。

    力矩最小 角速度固定 满足交换律 四元数线性插值 Yes No Yes 四元数球面插值 Yes Yes NO 四元数对数插值 NO Yes Yes \begin{array}{c|ccc} & \text{力矩最小} & \text{角速度固定} & \text{满足交换律} \\ \hline \text{四元数线性插值} & \text{Yes} & \text{No} & \text{Yes} \\ \hline \text{四元数球面插值} & \text{Yes} & \text{Yes} & \text{NO} \\ \hline \text{四元数对数插值} & \text{NO} & \text{Yes} & \text{Yes} \\ \end{array} 四元数线性插值四元数球面插值四元数对数插值力矩最小YesYesNO角速度固定NoYesYes满足交换律YesNOYes

    比较不幸的是,虽然我们前面讨论了四元数的方法要比欧拉角、旋转矩阵的方法更优秀,但它也不是完美的。没有一个四元数方法满足所有的三个特性。因此对不同的场景,要选取不同的插值方法。

    对于动画系统(animation systems),比如物体或者相机的旋转操作,球面插值是最优选择。而在实时游戏当中,线性插值可以作为一个次优备选,至少线性插值的计算效率更高,虽然它并不是匀速的,但是很多时候用户并不会注意到这个细节。

    未尽事宜

    本文主要介绍了模型变换和四元数的基本概念,并讨论了用单位四元数做旋转插值的几种方案。篇幅所限,有一些话题没有涉及到,但是同样很重要,列举在此:

    • 缩放部分:沿任意轴的缩放。
    • 剪切变换(shear transform)。
    • 插值部分:贝塞尔曲线、样条曲线插值方法。线性插值的缺点是连接处不够平滑,即连续但不可导,而贝塞尔曲线、样条曲线能保证连接处n阶可导。
    • 对法向量的变换操作,为保证变换后法向仍然垂直于切平面,法向变换的变换矩阵需要做取逆+转置操作,细节可以参考PBRT或RTR。
    • 对球面插值的优化。粗暴的球面插值需要做反三角函数运算,计算代价高。具体的优化措施可以参见[6]。

    另外还有几个不太明确的点,同样列举出来,以免误导读者:

    • 介绍旋转的插值操作时,提到的三种性质中的结合律,不确定是否正确理解了。找到的资料很有些,而且有些矛盾。可以参考[2][3][4]的介绍。
    • 关于四元数对数插值,同样不确定理解是否正确。未找到示例代码,所以实现也可能有问题,主要是因为往反方向转太奇怪了。

    欢迎指正!

    Reference

    [1] Linear and Logarithmic Interpolation

    [2] Understanding Slerp, Then Not Using It

    [3] Practical Parameterization of Rotations Using the Exponential Map

    [4] Quaternions, Interpolation and Animation

    [5] Quaternion calculus as a basic tool in computer graphics

    [6] Slerping Clock Cycles

    [7] Animating Rotation with Quaternion Curves

    [8] Math Magician – Lerp, Slerp, and Nlerp

    [9] Matrix Animation and Polar Decomposition

    [10] Real-time Rendering, 4th edition.

    [11] Physically Based Rendering, 3rd edition.

    展开全文
  • 在vc++6.0环境下,基于OPENGL实现图形学设计内容。包括实现五边形到五角星的动态变换;利用DDA、中点画线法、Bresenham算法画直线;利用中点画圆法、Bresenham算法画圆;利用栅栏填充、扫描线填充实现多边形内部颜色...
  • OpenGL的正方体的绘制代码,采用了变换矩阵实现了键盘交互,用于x、y、z轴的旋转,以及平移和缩放。。。。
  • 矩阵的加、减法 两个矩阵必须规模相同 [123456]+[123456]=[1+12+23+34+45+56+6]=[24681012]\begin{bmatrix}1&2\\3&4\\5&6\end{bmatrix}+\begin{bmatrix}1&2\\3&4\\5&6\end{bmatrix}=\begin{...
    矩阵的加、减法

    两个矩阵必须规模相同
    [ 1 2 3 4 5 6 ] + [ 1 2 3 4 5 6 ] = [ 1 + 1 2 + 2 3 + 3 4 + 4 5 + 5 6 + 6 ] = [ 2 4 6 8 10 12 ] \begin{bmatrix}1&2\\3&4\\5&6\end{bmatrix}+\begin{bmatrix}1&2\\3&4\\5&6\end{bmatrix}=\begin{bmatrix}1+1&2+2\\3+3&4+4\\5+5&6+6\end{bmatrix}=\begin{bmatrix}2&4\\6&8\\10&12\end{bmatrix} 135246+135246=1+13+35+52+24+46+6=26104812

    矩阵的直加

    两个不同规模的矩阵可以直加,组合后的矩阵空位用0补位
    [ 1 2 3 4 ] ⨁ [ 1 2 3 4 5 6 ] = [ 1 2 0 0 3 4 0 0 0 0 1 2 0 0 3 4 0 0 5 6 ] \begin{bmatrix}1&2\\3&4\end{bmatrix}\bigoplus\begin{bmatrix}1&2\\3&4\\5&6\end{bmatrix}=\begin{bmatrix}1&2&0&0\\3&4&0&0\\0&0&1&2\\0&0&3&4\\0&0&5&6\end{bmatrix} [1324]135246=13000240000013500246

    矩阵的乘除法

    几何意义:A x B = 结合两种操作的操作集合。
    1、当矩阵A的列数(column)等于矩阵B的行数(row)时,A与B可以相乘。
    2、矩阵C的行数等于矩阵A的行数,C的列数等于B的列数。
    3、乘积C的第m行第n列的元素等于矩阵A的第m行的元素与矩阵B的第n列对应元素乘积之和
    A = [ a 1 a 2 a 3 a 4 a 5 a 6 ] B = [ b 1 b 2 b 3 b 4 b 5 b 6 ] A = \begin{bmatrix}a1&a2&a3\\a4&a5&a6\end{bmatrix}B = \begin{bmatrix}b1&b2\\b3&b4\\b5&b6\end{bmatrix} A=[a1a4a2a5a3a6]B=b1b3b5b2b4b6
    C = A B = [ a 1 b 1 + a 2 b 3 + a 3 b 5 a 1 b 2 + a 2 b 4 + a 3 b 6 a 4 b 1 + a 5 b 3 + a 6 b 5 a 4 b 2 + a 5 b 4 + a 6 b 6 ] C=AB=\begin{bmatrix}a1b1+a2b3+a3b5&a1b2+a2b4+a3b6\\a4b1+a5b3+a6b5&a4b2+a5b4+a6b6\end{bmatrix} C=AB=[a1b1+a2b3+a3b5a4b1+a5b3+a6b5a1b2+a2b4+a3b6a4b2+a5b4+a6b6]

    矩阵的平移

    设 x , y 为 原 坐 标 , d x 、 d y 为 x y 坐 标 的 移 动 量 , 则 x 1 、 y 1 为 变 换 后 的 新 坐 标 设x,y为原坐标,dx、dy为xy坐标的移动量,则x1、y1为变换后的新坐标 xy,dxdyxyx1y1【公式】
    ( x 1 y 1 ) = ( d x d y ) + ( x y ) \dbinom{x_{1}}{y_{1}}=\dbinom{dx}{dy}+\dbinom{x}{y} (y1x1)=(dydx)+(yx)
    【代码的实现】

    //shader中的写法
    //新x1坐标 = 原x坐标*cos(角度值) - 原y坐标*sin(角度值),角度值的取值区间是pi = 3.14, 相当于180度。该公式其实就是已知两直角边与夹角求第三遍长
    x1 = x + dx;
    y1 = y + dy;
    
    空间矩阵的旋转
    二维空间

    设 x , y 为 原 坐 标 , θ 为 旋 转 角 度 , 则 x 1 、 y 1 为 变 换 后 的 新 坐 标 设x,y为原坐标,\theta为旋转角度,则x1、y1为变换后的新坐标 xy,θx1y1【公式】
    ( x 1 y 1 ) = ( c o s θ − s i n θ s i n θ c o s θ ) × ( x y ) \dbinom{x_{1}}{y_{1}}=\begin{pmatrix}cos\theta&-sin\theta\\sin\theta& cos\theta\end{pmatrix}\times\dbinom{x}{y} (y1x1)=(cosθsinθsinθcosθ)×(yx)
    【代码的实现】

    //shader中的写法
    //新x1坐标 = 原x坐标*cos(角度值) - 原y坐标*sin(角度值),角度值的取值区间是pi = 3.14, 相当于180度。该公式其实就是已知两直角边与夹角求第三遍长
    x1 = x * cos(angle) - y * sin(angle);
    y1 = x * sin(angle) + y * cos(angle);
    
    三维空间

    设 x , y 为 原 坐 标 , θ 为 旋 转 角 度 , 则 x 1 、 y 1 为 变 换 后 的 新 坐 标 设x,y为原坐标,\theta为旋转角度,则x1、y1为变换后的新坐标 xy,θx1y1【公式】
    ( x 1 y 1 ) = ( c o s θ − s i n θ s i n θ c o s θ ) × ( x y ) \dbinom{x_{1}}{y_{1}}=\begin{pmatrix}cos\theta&-sin\theta\\sin\theta& cos\theta\end{pmatrix}\times\dbinom{x}{y} (y1x1)=(cosθsinθsinθcosθ)×(yx)
    【代码的实现】

    //shader中的写法
    //新x1坐标 = 原x坐标*cos(角度值) - 原y坐标*sin(角度值),角度值的取值区间是pi = 3.14, 相当于180度。该公式其实就是已知两直角边与夹角求第三遍长
    x1 = x * cos(angle) - y * sin(angle);
    y1 = x * sin(angle) + y * cos(angle);
    
    矩阵的缩放

    设 x , y 为 原 坐 标 , S x 、 S y 为 缩 放 倍 数 , 则 x 1 、 y 1 为 变 换 后 的 新 坐 标 设x,y为原坐标,S_{x}、S_{y}为缩放倍数,则x1、y1为变换后的新坐标 xy,SxSyx1y1【公式】
    ( x 1 y 1 ) = ( S x 0 0 S y ) × ( x y ) \dbinom{x_{1}}{y_{1}}=\begin{pmatrix}S_{x}&0\\0& S_{y}\end{pmatrix}\times\dbinom{x}{y} (y1x1)=(Sx00Sy)×(yx)
    【代码的实现】

    //shader中的写法
    x1 = x * size_x + y * 0;
    y1 = x * 0 + y * size_y;
    
    矩阵的切变

    设 x , y 为 原 坐 标 , K 为 切 变 系 数 , 则 x 1 、 y 1 为 变 换 后 的 新 坐 标 。 设x,y为原坐标,K为切变系数,则x1、y1为变换后的新坐标。 xy,Kx1y1【公式】
    第一种,x方向切变:
    ( x 1 y 1 ) = ( 1 k 0 1 ) × ( x y ) \dbinom{x_{1}}{y_{1}}=\begin{pmatrix}1&k\\0&1\end{pmatrix}\times\dbinom{x}{y} (y1x1)=(10k1)×(yx)
    第二种,y方向切变:
    ( x 1 y 1 ) = ( 1 0 k 1 ) × ( x y ) \dbinom{x_{1}}{y_{1}}=\begin{pmatrix}1&0\\k&1\end{pmatrix}\times\dbinom{x}{y} (y1x1)=(1k01)×(yx)
    【代码的实现】

    //shader中的写法
    //x方向的切变 
    x1 = x * 1 + y * k;
    y1 = x * 0 + y * 1
    //y方向的切变 
    x1 = x * 1 + y * 0;
    y1 = x * k + y * 1
    
    矩阵的反射(?)

    设 x , y 为 原 坐 标 , u x , u y 为 直 线 方 向 的 单 位 向 量 , 则 x 1 、 y 1 为 变 换 后 的 新 坐 标 设x,y为原坐标,ux,uy为直线方向的单位向量,则x1、y1为变换后的新坐标 xy,ux,uy线x1y1【公式】
    ( x 1 y 1 ) = ( 2 u x 2 − 1 2 u x u y 2 u x u y 2 u y 2 − 1 ) × ( x y ) \dbinom{x_{1}}{y_{1}}=\begin{pmatrix}2u_{x}^2-1&2u_{x}u_{y}\\2u_{x}u_{y}&2u_{y}^2-1\end{pmatrix}\times\dbinom{x}{y} (y1x1)=(2ux212uxuy2uxuy2uy21)×(yx)
    【代码的实现】

    //shader中的写法
    x1 = x * 2 * pow(ux,2) - 1 + y * 2 * ux * uy;
    y1 = x * 2 * ux * uy + y * 2 * pow(uy, 2) - 1;
    
    矩阵的正投影

    设 x , y 为 原 坐 标 , u x , u y 为 直 线 方 向 的 单 位 向 量 , 则 x 1 、 y 1 为 变 换 后 的 新 坐 标 设x,y为原坐标,ux,uy为直线方向的单位向量,则x1、y1为变换后的新坐标 xy,ux,uy线x1y1【公式】
    ( x 1 y 1 ) = ( u x 2 u x u y u x u y u y 2 ) × ( x y ) \dbinom{x_{1}}{y_{1}}=\begin{pmatrix}u_{x}^2&u_{x}u_{y}\\u_{x}u_{y}&u_{y}^2\end{pmatrix}\times\dbinom{x}{y} (y1x1)=(ux2uxuyuxuyuy2)×(yx)
    【代码的实现】

    //shader中的写法
    x1 = x * pow(ux,2) + y * ux * uy;
    y1 = x * ux * uy + y * pow(uy, 2);
    
    矩阵的线性变换
    矩阵的滤波
    图像处理算法部分
    展开全文
  •  对于变换的原理,只需要将原图形的点通过极坐标或者相加、相乘,再结合二维矩阵的原理即可实现,如果图形需要对图形对象进行旋转和放缩两类变换进行多次操作,则可以首先将两变换矩阵合成一个复合变换矩阵。...
  • C++实现实现逆时针旋转矩阵

    千次阅读 2019-02-22 18:46:47
    今天遇到一道面试题,要求输入N,并且构造一个逆时针N*N的旋转矩阵。花了一下午时间写完的,把这个分享给大家。 //构造逆时针矩阵 int **BuildMatrix(int width) {  int **pMatrix = new int*[width];  for (int i...
  • 图形学实验 旋转茶壶 OpenGL实现

    千次阅读 2018-06-06 15:16:22
    图像实验 旋转茶壶环境Windows10Visual Studio 2017配置32和64位OpenGL环境。配置方法和工程文件:点击下载配置文件实验代码// 实验三 旋转茶壶.cpp: 定义控制台应用程序的入口点。 // #include"stdafx.h&...
  • 3)旋转变换 将图形上的点旋转θ角度,得到新的坐标。 4)平移变换 利用平移变换矩阵即可。 4. 算法设计 创建DrawPolaris(CDC* pDC, long x[18] ,long y[18])画图函数,将输入的18个点按照规则连线。 5. 源代码 //...
  • 世界著名图形学教材,完整版,带...《计算机图形学(OpenGL版)第3版》通过最能代表技术发展状况的示例综合介绍了计算机图形学方面的原则和技巧,《计算机图形学(OpenGL版)第3版》对每个概念都进行了详细介绍...
  • 图形学变换——平移、旋转和缩放

    千次阅读 2020-06-04 00:38:21
    图形学变换一、概述二、平移新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants...
  • 旋转矩阵怎么来的我倒一直都没有概念,这本书里面对旋转矩阵的来历倒是给了我一些启发。 首先从二维的旋转矩阵开始 [cosθsinθ−sinθcosθ] \begin{bmatrix} cos\theta & sin\theta \\ -sin\theta...
  • 本次作业的任务是填写一个旋转矩阵和一个透视投影矩阵。给定三维下三个 点 v0(2.0, 0.0, −2.0), v1(0.0, 2.0, −2.0), v2(−2.0, 0.0, −2.0), 你需要将这三个点的坐 标变换为屏幕坐标并在屏幕上绘制出对应的线框...
  • 博文将原第三讲分为四部分来讲解:1、旋转矩阵和变换矩阵;2、旋转向量表示旋转;3、欧拉角表示旋转;4、四元数表示变换。本文相对于原文会适当精简,同时为便于理解,会加入一些注解和补充知识点,本篇为第一部分:...
  • 图形学VS2008 MFC制作的6色旋转球体 代码加程序 图形学初级设计 操作:通过键盘实现球体的旋转。 编程:通过数学坐标变换矩阵实现球体的旋转。 可能哪里忘了delete ,旋转越多越占内存。
  • 目录综述做题前提解决思路函数get_model_matrix函数get_projection_matrix构建透视投影...代码框架见:计算机图形学与混合现实研讨会-games101往期作业汇总帖 做题前提 我认为做这道题,应该先看lecture4:变换(模型、
  • 旋转矩阵中找到所有可能的欧拉角的简单方法,在计算机图形学、视觉学、机器人学和运动学中,欧拉角的确定有时是必要的一步。然而,解决方案可能是明显的,也可能不是。 旋转矩阵 我们从三个主要轴的旋转的标准定义...
  • 旋转变换(一)旋转矩阵

    千次阅读 2020-12-20 19:43:37
    1.简介计算机图形学中的应用非常广泛的变换是一种称为仿射变换的特殊变换,在仿射变换中的基本变换包括平移、旋转、缩放、剪切这几种。本文以及接下来的几篇文章重点介绍一下关于旋转的变换,包括二维旋转变换、三维...
  • 话不多说,直接看主代码吧,这节课停下来有点懵,就把代码记录一下,没什么心得,我还得再研究一下,主要就是各个点用矩阵的方式来表示它们的位移,emm先就这样吧,哈哈哈下次再见 #include<stdio.h> #include...
  • 写在前面:这是我的图形学上机报告,代码都是自己写的,实测通过,查阅了许多资料,现以链接的形式给出 glut初始化 glutSpecialFunc响应键盘方向控制键 glutBitmapCharacter函数实现字符显示 glutPostRedisplay...
  • 实现的投影矩阵填到此处,此时你可以运行 ./Rasterizer output.png normal 来观察法向量实现结果。 3. 修改函数 phong_fragment_shader() in main.cpp: 实现 Blinn-Phong 模型计 算 Fragment Color. 4. 修改函数 ...
  • void ratateAxis(Eigen::Vector3d& axis, double angle, Eigen::MatrixXd& rm) { double n1 = axis(0); double n2 = axis(1);... 若绕x轴旋转 axis为(1, 0, 0)的转置 2.rm为旋转矩阵 3.angle是旋转的角度

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 11,852
精华内容 4,740
关键字:

图形学实现旋转矩阵代码,