更新于 

进入三维世界

三维世界的观察者

立方体是由三角形构成的
12个三角形组成的立方体
12个三角形组成的立方体

在绘制三维物体时,需要考虑

  • 物体的 深度信息(depth information)
  • 三维世界的观察者
如何定义三维世界的观察者?

需要几个关键信息:

  • 他在哪
  • 他朝哪看
  • 他视野有多宽
  • 他能看多远
视点和视线
为了定义一个观察者,需要考虑2点:
  • 观察方向
  • 可视距离
如何描述观察者的属性?
  • 视点(eye point) 观察者的位置
  • 视线(viewing direction) 观察者沿观察方向的射线
3项信息可以确定观察者状态:
三项信息确定观察者状态
三项信息确定观察者状态
视点(eye point)

即观察者在 三维空间中的位置
也即 视线的起点
一般使用 ( eyeX, eyeY, eyeZ)表示

观察目标点(look-at point)

被观察目标所在的点,可以用来确定视线
一般使用 ( atX, atY, atZ)表示

同时知道观察目标点视点就能计算出视线方向

上方向(up direction)

最终绘制在屏幕上的 影像中的向上的方向
一般使用 3维矢量( upX, upY, upZ)表示

为什么一定要指定上方向?

答: 区分出观察者以视线为轴旋转的状态(想想你歪头看东西的时候)

视图矩阵(view matrix)
视图矩阵是做什么的?

答: 用来表示观察者的状态,包括视点、观察目标点、上方向等信息。

视图矩阵的使用位置?

答:顶点着色器中使用。

为什么要叫视图矩阵?

答: 因为它最终影响的是显示在屏幕上的视图。

Matrix4.setLookAt()

矩阵工具库 cuon-matrix.js 引入的 Matrix4 类型提供了创建视图矩阵的方法:

WebGL中观察者的默认状态
  • 视点: (0,0,0)
  • 观察点: (0,0,-1)
  • 视线: Z轴负方向
  • 上方向: Y轴负方向,即(0,1,0)
创建一个表示默认状态的矩阵
1
2
3
4
5
6
var initialViewMatrix = new Matrix4();
initialViewMatrix.setLookAt(
0,0,0, //视点
0,0,-1, //目标观察点
0,1,0 //上方向
)

LookAtTriangles

LookAtTriangles Code
LookAtTriangles运行结果
LookAtTriangles运行结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../examples/lib/cuon-matrix.js"></script>
<script src="../examples/lib/cuon-utils.js"></script>
<script src="../examples/lib/webgl-debug.js"></script>
<script src="../examples/lib/webgl-utils.js"></script>
<script src="./LookArTriangles.js"></script>
</head>
<body onload="main()">
<canvas id="webgl" width="400" height="400"></canvas>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
let VSHADER_SOURCE =
`
attribute vec4 a_Position;
attribute vec4 a_Color;
uniform mat4 u_ViewMatrix;
varying vec4 v_Color;
void main(){
gl_Position = u_ViewMatrix * a_Position;
v_Color = a_Color;
}
`;
let FSHADER_SOURCE =
`
#ifdef GL_ES
precision mediump float;
#endif
varying vec4 v_Color;
void main(){
gl_FragColor = v_Color;
}
`;
function main() {
let canvas = document.getElementById('webgl');
let gl = getWebGLContext(canvas);
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('着色器初始化失败');
return;
}
let n = initVertexBuffer(gl);

// 获取u_ViewMatrix
let u_ViewMatrix = gl.getUniformLocation(gl.program, 'u_ViewMatrix');

// 设置视点、视线、上方向
let viewMatrix = new Matrix4();
viewMatrix.setLookAt(0.20, 0.25, 0.25, 0, 0, 0, 0, 1, 0);

// 将视图矩阵传入顶点着色器
gl.uniformMatrix4fv(u_ViewMatrix, false, viewMatrix.elements);

gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);

