【WebGL之巅】21-三维世界-绘制三个三角形

By 大Van家 on 2021-08-01
阅读时间 33 分钟
文章共 7.2k
阅读量

对应《WebGL编程指南》第七章 22-LookAtTriangles、23-LookAtRotatedTriangles、24-LookAtRotatedTriangles_mvMatrix、25-LookAtTrianglesWithKeys

要点:视点和视线、景深、观察者视图、模型视图矩阵

知识点

一、立方体由三角形构成

1.1 深度

​ 之前讨论过,三维物体也是由二维图形(特别是三角形)组成的。既然如此,是不是我们只需要像前几章一样逐个绘制组成物体的每个三角形,最终就可以绘制出整个三维物体了呢?

​ 实际上,三维与二维还有一个显著区别:在绘制三维物体时,还得考虑它们的深度信息

1.2 三维世界的观察者

​ 定义:三维世界的观察者:在什么地方朝哪里看视野有多宽能看多远

二、视点和视线

​ 三维物体与二维物体的显著区别:三维具有深度,也就是Z轴。事实上,我们最后还是把三维场景绘制到二维的屏幕上,即绘制观察者看到的世界,而观察者可以处在任意位置观察

2.1 定义

定义一个观察者需要考虑两点:

  • 观察方向,即观察者自己在什么位置,在看场景的哪一部分
  • 可视距离,即观察者能够看多远

​ 我们将观察者所处的位置称为视点,从视点出发沿着观察方向的射线称作视线

​ 在WebGL系统中,视点默认处于原点(0, 0, 0),视线为Z轴负半轴(指向屏幕内部),这一节,我们将视点移动到另一个位置,来观察三维场景。

2.2 程序示例

​ 该程序修改了视点,位于(0.20. 0.25. 0.25),视线沿着原点(0, 0, 0)方向,可以看到原点附近有三个三角形,程序中的这三个三角形错落摆放,以帮助你理解三维场景中深度的概念。

1
viewMatrix.setLookAt(0.20, 0.25, 0.25, 0, 0, 0, 0, 1, 0);

1

2

2.3 视点、观察目标点和上方向

​ 为了确定观察者的状态,你需要获取两项信息:视点,即观察者的位置;观察目标点,即被观察目标所在的点,它可以用来确定视线。此外,因为我们最后要把观察到的景象绘制到屏幕上,还需要知道上方向。有了这三项信息,就可以确定观察者的状态了。

3

视点观察者所在的三维空间中位置,视线的起点。在接下来的几节中,视点坐标都用$(eyeX, eyeY, eyeZ)$表示。

观察目标点被观察目标所在的点。视线从视点出发,穿过观察目标点并继续延伸。注意,观察目标点是一个点,而不是视线方向,只有同时知道观察目标点和视点,才能算出视线方向。观察目标点的坐标用$(atX, atY, atZ)$表示。

上方向最终绘制在屏幕上的影像中的向上的方向。试想,如果仅仅确定了视点和观察点,观察者还是可能以视线为轴旋转的。所以,为了将观察者固定住,我们还需要指定上方向。上方向是具有3个分量的矢量,用$(upX, upY, upZ)$表示。

4

2.4 视图矩阵—Matrix4.setLookAt()

​ 在 WebGL 中,我们可以用上述三个矢量创建一个视图矩阵,然后将该矩阵传给顶点着色器。试图矩阵可以表示观察者的状态,含有观察者的视点,观察目标点,上方向等信息。之所以被成为视图矩阵,是因为它最终影响了显示在屏幕上的视图,也就是观察者观察到的场景。 Matrix4.setLookAt()函数可以根据上述三个矢量:视点、观察点和上方向,来创建出视图矩阵。

参数 描述
eyeX, eyeY, eyeZ 指定视点
atX, atY, atZ 指定观察点
upX, upY, upZ 指定上方向,如果Y轴是上方向,那么就是(0, 1, 0)

