阅读《计算机图形学编程(使用OpenGL和C++)》5 - 数学部分

计算机图形学中大量使用了数学原理,尤其是矩阵和矩阵代数,来实现移动、缩放、透视、纹理、光照、阴影等效果,书中列举了一些基础矩阵运算实现移动、缩放等效果。

一、数学基础

1、点、3D坐标系

3D空间通常用3个坐标轴X、Y和Z来表示。还记得右手坐标系吗,OpenGL中的坐标系大体是右手坐标系,而Direct3D中大体是左手坐标系。

图形学中还会使用齐次坐标,在每个点的齐次坐标有4个值。前3个值表示X、Y和Z,第四个值W总是非零值,通常为1。齐次坐标将会使我们的图形学计算更高效。

用来存储齐次3D坐标的GLSL数据类型是vec4(“vec”代表向量,同时也可以用来表示点)。GLM库包含适合在C++/OpenGL应用中创建和存储3元和4元(齐次)点的类,分别叫作vec3和vec4。

2、矩阵

矩阵可以用来将点从一处移动到另一处。

3D图形计算中要用到的矩阵大多数大小为4×4,GLSL语言中的mat4数据类型用来存储4×4矩阵。同样,GLM中有mat4类用以实例化并存储4×4矩阵。

在GLM中,调用构造函数glm::mat4 m(1.0f)以在变量m中生成单位矩阵。单位矩阵中一条对角线的值为1,其余值全为0。任何值乘以单位矩阵都不会改变。

 矩阵转置的计算是通过交换矩阵的行和列完成的。GLM库和GLSL库都有转置函数,分别是glm::transpose(mat4)和transpose(mat4)。

 矩阵加法,在GLSL中,+运算符在mat4上进行了重载,以支持矩阵加法。

 在3D图形学中,点与矩阵相乘通常从右向左,得到点。注意,我们用齐次坐标将点(X, Y, Z)表示为列数为1的矩阵。GLSL和GLM都支持点(确切地说是vec4)与矩阵使用*操作符相乘。

 4×4矩阵与4×4矩阵相乘如下:

 矩阵相乘也经常叫作合并,它可以用于将一系列矩阵变换合并成一个矩阵。这种合并矩阵变换的能力来自矩阵乘法的结合律。

 我们先将3个矩阵相乘,建立Matrix1、Matrix2、Matrix3的连接。如果我们称其为MatrixC,我们就可以将之前的运算写作:

  

 我们需要经常将相同的一系列矩阵变换应用到场景中的每个点上。通过预先一次计算好这些矩阵的合并,就可以成倍减少总的矩阵运算量。

 一个4×4矩阵的逆矩阵是另一个4×4矩阵,用

表示,在矩阵乘法中有如下性质:

GLSL和GLM都提供了mat4.inverse()函数。需要知道的是计算矩阵的逆矩阵的运算量很大。幸运的是我们只有很少情况下需要用到它。

3、向量

向量表示大小和方向。它们没有特定位置。“移动”向量并不改变它所代表的含义。

在3D图形学中,向量一般用空间中的单个点表示,向量的大小是原点到该点的距离,方向则是原点到该点的方向。

许多图形系统并不区分点和向量,如GLSL和GLM,它们所提供的vec3/vec4类型既能用来存储点,又能用来存储向量。对于点和向量使用同一种类型还是不同的类型哪个更好这件事上仍然没有定论。

在GLM和GLSL中有许多3D图形学中经常用到的向量操作。如假设有向量A(u, v, w)和B(x, y, z):

 其他有用的向量函数如magnitude(在GLSL和GLM中是length())、reflection和refraction(在GLSL和GLM中都有)。

4、点积的应用

点积最重要也最基本的应用是求解两向量夹角。

  如果V和W是正规化向量(有着单位长度的向量——这里用“^”标记正规化),则有:

 点积同时还有许多其他用途。

 5、叉积的应用

两向量叉积的一个重要特性是,它会生成一个新的向量,新的向量正交(垂直)于之前两个向量所定义的*面。

