OpenGL(6)之变换

557 阅读12分钟

title: OpenGL(6)之变换

date: 2020-07-03 10:34

category: 图形学

tags: opengl

OpenGL(6)之变换

1. 概述

在之前的学习过程中,系统的学习了一个物体是如何创建,着色,加入纹理,但是它们始终还是静态的物体,那么可以尝试在每一帧改变物体的顶点并且重新配置缓冲区使得其移动, 但是这种方式比较耗费时间,通过多个矩阵(matrix)对象可以很好的完成对一个物体进行变换,在了解矩阵之前先来看看什么是向量吧。

2. 向量

向量最基本的定义就是一个方向,有一个方向和大小,好比小时候玩大富翁,向左边走4步,向北走2步,其中的左与北是方向,4步就是向量的长度。 向量的维度是任意的,通常使用的是2至4维,如果一个向量有2个维度,表示的是一个 平面,当有3个维度表达的是一个3D世界。 如下图:

其中的向量在2D图像中使用的一个箭头(x,y)表示,由于向量更加强调的是方向, 所以起始点并不会改变它的值,其中向量V和向量W是相等的;

向量与标量运算

标量只是一个数字,当把一个向量加减乘除一个标量,可以简单的理解把向量的每个分量分别进行该运算;

对于加法来说就好比这样:

\left\{
 \begin{matrix}
     1\\
  	 2\\
  	 3\\
  \end{matrix} \right\}+x =\left\{
 \begin{matrix}
     1+x\\
  	 2+x\\
  	 3+x\\		
  \end{matrix} 
\right\}

注意的是➖和➗运行时不能颠倒(标量(x)➖/➗向量)这样做法没有意义

向量取反

对一个向量取反会将其方向逆转,因此在一个向量的每个分量前加上负号就可以实现取反;

-v=-\left\{
 \begin{matrix}
     a\\
  	 f\\
  	 k\\
  	 p\\
  \end{matrix} \right\} = 
\left\{
 \begin{matrix}
     -a\\
  	 -f\\
  	 -k\\		
   	 -p\\
  \end{matrix} 
\right\}

向量加减

向量的加法可以被定为分量的相加,即将一个向量中的每一个分量加上另一个向量的对应的分量

v=
\left\{
 \begin{matrix}
     1\\
  	 2\\
  	 3\\		
  \end{matrix} 
\right\},

k=
\left\{
 \begin{matrix}
     4\\
  	 5\\
  	 6\\		
  \end{matrix} 
\right\}	➡️

v+k =
\left\{
 \begin{matrix}
     1+4\\
  	 2+5\\
  	 4+6\\		
  \end{matrix} 
\right\}=\left\{
 \begin{matrix}
     5\\
  	 7\\
  	 9\\		
  \end{matrix} 
\right\}

向量V(3,2)和向量W(1,2)可以直观的表达为如下图所示,自然V+W就是向量对应的分量相加的结果,同样的V-W也是如此;

长度

使用勾股定理来获取向量的长度或者说大小,如果把向量的x与y分量画出来,该向量会与x与y分量形成一个三角形。

x和y已知,斜边v的长度通过勾股定理计算可得

\mid\mid v \mid\mid=\sqrt{x^2+y^2}
\mid\mid v \mid\mid  表示向量v的长度

有一种特殊向量叫做 单位向量 ,特点就是长度是1;

n = \frac{v}{\mid\mid v \mid\mid}

一般这种方法叫做一个向量的标准化,特别是在只关心方向不关心长度的时候应用多。