在WebGL 中,观察者的默认状态应该是这样的:

  • 视点位于坐标系统原点(0, 0, 0)

  • 视线为 Z 轴负方向,观察点为(0, 0, -1),上方向为为Y 轴正方向, 即(0, 1, 0)

​ 如果将上方向改为 X 轴正半轴方向(1, 0, 0),你将看到场景旋转了90度。创建这样一个矩阵,你只需要简单地使用如下代码。

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

三、代码分析

3.1 LookAtTriangles 与 ColoredTriangle

​ 本节代码1根据【WebGL之巅】17-颜色与纹理-绘制彩色与渐变三角形实例改编,片元着色器、传入定点数据的方式等与 ColoredTriangle.js 中的一样,主要有以下三点区别:

  • 视图矩阵被传给顶点着色器,并与顶点坐标相乘。

  • initVertexBuffers()函数创建了3个三角形的顶点坐标和颜色数据,并在 main()函数中调用。

  • main()函数计算了视图矩阵并传给了顶点着色器的 uniform 变量 u_viewMatrix。视点坐标为(0.25, 0.25, 0.25),观察点坐标为(0, 0, 0),上方向为(0, 1, 0)。

​ 首先,来看一下上述第2点中提到 initVertexBuffers()函数。该函数与 ColorTriangle.js 中的区别在于 verticesColors 数组。原先,该数组中只有一个三角形的顶点坐标和颜色数据,修改后数组包含了3个三角形共计9个顶点的数据,而且顶点坐标的 z 分量也不再是0了。接着我们创建了缓冲区对象,并将数组中的数据填了进去。此外,我们还把 gl.drawArrays()的第3个参数改成了9因为这里共有9个顶点。

​ 然后,根据上述第3点,需要建立视图矩阵(包含了视点、视线和上方向信息)并传给顶点着色器。为此,我们先创建了一个 Matrix4 对象 viewMatrix,然后用 setLookAt()方法将其设置为视图矩阵,最后将视图矩阵中的元素传给顶点着色器中的 u_viewMatrix 变量。

1
2
3
4
5
// 设置视点、视线、上方向
var viewMatrix = new Matrix4();
viewMatrix.setLookAt(0.20, 0.25, 0.25, 0, 0, 0, 0, 1, 0);
// 将视图矩阵传给u_viewMatrix变量
gl.uniformMatrix4fv(u_ViewMatrix, false, viewMatrix.elements);
1
2
3
4
5
6
7
8
9
10
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'attribute vec4 a_Color;\n' +
'uniform mat4 u_ViewMatrix;\n' + // 修改处
'varying vec4 v_Color;\n' +
'void main() {\n' +
' gl_Position = u_ViewMatrix * a_Position;\n' + //修改处
' gl_PointSize = 10.0;\n' +
' v_Color = a_Color;\n' +
'}\n';

​ 与ColoredTriangle.js 相比,顶点着色器有两处改动:定义 uniform 变量 u_viewMatrix;将视图矩阵与顶点坐标相乘再赋值给 gl_Position。那么这样的改动会怎样影响观察到的景象呢?接着来看。

3.2LookAtTriangles 与 RotatedTriangle_Matrix4

​ 仔细观察示例中的顶点着色器,你会发现它和第4章的 RotatedTriangle_Matrix4.js 很像。后者在顶点着色器中创建了一个 Matrix4 类型的旋转矩阵对象,用它去旋转三角形。我们来回顾一下这个着色器:

1
2
3
4
5
6
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'uniform mat4 u_xformMatrix;\n' +
'void main() {\n' +
'gl_Position = u_xformMatrix * a_Position;\n' +
'}\n';

​ 本例 LookAtTriangle.js 的顶点着色器程序如下所示:

1
2
3
4
5
6
7
8
9
10
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'attribute vec4 a_Color;\n' +
'uniform mat4 u_ViewMatrix;\n' + // 修改处
'varying vec4 v_Color;\n' +
'void main() {\n' +
' gl_Position = u_ViewMatrix * a_Position;\n' + //修改处
' gl_PointSize = 10.0;\n' +
' v_Color = a_Color;\n' +
'}\n';

