WebGL 的矩阵变换【重要】

1,562 阅读8分钟

在二维屏幕上展现三维的世界的物体,需要经过一系列坐标变换。理解这一些列变换是进入 WebGL 三维世界的基础,最好可以亲自推导一遍,不手动推导也要多理解,反复举例验证。

从长远来看,处理非常复杂的的空间和纹理问题时,矩阵的知识是必不可少的。但是,对于数学能力一般的同学来说能把这些知识理解透彻是很困难的。所以,先要做到能熟练使用矩阵运算的插件和外部库,当熟练进行3D开发之后,熟练知道矩阵能做什么,然后再逐步深入了解这些知识是一种比较理想的学习路径。

例如一个边长为 2 的立方体 Box,其八个顶点的坐标分别是以立方体中心为原点的,是局部坐标系坐标;把立方体放到和相机同一个场景下,这个场景的坐标就是世界坐标系,立方体会有一个新的世界坐标;相机去观察立方体的时候,以相机为原点,构建了一个相机坐标系,相机模拟了人眼去观察物体,所以也叫视觉空间;人眼观察范围有限,所以相机观察也要模拟这一特点,将观察结果归一化到-1~1的裁剪空间坐标系内。

推荐看下 GAMES101 课程前几节。

一、基础变换矩阵

1.1、二维矩阵变换

二维矩阵的推导较为简单,由二维可以类举三维。思路可以参考:

在计算过程中要把二维向量转为三维向量(齐次坐标),以便进行矩阵运算。

具体的代码可以在三维矩阵变换中练习。

1.1.1、平移

var sMatrix = new Float32Array([
  1, 0, 0,
  0, 1, 0,
  tx, ty, 1
]);

1.1.2、缩放

var sMatrix = new Float32Array([
  sx, 0, 0,
  0, sy, 0,
  0, 0, 1
]);

1.1.3、逆时针旋转

var c = Math.cos(angleInRadians);
var s = Math.sin(angleInRadians);
var sMatrix = new Float32Array([
  c, s, 0,
  -s, c, 0,
  0, 0, 1
]);

1.2、三维矩阵变换

WebGL中矩阵变换运算遵循这个规则:

  • 变换矩阵 = 当前变换矩阵 * 下一步变换矩阵;
  • 变换后的坐标 = 变换矩阵 * 原始坐标;

可以对空间坐标依次进行变换,也可以先把变换计算之后一次性对坐标进行变换。计算的时候要把三维向量转为四维向量(齐次坐标),以便进行矩阵运算。

关于各种变换矩阵的推导可以参考:

1.2.1、平移变换矩阵

// WebGL中的矩阵是列主序,所以是这种顺序
var tMatrix = new Float32Array([
   1.0, 0.0, 0.0, 0,0,
   0.0, 1.0, 0.0, 0.0,
   0.0, 0.0, 1.0, 0.0,
   Tx,	Ty,	Yz,	1.0,
])

1.2.2、缩放变换矩阵运算

// WebGL中的矩阵是列主序,所以是这种顺序
var sMatrix = new Float32Array([
    Sx, 0.0, 0.0, 0.0, 
    0.0, Sy, 0.0, 0.0, 
    0.0, 0.0, Sz, 0.0, 
    0.0, 0.0, 0.0, 1.0
])

1.2.3、逆时针旋转变换矩阵运算

旋转要复杂一些,存在围绕xyz三个轴旋转的情况,矩阵是一个 3×3 的正交矩阵:

为了和四维齐次坐标进行运算,要把 3x3 的矩阵对角线位置补充1,变成 4x4 的矩阵。

// WebGL中的矩阵是列主序,所以上面那个矩阵的JS数组是这样的
var rzMatrix = new Float32Array([
   cosB, sinB, 0.0, 0.0,
  -sinB, cosB, 0.0, 0.0,
    0.0,  0.0, 1.0, 0.0,
    0.0,  0.0, 0.0, 1.0
])

二、WebGL里的矩阵变换

在一个完整的三维场景中,需要把三维模型的坐标投影映射到WebGL的裁剪空间,然后通过屏幕映射将三维坐标投影映射到二维平面。

场景变化后输出结果通常有两种做法,1是移动物体,二是移动相机,最长用的是第二种做法,可以一次性对场景中的所有物体进行变换投影。

常用的矩阵运算库,例如glMatrix,都可以输入参数生成响应的矩阵,进行加减乘除运算。

2.1、MVP 矩阵

MVP矩阵是模型(Model)、观察(View)、投影(Projection)三个矩阵的合称。通过 MVP 矩阵变换可以把模型局部坐标变换到 WebGL 的 NDC 裁剪坐标,是模型和真实世界的映射关系。

运算公式:注意顺序: 矩阵相乘满足结合律不满足交换律)

2.1.1 模型矩阵

一个独立的模型其坐标都是基于模型自身的原点(局部坐标系),在三维场景中会有许多模型,每个模型又有各自的世界坐标,所以在渲染三维场景的时候需要把模型坐标转换为世界坐标。

模型矩阵可以把模型的局部坐标变换为世界坐标。需要对模型进行缩放、绕xyz轴旋转、平移(注意:三个操作的先后顺序不能变)

旋转以绕x轴为例