gl.drawArrays(gl.TRIANGLES, 0, n);
}
function initVertexBuffer(gl) {
let verticesColors = new Float32Array([
// 绿色三角形
0.0, 0.5, -0.4, 0.4, 1.0, 0.4,
-0.5, -0.5, -0.4, 0.4, 1.0, 0.4,
0.5, -0.5, -0.4, 1.0, 0.4, 0.4,
// 黄色三角形
0.5, 0.4, -0.2, 1.0, 0.4, 0.4,
-0.5, 0.4, -0.2, 1.0, 1.0, 0.4,
0.0, -0.6, -0.2, 1.0, 1.0, 0.4,
// 蓝色三角形
0.0, 0.5, 0.0, 0.4, 0.4, 1.0,
-0.5, -0.5, 0.0, 0.4, 0.4, 1.0,
0.5, -0.5, 0.0, 1.0, 0.4, 0.4
]);
let n = 9;
let FSIZE = verticesColors.BYTES_PER_ELEMENT;
let a_Position = gl.getAttribLocation(gl.program, 'a_Position');
let a_Color = gl.getAttribLocation(gl.program, 'a_Color');
let vertexColorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);

gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE * 6, 0);
gl.enableVertexAttribArray(a_Position);
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 6, FSIZE * 3);
gl.enableVertexAttribArray(a_Color);
return n;
}
比较变换矩阵与视图矩阵
变换矩阵(以旋转矩阵为例)

着色器程序

1
2
3
4
5
6
7
8
attribute vec4 a_Position;
attribute vec4 a_Color;
varying vec4 v_Color;
uniform mat4 u_rotMatrix;
void main(){
gl_Position = u_rotMatrix * a_Position;
v_Color = a_Color;
}

变换矩阵的计算与传入

1
2
3
4
let u_rotMatrix = gl.getUniformLocation(gl.program, 'u_rotMatrix');
let rotMatrix = new Matrix4();
rotMatrix.setRotate(90, 0, 0, 1); //绕z轴旋转90°
gl.uniformMatrix4fv(u_rotMatrix, false, rotMatrix.elements);
视图矩阵

着色器程序

1
2
3
4
5
6
7
8
attribute vec4 a_Position;
attribute vec4 a_Color;
uniform mat4 u_ViewMatrix;
varying vec4 v_Color;
void main(){
gl_Position = u_ViewMatrix * a_Position;
v_Color = a_Color;
}

视图矩阵的计算与传入

1
2
3
4
5
6
7
8
let u_ViewMatrix = gl.getUniformLocation(gl.program, 'u_ViewMatrix');
let viewMatrix = new Matrix4();
viewMatrix.setLookAt(
0.2, 0.25, 0.25,
0.0, 0.0, 0.0,
0.0, 1.0, 0.0
);
gl.uniformMatrix4fv(u_ViewMatrix, false, viewMatrix.elements);

改变观察者的状态对整个世界进行平移和旋转变换
这二者本质上是 一样 的。

LookAtRotatedTriangles

从指定视角观察旋转后的三角形
这个案例的关键问题在于:

怎么确定旋转矩阵视图矩阵的使用顺序。

答: 先旋转图形,再移动视角。
下式展示的是 观察变换图形的矩阵计算式

1
resultMatrix = <视图矩阵> * <模型矩阵> * <原始顶点坐标>
LookAtRotatedTriangles Code
LookAtRotatedTriangles运行结果
LookAtRotatedTriangles运行结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../examples/lib/cuon-matrix.js"></script>
<script src="../examples/lib/cuon-utils.js"></script>
<script src="../examples/lib/webgl-debug.js"></script>
<script src="../examples/lib/webgl-utils.js"></script>
<script src="./LookAtRotatedTriangles.js"></script>
</head>
<body onload="main()">
<canvas id="webgl" width="400" height="400"></canvas>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
let VSHADER_SOURCE = `
attribute vec4 a_Position;
attribute vec4 a_Color;
varying vec4 v_Color;
uniform mat4 u_ModelMatrix;
uniform mat4 u_ViewMatrix;
void main(){
gl_Position = u_ViewMatrix * u_ModelMatrix * a_Position;
v_Color = a_Color;
}
`;