​ 可见,后者与前者相比增加了 attribute 变量 a_Color存储顶点颜色值,增加了 varying 变量 v_Color把颜色传给片元着色器,uniform 变量由 u_RotMatrix 改成了 u_ViewMatrix。尽管存在上述这些差异,但是在两个着色器中,使用 mat4 对象乘以顶点坐标再赋值给 gl_Position 的行为却非常相似。

​ 实际上,“根据自定义的观察者状态,绘制观察者看到的景象”与“使用默认的观察状态,但是对三维对象进行平移,旋转等变换,再绘制观察者看到的景象”,这两种行为是等价的。

​ 举个例子,默认情况下视点在原点,视线沿着Z轴负方向进行观察。假如我们将点移动到(0, 0, 1),如下图所示。这时,视点与被观察的三角形在Z轴上的距离增加了 1.0 个单位。实际上,如果我们使三角形沿着Z轴负方向1.0个单位,也可以达到同样的效果,因为观察者看上去是一样的。
5

​ 事实上,上述过程就发生在示例程序 LookAtTriangles.js 中。根据视点、观察点和上方向参数,setLookAt()方法计算出的视图矩阵恰恰就是“沿着Z轴负方向移动1.0个单位”的变换矩阵。所以,把这个矩阵与顶点坐标相乘,就相当于获得了“将视点设置在(0.0, 0.0, 1.0)”的效果。视点移动的方向与被观察对象移动的方向正好相反。对于视点的旋转,也可以采用类似的方式。

​ “改变观察者的状态”与“对整个世界进行平移和旋转变换”,本质上是一样的,它们都可以用矩阵来描述。接下来,我们将从一个指定的视点来观察旋转后的三角形。

3.3 从指定视点观察旋转后的三角形

​ 第4章 RotatedTriangle_Matrix 程序绘制了一个绕Z轴旋转一定角度后的三角形。本节将修改 LookAtTriangles 程序来绘制一个从指定位置看过去的旋转后的三角形。这时,我们需要两个矩阵:旋转矩阵视图矩阵。首先有一个问题是,以怎样的顺序相乘这两个矩阵。

​ 我们知道,矩阵乘以顶点坐标,得到的结果是顶点经过矩阵变换之后的新坐标。也就是说,用旋转矩阵乘以顶点坐标,就可以得到旋转后的顶点坐标

用视图矩阵乘以顶点坐标会把顶点变换到合适的位置,使得观察者(以默认状态)观察新位置的顶点,就好像在观察者处在(视图矩阵描述的)视点上观察原始顶点一样。现在要在某个视点处观察旋转后的三角形,我们需要先旋转三角形然后从这个视点来观察他。换句话说,我们需要先对三角形进行旋转变换,再对旋转后的三角形进行与”移动视点“等效的变换。我们按照上述顺序相乘两个矩阵。具体看一下等式。

​ 我们知道,如果想旋转图形,就需要用旋转矩阵乘以旋转前的顶点坐标:

<旋转后顶点坐标> = <旋转矩阵> * <原始顶点坐标>​​

​ 用视图矩阵乘以旋转后的顶点坐标,就可以获得”从视点看上去“的旋转后的顶点坐标:

<"从顶点看上去"的旋转后顶点坐标> = <视图矩阵> * <旋转后顶点坐标>​​​

​ 将第1个式子带入第2个,可得:

<"从顶点看上去"的旋转后顶点坐标>=<视图矩阵>*<旋转矩阵>*<原始顶点坐标>

​ 除了旋转矩阵,你还可以使用平移、缩放等基本变换矩阵或它们的组合,这时矩阵被称为模型矩阵。这样,上式就可以写成:

<视图矩阵>*<模型矩阵>*<原始顶点坐标>​