M矩阵是由3个矩阵构成的,由于 WebGL 里矩阵是左乘,所以书写上是 MtMrMs;

// 模型矩阵
let mMatrix = M4.identity(); // 单位矩阵表示模型的坐标已经是世界坐标数值了,不需要进一步转换

2.1.2 视图矩阵

把世界坐标变换为相机坐标。三维场景中相机的默认位置是在原点(0,0,0),默认的视图矩阵就是单位矩阵。

改变相机位置后,对应的输出结果需要经过平移、旋转、 z轴取反(右手坐标系,相机z轴指向和裁剪空间z轴是相反的)。

可以结合相机矩阵学习理解。

旋转以绕x轴为例

let vMatrix = M4.identity();
// 分别是 相机位置、目标位置、up方向
vMatrix = M4.inverse(M4.lookAt([100, 150, 200], [0, 35, 0], [0, 1, 0]));

2.1.3 投影矩阵

物理世界里人眼感官具有近大远小的透视效果,因此投影矩阵有透视投影矩阵(perspective) ;在实际科学研究中也需要1:1的真实结果,因此投影矩阵也有正交投影矩阵(orthgraphic) 。这两种投影也对应两种视锥体,一个是梯形体,一个是矩形体。

投影矩阵把相机坐标系变换为裁剪空间坐标系(NDC空间),为二维投影做准备的(投影矩阵并不会投影为二维坐标)。

透视投影的近大远小是需要对xy坐标按照深度进行缩放,在运算中是用w分量做缩放范围值;

需要注意下,透视相机和正交相机的近裁面和远裁面是朝向裁剪空间的负z轴方向的(默认相机在裁剪空间的中心0点,看向屏幕里,所以是朝向负z)。

// 正交投影,长宽是容器大小,深度是6000(大点,不容易被裁减)
let pMatrix = M4.orthographic(-gl.canvas.width / 2, gl.canvas.width / 2, -gl.canvas.height / 2, gl.canvas.height / 2, -3000, 3000);
// 透视投影,90度视场角,深度是6000(大点,不容易被裁减)
let pMatrix = M4.perspective((60 * Math.PI) / 180, gl.canvas.width / gl.canvas.width, 1, 6000);

2.2 世界矩阵

世界矩阵:表示物体在世界坐标的缩放旋转平移的变换关系

let wMatrix = M4.identity();
wMatrix = M4.translate(wMatrix, -100, -100, -300); 				// 平移矩阵
wMatrix = M4.xRotate(wMatrix, (zDeg * Math.PI) / 180); 		// 旋转矩阵(动画)
wMatrix = M4.yRotate(wMatrix, (zDeg * Math.PI) / 180); 		// 旋转矩阵(动画)
wMatrix = M4.scale(wMatrix, 1, 1, 1); 										// 缩放矩阵

2.3、相机矩阵和lookAt 矩阵

2.3.1 相机矩阵

在默认的三维场景中相机是在原点(0,0,0)位置,看向屏幕里面,动画效果是场景内的物体的位置改变了,然后MVP矩阵投影输出。但是在实际动画应用中往往需要改变相机的位置和朝向来更新场景,这里就需要使用相机矩阵来计算新视角下投影的结果了。

计算相机矩阵需要知道相机的姿态信息,即相机在世界坐标中的角度和位置,然后会相机进行绕xyz轴旋转和平移(顺序不可变) ,其实就是把相机的局部坐标变换为世界坐标,可以把相机矩阵理解为相机的 Model 矩阵,所以相机矩阵就是平移矩阵和旋转矩阵的乘积。

旋转矩阵以绕y轴为例子

相机矩阵相当于改变了三维场景下世界坐标的参考系(世界坐标系的原点和方向变成了相机的原点和方向),所以,对相机矩阵求逆就是 MVP 矩阵中的 View 矩阵,常用来计算 View 矩阵。

2.3.2 lookAt矩阵

在相机改变的时候,默认是一直观察着(0,0,0)位置,在实际需求中,往往要一直观察其它某个特定坐标,锁定目标,这个时候就需要用到 lookAt 矩阵了。

如果知道了相机的位置和相机的朝向,那么就可以唯一确定一个lookAt矩阵了。相机的朝向可以用相机的XYZ三轴方向向量表示,计算相机三轴向量的步骤如下 :

      • 利用 cameraPosition 和 targetPosition 向量相减,计算出相机的Z
      • 利用计算出来的相机Z轴和相机up方向叉乘,计算出相机的X轴(如果相机自身没有旋转,那么up方向就是世界坐标的z轴)
      • 利用计算出来的相机Z轴和相X轴叉乘,计算出相机的Y

lookAt 矩阵的数学表达:

R、U、D分别是相机的xyz方向向量

2.3、Viewport 矩阵

通过 Viewport 矩阵变换可以把 WebGL 的 NDC 空间坐标变换到屏幕的像素坐标系,也成为视口变换(viewport transformation)。这里是由三维到二维的过程。

二维显示区域的起点坐标(xy)和长宽w h决定了 Viewport 矩阵变换参数。

矩阵运算公式:



这是WebGL 系列的入门文章,免费订阅,如有帮助请点赞收藏,纰漏之处欢迎指正!

qrcode_for_gh_3695c3ae18f4_258.jpg

也欢迎关注公众号交流知识哇😄