其所得法向量的方向遵循右手定则,即将右手手指从V向W卷曲会使得大拇指指向法向量R。注意,这里顺序很重要。W ×V将会得到与R方向相反的向量。

通过叉积来获得法向量的能力对我们后面要学习的光照部分非常重要。为了确定光照效果,我们需要知道所渲染模型的外向法向量。

展示一个例子,其中有一个6个点(顶点)构成的简单模型,使用叉积计算来获得其中一面的外向法向量。

二、变换矩阵

变换矩阵的重要特性之一就是它们都是4×4矩阵。

OpenGL有一个固定在原点(0,0,0)并向下看向Z轴负方向的相机。为了应用OpenGL相机,我们需要做的是将它模拟移动到适合的位置和方向。

我们可以构建一个单一变换矩阵以完成旋转和*移,这个矩阵叫作视图变换(viewing transform)矩阵V。

通常,V矩阵与模型矩阵M合并成为一个模型-视图(model-view,MV)矩阵。

点PM在从自己的模型空间通过如下一个步骤就可以直接转换至相机空间。PC=MV * PM

1、 * 移矩阵

* 移矩阵用于将物体从一个位置(X, Y, Z)移至另一位置(X+Tx, Y+Ty, Z+Tz)。它包含一个单位矩阵,其结果是一个以*移值“移动过”的点。这个乘法是从右向左相乘。

GLM中有一些函数是用于构建与点相乘的*移矩阵的。其中相关的操作有:

●glm::translate(x, y, z)构建*移(x, y, z)的矩阵;

●mat4 × vec4。

 2、缩放矩阵

缩放矩阵用于改变物体的大小或者将点向原点相反方向移动。虽然缩放点这个操作乍一看有点奇怪,不过OpenGL中的物体都是用一组或多组的点定义的。因此,缩放物体涉及缩放它的点的集合。

缩放还可以用来切换坐标系。

 GLM中有一些函数是用于构建与点相乘的缩放矩阵的。其中相关的操作有:

●glm::scale(x, y, z) 构建缩放(x, y, z)的矩阵;

●mat4 × vec4。

反转Z坐标就可以在右手坐标系和左手坐标系中切换,因此,用来切换坐标系的缩放矩阵变换是

 3、旋转矩阵

在3D空间中旋转物体需要指定旋转轴和旋转的角度或弧度。旋转变换有3种,分别是绕X、Y和Z轴旋转。

 同时GLM中也有一些用于构建旋转矩阵的函数。

●glm::rotate(mat4, θ, x, y, z)构建绕X, Y, Z轴旋转θ度的缩放矩阵。

●mat4 × vec4。

实践中,当3D空间中旋转轴不穿过原点时,物体使用欧拉角进行旋转需要几个额外的步骤。一般有:

(1)* 移旋转轴以使它经过原点;

(2)绕X、Y和Z轴旋转适当的欧拉角;

(3)复原步骤(1)中的*移。

3个旋转变换都有自己有趣的特性,即反向旋转的矩阵恰等于其转置矩阵。通过观察之前这些矩阵,同时有cos(−θ)=cos(θ)和sin(−θ)=−sin(θ),即可验证。

欧拉角在某些3D图形应用中会导致一些瑕疵。因此,通常在计算旋转时推荐使用四元数。有兴趣探索四元数的读者可以寻求很多已有的资源。欧拉角足以满足我们的大部分需求。

 4、投影矩阵

我们需要学习的两个重要的投影矩阵是:透视投影和正射投影。

4.1 透视投影矩阵

透视投影通过使用透视概念模仿我们看真实世界的方式,尝试让2D图像看起来像是3D的。物体*大远小,3D空间中有的*行线用透视法画出来就不再*行。

透视矩阵或者透视变换,通过定义4个参数来进行视体(view volume)的构建。其中4个参数是纵横比、视场、投影*面或*剪裁*面、远剪裁*面。