代码2着色器代码实现了这一等式,以下是代码核心部分截取:

1
2
3
4
5
6
7
8
9
10
11
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'attribute vec4 a_Color;\n' +
'uniform mat4 u_ViewMatrix;\n' +
'uniform mat4 u_ModelMatrix;\n' +
'varying vec4 v_Color;\n' +
'void main() {\n' +
' gl_Position = u_ViewMatrix * u_ModelMatrix * a_Position;\n' +
' gl_PointSize = 10.0;\n' +
' v_Color = a_Color;\n' +
'}\n';
1
2
3
4
5
6
7
8
9
10
11
// 视图矩阵
var u_ViewMatrix = gl.getUniformLocation(gl.program, 'u_ViewMatrix');
var viewMatrix = new Matrix4();
viewMatrix.setLookAt(0.20, 0.25, 0.25, 0, 0, 0, 0, 1, 0);//设置视点、视线、上方向
gl.uniformMatrix4fv(u_ViewMatrix, false, viewMatrix.elements);

// 模型矩阵
var u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix');
var modelMatrix = new Matrix4();
modelMatrix.setRotate(-10, 0, 0, -1); // 计算旋转矩阵:绕z轴旋转10度
gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);

复习:gl.uniformMatrix4fvsetRotate(angle, x, y, z)

3.4 模型视图矩阵

​ 上一节实现了<视图矩阵>*<模型矩阵>*<原始顶点坐标>,这样,程序对每个顶点都要计算<视图矩阵>*<模型矩阵>,如果顶点数量很多,就会造成不必要的开销。这是因为,无论对哪个顶点而言,两个矩阵相乘的结果都是一样的。

​ 所以我们可以在js中事先把这两个矩阵相乘的结果计算出来,再传给顶点着色器。这两个矩阵相乘得到的结果被称为模型视图矩阵,如下:

<模型视图矩阵> = <视图矩阵>*<模型矩阵>​

​ 这样,上一节的式子就可以改成:

<模型视图矩阵>*<原始顶点坐标>​​

代码3实现了这一等式,以下是代码核心部分截取:

1
2
3
4
5
6
7
8
9
10
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'attribute vec4 a_Color;\n' +
'uniform mat4 u_ModelViewMatrix;\n' +
'varying vec4 v_Color;\n' +
'void main() {\n' +
' gl_Position = u_ModelViewMatrix * a_Position;\n' +
' gl_PointSize = 10.0;\n' +
' v_Color = a_Color;\n' +
'}\n';
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
方法一相乘
/*
var viewMatrix = new Matrix4();
viewMatrix.setLookAt(0.20, 0.25, 0.25, 0, 0, 0, 0, 1, 0);

var modelMatrix = new Matrix4();
modelMatrix.setRotate(-50, 0, 0, -1);
*/

方法二相乘
var u_ModelViewMatrix = gl.getUniformLocation(gl.program, 'u_ModelViewMatrix');
//两个矩阵相乘
//var modelViewMatrix = viewMatrix.multiply(modelMatrix);
var modelViewMatrix = new Matrix4();
modelViewMatrix.setLookAt(0.20, 0.25, 0.25, 0, 0, 0, 0, 1, 0).rotate(-10, 0, 0, -1);
gl.uniformMatrix4fv(u_ModelViewMatrix, false, modelViewMatrix.elements);

复习:rotate(angle, x, y, z)

3.5 利用键盘改变视点

​ 这一节将在LookAtTriangles 的基础上进行修改,使得当键盘上的方向键被按下时,观察者的视点也随之移动。在新程序 LookAtTrianglesWithKeys 中,如果右方向键被按下,视点 X 坐标将增大 0.01; 如果左方向键被按下,视点的 X 坐标将减少 0.01。

代码4实现了这一效果,以下是代码核心部分截取。

