摘 要 在移动web页面应用中,我们可以通过WebGL的上下文对象很轻松地进行3D动画的绘制工作。WebGL的上下文对象使用一个drawScene的函数来绘制图形。在制作动画时,每一次重绘都需要调用该函数,然后使用不同的方法来重绘图形。对于WebGL技术来说,一个不可回避的问题就是浏览器的支持,建议采用渐进式的支持方法。
关键词 WebGL;上下文对象;3D
WebGL是一种3D绘图标准,这种绘图技术标准允许把JavaScript和OpenGL ES2.0结合在一起,通过增加OpenGL ES 2.0的一个JavaScript绑定,WebGL可以为HTML5 Canvas提供硬件3D加速渲染,这样Web开发人员就可以借助系统显卡在浏览器里面更流畅地展示3D场景和模型了。在移动web页面应用中,我们可以通过WebGL的上下文对象很轻松地进行3D动画的绘制工作。在X点出绘制一个图形时,下一次绘制动画时在Y点处重新绘制该图形,再下一次绘制动画时在Z点处重新绘制该图形,以此类推。可能有人会问,为什么重绘的工作原理是每一次都要重新绘制图形呢?其实原因是WebGL的上下文對象使用一个drawScene的函数来绘制图形。在制作动画时,每一次重绘都需要调用该函数,然后使用不同的方法来重绘图形。
接下来,我们通过一个实例来看一下如何使用WebGL的上下文对象来绘制三维动画。在这个实例中,我们主要实现的功能是在网页中分别绘制一个有颜色的并且具有3D效果的三角形和矩形。
1 首先,设置调用WebGL的脚本代码的语句
通过canvas设置一块绘图区域主要的代码如下:
2 接下来编写JavaScript脚本代码
(1)创建WebGL上下文对象,定义函数initGL,并初始化WebGL。
function initGL(canvas)
{
try
{
gl = canvas.getContext(“experimental-webgl”);
gl.viewportWidth = canvas.width;
gl.viewportHeight = canvas.height;
}
catch (e)
{
}
if (!gl)
alert(“抱歉,您不能初始化WebGL。”);
}
(2)编写创建渲染器getShader。
function getShader(gl, id)
{
var shaderScript = document.getElementById(id);
if (!shaderScript)
return null;
var str =””;
var k = shaderScript.firstChild;
while (k)
{
if (k.nodeType==3)
str+= k.textContent;
k = k.nextSibling;
}
var shader;
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,str);
gl.compileShader(shader);
if(!gl.getShaderParameter(shader,gl.COMPILE_STATUS))
{
alert(gl.getShaderInfoLog(shader));
return null;
}
return shader;
}
(3)定义函数mvPushMatrix将视图-模型矩阵保存在堆栈中。
function mvPushMatrix()
{
var copy = mat4.create();
mat4.set(mvMatrix, copy);
mvMatrixStack.push(copy);
}
(4)定义mvPopMatrix取出堆栈中保存的视图。
function mvPopMatrix()
{
if (mvMatrixStack.length == 0)
throw “Invalid popMatrix!”;
mvMatrix = mvMatrixStack.pop();
}
function setMatrixUniforms()
{
gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, pMatrix);
gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, mvMatrix);
}
var triangleVertexPositionBuffer;
var triangleVertexColorBuffer;
var squareVertexPositionBuffer;
var squareVertexColorBuffer;
由此可见,函数mvPushMatrix的作用是将当前的视图-模型矩阵保存在堆栈中[1]。
(5)定义函数initBuffers,初始化缓冲区。
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;
squareVertexPositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
//使用JavaScript列表定义一个矩形的一组顶点信息
vertices =
[
1.0, 1.0, 0.0,
-1.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);
//緩冲区中存放四个顶点信息,每个顶点由三个数据(三维)构成
squareVertexPositionBuffer.itemSize = 3;
squareVertexPositionBuffer.numItems = 4;
squareVertexColorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexColorBuffer);
colors = []
for (var i=0; i < 4; i++)
{
colors = colors.concat([0.5, 0.5, 1.0, 1.0]);
}
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
squareVertexColorBuffer.itemSize = 4;
squareVertexColorBuffer.numItems = 4;
}
var rTri = 0;
var rSquare = 0;
(6)定义绘制图形的函数drawScene[2]。
function drawScene()
{
gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
mat4.perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0, pMatrix);
mat4.identity(mvMatrix);
mat4.translate(mvMatrix, [-1.5, 0.0, -7.0]);
mvPushMatrix();
mat4.rotate(mvMatrix, degToRad(rTri), [0, 1, 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);
mvPopMatrix();
mat4.translate(mvMatrix, [3.0, 0.0, 0.0]);
mvPushMatrix();
mat4.rotate(mvMatrix, degToRad(rSquare), [1, 0, 0]);
gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
/*通知WebGL缓冲区中存放的顶点位置信息将被作为矩形各顶点位置信息来使用,每个矩形使用三个数据(三维信息)*/
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexColorBuffer);
gl.vertexAttribPointer(shaderProgram.vertexColorAttribute, squareVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0);
setMatrixUniforms();
gl.drawArrays(gl.TRIANGLE_STRIP, 0, squareVertexPositionBuffer.numItems);
mvPopMatrix();
}
function degToRad(degrees) {
return degrees * Math.PI / 180;
}
在使用WebGL上下文对象进行3D图形的绘制时,需要告诉WebGL上下文对象当前图形的绘制位置及旋转角度,而当前图形的绘制位置及旋转角度都被保存在模型-视图矩阵中,因此,mat4.rotate方法的作用就比较明显了,该方法的书写代码类似于代码:mat4.rotate(mvMatrix,degToRad(rTri),[0,1,0]);
其中mvMatrix表示视图-模型矩阵,该代码的含义为:将视图-模型矩阵中的旋转角度围绕垂直方向(参数为[0,1,0])旋转rTi度。因为在WebGL API中,使用弧度来制定旋转角度,所以使用degToRad函数将角度转变为弧度,该函数代码如下所示:
Function degToRad(degrees) {
Return degrees *Math.PI/180;
}
(7)定义函数webGLStart绘制3D图形,此函数调用了tick函数,在tick函数中调用drawScene函数来绘制图形[3]。
function webGLStart()
{
var canvas = document.getElementById(“canvas1”);
initGL(canvas);
initShaders();
initBuffers();区
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.enable(gl.DEPTH_TEST);
tick();}
(8)定义函数tick,在此函数中使用WebGL API中的动画功能,每一次使用不同的方法来绘制图形,同时要在该函数中制定一个每次调用drawScene函数重绘图形的间隔时间。
function tick()
{
requestAnimFrame(tick);
drawScene();
animate();
}
var lastTime = 0;
(9)定义函数animate设置动画参数。
function animate()
{
var timeNow = new Date().getTime();
if (lastTime != 0)
{
var elapsed = timeNow - lastTime;
rTri += (90 * elapsed) / 1000.0;
rSquare += (75 * elapsed) / 1000.0;
}
lastTime = timeNow;
}
到此为止,使用WebGL上下文对象绘制的三维动画制作完毕[4]。效果如下图所示:
3 关于浏览器支持的问题
对于WebGL技术来说,一个不可回避的问题就是浏览器的支持,虽然IE已经开始支持WebGL了,但很多用户的浏览器可能还不支持。所以建议开发者采用渐进式的支持方法,即给不同的浏览器不同的版本,以确保最先进的浏览器用户获得最好体验,而低版本浏览器用户也能获得良好的效果。
参考文献
[1] 于坤,周大庆.JavaScript基础与案例开发详解[M].北京:清华大学出版社,2009:57.
[2] 張亚飞,高红霞.jQuery全能权威指南:JQuery Core+JQuery Plugin+JQuery Ul+Query[M].北京:清华大学出版社,2012:41-55.
[3] 王翠萍.移动Web开发从入门到精通:基于HTML5+jQuery Mobile+PhoneGap[M].北京:中国铁道出版社,2015:78.
[4] Jon Duckett.JavaScript & jQuery交互式Web前端开发[M].北京:清华大学出版社,2014:17.