样板代码说明
后面的示例入口都是以这个html页面为样板。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Draw a point (1)</title>
</head>
<body onload="main()">
<canvas id="webgl" width="400" height="400">
Please use a browser that supports "canvas"
</canvas>
<script src="../lib/webgl-utils.js"></script>
<script src="../lib/webgl-debug.js"></script>
<script src="../lib/cuon-utils.js"></script>
<script src="HelloPoint1.js"></script>
</body>
</html>
其中 webgl-utils.js 、webgl-debug.js、cuon-utils.js 等,都是这本书配套的工具方法。
相关源码我自己做了收藏,可以再这里下载。《WebGL 编程指南》书中涉及的源码
最短的WebGL程序:清空绘图区
function main() {
// 获取 <canvas> 元素
var canvas = document.getElementById('webgl');
// 获取 WebGL 绘图上下文
var gl = getWebGLContext(canvas);
if (!gl) {
console.log('Failed to get the rendering context for WebGL');
return;
}
// 设置背景颜色
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// 清空颜色缓冲区
gl.clear(gl.COLOR_BUFFER_BIT);
}
下面我们看下这里涉及的方法:
获取 webgl 绘图上下文
可以直接使用 canvas.getContext('webgl') 直接获取,不过不同浏览器可能有差异,所以这本书封装了一个 getWebGLContext() 方法去获取。
指定绘图区域的背景色
// 这几个参数都是从0 ~ 1。
gl.clearColor(red, green, bule, alpha);
一旦指定了背景色后,背景色就会保存在WebGL系统中,在下次调用 gl.clearColor() 方法之前都不会进行改变。
清空 canvas
/**
* 把指定的缓冲区清空为预设的值。若清空的是颜色缓冲区,那么将使用gl.clearColor()指定的预设值。
* @param {*} buffer 指定待清空的缓冲区,可以用 | 来指定多个。
* gl.COLOR_BUFFER_BIT 颜色缓冲区
* gl.DEPTH_BUFFER_BIT 深度缓冲区
* gl.STENCIL_BUFFER_BIT 模板缓冲区
*/
gl.clear(buffer);
调用该方法以后,就会用之前指定的背景颜色清空绘图区域。只是清空了颜色缓冲区,而不是 canvas 绘图区。
WebGL 有多个缓冲区,而gl.COLOR_BUFFER_BIT指的是颜色缓冲,使用gl.clearColor()指定的颜色;深度缓冲区gl.DEPTH_BUFFER_BIT 在画三维图形的时候会用到; gl.STENCIL_BUFFER_BIT 在这本书上没有涉及。
绘制一个点
在画图之前,我们要知道 webgl 依赖一种叫做着色器(shader)的绘图机制。着色器提供了强大的绘制二维或三维图形的方法,它很强大,不过也很复杂。后面我们会一步一步了解着色器。
// 顶点着色器
var VSHADER_SOURCE = `
void main() {
gl_Position = vec4(0.0, 0.0, 0.0, 1.0); // 设置顶点坐标
gl_PointSize = 10.0; // 设置点的大小
}`;
// 片元着色器
var FSHADER_SOURCE =`
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 设置颜色
}`;
function main() {
// 获取 <canvas> 元素
var canvas = document.getElementById('webgl');
// 获取 WebGL 绘图上下文
var gl = getWebGLContext(canvas);
// 初始化着色器
initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)
// 设置背景颜色
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// 清空颜色缓冲区
gl.clear(gl.COLOR_BUFFER_BIT);
// 绘制一个点
gl.drawArrays(gl.POINTS, 0, 1);
}
运行效果
着色器是什么?
我们现在使用的着色器是用字符串形式内嵌到 JavaScript 程序中的。 webgl 需要两种着色器:
- 顶点着色器 (Vertex shader): 顶点着色器是用来描述顶点相关特性(位置、大小)的程序。其中顶点是指二维或三维空间中的一个点。
- 片元着色器 (Fragment shader):片元着色器是逐片元处理(比如光照)的程序。片元是webgl的一个术语,可以理解为像素。
我们看一下从执行JavaScript程序到浏览器显示结果的过程:
其结果可描述为:
- 运行 JavaScript 程序,调动 webgl 相关方法;
- 执行顶点着色器和片元着色器,在颜色缓冲区中进行绘制
- 清空缓冲区
- 颜色缓冲区的内容展示在canva中
着色器程序
着色器程序是使用类似C的 OpenGL ES (GLSL ES) 来编写的。这本书的案例,都是将着色器程序代码当做字符串存储在变量中。
初始化着色器
initShaders()是这本书提供的辅助函数,作用是初始化着色器,具体实现在lib/cuon-utils.js文件中,有兴趣的话,可以看一下。不过作为初学者可以暂时不用纠结其中的细节,等熟悉了webgl 以后,可以再回过头来详细学习其中涉及的知识。现在我们只要知道调用这个方法,传入上下文、顶点着色器代码字符串、片段着色器代码字符换,就可以初始化着色器就行了。
初始化着色器完成后,在webgl系统中着色器就建立好了,并随时可以使用。在绘图时,顶点着色器先执行,它对 gl_Position 变量和 gl_PointSize 变量进行赋值,并将它们传入片元着色器,然后片元着色器再执行。实际上片元着色器收到的是经过光栅化处理后的片元值,目前可以简单的理解为这两个变量从顶点着色器传入片元着色器。
注意:着色器程序是运行在 webgl 系统中,而不是JavaScript程序中的
顶点着色器
着色器程序和C语言程序一样,都需要一个 main() 函数。同时我们看到了两个特殊的变量 gl_Position 和 gl_PointSize 。这两个变量都是内置在顶点着色器中的,其中:
gl_Position表示顶点的位置,这个变量是必须被赋值的,否则着色器没有办法执行,类型为vec4;gl_PointSize表示点的尺寸,可以不赋值,默认为1.0,类型为float。
GLSL ES 是一种强类型的编程语言:
- vec4 表示由4个浮点数组成的矢量,可以使用 vec4(v0, v1, v2, v3) 进行创建。
- float 表示浮点数。
片元着色器
顶点着色器控制着点的位置和大小,片元着色器控制着点的颜色。片元着色器的作用就是处理片元,将其显示在屏幕上。
片元着色器将点的颜色赋值给 gl_FragColor 变量,该变量是片元着色器中唯一内置的变量,它控制着显示在屏幕上像素的最终颜色。其类型是 vec4。
绘制操作
gl.drawArray() 是一个强大的方法,它能绘制各种图形。
/**
* 执行顶点着色器,按照mode参数指定的方式进行绘制
* @param {*} mode 绘制方式。gl.POINTS gl.LINES gl.LINE_STRIP gl.LINE_LOOP gl.TRIANGLES gl.TRIANGLE_STRIP gl.TRIANGLE_FAN
* @param {int} first 从哪个顶点开始绘制
* @param {int} count 绘制次数
*/
gl.drawArrays(mode, first, count);
在例子里,我们只需要绘制一个点。所以第一个参数设置为 gl.POINTS ;第二个设置为0,表示从第一个顶点开始画;第三个参数设为1,表示仅绘制一次,即一个点。
WEBGL 坐标系
webgl使用的是三维坐标系,有x轴、y轴、z轴:
- x轴正方向为水平向右,y轴正方向为水平向上,z轴正方向为外。
- canvas中心点为(0.0, 0.0, 0.0)
- canvas 上边缘和下边缘为(0.0, 1.0, 0.0) 、 (0.0, -1.0, 0.0);
- canvas 左边缘和右边缘为(-1.0, 0.0, 0.0) 、 (1.0, 0.0, 0.0);