let FSHADER_SOURCE = `
#ifdef GL_ES
precision mediump float;
#endif
varying vec4 v_Color;
void main(){
gl_FragColor = v_Color;
}
`;

function main() {
let canvas = document.getElementById('webgl');
let gl = getWebGLContext(canvas);
if (!gl) {
console.log('failed to get context');
return;
}
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('failed to init shaders');
return;
}
let n = initVertexBuffers(gl);
if (n < 0) {
console.log('failed to init vertex buffer');
return;
}
let u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix');
let u_ViewMatrix = gl.getUniformLocation(gl.program, 'u_ViewMatrix');
if (!(u_ModelMatrix && u_ViewMatrix)) {
console.log('failed to get uniform variable');
return;
}
let modelMatrix = new Matrix4();
let viewMatrix = new Matrix4();
modelMatrix.setRotate(-10, 0, 0, 1);
viewMatrix.setLookAt(
0.2, 0.25,0.25,
0, 0, 0,
0, 1, 0
);
gl.uniformMatrix4fv(u_ViewMatrix, false, viewMatrix.elements);
gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);

gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, n);
}

function initVertexBuffers(gl) {
let verticesColors = new Float32Array([
// 红色三角形 最下层
0.0, 0.5, -0.4, 1.0, 0.4, 0.4,
-0.5, -0.5, -0.4, 1.0, 0.3, 0.5,
0.5, -0.5, -0.4, 1.0, 1.0, 0.3,
// 绿色三角形 中间层
0.0, -0.6, -0.2, 0.4, 1.0, 0.4,
-0.5, 0.5, -0.2, 0.3, 1.0, 0.5,
0.5, 0.5, -0.2, 1.0, 1.0, 0.3,
// 蓝色三角形 最上层
0.0, 0.5, 0.0, 0.4, 0.4, 1.0,
-0.5, -0.5, 0.0, 0.3, 0.5, 1.0,
0.5, -0.5, 0.0, 0.5, 1.0, 1.0
]);
let n = 9;
let FSIZE = verticesColors.BYTES_PER_ELEMENT;
let a_Position = gl.getAttribLocation(gl.program, 'a_Position');
let a_Color = gl.getAttribLocation(gl.program, 'a_Color');
let vertexColorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);

gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE * 6, 0);
gl.enableVertexAttribArray(a_Position);
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 6, FSIZE * 3);
gl.enableVertexAttribArray(a_Color);
return n;
}

模型视图矩阵

模型视图矩阵(model view matrix)
LookAtRotatedTriangle为什么需要改进?
1
gl_Position = u_ViewMatrix * u_ModelMatrix * a_Position;

对于每一个顶点,Vertex_Shader都需要重复做一遍矩阵相乘计算。
这显然会造成不必要的开销,最好把这一步提到Js中。

<模型视图矩阵> = <视图矩阵> * <模型矩阵>
gl_Position = <模型视图矩阵> * <顶点坐标>

LookAtRotatedTriangle_mvMatrix Code
Tip1:multiply方法进行矩阵相乘的顺序

mA.multiply(mB) = mB * mA

Tip2:js部分还可以更加简化
1
2
3
4
let u_mvMatrix = gl.getUniformLocation(gl.program, 'u_ModelViewMatrix');
let mvMatrix = new Matrix4();
mvMatrix.setLookAt(0.20, 0.25, 0.25, 0, 0, 0, 0, 1, 0).rotate(-10,0,0,1);
gl.uniformMatrix4fv(u_mvMatrix, false, mvMatrix.elements);
1
2
3
4
5
6
7
8
attribute vec4 a_Position;
attribute vec4 a_Color;
varying vec4 v_Color;
uniform mat4 u_ModelViewMatrix;
void main(){
gl_Position = u_ModelViewMatrix * a_Position;
v_Color = a_Color;
}
1
2
3
4
5
6
7
8
9
10
11
let u_mvMatrix = gl.getUniformLocation(gl.program, 'u_ModelViewMatrix');
let modelMatrix = new Matrix4();
let viewMatrix = new Matrix4();
modelMatrix.setRotate(-10, 0, 0, 1);
viewMatrix.setLookAt(
0.2, 0.25,0.25,
0, 0, 0,
0, 1, 0
);
let mvMatrix = viewMatrix.multiply(modelMatrix);
gl.uniformMatrix4fv(u_mvMatrix, false, mvMatrix.elements);

