webgl基础3-三维
# 创建视图矩阵
# 决定视图的参数
三维场景的名词定义:视点、目标点、上方向
为确定观察者状态,需要获取视点【观察者的位置】、目标点【被观察目标所在的点】两项信息。最后要把观察到的景象绘制到屏幕上,还需要知道上方向; 有这三个信息可以确定观察者看到的内容。
- 视点:观察者所在的三维空间中位置,视点坐标用(eyeX, eyeY, eyeZ)表示
- 目标点:被观察目标所在的位置,目标点坐标用(atX, atY, atZ)表示。目标点和视点之间的连线,是视线方向。
- 上方向:要想最终确定在屏幕上显示的内容,还需要确定一个上方向;如果仅仅确定视点和观察点,观察者还可以以视线为轴进行旋转,这样导致看到的目标点形状就会发生变化;上方向用3个分量的矢量来表示(upX, upY, upZ).
# 可视范围
三维空间中的三维物体只有在可视范围内,WEBGL才会绘制它;
人类只能看到眼前的东西,水平视角大约200度左右。绘制可视范围外的对象没有意义。
# 辅助函数
- 归一化函数 normalized:将数据调整到
-1到1
或者0到1
之间 - 叉集 cross : 获取两个平面的法向量
- 点集 dot : 获取某点在x、y、z轴的投影长度
- 向量差:获取目标点和视点的向量
// 归一化函数
function normalized(arr) {
let sum = 0;
for (let i = 0; i < arr.length; i++) {
sum += arr[i] * arr[i]
}
const middle = Math.sqrt(sum);
for (let i = 0; i < arr.length; i++) {
arr[i] = arr[i] / middle;
}
}
// 叉积函数 获取法向量
function cross(a,b) {
return new Float32Array([
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
])
}
// 点积函数 获取投影长度
function dot(a, b) {
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}
// 向量差
function minus(a, b) {
return new Float32Array([
a[0] - b[0],
a[1] - b[1],
a[2] - b[2],
])
}
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
通过使用以上函数,推导出视图矩阵
// 视图矩阵获取
function getViewMatrix(eyex, eyey, eyez, lookAtx, lookAty, lookAtz, upx, upy, upz) {
// 视点
const eye = new Float32Array([eyex, eyey, eyez])
// 目标点
const lookAt = new Float32Array([lookAtx, lookAty, lookAtz])
// 上方向
const up = new Float32Array([upx, upy, upz])
// 确定z轴
const z = minus(eye, lookAt);
normalized(z);
normalized(up);
// 确定x轴
const x = cross(z, up);
normalized(x);
// 确定y轴
const y = cross(x, z);
return new Float32Array([
x[0], y[0], z[0], 0,
x[1], y[1], z[1], 0,
x[2], y[2], z[2], 0,
-dot(x,eye),-dot(y,eye),-dot(z,eye),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
# 视图矩阵实例代码
代码通过改变视点位置坐标,形成视图动画效果。 jcode (opens new window)
# 正射投影(平行投影)
# 概念定义
左侧为透视投影,右侧为正射投影;
在透视投影下,产生的三维场景有近大远小的透视感,更符合真是场景。
正射投影的好处是用户可以方便比较场景中物体的大小,这是因为物体看上去大小与其所在的位置没有关系,在建筑平面图等测绘图中使用。
下图是正射投影,投射到xy平面的示例。
A点做正射投影映射,转化到A';
# 首先推导左右区间
用L表示left的值,用R表示right的值
# 以此类推上下区间和远近区间公式
根据矢量点和一个矩阵相乘,推导出x'、y'、z'、w'的值
x' = ax + by+ cz + d;
y' = ex + fy + gz + h;
z' = ix + jy + kz + l;
w' = mx + ny + oz + p;
2
3
4
将左右区间、上下区间、远近区间公式和上面图中的x'、y'、z'、w'进行等式替换;
// 只有 a= 2/(r-l) 且 d=-(r+l)/(r-l)、b=c=0 时等式成立
// 只有 f= 2/(t-b) 且 h=-(t+b)/(t-b)、e=g=0 时等式成立
// 只有 k= 2/(f-n) 且 l=-(f+n)/(f-n)、i=j=0 时等式成立
// m=n=o=0且p=1
2
3
4
将以上等式矩阵,转为列矩阵,就是下面的正射投影矩阵
# 正射投影矩阵
// 获取正射投影矩阵
function getOrtho(l, r, t, b, n, f) {
return new Float32Array([
2 / (r - l), 0, 0, 0,
0, 2/(t-b), 0, 0,
0, 0, -2/(f-n), 0,
-(r+l)/(r-l),-(t+b)/(t-b),-(f+n)/(f-n),1
])
}
2
3
4
5
6
7
8
9
# 完整示例代码
案例通过移动视点坐标,实现视图改变的动画效果。 jcode (opens new window)
# 透视投影
# 概念定义
下图左侧为透视投影;
下图右侧在WEBGL场景中放置3个大小同样的三角形,区别是在Z轴的位置不同;视点在(0, 0, 5)位置,可以产生出如下图左侧的效果。
透视投影符合近大远小的规律;
先将透视投影缩放到正射投影,然后通过缩放矩阵和正射投影矩阵相乘,得到透视投影矩阵
# 推导透视矩阵公式
# 获取透视比例关系
根据相似三角形定律,可以得出左边的比例关系,从而推导出 y' = yn/f
和 x'=xn/f
在做映射时,z坐标的长度f是固定不变,f为定长1,所以 y' = yn
和x'=xn
# 第一步:推算出a和b的值
从而可以推导出缩放矩阵的第一步
# 第二步:获取参数c和d值
然后进行透视矩阵的第二步推导,c和d的值;
根据相似三角形关系,得到如下等式
利用齐次坐标的一个性质,所谓齐次坐标就是使用n+1维来表示n维坐标。这里需要用到齐次坐标的一个性质,如果(x,y,z,1)表示投影到w=1平面的点坐标,那么(nx,ny,nz,n)坐标投影到w=1平面的是同一个点(nx/n,nz/n,nz/n,n/n)=(x,y,z,1),这就是齐次坐标的尺度不变性。
利用齐次坐标特性,将上面的公式得到的结果同时乘以Ze
Mpersp为4x4矩阵,根据上面的公式我们能得出矩阵的第一、二、四行的值
# 第三步根据收缩矩阵和正射投影矩阵的乘积,得到透视投影矩阵
[
n*2/(r-l) + 0*0 + 0*0 + 0*-(r+l)/(r-l), n*0 + 0*2/(t-b) + 0*0 + 0* -(t+b)/(t-b), n*0 + 0*0 + 0* -2/(f-n) + 0*-(f+n)/(f-n), n*0 + 0*0 + 0*0 +0*1,
0*2/(r-l) + n*0 + 0*0 + 0*-(r+l)/(r-l), 0*0 + n*2/(t-b) + 0*0 + 0* -(t+b)/(t-b), 0*0 + n*0 + 0* -2/(f-n) + 0*-(f+n)/(f-n), 0*0 + n*0 + 0*0 +0*1,
0*2/(r-l) + 0*0 + (f+n)*0+ -1*-(r+l)/(r-l), 0*0+0*2/(t-b)+(f+n)*0+ -1*-(t+b)/(t-b), 0*0+0*0+(f+n)*-2/(f-n)+ -1 *-(f+n)/(f-n),0*0+0*0+(f+n)*0+-1*1,
0*2/(r-l)+0*0+fn*0+0*-(r+l)/(r-l), 0*0+0*2/(t-b)+fn*0+0*-(t+b)/(t-b), 0*0 + 0*0 + fn* -2/(f-n) + 0*-(f+n)/(f-n), 0*0+0*0+fn*0 + 0*1,
]
// 由于r+l=0 以及 t+b = 0
// 可以将矩阵简化为
[
2n/(r-l), 0, 0, 0,
0, 2n/(t-b), 0, 0,
0, 0, -(f+n)/(f-n), -1,
0, 0, (-2nf)/(f-n), 0
]
// js中矩阵是列主序的矩阵,所以等到如下图公式
2
3
4
5
6
7
8
9
10
11
12
13
14
15
第一列*第一行 = 第一行的第一列
第1列*第2行 = 第1行的第2列
第1列*第3行 = 第1行的第3列
第1列*第4行 = 第1行的第4列
第2列*第1行 = 第2行的第1列
.......
收缩矩阵和正交投影矩阵相乘结果如下:
# 第四步通过角度将透视矩阵进行转换
根据视角α 和 宽高比 aspect 求出 top、bottom、left、right四个边界方向
t = n * tan(α /2)
b = -t
r = n * aspect * tan(α/2)
l = -r
计算之后结果
r-l = 2* n * aspect * tan(α/2)
t-b = 2n* tan(α/2)
r+l = 0;
t+b=0
将结果带入到透视投影矩阵中
# 透视投影实例代码
jcode (opens new window) 通过点击上下左右键盘,可以对视图的物体进行转动
# 三维场景中2个重要概念
# 隐藏面消除
正确处理物体的前后关系,是三维场景中重要的概念。前面的物体会阻挡后边物体的显示。在webgl中叫做隐藏面消除。
# 正常显示前后关系
var verticesColors = new Float32Array([
// 顶点坐标和颜色
0.0, 1.0, -4.0 , 0.4, 1.0, 0.4, // 绿色三角形在后边
-0.5, -1.0, -4.0 , 0.4, 1.0, 0.4,
0.5, -1.0, -4.0 , 1.0, 0.4, 0.4,
0.0, 1.0, -2.0 , 1.0, 1.0, 0.4, // 黄色的在中间
-0.5, -1.0, -2.0 , 1.0, 1.0, 0.4,
0.5, -1.0, -2.0 , 1.0, 0.4, 0.4,
0.0, 1.0, -1.5 , 0.4, 0.4, 1.0, // 蓝色的前面
-0.5, -1.0, -1.5 , 0.4, 0.4, 1.0,
0.5, -1.0, -1.5 , 1.0, 0.4, 0.4,
]);
2
3
4
5
6
7
8
9
10
11
12
13
14
WEBGL中按照定义顶点坐标的顺序来绘制,蓝色的最后定义,所以可以绘制到最外层,同时Z轴坐标也是最靠前,能够正确显示物体的前后关系。
# 完整示例代码
# 非正常显示前后关系
接下来调整下顶点坐标的绘制顺序,将蓝色的放到第一个绘制。
jcode (opens new window) 蓝色显示在最后,绿色显示在最前面。这个和定义的Z轴坐标是不符合的。按照定义Z轴坐标的关系,绿色的三角形依然在最后。
# 使用隐藏消除面,解决前后关系
为了解决不符合Z轴前后关系的问题,webgl提供隐藏消除面的功能。
开启隐藏面消除功能,需要以下2步。
- 开启隐藏面消除,
gl.enable(gl.DEPTH_TEST)
- 在绘制之前,清除深度缓冲区
gl.clear(gl.DEPTH_BUFFER_BIT);
gl.enable(cap)函数的使用,cap表示开启的功能
- cap: gl.DEPTH_TEST 或 gl.BLEND 或 gl.POLYGON_OFFSET_FILL
gl.clear()方法清除深度缓冲区
# 深度冲突
隐藏面消除可以解决坐标位置前后关系,但是如果Z轴坐标相同的图形,还是会出现问题,使得图像出现斑斑驳驳。这种现象称为深度冲突。
webgl提供一个多边形偏移的功能,可以解决这个问题。机制是在Z轴上添加一个偏移量,偏移量的值由物体表面相对于观察者视线的角度来确定。
- 启用多边形偏移,
gl.enable(gl.POLYGON_OFFSET_FILL);
- 在绘制之前指定偏移量的参数,
gl.polygonOffset(1.0, 1.0);
const vm = getViewMatrix(eyex, eyey, eyez, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
const perspective = getPerspective(35, ctx.width / ctx.height, 100, 1);
// 创建隐藏面消除
gl.clearColor(0, 0, 0, 1);
gl.enable(gl.DEPTH_TEST);
// 清除视图缓存区
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.uniformMatrix4fv(mat, false, mixMatrix(vm, perspective));
gl.enable(gl.POLYGON_OFFSET_FILL);
gl.drawArrays(gl.TRIANGLES, 0, 3 * 1);
gl.polygonOffset(1.0, 1.0);
gl.drawArrays(gl.TRIANGLES, 0, 3 * 1);
2
3
4
5
6
7
8
9
10
11
12
13
14
# 绘制立方体图形
# 顶点法
绘制立方体需要定义8个顶点;
# 创建基础模板
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
* {
margin: 0;
padding: 0;
}
canvas{
margin: 50px auto 0;
display: block;
background: yellow;
}
</style>
</head>
<body>
<canvas id="canvas" width="400" height="400">
此浏览器不支持canvas
</canvas>
</body>
</html>
<script>
function initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE) {
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(vertexShader, VERTEX_SHADER_SOURCE) // 指定顶点着色器的源码
gl.shaderSource(fragmentShader, FRAGMENT_SHADER_SOURCE) // 指定片元着色器的源码
// 编译着色器
gl.compileShader(vertexShader)
gl.compileShader(fragmentShader)
// 创建一个程序对象
const program = gl.createProgram();
gl.attachShader(program, vertexShader)
gl.attachShader(program, fragmentShader)
gl.linkProgram(program)
gl.useProgram(program)
return program;
}
// 视图矩阵获取
function getViewMatrix(eyex, eyey, eyez, lookAtx, lookAty, lookAtz, upx, upy, upz) {
// 视点
const eye = new Float32Array([eyex, eyey, eyez])
// 目标点
const lookAt = new Float32Array([lookAtx, lookAty, lookAtz])
// 上方向
const up = new Float32Array([upx, upy, upz])
// 确定z轴
const z = minus(eye, lookAt);
normalized(z);
normalized(up);
// 确定x轴
const x = cross(z, up);
normalized(x);
// 确定y轴
const y = cross(x, z);
return new Float32Array([
x[0], y[0], z[0], 0,
x[1], y[1], z[1], 0,
x[2], y[2], z[2], 0,
-dot(x,eye),-dot(y,eye),-dot(z,eye),1
])
}
// 矩阵复合函数
function mixMatrix(A, B) {
const result = new Float32Array(16);
for (let i = 0; i < 4; i++) {
result[i] = A[i] * B[0] + A[i + 4] * B[1] + A[i + 8] * B[2] + A[i + 12] * B[3]
result[i + 4] = A[i] * B[4] + A[i + 4] * B[5] + A[i + 8] * B[6] + A[i + 12] * B[7]
result[i + 8] = A[i] * B[8] + A[i + 4] * B[9] + A[i + 8] * B[10] + A[i + 12] * B[11]
result[i + 12] = A[i] * B[12] + A[i + 4] * B[13] + A[i + 8] * B[14] + A[i + 12] * B[15]
}
return result;
}
// 归一化函数
function normalized(arr) {
let sum = 0;
for (let i = 0; i < arr.length; i++) {
sum += arr[i] * arr[i]
}
const middle = Math.sqrt(sum);
for (let i = 0; i < arr.length; i++) {
arr[i] = arr[i] / middle;
}
}
// 叉积函数 获取法向量
function cross(a,b) {
return new Float32Array([
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
])
}
// 点积函数 获取投影长度
function dot(a, b) {
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}
// 向量差
function minus(a, b) {
return new Float32Array([
a[0] - b[0],
a[1] - b[1],
a[2] - b[2],
])
}
// 获取透视投影矩阵
function getPerspective(fov, aspect, far, near) {
fov = fov * Math.PI / 180;
return new Float32Array([
1/(aspect*Math.tan(fov / 2)), 0, 0, 0,
0, 1/(Math.tan(fov/2)),0,0,
0,0,-(far+near)/(far-near),-(2*far*near)/(far-near),
0,0,-1,0,
])
}
const ctx = document.getElementById('canvas')
const gl = ctx.getContext('webgl')
// 创建着色器源码
const VERTEX_SHADER_SOURCE = `
attribute vec4 aPosition;
attribute vec4 aColor;
varying vec4 vColor;
uniform mat4 mat;
void main() {
gl_Position = mat * aPosition;
vColor = (1.0, 0.0, 0.0, 1.0);
}
`; // 顶点着色器
const FRAGMENT_SHADER_SOURCE = `
precision lowp float;
varying vec4 vColor;
void main() {
gl_FragColor = vColor;
}
`; // 片元着色器
const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)
const aPosition = gl.getAttribLocation(program, 'aPosition');
const aColor = gl.getAttribLocation(program, 'aColor');
const mat = gl.getUniformLocation(program, 'mat');
let eyex = 0.0;
let eyey = -0.1;
let eyez = 0.2;
function draw() {
const vm = getViewMatrix(eyex,eyey,eyez,0.0,0.0,0.0,0.0,0.6,0.0);
const perspective = getPerspective(150, ctx.width / ctx.height, 100, 1);
gl.uniformMatrix4fv(mat, false, mixMatrix(perspective, vm));
gl.drawArrays(gl.TRIANGLES, 0, 3 * 6);
requestAnimationFrame(draw);
}
draw()
</script>
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# 创建出需要的8个顶点数据
// 顶点
const v0 = [1,1,1];
const v1 = [-1,1,1];
const v2 = [-1,-1,1];
const v3 = [1,-1,1];
const v4 = [1,-1,-1];
const v5 = [1,1,-1];
const v6 = [-1,1,-1];
const v7 = [-1,-1,-1];
// 通过结构顶点数据,组成的面
const points = new Float32Array([
...v0,...v1,...v2, ...v0,...v2, ...v3, // 前
...v0,...v3,...v4, ...v0,...v4, ...v5, // 右
...v0,...v5,...v6, ...v0,...v6, ...v1, // 上面
...v1,...v6,...v7, ...v1,...v7, ...v2, // 左
...v7,...v4,...v3, ...v7,...v3, ...v2, // 底
...v4,...v7,...v6, ...v4,...v6, ...v5, // 后
])
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
完整绘制代码 jcode (opens new window) 绘制出来一个红色正方形,现在还看不出来立方体的形状,可以通过改变视角,进行查看
通过调整 eyeX、eyeY、eyeZ的值,可以看到透视的正方形
# 添加旋转动画
# 给每个面添加自定义颜色
主要是重新定义个颜色的缓冲对象buffer;
将bufferData数据进行绑定,然后进行colorData赋值;
之后通过gl.vertexAttribPointer给aColor设置值;
# 索引法
# 创建顶点数据信息
// 创建顶点数据信息vertices
const vertices = new Float32Array([
1, 1, 1,
-1, 1, 1,
-1,-1, 1,
1,-1, 1,
1,-1,-1,
1, 1,-1,
-1, 1,-1,
-1,-1,-1,
])
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
gl.vertexAttribPointer(aPosition, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(aPosition)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 通过索引进行设置面
const indeces = new Uint8Array([
// 3点组成一个3角形,2个三角形组成一个面
0,1,2,0,2,3,
0,3,4,0,4,5,
0,5,6,0,6,1,
1,6,7,1,7,2,
7,4,3,7,3,2,
4,6,7,4,6,5,
])
const indexBuffer = gl.createBuffer();
// 绑定索引的buffer数据,需要使用 ELEMENT_ARRAY_BUFFER 方法
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indeces, gl.STATIC_DRAW);
2
3
4
5
6
7
8
9
10
11
12
13
通过索引绘制面,需要使用drawElements
函数;
# drawElements方法函数的使用
用了绘制面信息,包含4个参数
- mode:同drawArrays时的参数值
- count: 要绘制的顶点数量
- type: 顶点数据类型,值为gl.UNSIGNED_BYTE 或者 gl.UNSIGNED_SHORT
- offset: 索引数组开始绘制的位置
# 完整的索引创建立方体代码
# 给每个面添加不同颜色
# 创建顶点缓存数据
// 顶点数据,组成面
const vertices = new Float32Array([
// 0123,组成前面
1, 1, 1,
-1, 1, 1,
-1,-1, 1,
1,-1, 1,
// 0345,组成右面
1, 1, 1,
1,-1, 1,
1,-1,-1,
1, 1,-1,
// 0156,组成上面
1, 1, 1,
1, 1, -1,
-1, 1,-1,
-1, 1,1,
// 1267,组成左面
-1, 1, 1,
-1,1, -1,
-1, -1,-1,
-1,-1,1,
// 2347组成下面
-1,-1, 1,
1,-1, 1,
1,-1,-1,
-1,-1,-1,
// 4567组成后面
1,-1,-1,
1, 1,-1,
-1, 1,-1,
-1,-1,-1,
])
// 顶点位置缓存区
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
gl.vertexAttribPointer(aPosition, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(aPosition)
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
创建颜色缓存数据
// 创建颜色数据
const colors = new Float32Array([
0.4,0.4,1.0, 0.4,0.4,1.0, 0.4,0.4,1.0, 0.4,0.4,1.0,
0.4,1.0,0.6, 0.4,1.0,0.6, 0.4,1.0,0.6, 0.4,1.0,0.6,
1.0,0.4,0.4, 1.0,0.4,0.4, 1.0,0.4,0.4, 1.0,0.4,0.4,
1.0,0.8,0.4, 1.0,0.8,0.4, 1.0,0.8,0.4, 1.0,0.8,0.4,
1.0,0.0,1.0, 1.0,0.0,1.0, 1.0,0.0,1.0, 1.0,0.0,1.0,
0.0,1.0,1.0, 0.0,1.0,1.0, 0.0,1.0,1.0, 0.0,1.0,1.0,
])
// 创建颜色缓存区
const colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
gl.vertexAttribPointer(aColor, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(aColor)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
内容参考 《webgl编程指南》
更多资源文档和代码下载地址 (opens new window)