​ 在本例中,我们注册了键盘事件响应函数。每当左方向键或右方向键被按下时,就会改变视点的位置,然后调用draw()函数重绘场景。在研究键盘事件响应函数前,先来看一下 draw()函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
var g_eyeX = 0.20, g_eyeY = 0.25, g_eyeZ = 0.25; // 视点

function draw(gl, n, u_ViewMatrix, viewMatrix) {
//设置视点和视线
viewMatrix.setLookAt(g_eyeX, g_eyeY, g_eyeZ, 0, 0, 0, 0, 1, 0);

//将视图矩阵传递给u_ViewMatrix变量
gl.uniformMatrix4fv(u_ViewMatrix, false, viewMatrix.elements);

gl.clear(gl.COlOR_BUFFER_BIT);

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

draw()函数的流程十分直接:首先根据全局变量 g_eyeXg_eyeYg_eyeZ 计算视图矩阵,这三个变量的初始值分别是0.2、0.25、0.25;然后计算得到的视图矩阵传给顶点着色器中的 u_ViewMatrix 变量。注意 main()函数调用 draw()函数时以参数的形式传入了之前获取的着色器中 u_ViewMatrix 的存储地址,和一个新创建的 Matrix4 对象。这样做的目的是为了提高 draw()函数的效率,否则我们就得在每次调用 draw()函数时都重新获取 u_ViewMarix 的地址并新建 Matrix4 对象。

​ 全局变量 g_eyeX、g_eyeY、g_eyeZ 中存储着视点的坐标,键盘事件响应函数将更新 g_eyeX 的值。为了在按键被按下时调用该函数,我们必须把函数注册到 document 对象的 onkeydown 属性上去。我们定义了一个匿名函数作为键盘事件响应函数:

1
2
3
4
5
// function main()
// 注册键盘事件响应函数
document.onkeydown = function (ev) {
keydown(ev, gl, n, u_ViewMatrix,viewMatrix);
};

​ 匿名函数调用了 keydown()函数,并传入了相关的参数。让我们来看一下 keydown()函数的实现。

1
2
3
4
5
6
7
8
9
10
function keydown(ev, gl, n, u_ViewMatrix, viewMatrix) {
if(ev.keyCode == 39){ //按下右键
g_eyeX += 0.01;
}else if(ev.keyCode == 37){ //按下左键
g_eyeX -= 0.01;
}else {
return ;
}
draw(gl, n, u_ViewMatrix, viewMatrix);
}

keydown()函数的第1个参数 ev 是一个事件对象,该函数的逻辑很直接,首先根据 ev.keyCode 属性检查哪个案件被按下,然后更新 g_eyeX。如果是右方向键,就令 g_eyeX 增加0.01,如果是左方向键,就令 g_eyeX 减少0.01。最后调用 draw()函数绘制三角形。

​ 运行程序,每当你按下左或右方向键时,三角形都会改变一下方向,实际上这时因为观察者的位置发生了变化。预览效果(可交互)

​ 仔细观察运行效果,你会注意到当视点在极右/左的位置时,三角形会缺少一部分,下一节可视范围将详细讲解。

实例

代码1

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
//LookAtTriangles.js
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'attribute vec4 a_Color;\n' +
'uniform mat4 u_ViewMatrix;\n' +
'varying vec4 v_Color;\n' +
'void main() {\n' +
' gl_Position = u_ViewMatrix * a_Position;\n' +
' gl_PointSize = 10.0;\n' +
' v_Color = a_Color;\n' +
'}\n';

var FSHADER_SOURCE=
'precision mediump float;\n' +//!!! 需要声明浮点数精度,否则报错No precision specified for (float)
'varying vec4 v_Color;\n' +
'void main(){\n'+
' gl_FragColor = v_Color;\n'+
'}\n';

function main() {

var canvas = document.getElementById("webgl");

var gl = getWebGLContext(canvas);

if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log("Failed to initialize shaders.");
return;
}

//设置顶点位置
var n = initVertexBuffers(gl);

var u_ViewMatrix = gl.getUniformLocation(gl.program, 'u_ViewMatrix');

//设置视点、视线、上方向
var 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(1.0, 1.0, 1.0, 1.0);

//清空<canvas>
gl.clear(gl.COLOR_BUFFER_BIT);

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

function initVertexBuffers(gl) {
var verticesColors = new Float32Array(
[
0.0, 0.5, -0.4, 0.4, 1.0, 0.4, // The back green one
-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, // The middle yellow one
-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, // The front blue one
-0.5, -0.5, 0.0, 0.4, 0.4, 1.0,
0.5, -0.5, 0.0, 1.0, 0.4, 0.4
]
);
var n = 9; //点的个数

//创建缓冲区对象
var verteColorBuffer = gl.createBuffer();

//将缓冲区对象保存到目标上
gl.bindBuffer(gl.ARRAY_BUFFER, verteColorBuffer);

//向缓存对象写入数据
gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);

var FSIZE = verticesColors.BYTES_PER_ELEMENT;

var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
//将缓冲区对象分配给a_Postion变量
gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE *6, 0);
//连接a_Postion变量与分配给它的缓冲区对象
gl.enableVertexAttribArray(a_Position);