向量相乘

  • 点乘

    两个向量的点乘等于它们的数乘结果乘以两个向量之间的夹角的余弦值。

    v\cdot n = \mid\mid v \mid\mid \cdot \mid\mid n \mid\mid \cos \theta
    v与n向量之间的夹角为\theta,如果v和n向量都是单位向量,长度就为1

    90度的余弦值为0,0度的余弦值为1;使用点乘可以测试两个向量是否正交或者平行(正交意味着两个向量互为直角)

    当然通过点乘结果来计算两个非单位向量的夹角,点乘的结果除以两个向量的长度之积,得到结果就是夹角的余弦值, 即

    \cos\theta\
    \cos\theta= \frac{v \cdot k}{\mid\mid v\mid\mid \cdot \mid\mid k \mid\mid}

    点乘计算:通过将对应的分量逐个相乘,然后再把所得积相加计算。

    \left\{
   	 \begin{matrix}
   	     1\\
   	  	 2\\
   	  	 4\\		
   	  \end{matrix} 
   	\right\}
   	\cdot
   	\left\{
   	 \begin{matrix}
   	     1\\
   	  	 -1\\
   	  	 0\\		
   	  \end{matrix} 
\right\}=(1\ast1)+(2\ast-1)+(4\ast0)=-1
  • 叉乘

叉乘只在3D空间中有定义,需要两个不平行向量作为输入,生成一个正交于两个输入向量的第三个向量,如果输入的两个向量也是正交的,那么叉乘之后将会产生3个互相正交的向量。

\left\{
		 \begin{matrix}
		     A_x\\
		  	 A_y\\
		  	 A_z\\		
		  \end{matrix} 
		\right\}
		\ast
		\left\{
		 \begin{matrix}
		     B_x\\
		  	 B_y\\
		  	 B_z\\		
		  \end{matrix} 
		\right\}=
		\left\{
		\begin{matrix}
		A_y \cdot B_z - A_z \cdot B_y\\
		A_z \cdot B_x - A_x \cdot B_z\\
		A_x \cdot B_y - A_y \cdot B_x\\
		\end{matrix}
		\right\}

为了便于记忆: x分量相乘,操作的是另外两个yz分量,其中A右乘B,那么就由 A_y 与 B_z 以及 A_z 与B_y组合做差值,当然它们之间的计算是通过点乘的即 A_y · B_z以及 A_z · B_y ; 其他分量同样可以分析可得;

3. 矩阵

矩阵就是一个矩形的数字、符号或表达式数组。矩阵中每一项叫做矩阵的元素, 以下就是一个2*3的矩阵的例子:

\left[{
	\begin{matrix}
	1 & 2 & 3\\
	4 & 5 & 6 \\
\end{matrix}
}\right]

矩阵可以通过(i,j)进行索引,i是行,j是列, 这与在求2D图像索引时候的 (x,y)相反,获取4的索引是(2,1)[第二行,第一列], 如果是图像索引应该是(1,2),先算列,在算行。

矩阵的加减

矩阵与标量的加减定义如下:

\left[{
\begin{matrix}
1 & 2 \\
3 & 4 \\
\end{matrix}\
} \right]+ 3 =\left[{
\begin{matrix}
1+3 & 2+3\\
3+3 & 4+3\\
\end{matrix}\
}\right]= \left[{
	\begin{matrix}
	4 & 5\\
	6 & 7\\
	\end{matrix}\
}\right]

上述矩阵加法就是通过标量值要加到矩阵的每一个元素上,同样减法也是如此

\left[{
\begin{matrix}
1 & 2 \\
3 & 4 \\
\end{matrix}
}\right]  - 1 = \left[{
\begin{matrix}
1-1 & 2-1 \\
3-1 & 4-1 \\
\end{matrix}\
}\right] = \left[{
	\begin{matrix}
	0 & 1\\
	2 & 3\\
	\end{matrix}
}\right]

矩阵与矩阵之间的加减法就是两个矩阵对应的元素的加减运算,只不过是相同索引下的元素进行运算,也就是说加法和减法只对同纬度的矩阵才有定义,一个3*2矩阵和2*3矩阵是不能进行加减的。

