WebGL初探

Coursera 上最近新开了一门课 Interactive Computer Graphics with WebGL,通过 WebGL 来介绍计算机图形学。

从本科时学习 OpenGL 1.x ,到研一重新学习 OpenGL 3.x 之后基于 shader 的方法,OpenGL 也算是我的一位老朋友了。现在 Web 开发热火朝天,桌面应用 Web 化早已成为趋势,我正好借着这门课学习下 WebGL,同时也了解一下网络前端开发。


OpenGL

OpenGL = Open Graphics Library,是图形硬件的一种标准软件接口,其最初由 SGI 创建,用于在不同硬件体系结构的图形设备上进行二维和三维图形的绘制。OpenGL 不是一种编程语言,也不是 OpenCV 之类的算法工具库,而是一个图形显示标准 API 集合。


Modern OpenGL

学习过 OpenGL 1.x 和 2.x 的朋友应该对 glBegin()glVertex()glEnd() 印象深刻,使用这种方式绘制属于“立即模式”(immediate mode),每次顶点被创建后都会由 CPU 发送给 GPU 进行绘制,从而造成了 CPU 和 GPU 之间的性能瓶颈。

立即模式在 OpenGL 3.1 及之后的版本已经被移除,Modern OpenGL 是基于 Shader 来进行图形的绘制的,即所有绘制工作都应由 GPU 负责,CPU 仅仅负责将要绘制的数据传送给 GPU。

Shader(着色器) 也是一种程序,其使用 GLSL(GL Shading Language) 编写,分为顶点着色器(vertex shader)和片段着色器(fragment shader),前者处理顶点相关的属性,后者处理像素点相关的属性。着色器在程序运行时进行编译。

Modern OpenGL 通过“保留模式”(retained mode) 进行图形绘制:将所有顶点数据放在一个数组中,将该数组发送给 GPU ,通过 shaders 完成绘制。


WebGL

WebGL 是 OpenGL ES 2.0 的 JavaScript 实现,是面向浏览器的 OpenGL。WebGL 可以与 HTML5、CSS 结合使用,借助本地系统显卡在浏览器中构建和显示三维场景与模型。

WebGL 使用 JavaScript 作为编程语言,尽管有一些问题和局限性,但能在浏览器轻松构建三维图形程序仍是一件很酷的事!


第一个例子:绘制三角形

现在开始我们的第一个 WebGL 例子:绘制一个有颜色的二维三角形。

这里利用 Edward Angel 教授提供的辅助脚本进行实现。

创建一个 WebGL 应用程序的基本步骤:

  • 创建 WebGL 上下文
  • 创建顶点缓冲 (vertex buffer)
  • 创建顶点着色器(vertex shader)和片段着色器(fragment shader)
  • 编译着色器
  • 创建程序对象,链接着色器
  • 绘制场景

Angel 教授提供的辅助脚本已经为我们封装了一些基本 WebGL 操作,因此我们只需要关心三个元素:

  • vertex shader
  • fragment shader
  • HTML canvas

HTML 文件与 JS 文件各有分工:

  • HTML文件
    • 描述页面布局,包含 canvas 元素
    • 加载 JS 脚本文件
    • 包含顶点和片段着色器
  • JS 文件
    • 创建 WebGL 上下文
    • 创建顶点缓冲
    • 编译、链接着色器
    • 创建数据
    • 渲染数据(绘制)

先来看一看 HTML 文件(myTriangle.html):

<!DOCTYPE html>
<html>
<head>
<script id="vertex-shader" type="x-shader/x-vertex">
attribute vec4 vPosition;
void main() {
  gl_Position = vPosition;
}
</script>

<script id="fragment-shader" type="x-shader/x-fragment">
precision mediump float;
void main() {
  gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);
}
</script>

<script type="text/javascript" src="../Common/webgl-utils.js"></script>

<script type="text/javascript" src="../Common/initShaders.js"></script>

<script type="text/javascript" src="myTriangle.js"></script>
</head>

</head>

<body>
<canvas id="gl-canvas" width="512" height="512">
Oops ... your browser doesn't support the HTML5 canvas element
</canvas>
</body>

</html>

上面代码的底部是如下 HTML 代码:

<body>
<canvas id="gl-canvas" width="512" height="512">
Oops ... your browser doesn't support the HTML5 canvas element
</canvas>
</body>

整个文件只有这一段是页面的body部分代码,其余都是 js 代码。<canvas> 标签就是要显示图形的部分,这里设置了其 id 和宽高,在之后的 js 文件中会用到。

上面 HTML 文件中的 <head> 标签中定义了顶点着色器和片段着色器:

<script id="vertex-shader" type="x-shader/x-vertex">
attribute vec4 vPosition;
void main() {
  gl_Position = vPosition;
}
</script>

<script id="fragment-shader" type="x-shader/x-fragment">
precision mediump float;
void main() {
  gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);
}
</script>

这里顶点着色器通过 vPosition 属性获取每个顶点的位置;片段着色器第一行声明浮点数精度,然后通过RGBA的四元向量设置片段着色器的输出颜色。

下面看一下 JS 文件(myTriangle.js):

"use strict";

var gl;
var points;

window.onload = function init() {
    var canvas = document.getElementById("gl-canvas");
    gl = WebGLUtils.setupWebGL(canvas);
    if (!gl) {
        alert("WebGL isn't available");
    }

    var vertices = new Float32Array([-1, -1, 0, 1, 1, -1]);

    // configure WebGL
    gl.viewport(0, 0, canvas.width, canvas.height);
    gl.clearColor(0.0, 1.0, 1.0, 1.0);

    // load shaders and initialize attribute buffers
    var program = initShaders(gl, "vertex-shader", "fragment-shader");
    gl.useProgram(program);

    // load the data into the GPU
    var bufferId = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, bufferId);
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

    // associate our shader variables with our data buffer
    var vPosition = gl.getAttribLocation(program, "vPosition");
    gl.vertexAttribPointer(vPosition, 2, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(vPosition);

    render();
};

function render() {
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.drawArrays(gl.TRIANGLES, 0, 3);
}

首先通过 document.getElementById 找到 HTML 中的 canvas 元素;然后创建并设置 WebGL 上下文,加载着色器,创建数据缓冲;接着将数据载入 GPU,将着色器与顶点缓冲进行关联,完成绘制。

注意 gl.vertexAttribPointer 方法,该方法指示 WebGL 刚刚绑定在 gl.ARRAY_BUFFER 上的缓冲数据是如何组织的。该方法各个参数的意义如下:

  • 第一个参数告诉 WebGL 绑定的缓冲应作为哪个通用属性索引的输入
  • 第二个参数定义每个位置信息要用多少个元素表示。这里是二维图形,每个位置由 x, y 两个分量组成
  • 第三个参数表示元素为浮点类型
  • 第四个参数为规范化标志
  • 第五个参数为步长,0 表示默认紧凑存储
  • 第六个参数是顶点数据类型中第一个元素的偏移值,由于这里只有位置数据,偏移值为 0

gl.enableVertexAttribArray(vPosition); 用于激活顶点着色器中的位置属性。

WebGL 创建的应用可以轻松的通过网页展示。这里我使用 CodePen 展示上面绘制三角形的例子,效果如下:

See the Pen webgl_demos by insaneguy (@insaneguy) on CodePen.