上篇文章简单介绍了WebGL的一些相关概念、现状以及工作原理,没有涉及到具体编码,本文将使用WebGL的相关技术来实现一个简单的效果示例,从而让大家了解WebGL编程的基本流程和关键操作。最后,还介绍了一些WebGL的库和框架,恰当的使用这些第三方框架,可以大大提高我们的工作效率。
开发流程
WebGL的开发过程主要包括以下几个步骤:
1.准备容器
WebGL要在页面上执行相关的渲染,必须依赖一个特定的容器,在HTML5中,使用的是Canvas作为容器。
1 2 3 4 5
| <body onload="start()"> <canvas id="glcanvas" width="640" height="480"> Your browser doesn't appear to support the HTML5 canvas element. </canvas> </body>
|
如上所示,我们在HTML中添加了一个canvas元素,并且设置了onload事件作为入口,后续的所有业务逻辑都会在start函数中进行处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| var gl; function start() { var canvas = document.getElementById("glcanvas"); gl = initGL(canvas); initShaders(); initBuffers();
gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.enable(gl.DEPTH_TEST); drawScene(); }
|
从上面的代码可以看出,我们整个的过程主要包含了四个比较重要的阶段:初始化上下文、初始化着色器、加载模型数据和渲染。下面会对这几个过程做详细介绍。
2.初始化上下文
initGL方法主要用来初始化WebGL的绘制环境:
1 2 3 4 5 6 7 8 9 10 11 12 13
| function initGL(canvas) { gl=null; try { gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl"); } catch(e) {}
if (!gl) { alert("Unable to initialize WebGL. Your browser may not support it."); } }
|
需要注意的是,我们为了得到WebGL上下文,需要使用canvas的getContext方法,这里有两个参数值可以使用:webgl或者experimental-webgl。不同浏览器环境下使用的参数不一样,具体可参照WebGL - 3D Canvas graphics。
3.初始化着色器
在3D场景的开发过程中会涉及到非常多的计算过程(颜色、位置等),为了提高效率,使用了硬件加速,这个时候GPU的强大计算能力得到了运用。但是,怎样告诉它正确执行相关计算逻辑呢?答案是着色器。着色器告诉相关计算单元做什么,而着色器语言(GLSL)来告诉它们怎么做。
The OpenGL ES Shading Language
一般情况下,我们在HTML中定义着色器,然后在代码中获取并使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <script id="shader-fs" type="x-shader/x-fragment"> precision mediump float; varying vec4 vColor;
void main(void) { gl_FragColor = vColor; } </script> <script id="shader-vs" type="x-shader/x-vertex"> attribute vec3 aVertexPosition; attribute vec4 aVertexColor;
uniform mat4 uMVMatrix; uniform mat4 uPMatrix;
varying vec4 vColor;
void main(void) { gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); vColor = aVertexColor; } </script>
|
上文的代码中定义了两个着色器:片段着色器和顶点着色器。
顶点着色器定义了模型中顶点的位置和颜色信息。
片段着色器通过差值的方法来处理WebGL中像素的相关数据,这里定义了像素上颜色的计算方法。
我们在initShaders方法中来查找使用着色器:
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
| var shaderProgram;
function initShaders() { var fragmentShader = getShader(gl, "shader-fs"); var vertexShader = getShader(gl, "shader-vs"); 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.vertexColorAttribute = gl.getAttribLocation(shaderProgram, "aVertexColor"); gl.enableVertexAttribArray(shaderProgram.vertexColorAttribute);
shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix"); shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix"); }
|
上述代码加载了模型数据,并且通过一系列的绑定操作和相关设置,告诉GPU如何去处理相关的渲染过程。
这里使用了一个工具函数getShader,它的作用是从DOM中获取定义的相关着色器程序,并返回编译好的渲染器程序:
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
| function getShader(gl, id) { var shaderScript, theSource, currentChild, shader; shaderScript = document.getElementById(id); if (!shaderScript) { return null; } theSource = ""; currentChild = shaderScript.firstChild; while(currentChild) { if (currentChild.nodeType == currentChild.TEXT_NODE) { theSource += currentChild.textContent; } currentChild = currentChild.nextSibling; } if (shaderScript.type == "x-shader/x-fragment") { shader = gl.createShader(gl.FRAGMENT_SHADER); } else if (shaderScript.type == "x-shader/x-vertex") { shader = gl.createShader(gl.VERTEX_SHADER); } else { return null; } gl.shaderSource(shader, theSource); gl.compileShader(shader); if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { alert("An error occurred compiling the shaders: " + gl.getShaderInfoLog(shader)); return null; } return shader; }
|
4.生成/加载模型数据
下面的initBuffers函数来将模型数据加载到缓冲器中,这样将顶点位置和颜色数据在上下文中准备就绪,随后才可以进行下一步的渲染操作。
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
| var triangleVertexPositionBuffer; var triangleVertexColorBuffer;
function initBuffers() { triangleVertexPositionBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer); var vertices = [ 0.0, 1.0, 0.0, -1.0, -1.0, 0.0, 1.0, -1.0, 0.0 ]; gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); triangleVertexPositionBuffer.itemSize = 3; triangleVertexPositionBuffer.numItems = 3;
triangleVertexColorBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexColorBuffer); var colors = [ 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0 ]; gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW); triangleVertexColorBuffer.itemSize = 4; triangleVertexColorBuffer.numItems = 3; }
|
5.渲染
经过上一步的模型数据准备后,就可以直接通过WegGL来进行渲染了。下面的drawScene方法,对3D模型进行了一些基本设置,然后根据缓冲区中的相关数据,来绘制出相应的效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function drawScene() { gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
pMatrix = okMat4Proj(45.0, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0); mvMatrix = okMat4Trans(-1.5, 0.0, -7.0);
gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer); gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, triangleVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexColorBuffer); gl.vertexAttribPointer(shaderProgram.vertexColorAttribute, triangleVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0);
setMatrixUniforms(); gl.drawArrays(gl.TRIANGLES, 0, triangleVertexPositionBuffer.numItems); }
|
至此,一个完整的WebGL程序基本开发完毕了,通过上面的过程我们发现,使用原生的WebGL来进行特殊场景的开发简直就是一种折磨。我们需要了解各种实现细节和专业知识,这对于非专业的开发人员是一种极大的挑战。所以,自然而然就有大量的类库和框架出现,来简化我们的开发过程,提高我们的工作效率。
点击这里可以看到用three.js来实现的上文效果
WebGL库和框架
下面简单介绍一些WebGL的类库或者框架:
three.js,这是一个低复杂、轻量级的开源框架,也是目前应用最广泛的3D框架,模型文件支持多种格式,渲染器的选择也非常灵活,方便使用者快速开发各种效果。
PhiloGL,这也是一个开源框架,注重性能,拥有非常强大的接口,比较适合数据可视化和游戏开发。
Babylon.js,这是一款非常适合作为游戏引擎的开源框架,让WebGL的使用更加简单、强大,非常适合游戏开发。
SceneJS,这一开源框架的特点在于,对于高精度的细节控制非常好,适合的领域有工程学、医学建模等。
CopperLicht,这是一个非常出色的3D引擎,并且带有编辑器,唯一的缺点是,他不是开源的,如果商用需要购买。
参考
- 突袭HTML5之WebGL 3D概述
- Adding 2D content to a WebGL context
- The OpenGL® ES Shading Language
- WebGL 入门 – WebGL框架