WebGL简易教程(一):第一个简单示例
概述了这篇教程的目的,编写了WebGL的第一个示例。
1. 概述
不得不说现在三维图形渲染技术更新换代实在是太快,OpenGL很多资料还没来得及学习就已经有点落伍了。NeHe的学习教程还有之前用的《OpenGL编程指南》第七版(也就是红宝书)都非常好,可惜它们都是从固定管线开始讲起的;而现在可编程管线的技术已经是非常常见的基础技术了。后来我还看过《OpenGL编程指南》第八版(白皮书),这本教程是从可编程管线(着色器)开始讲起的,看的时候就觉得没有前面的基础打底,显得非常的晦涩,远不如红宝书易懂。羞愧的说,我已经多次入门失败了。
这也正是我写这篇教程的原因,希望从繁杂的资料中总结真正有用的知识(当然也希望能帮助到你)。我觉得WebGL是学习OpenGL系列三维图形渲染技术很好的入门点。WebGL是OpenGL的浏览器版本,基本上可以认为是OpenGL的子集,能被WebGL保留而不剔除的技术,必须是三维图形渲染技术的精华。在这里给大家强烈推荐《WebGL编程指南》这本书,我这篇教程正是在这本书的基础之上总结出来的。
在学习OpenGL/WebGL的时候,我还感觉到很多资料举得例子往往都太简单了,确实是一看就懂,但是在实际遇到的问题的时候却往往解决不了。我还是认为在实际中解决问题,更能加深对知识的理解。正好最近我在研究GIS中地形的绘制,那么我就通过一步一步绘制地形的示例,来总结WebGL的相关知识。如果你不懂GIS这些术语也不要紧,只需要知道我这里的最终目的是想绘制的是一个大地高程模型,是一个包含XYZ坐标的点集,表达了地形的情况。
2. 示例:绘制一个点
编写WebGL程序跟编写Web前端程序的步骤是一样的,包含HTML和JavaScript两个部分,通过浏览器进行调试。
1) HelloPoint1.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>
这一段HTML非常简单,从实际表现上来说就是创建了一个画布<canvas>。<canvas>是HTML5引入的的一个绘制标签,可以在画布中绘制任意图形。WebGL正是通过<canvas>元素进行绘制的。
除此之外,这段代码还通过<script>标签引入了几个外部JS文件。其中lib目录中的几个JS文件是一些通用的组件(来自《WebGL编程指南》的源码),可以先暂时不用关心其具体实现;最后一个导入的HelloPoint1.js正是我们编写的绘制模块。而在<body>标签中定义的onload事件属性绑定的正是HelloPoint1.js中的main()函数。
2) HelloPoint1.js
// 顶点着色器程序
var VSHADER_SOURCE =
\'void main() {\n\' +
\' gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n\' + // Set the vertex coordinates of the point
\' gl_PointSize = 10.0;\n\' + // Set the point size
\'}\n\';
// 片元着色器程序
var FSHADER_SOURCE =
\'void main() {\n\' +
\' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n\' + // Set the point color
\'}\n\';
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;
}
// 初始化着色器
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log(\'Failed to intialize shaders.\');
return;
}
// 指定清空<canvas>的颜色
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// 清空<canvas>
gl.clear(gl.COLOR_BUFFER_BIT);
// 绘制一个点
gl.drawArrays(gl.POINTS, 0, 1);
}
这段JS代码的主要内容就是前面提到的main函数,一旦HTML被浏览器加载成功,这段脚本就会执行。在main函数中主要有一下几步:
(1) 准备工作
document.getElementById(\’webgl\’):文档对象模型DOM的函数,获取到HTML页面的<canvas>元素。
getWebGLContext(canvas):获取WebGL渲染上下文,保存在gl变量中。因为不同浏览器获取函数不太一样,所以通过组件cuon-utils提供的函数来统一行为。
(2) 着色器
initShaders:初始化着色器。
首先要知道什么是着色器。如果你只学习过固定管线或者其他的二维绘图组件(如GDI),就会非常困惑着色器是什么,为什么要用着色器。比如说在固定管线中,绘制点就是drawPoint,绘制线就drawLine。而在WebGL中,绘制工作则主要被分解成顶点着色器和片元着色器两个步骤了。
在启动JS程序后,绘制工作首先进入的是顶点着色器,在顶点着色器中描述顶点特性(如位置、颜色等),顶点就是三维空间的点,比如三角形的三个顶点;然后进入到片元着色器,在片元着色器中逐片元处理像素(如光照、阴影、遮挡)。最后片元传入到颜色缓冲区,进行显示。渲染过程如下:
这个过程是一个类似水流的流向过程,所以这个过程被称为渲染管线(Pipeline)。并且,这个过程是需要我们去编程控制的,比如观察者的视角变化需要在顶点着色器去调控;光线对颜色的变化需要在片元着色器去调控等;因此,这个过程就是可编程管线。通过着色器程序,三维图像渲染就更加的灵活强大。
在initShaders()函数中,传入了预先定义的JS字符串VSHADER_SOURCE和FSHADER_SOURCE。需要说明是,着色器程序是以字符串的形式嵌入到JS文件中运行的。这个函数同样是cuon-utils组件提供的,调用之后就告诉WebGL系统着色器已经建立好了并可以随时使用。
(3) 顶点着色器
顶点着色器的定义如下:
// 顶点着色器程序
var VSHADER_SOURCE =
\'void main() {\n\' +
\' gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n\' + // Set the vertex coordinates of the point
\' gl_PointSize = 10.0;\n\' + // Set the point size
\'}\n\';
前面说到顶点着色器程序是嵌入在JS中的程序,所以虽然传入的是字符串,但其实本质是着色器描述语言(GLSL:OpenGL Shading Language)。既然是语言也就有自己的函数与变量定义。main()函数是每个着色器程序定义的入口。在main函数中,将顶点的坐标赋值给内置变量gl_Position,点的尺寸赋值给内置变量gl_PointSize。
注意这里的gl_Position是必须赋值的,否则着色器不会正常工作。赋值的类型是vec4,也就是一个四维矢量。一般来说,描述点位只需要三维矢量就可以了,但是很多情况下需要四个分量的齐次坐标。齐次坐标(x,y,z,w)等价于三维坐标(x/w,y/w,z/w)。所以如果第四个分量是1,那么就是普通的三维坐标;如果第四分量为0,就表示无穷远的点。
(4) 片元着色器
片元着色器的定义如下:
// 片元着色器程序
var FSHADER_SOURCE =
\'void main() {\n\' +
\' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n\' + // Set the point color
\'}\n\';
如同顶点着色器一样,片元着色器将点的颜色赋值给gl_FragColor变量,gl_FragColor是片元着色器唯一的内置变量,控制像素在屏幕上的最终颜色。
(5) 清空缓冲区
gl.clearColor():设置清空的背景色。
gl.clear(gl.COLOR_BUFFER_BIT): 清空颜色缓冲区。
(6) 绘制操作
gl.drawArrays(gl.POINTS, 0, 1):绘制一个点。
顶点着色器只是指定了绘制的顶点,还需要指定顶点到底成点、成线还是成面,gl.drawArrays()就是这样一个函数,这里告诉WebGL系统应该绘制一个点。
3. 结果
最终的运行结果很简单,在Chrome打开HelloPoint1.html,页面显示了一个绘制一个点的窗口:
4. 参考
本来部分代码和插图来自《WebGL编程指南》。