快速导航(持续更新中)
WebGL系列教程一(开篇)
WebGL系列教程二(环境搭建及着色器初始化)
WebGL系列教程三(使用缓冲区绘制三角形)
WebGL系列教程四(绘制彩色三角形)
WebGL系列教程五(使用索引绘制彩色立方体)
WebGL系列教程六(纹理映射与立方体贴图)
WebGL系列教程七(二维及三维旋转、平移、缩放)
WebGL系列教程八(GLSL着色器基础语法)
WebGL系列教程九(动画)
WebGL系列教程十(模型Model、视图View、投影Projection变换)
WebGL系列教程十一(光照原理及Blinn Phong着色模型)
目录
1 前言
上一讲我们讲了动画,现在大家已经知道该如何让一个三维物体动起来了,那么接下来我们就要搞明白如何进行模型、视图、投影变换,即MVP
变换,也即Model、View、Projection
的变换。
2 模型变换
什么是模型变换?模型变换就是对模型做旋转、平移、缩放,即我们在WebGL系列教程七(二维及三维旋转、平移、缩放)中讲的就是模型变换。模型变换,用大白话解释就是,把模型以某种姿态放在某个位置。那怎么样进行旋转平移缩放?自然是乘以相应的旋转、平移、缩放矩阵。这一块的代码本节我们就不赘述了,有兴趣的同学可以自行查看教程七。
3 视图变换
什么是视图变换?视图变换的意思就是我从哪里看向哪里。这里面就牵扯到了三个概念,视点、目标点和上方向。视点就是我们在哪个地方看,目标点就是我要看向哪里,上方向就是我看的角度。试想一下,我站着看一个物体,和躺着看一个物体,看到的东西或许是不一样的。比如我站着看看一个立方体,我能看到立方体的顶面,但是当我躺着看的时候,我就看不到顶面了,我看到的是正对着我的面。
我们分别用三个坐标来表示视点、目标点和上方向。
视点:eyeX,eyeY,eyeZ
目标点:atX,atY,atZ
上方向:upX,upY,upZ
那我们怎么求一个视图变换的矩阵呢?我们的做法是:首先约定,相机始终位于原点,看向负Z轴方向,所以要将摄像机移到原点并调整方向,使得摄像机的视线对准坐标系的负Z轴。那么首先就是用齐次坐标去平移,然后旋转。由于从物体旋转到约定位置比较困难,因此改为从约定位置旋转到物体位置,得到旋转矩阵,进行得到逆矩阵,就是我们要的变换了。而又因为旋转矩阵是正交矩阵,它的逆矩阵就是它的转置。这样我们就很容易得到视图变换矩阵了。
3.1 公式推导
3.1.1 确定摄像机的参数
假设摄像机的位置为 E \mathbf{E} E(Eye),摄像机的朝向为 D \mathbf{D} D(Direction),摄像机的上向量为 U \mathbf{U} U(Up Vector)。
- E = ( eyeX , eyeY , eyeZ ) \mathbf{E} = (\text{eyeX}, \text{eyeY}, \text{eyeZ}) E=(eyeX,eyeY,eyeZ) 是摄像机的位置。
- D = ( atX − eyeX , atY − eyeY , atZ − eyeZ ) \mathbf{D} = (\text{atX} - \text{eyeX}, \text{atY} - \text{eyeY}, \text{atZ} - \text{eyeZ}) D=(atX−eyeX,atY−eyeY,atZ−eyeZ) 是摄像机的朝向。
- U = ( upX , upY , upZ ) \mathbf{U} = (\text{upX}, \text{upY}, \text{upZ}) U=(upX,upY,upZ) 是摄像机的上向量,用于定义视图的顶部方向。
3.1.2 构建摄像机坐标系
首先,我们需要构建摄像机的坐标系,该坐标系由三个正交单位向量组成,我们通过归一化和向量差积获得:
- f \mathbf{f} f 是摄像机的前向量,也称为视线向量:
f = D ∣ D ∣ \mathbf{f} = \frac{\mathbf{D}}{|\mathbf{D}|} f=∣D∣D
- r \mathbf{r} r 是摄像机的右向量,定义为 U \mathbf{U} U 和 f \mathbf{f} f 的叉积:
r = U × f ∣ U × f ∣ \mathbf{r} = \frac{\mathbf{U} \times \mathbf{f}}{|\mathbf{U} \times \mathbf{f}|} r=∣U×f∣U×f
- u \mathbf{u} u 是新的上向量,定义为 f \mathbf{f} f 和 r \mathbf{r} r 的叉积:
u = f × r \mathbf{u} = \mathbf{f} \times \mathbf{r} u=f×r
3.1.3 构建视图变换矩阵
视图变换矩阵由旋转矩阵 R R R 和平移矩阵 T T T 组合而成:
- 旋转矩阵 R R R:
R = [ r x r y r z 0 u x u y u z 0 − f x − f y − f z 0 0 0 0 1 ] R = \begin{bmatrix} r_x & r_y & r_z & 0 \\ u_x & u_y & u_z & 0 \\ -f_x & -f_y & -f_z & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} R= rxux−fx0ryuy−fy0rzuz−fz00001
- 平移矩阵 T T T:
T = [ 1 0 0 − eyeX 0 1 0 − eyeY 0 0 1 − eyeZ 0 0 0 1 ] T = \begin{bmatrix} 1 & 0 & 0 & -\text{eyeX} \\ 0 & 1 & 0 & -\text{eyeY} \\ 0 & 0 & 1 & -\text{eyeZ} \\ 0 & 0 & 0 & 1 \end{bmatrix} T= 100001000010−eyeX−eyeY−eyeZ1
3.1.4 组合视图矩阵
视图变换矩阵 V V V 是旋转矩阵和平移矩阵的组合:
V = R × T V = R \times T V=R×T
将两者相乘得到:
V = [ r x r y r z − eyeX ⋅ r x − eyeY ⋅ r y − eyeZ ⋅ r z u x u y u z − eyeX ⋅ u x − eyeY ⋅ u y − eyeZ ⋅ u z − f x − f y − f z eyeX ⋅ f x + eyeY ⋅ f y + eyeZ ⋅ f z 0 0 0 1 ] V = \begin{bmatrix} r_x & r_y & r_z & -\text{eyeX} \cdot r_x - \text{eyeY} \cdot r_y - \text{eyeZ} \cdot r_z \\ u_x & u_y & u_z & -\text{eyeX} \cdot u_x - \text{eyeY} \cdot u_y - \text{eyeZ} \cdot u_z \\ -f_x & -f_y & -f_z & \text{eyeX} \cdot f_x + \text{eyeY} \cdot f_y + \text{eyeZ} \cdot f_z \\ 0 & 0 & 0 & 1 \end{bmatrix} V= rxux−fx0ryuy−fy0rzuz−fz0−eyeX⋅rx−eyeY⋅ry−eyeZ⋅rz−eyeX⋅ux−eyeY⋅uy−eyeZ⋅uzeyeX⋅fx+eyeY⋅fy+eyeZ⋅fz1
这个矩阵 V V V 就是将世界坐标系中的点转换到摄像机视图坐标系的视图变换矩阵。
3.2 方法调用
公式推导可能有点复杂,但是方法调用很简单,各种框架都类似,WebGL编程指南中提供的方法如下:
let viewProjMatrix = new Matrix4();
//从0,2,7看向0,0,0,上方向是0,1,0
viewProjMatrix.lookAt(0,2,7,0,0,0,0,1,0);
4 投影变换
什么是投影?投影就是将一个三维物体,映射到平面上,就叫投影。我们这里的投影共分为两种,正交投影和透视投影。投影的最终目标约定为,我们要将物体的任一x、y、z
都变换到【-1,1】的范围内。三个值都需要变换到【-1,1】,所以也叫【-1,1】的三次方所形成的盒子。
4.1 正交投影推导
正交投影(也叫正射投影)的核心思想是将三维空间中的点沿着平行于某一轴的方向投影到二维平面上。由于投影方向是平行的,投影后的图像不会产生透视缩小效果,保持物体的比例和尺寸不变。正交投影即不符合近大远小规律的投影,不论投影平面离物体多远,投影之后,大小始终是不变的。
如图所示,可视空间中的红球和黄球,不论离近平面上多远,当投影到近平面之后,始终那么大。
我们先给定视体(View Volume)的边界参数:
- 左边界(Left): l l l
- 右边界(Right): r r r
- 下边界(Bottom): b b b
- 上边界(Top): t t t
- 近剪裁面(Near Plane): n n n
- 远剪裁面(Far Plane): f f f
将缩放矩阵 S S S 与平移矩阵 T T T 相乘,得到正交投影矩阵 O O O:
O = S × T O = S \times T O=S×T
具体计算如下:
S × T = [ 2 r − l 0 0 0 0 2 t − b 0 0 0 0 − 2 f − n 0 0 0 0 1 ] × [ 1 0 0 − r + l 2 0 1 0 − t + b 2 0 0 1 − f + n 2 0 0 0 1 ] S \times T = \begin{bmatrix} \frac{2}{r - l} & 0 & 0 & 0 \\ 0 & \frac{2}{t - b} & 0 & 0 \\ 0 & 0 & -\frac{2}{f - n} & 0 \\ 0 & 0 & 0 & 1 \\ \end{bmatrix} \times \begin{bmatrix} 1 & 0 & 0 & -\frac{r + l}{2} \\ 0 & 1 & 0 & -\frac{t + b}{2} \\ 0 & 0 & 1 & -\frac{f + n}{2} \\ 0 & 0 & 0 & 1 \\ \end{bmatrix} S×T= r−l20000t−b20000−f−n200001 × 100001000010−2r+l−2t+b−2f+n1
进行矩阵乘法,最终得到标准正交投影矩阵 O O O:
O = [ 2 r − l 0 0 − r + l r − l 0 2 t − b 0 − t + b t − b 0 0 − 2 f − n − f + n f − n 0 0 0 1 ] O = \begin{bmatrix} \frac{2}{r - l} & 0 & 0 & -\frac{r + l}{r - l} \\ 0 & \frac{2}{t - b} & 0 & -\frac{t + b}{t - b} \\ 0 & 0 & -\frac{2}{f - n} & -\frac{f + n}{f - n} \\ 0 & 0 & 0 & 1 \\ \end{bmatrix} O= r−l20000t−b20000−f−n20−r−lr+l−t−bt+b−f−nf+n1
4.2 正交投影调用
var projMatrix = new Matrix4();
//projMatrix.setOrtho(left, right, bottom, top, near, far);
projMatrix.setOrtho(-1.0, 1.0, -1.0, 1.0, 0, 0.5);
4.3 透视投影推导
与正交投影不同,透视投影符合近大远小的规律,因此在视觉上,保留了物体在空间中的视觉深度感。
如图所示,可视空间中的红球和黄球,离近平面上越近,投影到近平面之后越大,离近平面上越远,投影到近平面之后越小。根据侧视图我们可以看到如下规律:
远平面上的xyz,和近平面的xyz是相似三角形的关系。也就是说
[
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
]
⋅
(
x
y
z
1
)
=
(
x
′
y
′
?
1
)
=
(
n
x
/
z
n
y
/
z
?
1
)
=
(
n
x
n
y
?
z
)
\begin{bmatrix} ? & ? & ? & ? \\ ? & ? & ? & ? \\ ? & ? & ?& ? \\ ? & ? & ?& ? \\ \end{bmatrix}\cdot\begin{pmatrix} x \\ y \\ z\\ 1 \end{pmatrix} =\begin{pmatrix} x' \\ y' \\ ? \\ 1 \end{pmatrix}= \begin{pmatrix} {nx}/{z} \\{ny}/{z} \\ ? \\ 1 \end{pmatrix}= \begin{pmatrix} nx \\ ny \\ ? \\ z \end{pmatrix}
????????????????
⋅
xyz1
=
x′y′?1
=
nx/zny/z?1
=
nxny?z
可以推出
[
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
]
=
[
n
0
0
0
0
n
0
0
?
?
?
?
0
0
1
0
]
\begin{bmatrix} ? & ? & ? & ? \\ ? & ? & ? & ? \\ ? & ? & ?& ? \\ ? & ? & ?& ? \\ \end{bmatrix}=\begin{bmatrix} n & 0 & 0 & 0 \\ 0 & n & 0 & 0 \\ ? & ? & ?& ? \\ 0 & 0 & 1& 0 \\ \end{bmatrix}
????????????????
=
n0?00n?000?100?0
就差第三行了,继续观察
通过观察我们发现远平面的在挤压到近平面的过程中,近平面上的点始终是没变化,说明
(
x
y
n
1
)
=
(
n
x
n
y
n
2
1
)
\begin{pmatrix} x \\ y \\ n \\ 1 \end{pmatrix}= \begin{pmatrix} nx \\ ny \\ n^2 \\ 1 \end{pmatrix}
xyn1
=
nxnyn21
上式中n表示到近平面的距离,n变为了n平方,那现在问题来了
(
?
?
?
?
)
(
x
y
n
1
)
=
n
2
(????)\begin{pmatrix} x \\ y \\ n \\ 1 \end{pmatrix}= n^2
(????)
xyn1
=n2
推出
(
?
?
?
?
)
=
(
00
A
B
)
(????)=(0 0 AB)
(????)=(00AB)
通过观察我们发现又远平面的在挤压到近平面的过程中,中心点也是始终没变化,说明
(
0
0
f
1
)
=
(
0
0
f
2
f
)
\begin{pmatrix} 0 \\ 0 \\ f \\ 1 \end{pmatrix}= \begin{pmatrix} 0 \\ 0 \\ f^2 \\ f \end{pmatrix}
00f1
=
00f2f
注意 f 变为了 f 平方
(
00
A
B
)
(
0
0
f
1
)
=
f
2
(0 0 AB) \begin{pmatrix} 0 \\ 0 \\ f \\ 1 \end{pmatrix}= f^2
(00AB)
00f1
=f2
两个式子解方程
{
A
n
+
B
=
n
2
A
f
+
B
=
f
2
=>
{
A
=
f
+
n
B
=
−
f
n
\begin{cases} An + B = n^2 \\ Af + B = f^2 \end{cases}\text{ => } \begin{cases} A = f+n \\ B = -fn \end{cases}
{An+B=n2Af+B=f2 => {A=f+nB=−fn
现在我们就得到了完整的透视除法矩阵
[
n
0
0
0
0
n
0
0
0
0
f
+
n
−
f
n
0
0
1
0
]
\begin{bmatrix} n & 0 & 0 & 0 \\ 0 & n & 0 & 0 \\ 0 & 0 & f+n& -fn \\ 0 & 0 & 1& 0 \\ \end{bmatrix}
n0000n0000f+n100−fn0
但是还没完,还要对它再做一次正交矩阵变换,就得到了透视投影矩阵,首先定义参数:
视野角(FOV):摄像机的垂直视角。
宽高比(aspect):近平面宽度与高度比。
近裁剪面(n):摄像机到近平面距离。
远裁剪面(f):摄像机到远平面距离。
然后进行换算,上边界、右边界和宽高比的关系如下:
t
=
n
⋅
tan
(
FOV
2
)
t = n \cdot \tan\left(\frac{\text{FOV}}{2}\right)
t=n⋅tan(2FOV)
r
=
t
⋅
aspect
r = t \cdot \text{aspect}
r=t⋅aspect
正交投影矩阵和透视除法矩阵相乘
[
2
r
−
l
0
0
−
r
+
l
r
−
l
0
2
t
−
b
0
−
t
+
b
t
−
b
0
0
−
2
f
−
n
−
f
+
n
f
−
n
0
0
0
1
]
[
n
0
0
0
0
n
0
0
0
0
f
+
n
−
f
n
0
0
1
0
]
\begin{bmatrix} \frac{2}{r - l} & 0 & 0 & -\frac{r + l}{r - l} \\ 0 & \frac{2}{t - b} & 0 & -\frac{t + b}{t - b} \\ 0 & 0 & -\frac{2}{f - n} & -\frac{f + n}{f - n} \\ 0 & 0 & 0 & 1 \\ \end{bmatrix}\begin{bmatrix} n & 0 & 0 & 0 \\ 0 & n & 0 & 0 \\ 0 & 0 & f+n& -fn \\ 0 & 0 & 1& 0 \\ \end{bmatrix}
r−l20000t−b20000−f−n20−r−lr+l−t−bt+b−f−nf+n1
n0000n0000f+n100−fn0
简化后,得到最终的透视投影矩阵为
P
=
[
1
aspect
⋅
tan
(
FOV
2
)
0
0
0
0
1
tan
(
FOV
2
)
0
0
0
0
−
(
f
+
n
)
f
−
n
−
2
f
n
f
−
n
0
0
−
1
0
]
P = \begin{bmatrix} \frac{1}{\text{aspect} \cdot \tan(\frac{\text{FOV}}{2})} & 0 & 0 & 0 \\ 0 & \frac{1}{\tan(\frac{\text{FOV}}{2})} & 0 & 0 \\ 0 & 0 & \frac{-(f + n)}{f - n} & \frac{-2fn}{f - n} \\ 0 & 0 & -1 & 0 \end{bmatrix}
P=
aspect⋅tan(2FOV)10000tan(2FOV)10000f−n−(f+n)−100f−n−2fn0
4.3 透视投影调用
let mvpMatrix = new Matrix4();
//mvpMatrix.setPerspective(fov,aspect,near,far);
mvpMatrix.setPerspective(30,1,1,100);
5 总结
本文讲解了模型、视图、投影变换的原理,并具体分析了MVP变换过程中的推导过程及调用过程,在实际应用中,各个框架都提供了进行这三个变换的方法,因此不必拘泥于具体的调用,了解原理即可。本文在理解上是有一定难度的,希望读者仔细揣摩,回见~
想要WebGL编程指南书籍和源码的同学可以公众号回复关键字WebGL编程指南获取。