var a_Color = gl.getAttribLocation(gl.program, 'a_Color');
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 6, FSIZE * 3);
gl.enableVertexAttribArray(a_Color);

gl.bindBuffer(gl.ARRAY_BUFFER, null);//取消绑定的缓冲区对象
return n;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>LookAtTriangles</title>
</head>
<body onload="main()">
<canvas id="webgl" width="400" height="400">
Please use the browser supporting "canvas".
</canvas>

<script src="../lib/webgl-utils.js"></script>
<script src="../lib/webgl-debug.js"></script>
<script src="../lib/cuon-utils.js"></script>
<script src="../lib/cuon-matrix.js"></script>
<script src="LookAtTriangles.js"></script>
</body>
</html>
效果

6

代码2

<视图矩阵>*<模型矩阵>*<原始顶点坐标>

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
//LookAtTriangles.js
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'attribute vec4 a_Color;\n' +
'uniform mat4 u_ViewMatrix;\n' +
'uniform mat4 u_ModelMatrix;\n' +
'varying vec4 v_Color;\n' +
'void main() {\n' +
' gl_Position = u_ViewMatrix * u_ModelMatrix * a_Position;\n' +
' gl_PointSize = 10.0;\n' +
' v_Color = a_Color;\n' +
'}\n';

var FSHADER_SOURCE=
'precision mediump float;\n' +//!!! 需要声明浮点数精度,否则报错No precision specified for (float)
'varying vec4 v_Color;\n' +
'void main(){\n'+
' gl_FragColor = v_Color;\n'+
'}\n';

function main() {

var canvas = document.getElementById("webgl");

var gl = getWebGLContext(canvas);

if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log("Failed to initialize shaders.");
return;
}

//设置顶点位置
var n = initVertexBuffers(gl);

var u_ViewMatrix = gl.getUniformLocation(gl.program, 'u_ViewMatrix');
//设置视点、视线、上方向
var viewMatrix = new Matrix4();
viewMatrix.setLookAt(0.20, 0.25, 0.25, 0, 0, 0, 0, 1, 0);
gl.uniformMatrix4fv(u_ViewMatrix, false, viewMatrix.elements);

var u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix');
var modelMatrix = new Matrix4();
modelMatrix.setRotate(-10, 0, 0, -1);
gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);

gl.clearColor(1.0, 1.0, 1.0, 1.0);

//清空<canvas>
gl.clear(gl.COLOR_BUFFER_BIT);

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

function initVertexBuffers(gl) {
var verticesColors = new Float32Array(
[
0.0, 0.5, -0.4, 0.4, 1.0, 0.4, // The back green one
-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, // The middle yellow one
-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, // The front blue one
-0.5, -0.5, 0.0, 0.4, 0.4, 1.0,
0.5, -0.5, 0.0, 1.0, 0.4, 0.4
]
);
var n = 9; //点的个数

//创建缓冲区对象
var verteColorBuffer = gl.createBuffer();

//将缓冲区对象保存到目标上
gl.bindBuffer(gl.ARRAY_BUFFER, verteColorBuffer);

//向缓存对象写入数据
gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);