只有在远*剪裁*面间的物体才会被渲染。*剪裁*面同时也是物体所投影到的*面,通常放在离眼睛或相机较*的位置。视场是可视空间的纵向角度。纵横比是远*剪裁*面的宽度比高度。通过这些元素所形成的形状叫作视锥(frustum)。

  透视矩阵用于将3D空间中的点变换至*剪裁*面上合适的位置,它的构建需要先计算q、A、B、C的值,之后用这些值来构建透视矩阵。

 生成透视变换矩阵很容易,只需要将所描述的公式插入一个4×4矩阵。GLM库也包含了一个用于构建透视矩阵的函数glm::perspective()

4.2 正射投影矩阵

在正射投影中,*行线仍然是*行的,即不使用透视,

 正射投影是一种*行投影,其中所有的投影都与投影*面垂直。正射矩阵通过如下参数构建:(a)从相机到投影*面的距离Znear;(b)从相机到远剪裁*面的距离Zfar;(c)L、R、T、和B的值,其中L和R分别是投影*面左右边界的X坐标,T和B分别是投影*面上下边界的Y坐标

  5、LookAt矩阵

当你想要把相机放在某处并看向一个特定的位置时,就需要用到它了。

  LookAt变换依然由相机旋转决定。我们通过指定大致旋转朝向的向量(如世界Y轴)。通常,可以通过一系列叉积获得相机旋转的正面、侧面以及上面。

  GLM中已经有一个用来构建LookAt矩阵的函数glm::lookAt()。

三、GLSL矩阵变换

虽然GLM包含了许多预定义的3D变换函数,但GLSL只包含了基础的矩阵运算,如加法、合并等。因此,有时我们需要自己为GLSL写一些实用函数来构建3D变换矩阵,以在着色器中进行特定3D运算。

用于存储这些矩阵的GLSL数据类型是mat4。

GLSL中用于初始化mat4矩阵的语法以列为单位读入值。前4个参数会放入第一列,接下来4个参数放入下一列,直到第四列。

构建并返回*移矩阵GLSL函数

mat4 buildTranslate(float x, float y, float z)
{ mat4 trans = mat4(1.0, 0.0, 0.0, 0.0, // 注意,这是最左列,而非第一行
                    0.0, 1.0, 0.0, 0.0,
                    0.0, 0.0, 1.0, 0.0,
                    x, y, z, 1.0);
  return trans;
}

旋转

//构建并返回绕X轴的旋转矩阵
mat4 buildRotateX(float rad){
mat4 xrot = mat4(1.0, 0.0, 0.0, 0.0,
                   0.0, cos(rad), -sin(rad), 0.0,
                   0.0, sin(rad), cos(rad), 0.0,
                   0.0, 0.0, 0.0, 1.0);
  return xrot;
}

//构建并返回绕Y轴的旋转矩阵
mat4 buildRotateY(float rad){ 
mat4 yrot = mat4(cos(rad), 0.0, sin(rad), 0.0,
                   0.0, 1.0, 0.0, 0.0,
                   -sin(rad), 0.0, cos(rad), 0.0,
                   0.0, 0.0, 0.0, 1.0);
return yrot;
}

//构建并返回绕Z轴的旋转矩阵
mat4 buildRotateZ(float rad){ 
mat4 zrot = mat4(cos(rad), -sin(rad), 0.0, 0.0,
                   sin(rad), cos(rad), 0.0, 0.0,
                   0.0, 0.0, 1.0, 0.0,
                   0.0, 0.0, 0.0, 1.0);
return zrot;
}

缩放

//构建并返回缩放矩阵
mat4 buildScale(float x, float y, float z)
{ mat4 scale = mat4(x, 0.0, 0.0, 0.0, // 注意,这是最左列,而非第一行
                    0.0, y, 0.0, 0.0,
                    0.0, 0.0, z, 0.0,
                    0.0, 0.0, 0.0, 1.0);
  return scale;
}

想要弄通,还需要找些相关资料。

猜你喜欢

转载自blog.csdn.net/ttod/article/details/135346083