\left[{
\begin{matrix}
 1 & 2\\
 3 & 4 \\
\end{matrix}
}\right] + \left[{
	\begin{matrix}
	5 & 6 \\
	7 & 8\\
\end{matrix}
}\right]= \left[{
\begin{matrix}
1+5 & 2+6 \\
	3+7 & 4+8 \\
\end{matrix}
}\right] = \left[{
	\begin{matrix}
	6 & 8 \\
	10 & 12\\
	\end{matrix}
}\right]

同样的法则减法也适用:

\left[{
	\begin {matrix}
	4 & 2 \\
	6 & 1 \\
	\end{matrix}
}\right]  - \left[{\begin{matrix} 
	2 & 3 \\
	0 & 1 \\
	\end{matrix}
}\right] = \left[{\begin{matrix}
4-2 & 2-3\\
6-0 & 1-1\\
\end{matrix}
}\right] = \left[{
\begin{matrix}
	-2 & -1 \\
	6 & 0\\
\end{matrix}
}\right]

矩阵的数乘

和矩阵与标量的加减一样,矩阵与标量之间的乘法也是矩阵的每一个元素分别乘以该标量; 其实可以说标量就是用它的值缩放和放大矩阵的所有元素。

2\cdot\left[{\begin{matrix}
	1 & 2 \\
	3 & 4 \\
	\end{matrix}
}\right] = \left[{\begin{matrix}
2 \cdot 1 & 2 \cdot 2 \\
3 \cdot 2 & 3 \cdot 4 \\
\end{matrix}
}\right] = \left[{\begin{matrix}
	2 & 4 \\
	3 & 12 \\
\end{matrix}
}\right]

矩阵相乘 矩阵之间的乘法有如下现在:

  • 只有当左侧矩阵的列数与右侧的矩阵的行数相等,两个矩阵才能相乘
  • 矩阵相乘不遵守交换律,也就是A · B != B · A .

当左侧的矩阵为m*n的矩阵以及右侧为n*s的矩阵,其中n为大于0的整数,那么相乘的矩阵的维度就是(m,s)m为左侧矩阵的行数,s为右侧矩阵的列数,形成一个m*s的矩阵; 矩阵的乘法就是一系列乘法和加法组合的结果,使用到了左侧矩阵的行和右侧矩阵的列。

\left[{
	\begin{matrix}
	1 & 2 \\
	3 & 4 \\
	\end{matrix}
}\right] \cdot \left[{
	\begin{matrix}
	5 & 6\\
	7 & 8 \\
	\end{matrix}
}\right] = \left[{
	\begin{matrix}
	1\cdot5+2\cdot7 & 1\cdot6+2\cdot8 \\
	3\cdot5+4\cdot7 & 3\cdot6+4\cdot8 \\
	\end{matrix}
}\right] = \left[{
\begin{matrix}
19 & 22\\
43 & 50 \\
\end{matrix}
}\right]

4. 矩阵与向量相乘

我们可用向量来表示位置,表示颜色,甚至是纹理坐标,让我们更深入了解一下向量。

比如说N*1矩阵,N表示向量分量的个数(N维向量),只有1列, 有一个M*N的矩阵,用这个矩阵乘以我们的N*1的向量,因为这个矩阵的列数等于我们 向量的行数,因此是可以相乘的,并且可以将向量视为矩阵;

单位矩阵

OpenGL中,由于某些原因通常使用4*4的变换矩阵,而其中最重要的原因就是大部分的向量都是4分量,想到的最简单的变换矩阵就是单位矩阵。单位矩阵如下:

\left[{
	\begin{matrix}
	1 & 0 & 0 & 0\\
	0 & 1 & 0 & 0\\
	0 & 0 & 1 & 1\\
	0 & 0 & 0 & 1\\
	\end{matrix}
}\right] \cdot \left[{
\begin{matrix}
1 \\
2 \\
3 \\ 
4 \\
\end{matrix}
}\right] = \left[{
\begin{matrix}
1\cdot1\\
1\cdot2\\
1\cdot3\\
1\cdot4\\
\end{matrix}
}\right]=\left[{
	\begin{matrix}
	1\\
	2\\
	3\\
	4\\
\end{matrix}
}\right]