var FSIZE = verticesColors.BYTES_PER_ELEMENT;

var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
//将缓冲区对象分配给a_Postion变量
gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE *6, 0);
//连接a_Postion变量与分配给它的缓冲区对象
gl.enableVertexAttribArray(a_Position);

var a_Color = gl.getAttribLocation(gl.program, 'a_Color');
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 6, FSIZE * 3);
gl.enableVertexAttribArray(a_Color);

gl.bindBuffer(gl.ARRAY_BUFFER, null);//取消绑定的缓冲区对象
return n;
}

效果

7

代码3

<模型视图矩阵>*<原始顶点坐标>

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
100
//LookAtTriangles.js
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'attribute vec4 a_Color;\n' +
'uniform mat4 u_ModelViewMatrix;\n' +
'varying vec4 v_Color;\n' +
'void main() {\n' +
' gl_Position = u_ModelViewMatrix * a_Position;\n' +
' gl_PointSize = 10.0;\n' +
' v_Color = a_Color;\n' +
'}\n';

var FSHADER_SOURCE=
'precision mediump float;\n' +//!!! 需要声明浮点数精度,否则报错No precision specified for (float)
'varying vec4 v_Color;\n' +
'void main(){\n'+
' gl_FragColor = v_Color;\n'+
'}\n';

function main() {

var canvas = document.getElementById("webgl");

var gl = getWebGLContext(canvas);

if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log("Failed to initialize shaders.");
return;
}

//设置顶点位置
var n = initVertexBuffers(gl);
/*
var viewMatrix = new Matrix4();
viewMatrix.setLookAt(0.20, 0.25, 0.25, 0, 0, 0, 0, 1, 0);

var modelMatrix = new Matrix4();
modelMatrix.setRotate(-50, 0, 0, -1);
*/
var u_ModelViewMatrix = gl.getUniformLocation(gl.program, 'u_ModelViewMatrix');
//两个矩阵相乘
//var modelViewMatrix = viewMatrix.multiply(modelMatrix);
var modelViewMatrix = new Matrix4();
modelViewMatrix.setLookAt(0.20, 0.25, 0.25, 0, 0, 0, 0, 1, 0).rotate(-10, 0, 0, -1);
gl.uniformMatrix4fv(u_ModelViewMatrix, false, modelViewMatrix.elements);

gl.clearColor(1.0, 1.0, 1.0, 1.0);

//清空<canvas>
gl.clear(gl.COLOR_BUFFER_BIT);

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

function initVertexBuffers(gl) {
var verticesColors = new Float32Array(
[
0.0, 0.5, -0.4, 0.4, 1.0, 0.4, // The back green one
-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, // The middle yellow one
-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, // The front blue one
-0.5, -0.5, 0.0, 0.4, 0.4, 1.0,
0.5, -0.5, 0.0, 1.0, 0.4, 0.4
]
);
var n = 9; //点的个数


//创建缓冲区对象
var verteColorBuffer = gl.createBuffer();

//将缓冲区对象保存到目标上
gl.bindBuffer(gl.ARRAY_BUFFER, verteColorBuffer);

//向缓存对象写入数据
gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);

var FSIZE = verticesColors.BYTES_PER_ELEMENT;

var a_Position = gl.getAttribLocation(gl.program, 'a_Position');

//将缓冲区对象分配给a_Postion变量
gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE *6, 0);
//连接a_Postion变量与分配给它的缓冲区对象
gl.enableVertexAttribArray(a_Position);

var a_Color = gl.getAttribLocation(gl.program, 'a_Color');

gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 6, FSIZE * 3);
gl.enableVertexAttribArray(a_Color);