利用键盘改变视点

LookAtTrianglesWithKeys Code
LookAtTrianglesWithKeys运行结果
LookAtTrianglesWithKeys运行结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../examples/lib/cuon-matrix.js"></script>
<script src="../examples/lib/cuon-utils.js"></script>
<script src="../examples/lib/webgl-debug.js"></script>
<script src="../examples/lib/webgl-utils.js"></script>
<script src="./LookAtTrianglesWithKeys.js"></script>
</head>
<body onload="main()">
<canvas id="webgl" width="400" height="400"></canvas>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
let VSHADER_SOURCE = `
attribute vec4 a_Position;
attribute vec4 a_Color;
uniform mat4 u_mvMatrix;
varying vec4 v_Color;
void main(){
gl_Position = u_mvMatrix * a_Position;
v_Color = a_Color;
}
`;

let FSHADER_SOURCE = `
#ifdef GL_ES
precision mediump float;
#endif
varying vec4 v_Color;
void main(){
gl_FragColor = v_Color;
}
`;

function main() {
let canvas = document.getElementById('webgl');
let gl = getWebGLContext(canvas);
if (!gl) {
console.log('failed to get context');
return;
}
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('failed to init shaders');
return;
}
let n = initVertexBuffers(gl);
if (n < 0) {
console.log('failed to init vertex buffers');
return;
}
gl.clearColor(0.0, 0.0, 0.0, 1.0);

let u_mvMatrix = gl.getUniformLocation(gl.program, 'u_mvMatrix');
let mvMatrix = new Matrix4();

document.onkeydown = function (ev) {
keydown(ev,gl,n,u_mvMatrix,mvMatrix);
}
draw(gl, n, u_mvMatrix, mvMatrix);
}
let g_eye = [0.2, 0.25, 0.25];
function keydown(ev, gl, n, u_mvMatrix, mvMatrix) {
switch (ev.keyCode) {
case 39: g_eye[0] += 0.01; break; //右键
case 38: g_eye[2] += 0.01; break; //上键
case 37: g_eye[0] -= 0.01; break; //左键
case 40: g_eye[2] -= 0.01; break; //下键
default: break;
}
draw(gl, n, u_mvMatrix, mvMatrix);
}

function draw(gl, n, u_mvMatrix, mvMatrix) {
mvMatrix.setLookAt(
...g_eye,
0, 0, 0,
0, 1, 0
);
gl.uniformMatrix4fv(u_mvMatrix, false, mvMatrix.elements);

gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, n);
}

function initVertexBuffers(gl) {
let verticesColor = new Float32Array([
0.0, 0.5, -0.4, 1.0, 0.4, 0.4,
-0.5, -0.5, -0.4, 1.0, 0.4, 0.4,
0.5, -0.5, -0.4, 0.0, 0.4, 0.4,

-0.4, 0.4, -0.2, 0.4, 1.0, 0.4,
0.4, 0.4, -0.2, 0.4, 1.0, 0.4,
0.0, -0.6, -0.2, 0.4, 0.0, 0.4,

0.0, 0.5, 0.0, 0.4, 0.4, 1.0,
-0.5, -0.5, -0.0, 0.4, 0.4, 1.0,
0.5, -0.5, -0.0, 0.4, 0.4, 0.0
]);
let n = 9;
let FSIZE = verticesColor.BYTES_PER_ELEMENT;
let a_Position = gl.getAttribLocation(gl.program, 'a_Position');
let a_Color = gl.getAttribLocation(gl.program, 'a_Color');
let vertexColorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, verticesColor, gl.STATIC_DRAW);
gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, 6 * FSIZE, 0);
gl.enableVertexAttribArray(a_Position);
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, 6 * FSIZE, 3*FSIZE);
gl.enableVertexAttribArray(a_Color);

return n;
}