那么你可看到,上述的变换矩阵(单位矩阵)使得一个向量完全不变。 意义何在: 单位矩阵通常是生成其他变换矩阵的起点,是对证明定理,解线性方程非常有用的矩阵。

缩放

对一个向量进行缩放就是对向量的长度进行缩放,而且要保持它的方向不变。 由于操作的是2维或3维操作,可以分别定义一个有2或3个缩放变量的向量, 每个变量缩放一个轴(x,y或z)。

对于一个向量v=(3,2).可以把向量沿着x轴缩放0.5,使其宽度缩小为原来的二分之一,沿y轴把向量的高度缩放为原来的两倍,如下图所示:

注意

  • OpenGL通常在3D空间进行操作,对于2D的情况可以把z轴缩放1倍,z轴的值就不会变了,
  • 缩放的操作是不均匀缩放,x轴缩放0.5,y轴缩放2,缩放因子不一样,因此称为不均匀缩放。如果缩放因子相同,即每个轴缩放同样倍数,视为均匀缩放

缩放矩阵 从单位矩阵可以知道,每个对角线的元素与向量对应的元素相乘,使得一个向量完全不可变,单位矩阵+均匀缩放的概念形成的矩阵A,就可以使得向量的每一个分量进行均匀缩放,那么矩阵A就是定义为任意向量(x,y,z)的一个缩放矩阵;

\left[{
	\begin{matrix}
S1 & 0  & 0  & 0\\
0  & S2 & 0  & 0\\
0  & 0  & S3 & 0\\
0  & 0  & 0  & 1\\
\end{matrix}
}\right]  \cdot \left\{ 
 \begin{matrix}
     x\\
  	 y\\
  	 z\\1\\		
  \end{matrix} 
\right\} = \left\{
 \begin{matrix}
     S1·x\\
  	 S2·y\\
  	 S3·z\\
  	 1\\		
  \end{matrix} 
\right\}

上述可以这样去描述,把缩放变量表示为(S1,S2,S3)就是4*4的缩放矩阵, x分量缩放倍数为S1,y分量缩放倍数为S2,z分量缩放倍数为S3。 就可以这样去定义缩放变量表示为(S1,S2,S3)为任意向量(x,y,z)定义的一个缩放矩阵

位移

位移是在原始向量的基础上加上另一个向量从而获得一个在不同位置的新向量的过程,从而在位移向量基础上移动了原始向量。

位移矩阵 对比上述的缩放矩阵的关注点就是第四列最上面的3个值,这个三个值就称为位移向量,用位移向量表示为(Tx,Ty,Tz),则位移矩阵定义为:

\left[{
	\begin{matrix}
	1 & 0 & 0 & Tx \\
	0 & 1 & 0 & Ty \\
	0 & 0 & 1 & Tz \\
	0 & 0 & 0 & 1 \\
\end{matrix}
}\right]  \cdot \left\{
\begin{matrix}
x\\
y\\
z\\
1\\
\end{matrix}
 \right\} =\left\{
	\begin{matrix}
	x+Tx\\
	y+Ty\\
	z+Tz\\
	1\\
\end{matrix}
\right\}

注意到所有的位移值都要乘以向量的w行,也就是上述向量中的1,这样位移值会加到向量的原始值上。

小知识点: 齐次坐标:向量的w分量也叫齐次坐标,想要从齐次向量得到3D向量,可以把x,y,z坐标分别除以w坐标。 但是此处的w分量通常是1.0,使用齐次坐标的好处:

