上篇文章介绍了如何使用WebGL来绘制一个三角形。从那个教程中,我们可以看出,其实WebGL只是一个2D API,我们只需要在Vertex Shader里面指定绘制图元的裁剪坐标系的坐标(取值范围从-1到+1),同时在Fragment Shader里面指定顶点最终的像素颜色即可。如果要实现3D效果,必须由程序员自己来指定3D空间坐标变换,即Model-View-Projection变换。
而WebGL本身是不提供数学库来做这些事情的,所以我们需要借助一些第三方的库(当然,也可以自己编写,但是效率可能会有问题,除非是学习,否则建议不要自己写)。现在Js的数学库比较多,而本教程主要介绍如何使用glMatrix.js来做MVP变换,在文章的最后,我们通过集成Jquery可以让3D场景更具有交互性。(当然,本教程只是通过往场景里面添加一个slider来改变三角形的颜色)
在继续阅读教程之前,可以先看看本教程最终的样子:(别忘了拖动两下滑块哦)
下载和安装glMatrix.js
下载glMatrix很简单,直接去glMatrix.js首页上下载最新版本即可,当然也可以去github上面下载。当前的最新发布版本是v2.2.1,它与之前的API相比有一些不同。具体的内容在glMatrix.js首页上面都有介绍,这里就不赘述了。当然,这也导致了我们如果去看网上其它一些教程的时候,可能使用的是老版本的glMatrix.js。大家注意对照最新版本的API文档来进行编程即可。
因为是js库,所以安装过程基本为0。只需要把解压缩里面的dist目录下面的gl-matrix-min.js拷贝到我们的工程目录下面,并在html文件里面引入即可:
编写一些重用的功能
因为每一个WebGL程序都需要以下二个步骤:
所以这里,我们把这些操作封装成函数并放到webgl_utils.js里面:
首先是从canvas里面获取WebGL上下文:
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
| function createGLContext(canvasId)
{
var canvas = document.getElementById(canvasId);
var names = ["webgl",
"experimental-webgl",
"webkit-3d",
"moz-webgl"];
var gl;
for (var i = 0; i < names.length; ++i)
{
try
{
gl = canvas.getContext(names[i]);
}
catch(e)
{
throw("contex can't be created!");
}
if (gl) break;
}
if (gl == null){
alert("WebGL is not available");
}
return gl;
}
|
然后,是创建shader program的函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| //根据shdaer的str和type来创建vertex shader和fragment shader
function createShader(gl, str, type){
var shader = gl.createShader(type);
gl.shaderSource(shader,str);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
throw gl.getShaderInfoLog(shader);
}
return shader;
}
//使用vertex shader和fragment shader来创建shader program
function createProgram(gl, vstr,fstr){
var program = gl.createProgram();
var vshader = createShader(gl,vstr,gl.VERTEX_SHADER);
var fshader = createShader(gl, fstr, gl.FRAGMENT_SHADER);
gl.attachShader(program,vshader);
gl.attachShader(program,fshader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
throw gl.getProgramInfoLog(program);
}
return program;
}
|
有了这两个函数,我们每一次编写一个WebGL的程序的时候就不用重复造轮子了。另外,由于之前的vertex shader和fragment shader都是直接硬编码为字符串的,这样书写极为不便,同时也很难维护。
定义自己的script标签
本节我们将使用自定义的标签来放置vertex shader和fragment shader.
因为html5的标准规定,如果script标签里面的type不是text/javascript或者src="…“之类的话,浏览器会把这段script标签里面的内容解析成文本。
因此,我们自定义两种type分别用来存放vertex shader和fragment shader:
1
2
3
4
5
6
7
8
9
10
| //下面是vertex shader的定义
<script type="x-shader/x-vertex" id="vertex-shader">
attribute vec2 a_position;
uniform mat4 u_ModelViewMatrix;
uniform mat4 u_PMatrix;
void main()
{
gl_Position = u_PMatrix * u_ModelViewMatrix * vec4(a_position,0.0,1.0);
}
</script>
|
1
2
3
4
5
6
7
8
9
10
| //下面是fragment shader的定义
<script type="x-shader/x-fragment" id="fragment-shader">
//this line must be added
precision mediump float;
uniform vec4 u_color;
void main()
{
gl_FragColor = u_color;
}
</script>
|
最后,为了方便使用,我还封装了下面的函数用来从script id来创建shader program.
1
2
3
4
5
6
7
| function createProgramFromElementId(gl, vertexId, fragmentId)
{
var vertex = document.getElementById(vertexId);
var fragment = document.getElementById(fragmentId);
return createProgram(gl,vertex.text, fragment.text);
}
|
Model View Projection变换
在讨论Model View Projection变换之前,我们先指定Model坐标吧。这次为了简单起见,我们还是绘制一个三角形,只是这三角形的坐标不再是设备坐标系空间(normalized device space)的坐标了,如下所示:
1
2
3
4
5
6
| function initBuffers(gl)
{
var vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0,0, 480,0, 240,320]), gl.STATIC_DRAW);
}
|
我们的canvas大小是480*320,这是一个横版iPhone4S的大小。我们这里定义的三角形坐标正好是canvas的左下角,右下角和中间最顶端的点。这个坐标我们暂且把它称之为模型坐标。而这三个点最终在normalized device space的坐标系应该是(-1,-1),(1,-1)和(0,1)。我们可以经过Model-to-View和View-to-Projection变换把(0,0),(480,0)和(240,320)这三个点变换为(-1,-1),(1,-1)和(0,1)。
首先,介绍的是Model-to-View变换。在介绍这个变换之前,让我们先粗略地讲一下OpenGL的照相机原理。它其实就是模拟人的眼睛看周围世界的方式。而Model-to-View变换就是把模型空间变换成Camera空间。就好比,现在你的身后有个人,你想要看到他,你必须转过身去,或者他跑到你面前来。不管是哪种方式,都需要应用ModelView变换。
在OpenGL里面是使用glLookAt这个函数来进行ModelView变换的。
1
2
3
| var mvMatrix = mat4.create();
var zEye = 320 / 1.1566;
mat4.lookAt(mvMatrix, [240,160,zEye],[240,160,0],[0,1,0]);
|
这里的代码与cocos2d-x当中的ModelView变换是一模一样的(我表示拿来主义哈)。mat4.lookAt的第一个参数是一个out型参数,后面三个参数的意义分别是eye,center和up,即眼睛所在的位置,眼睛注意的物体的中心点位置和头的方向。这个解释不是很精确,强烈建议大家阅读10遍这篇文章.
模型视图变换完以后,此时的坐标已经在camera范围内了,但是最终opengl需要的是normalized device space坐标系的坐标,那个坐标系的取值范围是-1到+1.因此我们需要做一次View-to-Projection变换来把视图坐标系变换到normalized device space坐标系。
1
2
| var fov = 60 * 3.1415926 / 180;
mat4.perspective(pMatrix, fov , 480/320, 10, zEye + 160);
|
这段代码也没啥好解释的,建议看看这个就明白了.记得要下载可执行程序跑起来看看哦。
最后就是把这两个matrix通过uniform的形式传给vertex shader啦:
1
2
3
4
5
| mvMatrixLocation = gl.getUniformLocation(program,"u_ModelViewMatrix");
pMatrixLocation = gl.getUniformLocation(program, "u_PMatrix");
gl.uniformMatrix4fv(pMatrixLocation, false, pMatrix);
gl.uniformMatrix4fv(mvMatrixLocation, false, mvMatrix);
|
集成Jquery
接下来,我们往场景里面添加一个slider,用来控制三角形的红色通道的颜色值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| <div>
拖动这个滑块: <div id="slider" ></div>
</div>
$("#slider").slider({
orientation: "horizontal",
max: 1,
min: 0,
step:0.1,
value: 0.5,
slide : function(event, ui) {
sliderValue = $("#slider").slider("value");
drawScene(gl);
}
});
|
在拖动滑块的时候,我们调用了drawScene来重绘整个OpenGL场景 。
结语
最后,附上本教程的源码