gl.bindBuffer(gl.ARRAY_BUFFER, null);//取消绑定的缓冲区对象
return n;
}

效果

8

代码4

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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
//LookAtTrianglesWithKeys.js
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'attribute vec4 a_Color;\n' +
'uniform mat4 u_ViewMatrix;\n' +
'varying vec4 v_Color;\n' +
'void main() {\n' +
' gl_Position = u_ViewMatrix * a_Position;\n' +
' gl_PointSize = 10.0;\n' +
' v_Color = a_Color;\n' +
'}\n';

var FSHADER_SOURCE=
'precision mediump float;\n' +//!!! 需要声明浮点数精度,否则报错No precision specified for (float)
'varying vec4 v_Color;\n' +
'void main(){\n'+
' gl_FragColor = v_Color;\n'+
'}\n';

function main() {

var canvas = document.getElementById("webgl");

var gl = getWebGLContext(canvas);

if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log("Failed to initialize shaders.");
return;
}

//设置顶点位置
var n = initVertexBuffers(gl);

var u_ViewMatrix = gl.getUniformLocation(gl.program, 'u_ViewMatrix');

// 设置视图矩阵对象(视点、视线、上方向)
var viewMatrix = new Matrix4();

//注册键盘事件响应函数
document.onkeydown = function (ev) {
keydown(ev, gl, n, u_ViewMatrix,viewMatrix);
};

draw(gl, n, u_ViewMatrix, viewMatrix);
}

var g_eyeX = 0.20, g_eyeY = 0.25, g_eyeZ = 0.25; // 视点
function keydown(ev, gl, n, u_ViewMatrix, viewMatrix) {
if(ev.keyCode == 39){ //按下右键
g_eyeX += 0.01;
}else if(ev.keyCode == 37){ //按下左键
g_eyeX -= 0.01;
}else {
return ;
}
draw(gl, n, u_ViewMatrix, viewMatrix);
}

function draw(gl, n, u_ViewMatrix, viewMatrix) {
//设置视点和视线
viewMatrix.setLookAt(g_eyeX, g_eyeY, g_eyeZ, 0, 0, 0, 0, 1, 0);

//将视图矩阵传递给u_ViewMatrix变量
gl.uniformMatrix4fv(u_ViewMatrix, false, viewMatrix.elements);

gl.clear(gl.COlOR_BUFFER_BIT);

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

function initVertexBuffers(gl) {
var verticesColors = new Float32Array(
[
0.0, 0.5, -0.4, 0.4, 1.0, 0.4, // The back green one
-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, // The middle yellow one
-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, // The front blue one
-0.5, -0.5, 0.0, 0.4, 0.4, 1.0,
0.5, -0.5, 0.0, 1.0, 0.4, 0.4
]
);
var n = 9; //点的个数


//创建缓冲区对象
var verteColorBuffer = gl.createBuffer();

//将缓冲区对象保存到目标上
gl.bindBuffer(gl.ARRAY_BUFFER, verteColorBuffer);

//向缓存对象写入数据
gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);

var FSIZE = verticesColors.BYTES_PER_ELEMENT;

var a_Position = gl.getAttribLocation(gl.program, 'a_Position');

//将缓冲区对象分配给a_Postion变量
gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE *6, 0);
//连接a_Postion变量与分配给它的缓冲区对象
gl.enableVertexAttribArray(a_Position);

var a_Color = gl.getAttribLocation(gl.program, 'a_Color');

gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 6, FSIZE * 3);
gl.enableVertexAttribArray(a_Color);

gl.bindBuffer(gl.ARRAY_BUFFER, null);//取消绑定的缓冲区对象
return n;
}

效果4

模拟观察者(视点)改变自身位置(围绕观察物水平旋转)的场景。

尝试按下你键盘的键或键,或点击下面的按钮~

<!DOCTYPE html>

LookAtTriangles
Please use the browser supporting "canvas".

Tips: Please indicate the source and original author when reprinting or quoting this article.