webGL编程

webGL

百度百科对于webGL的定义:

WebGL(全写Web Graphics Library)是一种3D绘图协议,这种绘图技术标准允许把JavaScript和OpenGL ES 2.0结合在一起,通过增加OpenGL ES 2.0的一个JavaScript绑定,WebGL可以为HTML5 Canvas提供硬件3D加速渲染,这样Web开发人员就可以借助系统显卡来在浏览器里更流畅地展示3D场景和模型了,还能创建复杂的导航和数据视觉化。

从定义中可以看出,webGL就是给openGL ES套上了一层JavaScript的API,由于之前看过OpenGL编程,加上最近的项目都是JavaScript开发,看webGL也是较快就上手。

webGL编程

webGL编程流程跟openGL是一样的,首先是顶点数据和顶点坐标的准备,如果使用贴图的话,还需要有贴图uv坐标。顶点数据准备好了,接下来是编译shader代码和加载贴图资源。

在所有数据都准备好了之后,程序就可以开始渲染的主循环,渲染的主循环主要是在每一帧里,将前面准备好的数据填充到shader程序对应的变量上,实际上就是调用openGL接口,将数据推送到GPU内存。之前看过一些CUDA编程,这里的uniform变量对应的应该是global memory(如果使用的是英伟达显卡的话)。最后调用渲染接口,webGL负责在帧缓冲中将顶点的颜色值填好。

下面这个流程图描述了webGL渲染管线的工作流程:

不带光照的webGL demo

下面这个demo是经典的webGLdemo,实现了使用一张2d箱子贴图渲染的一个3d箱子,同时实现箱子的旋转和移动(键盘的上下左右控制旋转,pageup是沿Z轴负方向移动,pagedown是沿Z轴正方向移动)。不过这里改变的是箱子本身的旋转和坐标,摄像机的位置没有发生过变化。

代码

渲染数据准备

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
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

function initBuffers() {
//顶点缓冲
cubeVertexPositionBuffer = gl.createBuffer();
//绑定为当前的数组缓冲
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer);
vertices = [
// 立方体前面
-1.0, -1.0, 1.0,
1.0, -1.0, 1.0,
1.0, 1.0, 1.0,
-1.0, 1.0, 1.0,

// 立方体背面
-1.0, -1.0, -1.0,
-1.0, 1.0, -1.0,
1.0, 1.0, -1.0,
1.0, -1.0, -1.0,

// 立方体顶部
-1.0, 1.0, -1.0,
-1.0, 1.0, 1.0,
1.0, 1.0, 1.0,
1.0, 1.0, -1.0,

// 立方体底部
-1.0, -1.0, -1.0,
1.0, -1.0, -1.0,
1.0, -1.0, 1.0,
-1.0, -1.0, 1.0,

// 立方体右边
1.0, -1.0, -1.0,
1.0, 1.0, -1.0,
1.0, 1.0, 1.0,
1.0, -1.0, 1.0,

// 立方体左边
-1.0, -1.0, -1.0,
-1.0, -1.0, 1.0,
-1.0, 1.0, 1.0,
-1.0, 1.0, -1.0,
];
//将数据写入顶点缓冲中
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
//添加属性itemSize,numItems后面使用
cubeVertexPositionBuffer.itemSize = 3;
cubeVertexPositionBuffer.numItems = 24;
//纹理坐标缓冲
cubeVertexTextureCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexTextureCoordBuffer);
var textureCoords = [
// 立方体前面
0.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,
0.0, 0.0,

// 立方体顶部
0.0, 1.0,
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,

// 立方体底部
1.0, 1.0,
0.0, 1.0,
0.0, 0.0,
1.0, 0.0,

// 立方体右边
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
0.0, 0.0,

// 立方体左边
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoords), gl.STATIC_DRAW);
cubeVertexTextureCoordBuffer.itemSize = 2;
cubeVertexTextureCoordBuffer.numItems = 24;
//顶点索引缓冲
cubeVertexIndexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer);
var cubeVertexIndices = [
0, 1, 2, 0, 2, 3, // 立方体前面
4, 5, 6, 4, 6, 7, // 立方体背面
8, 9, 10, 8, 10, 11, // 立方体顶部
12, 13, 14, 12, 14, 15, // 立方体底部
16, 17, 18, 16, 18, 19, // 立方体右边
20, 21, 22, 20, 22, 23 // 立方体左边
]
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(cubeVertexIndices), gl.STATIC_DRAW);
cubeVertexIndexBuffer.itemSize = 1;
cubeVertexIndexBuffer.numItems = 36;
}




var texture;
//加载贴图
function initTexture() {
var crateImage = new Image();

texture = gl.createTexture();
texture.image = crateImage;


crateImage.onload = function () {
handleLoadedTexture(texture);
}
crateImage.src = "crate.gif";
}


function handleLoadedTexture(texture) {
//翻转y轴
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);

gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.image);
//设置放大使用线性插值
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
//设置缩小使用多级渐远
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
//生成mipmap
gl.generateMipmap(gl.TEXTURE_2D);

gl.bindTexture(gl.TEXTURE_2D, null);
}

渲染主循环

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

function drawScene() {
//设置视图大小
gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
//清空上一帧的缓冲
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
//设置观察矩阵
pMatrix = okMat4Proj(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0);

//移动箱子位置,z坐标根据用户输入变化
mvMatrix = okMat4Trans(0.0, 0.0, z);
//设置箱子旋转,xRot和yRot根据用户输入变化
mvMatrix.rotX(OAK.SPACE_LOCAL, xRot, true);
mvMatrix.rotY(OAK.SPACE_LOCAL, yRot, true);

//将顶点缓冲传到GPU中
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, cubeVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);

//将纹理缓冲传到GPU中
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexTextureCoordBuffer);
gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, cubeVertexTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0);

//激活纹理
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.uniform1i(shaderProgram.samplerUniform, 0);

gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer);
//设置矩阵
gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, pMatrix.toArray());
gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, mvMatrix.toArray());
//最后调用GL渲染接口
gl.drawElements(gl.TRIANGLES, cubeVertexIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
}

创建shader程序

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

var shaderProgram;

function initShaders() {
var fragmentShader = getShader(gl, "shader-fs");
var vertexShader = getShader(gl, "shader-vs");
//创建shader程序
shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);

if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
alert("Could not initialise shaders");
}

gl.useProgram(shaderProgram);
//获取顶点着色器中的顶点坐标变量
shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
//获取顶点着色器中的纹理坐标变量
shaderProgram.textureCoordAttribute = gl.getAttribLocation(shaderProgram, "aTextureCoord");
gl.enableVertexAttribArray(shaderProgram.textureCoordAttribute);
//获取顶点着色器中的观察矩阵变量
shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
//获取顶点着色器中的模型矩阵变量
shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");
//获取顶点着色器中的纹理位置变量
shaderProgram.samplerUniform = gl.getUniformLocation(shaderProgram, "uSampler");
}

shader代码

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

<script id="shader-vs" type="x-shader/x-vertex">
attribute vec3 aVertexPosition;
attribute vec2 aTextureCoord;

uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;

varying vec2 vTextureCoord;


void main(void) {
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);//转换为观察坐标
vTextureCoord = aTextureCoord;//将纹理坐标传给片段着色器
}
</script>
<script id="shader-fs" type="x-shader/x-fragment">

precision mediump float;


varying vec2 vTextureCoord;

uniform sampler2D uSampler;

void main(void) {
gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));//读取纹理颜色
}
</script>