1.允许在3D向量上进行位移(即没有w分量不能位移向量

2.可作为方向向量,当w分量为0时,这个向量就不能位移(不能位移一个方向

有了位移矩阵,可以在3个方向(x,y,z)上移动物体。

旋转

【1】 一个向量旋转代表什么?

无非就是在2D或者3D空间通过对坐标进行偏移相应的弧度或者是角度达到对坐标的旋转的过程。 说白了:一圈就是360度或者是2PI。 根据公式 半圈就是(2PI180.f/PI)/2=180度; 1/5圈就是(2PI180.f/PI)/5 = 360/5=72度。

  • 弧度转角度:角度 = 弧度 * (180.f /PI)
  • 角度转弧度:弧度 = 角度 * (PI / 180.f)

PI约等于3.141592653.

【2】3D空间旋转需要定义一个角和一个旋转轴。 旋转矩阵在3D空间中每个单位轴都有不同定义,通常给定一个角度 ?\theta?,可以把一个向量变换为一个经过旋转的新向量(手段是通过旋转矩阵来实现的)

沿着X轴旋转:

\left[{
\begin{matrix}
1 & 0 & 0 & 0 \\
0 & \cos\theta & - \sin\theta & 0\\
0 & \sin\theta & + \cos\theta & 0\\
0 & 		0		  & 		0 	& 1 \\
\end{matrix}
}\right] \cdot \left\{
 \begin{matrix}
 x\\
 y\\
 z\\
 1\\
\end{matrix}
\right\} = \left\{
 \begin{matrix}
 x \\
 \cos\theta\cdot y - \sin\theta\cdot z \\
 \sin\theta\cdot y + \cos\theta\cdot z \\
 1\\
\end{matrix}
\right\}

沿着Y轴旋转:

\left[{
 \begin{matrix}
 \cos\theta & 0 & \sin\theta & 0 \\
 0 & 1 & 0 & 0\\
 -\sin\theta & 0 &\cos\theta & 0\\
 0 & 		0		  & 		0 	& 1 \\
 \end{matrix}
}\right] \cdot \left\{
  \begin{matrix}
  x\\
  y\\
  z\\
  1\\
\end{matrix}
\right\}= \left\{
  \begin{matrix}
  \cos\theta\cdot x  + \sin\theta\cdot z\\
  y\\
  \cos\theta\cdot z  -  \sin\theta\cdot x \\
  1\\
\end{matrix}
\right\}

沿着Z轴旋转:

\left[{
   \begin{matrix}
   \cos\theta & -\sin\theta  & 0 & 0 \\
   \sin\theta & \cos\theta & 0 & 0\\
   0 & 0 & 1 & 0\\
   0 & 	0& 0 & 1 \\
   \end{matrix}
}\right] \cdot \left\{
	\begin{matrix}
	x\\
	y\\
	z\\
	1\\
\end{matrix}
\right\} = \left\{
	\begin{matrix}
	\cos\theta\cdot x  - \sin\theta\cdot y\\
	\sin\theta\cdot x  + \cos\theta\cdot y \\
	z\\
	1\\
\end{matrix}
\right\}

记忆小技巧: 针对哪个轴,可以这样去记忆,如果是x轴旋转,矩阵的x分量所对应的行和列都是单位矩阵,其中w分量对应的都是1,围绕的就是y和z分量的对应的余弦和正弦角度变换了。

组合矩阵变换

矩阵相乘,因为矩阵乘法不遵守交换律,意味着顺序很重要,当矩阵相乘的时候,最右边的矩阵是第一个与向量相乘的(即组合矩阵对向量进行旋转,平移,缩放,操作,都是最靠近向量的那个矩阵即最右边的矩阵),所以应该从右边向左边读乘法,组合矩阵的时候,建议先进行缩放操作,然后进行旋转,最后才是位移

理由如下:如果先位移在缩放,位移的向量也会同样倍缩放(比如想某方向移动,然后进行缩放,即2米也会会被缩放成1米

案例:假设有一个顶点(x,y,z),将其缩放2倍,然后位移(1,2,3)个单位,通过位移和缩放矩阵来完成这个变换。

Trans\cdot Scale = \left[{
	\begin{matrix}
	1 & 0 & 0 & 1\\
	0 & 1 & 0 & 2 \\
	0 & 0 & 1 & 3 \\
	0 & 0 & 0 & 1 \\
\end{matrix}
}\right] \cdot \left[{
	\begin{matrix}
	2 & 0 & 0 & 0\\
	0 & 2 & 0 & 0\\
	0 & 0 & 2 & 0\\
	0 & 0 & 0 & 1\\
\end{matrix}
}\right] = \left[{
	\begin{matrix}
	2 & 0 & 0 & 1\\
	0 & 2 & 0 & 2\\
	0 & 0 & 2 & 3\\
	0 & 0 & 0 & 1\\
\end{matrix}
}\right]

OK,用上述得到的矩阵左乘向量得出结果:

\left[{
	\begin{matrix}
	2 & 0 & 0 & 1\\
	0 & 2 & 0 & 2\\
	0 & 0 & 2 & 3\\
	0 & 0 & 0 & 1\\
	\end{matrix}
}\right] \cdot \left[{
	\begin{matrix}
	x\\
	y\\
	z\\
	1\\
	\end{matrix}
}\right] = \left[{
	\begin{matrix}
	2x+1\\
	2y+2\\
	2z+3\\
	1\\
	\end{matrix}
}\right]

向量先缩放2倍,然后位移了(1,2,3)个单位。

5.GLM

GLM:OpenGL Mathematics的缩写,只有头文件的库,即只需要引入其头文件,不需要进行链接和编译。.

下载链接

tips: GLM库从0.9.9版本起,默认将矩阵类型初始化为一个零矩阵(所有元素均为0),而不是单位矩阵,如果使用的是0.9.9或0.9.9版本以上的版本,需要将所有的矩阵初始化改为glm::mat4 mat= glm::mat4(1.0f).

重要的头文件如下


#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

使用glm完成上述对向量的平移操作,

思考?向量的位移操作是不是通过一个单位向量的变种,第四列为所要平移的单位数。当然如果是要进行3维方向的位移,还需要定义一个齐次向量(1.0,0.0,0.0,1.0)来完成最终的变换之后的向量输出。

涉及的矩阵和向量:

  • 4*4的单位矩阵 --- A
  • 3*1的位移单位向量 -- B
  • 4*1的齐次向量 --- C
//C
glm::vec4 v4(1.f,0.f,0.f,1.f);

//A
glm::mat4 trans = glm::mat4(1.0f);

//B
glm::vec3 v3(1.0f,1.f,0.f);

//matrix translate
trans = glm::translate(trans,v3);

//vec 
v4 = trans*v4;

std::cout<<v4.x<<v4.y<<v4.z<<std::endl;

旋转和缩放

原则肯定是先旋转然后缩放,创建变换矩阵:

glm::mat4 trnas = glm::mat4(1.0f);
//通过定义一个vec3(0.0,0.0,1.0)可知是绕Z轴旋转 角度为90度
trans = glm::rotate(trans,glm::radians(90.f),glm::vec3(0.0,0.0,1.0));
//x,y轴缩放为原来的0.5倍,z轴不变。
trans = glm::scale(trans,glm::vec3(0.5,0.5,1));

矩阵传递给着色器

GLSL中有一个mat4类型,可通过定义一个mat4的uniform变量,用于接收矩阵数据并发送给着色器,然后在与输入的顶点数据(即位置向量)进行相乘得到最终变换的向量位置。

#version core 330
layout (location = 0 )in vec3 aPos;
layout (location = 1 )in vec2 aTexCoord;

out vec2 TexCoord;
uniform mat4 transform;

void main(){
	gl_Position = transform * vce4(aPos,1.0f);
	TexCoord = vec2(aTexCoord.x,1.0-aTexCoord.y);
}

参考

learnopeng-cn