WebGL技术储备指南

395 阅读7分钟

背景

WebGL是html5草案的一部分,可以驱动canvas渲染三维场景。本文整理了WebGL相关知识点,供初学者使用的提纲。

WebGL 示例

寻找奥兹国
赛车游戏
划船的男孩(Goo Engine)

Canvas

canvas绘图先要获取绘图上下文

var context = canvas.getContext('2d');

context上调用各种函数绘制图形,比如:

// 绘制左上角为(0,0),右下角为(50, 50)的矩形
context.fillRect(0, 0, 50, 50);

WebGL同样需要获取绘图上下文:

var gl = canvas.getContext('webgl'); // 或experimental-webgl

但是接下来,如果想画一个矩形的话,就没这么简单了。实际上,canvas是浏览器封装好的一个绘图环境,在实际进行绘图操作时,浏览器仍然需要调用OpenGL API。而WebGL API几乎就是OpenGL API未经封装,直接套了一层壳。

矩阵变换

三维模型,从文件中读出来,到绘制在canvas中,经历了多次坐标变换。

关于坐标变换的更多内容,可以参考:

比较复杂的是模型变换中的绕任意轴旋转(通常用四元数生成矩阵)和投影变换(上面的例子都没收涉及到)。

关于绕任意轴旋转和四元数,可以参考:

关于齐次向量的更多内容,可以参考:

着色器和光栅化

在WebGL中,开发者是通过着色器来完成上述变换的。着色器是运行在显卡中的程序,以GLSL语言编写,开发者需要将着色器的源码以字符串的形式传给webgl上下文的相关函数。

着色器有两种,顶点着色器和片元(像素)着色器,它们成对出现。顶点着色器任务是接收顶点的局部坐标,输出CCV坐标。CCV坐标经过光栅化,转化为逐像素的数据,传给片元着色器。片元着色器的任务是确定每个片元的颜色。

顶点着色器接收的是attribute变量,是逐顶点的数据。顶点着色器输出varying变量,也是逐顶点的。逐顶点的varying变量数据经过光栅化,成为逐片元的varying变量数据,输入片元着色器,片元着色器输出的结果就会显示在canvas上。

着色器功能很多,上述只是基本功能。大部分炫酷的效果都是依赖着色器的。如果你对着色器完全没有概念,可以试着理解下一节hello world程序中的着色器再回顾一下本节。

关于着色器的更多内容,可以参考:

程序

关于ArrayBuffer的详细信息,可以参考:

关于gl.TRIANGLES等其他绘制方式,可以参考下面这张图或这篇博文

深度检测

当两个表面重叠时,前面的模型会挡住后面的模型。

WebGL的逻辑是这样的:依次处理片元,如果渲染缓冲区(这里就是canvas了)的那个与当前片元对应的像素还没有绘制时,就把片元的颜色画到渲染缓冲区对应像素里,同时把片元的z值缓存在另一个深度缓冲区的相同位置;如果当前缓冲区的对应像素已经绘制过了,就去查看深度缓冲区中对应位置的z值,如果当前片元z值小,就重绘,否则就放弃当前片元。

WebGL的这套逻辑,对理解蒙版(后面会说到)有一些帮助。

顶点索引

gl.drawArrays()是按照顶点的顺序绘制的,而gl.drawElements()可以令着色器以一个索引数组为顺序绘制顶点。

纹理

attribute变量不仅可以传递顶点的坐标,还可以传递其他任何逐顶点的数据。比如HelloTriangle程序把单个顶点的颜色传入了a_Color,片元着色器收到v_Color后直接赋给gl_FragmentColor,就决定了颜色。

attribute变量还可以帮助绘制纹理。绘制纹理的基本原理是,为每个顶点指定一个纹理坐标(在(0,0)与(1,1,)的正方形中),然后传入纹理对象。片元着色器拿到的是对应片元的内插后的纹理坐标,就利用这个纹理坐标去纹理对象上取颜色,再画到片元上。内插后的纹理坐标很可能不恰好对应纹理上的某个像素,而是在几个像素之间(因为通常的图片纹理也是离散),这时可能会通过周围几个像素的加权平均算出该像素的值(具体有若干种不同方法,可以参考)。

混合与蒙版

透明效果是用混合机制完成的。混合机制与深度检测类似,也发生在试图向某个已填充的像素填充颜色时。深度检测通过比较z值来确定像素的颜色,而混合机制会将两种颜色混合。

光照

WebGL没有为光照提供任何内置的方法,需要开发者在着色器中实现光照算法。

光是有颜色的,模型也是有颜色的。在光照下,最终物体呈现的颜色是两者共同作用的结果。

实现光照的方式是:将光照的数据(点光源的位置,平行光的方向,以及光的颜色和强度)作为uniform变量传入着色器中,将物体表面每个顶点处的法线作为attribute变量传入着色器,遵循光照规则,修订最终片元呈现的颜色。

光照又分为逐顶点的和逐片元的,两者的区别是,将法线光线交角因素放在顶点着色器中考虑还是放在片元着色器中考虑,逐片元光照更加逼真。

复杂模型

复杂模型可能有包括子模型,子模型可能与父模型有相对运动。比如开着雨刮器的汽车,雨刮器的世界坐标是受父模型汽车,和自身的状态共同决定的。若要计算雨刮器某顶点的位置,需要用雨刮器相对汽车的模型矩阵乘上汽车的模型矩阵,再乘以顶点的局部坐标。

复杂模型可能有很多表面,可能每个表面使用的着色器就不同。通常将模型拆解为组,使用相同着色器的表面为一组,先绘制同一组中的内容,然后切换着色器。每次切换着色器都要重新将缓冲区中的数据分配给着色器中相应变量。

动画

动画的原理就是快速地擦除和重绘。常用的方法是大名鼎鼎的requestAnimationFrame。不熟悉的同学,可以参考这篇笔记

WebGL库

目前最流行的WebGL库是ThreeJS,很强大,官网代码

调试工具

比较成熟的WebGL调试工具是WebGL Inspector

网络资源和书籍

英文的关于WebGL的资源有很多,包括:

国内最早的WebGL教程是由郝稼力翻译的,放在hiwebgl上,目前hiwebgl已经关闭,但教程还可以在这里找到。郝稼力目前运营着Lao3D

国内已经出版的WebGL书籍有: