Wayne Wang Stay Curious

WebGL 的关键概念和基本操作

Mar. 13, 2015 - Shang Hai

最近正在接触WebGL,正好是个机会来重温一下OpenGL中的诸多概念和可编程渲染管线中的基本操作。只是来梳理下基础的东西,没有太多细节,就当是温习加概括。实际上,这也是一个机会,重新学习一下。

首先,什么是WebGL?它是一组在浏览器中使用的三维模型渲染接口,通过这组接口开发者可以直接操作显卡的硬件资源,从而开发出更真实、更丰富的渲染效果。从实现上来看,WebGL是OpenGL ES(OpenGL ES又是OpenGL的一个子集)的一个子集,是专门为HTML Canvas元素设计的一套渲染接口。从应用上来看,它变的越来越流行且被重视,因为基于云运算的Web应用正在成为趋势,由此也促使3D图形渲染从桌面软件到Web端的转移,于是WebGL的出现和发展,为这个目的提供了很好的技术支持。另外一个额外的好处是,基于Web的应用可以很快的嵌入移动客户端。

WebGL的一些基本概念

状态机(State Machine)

WebGL作为OpenGL的子集,也是一个有限状态机

并且,系统创建时,会设置一组初始的状态。 WebGL(OpenGL)就是这样一组基于有限状态机的的软件接口。大部分的接口都是在操作系统的某个状态,比如当前使用的顶点属性数据,纹理对象,Shader等等,都是状态变量,一旦通过接口设置了它们的值,就会在下面的运行中一直起作用,直到被再一次改变。所以,WebGL(OpenGL)的接口可以大致分成这样几种类别,

WebGL(OpenGL)被设计成有限状态机是有其理由的,

渲染上下文( Context)

WebGL (OpenGL) Context可以理解为一个对象,或者一个大的结构体,又或者是WebGL (OpenGL)实现的一个实例,它包含了所有的状态变量,提供了所有的接口 。

对象资源( Resource)

OpenGL早在1.0版本是没有对象概念的,那时的接口都是用来操作全局状态的。不过在接下来的版本里,OpenGL逐渐的加入了各种对象。比如,1.1引入了Texture Object,1.5引入了VBO,2.0引入了Shader,等等。 OpenGL对象就是一个OpenGL资源或者结构,它由一组状态变量表示。所有对象的操作都需要首先和当前活动的Context绑定,于是对象的状态就相应的映射为Context的状态,然后接下来对这些状态的改变都会保存到该对象中,并且使用这些状态的操作会直接从该对象中读取。这样的接口设计也正是OpenGL“状态机”理念的体现。 这里只看下WebGL中的对象资源,

WebGL渲染的基本操作

从这一段开始,来看看要把3D模型渲染到网页中,都涉及了哪些WebGL的基本操作。在开始前,先确认浏览器 是否支持WebGL。 可以通过http://www.doesmybrowsersupportwebgl.com/ 来测试。

初始化WebGL Context

先创建一个HTML Canvas元素,作为WebGL渲染的画布。然后通过这个画布得到和它关联的WebGL context,

var canvas = document.getElementById("canvas_id");
gl = canvas.getContext("experimental-webgl");

使用“experimental-webgl”作为context type,是由其历史原因的,而且WebGL目前也是一直发展的状态。不过,如果浏览器同时也支持“webgl”字符串作为context type,那么这两者是完全一样的,会返回同一个context。 另外,当通过canvas得到context时候,浏览器应该做如下的操作,

设置Buffer

模型数据传给GPU去渲染,需要设置相应的Buffer。常听到的两种用途的buffer,比如,Vertex Buffer Object和Index Buffer Object。简单来说就是,一个是用来传递所有顶点数据的,一个是用索引告诉WebGL哪些顶点在一起表示一个基本元素的(三角片,线或点)。基本步骤包括,

设置Shader

WebGL需要用户提供Vertex和Fragment Shader。简单来说,

然后,使用以上的Shader涉及的WebGL调用包括,

关联Buffer和Shader

当顶点数据和Shader都准备好了,那么下一步就是要告诉Shader怎么使用顶点数据。

gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionBuffer);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute,
vertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);

一样的,在操作buffer之前,要确定先绑定它到当前context中,然后通过vertexAttribPointer告诉Shader,索引为vertexPositionAttribute的变量从绑定buffer里以如下的格式读取数据,首先buffer的元素被解析为float类型,每个顶点对应了itemSize个元素,最后两个参数(0,0),表示从buffer的起点开始读数据,并且相邻两个顶点数据间没有间隔。最后,中间的那个参数表示如果当前buffer是以整形格式来存储的,但需要以float格式来访问,那么是否需要把数据标准化。如果需要,那么WebGL在读取前会把有符号整数映射到[-1,1]或者无符号整数映射到[0,1],如果不需要,那么直接读取这个整数作为浮点数来对待。简单来说,经过上面的设置后,在Shader程序里,顶点坐标就能正确的从这个buffer里取得。

设置必要的状态

到这里,大部分的数据已经设置好了,不过距离正确的渲染,还差点。下面给出了其他一些主要状态的设置。

Draw

最终,终于,到了实际调用渲染的步骤了。这就是状态机的使用方式,把该设置的都弄好了,WebGL才能调动GPU做正确的渲染。

到此为止,如果一切顺利,那么画布上就应该渲染出想要的图形和效果。另外需要注意的,上述的draw调用后,会马上在画布上展示效果,并且WebGL的帧缓存会被清空。如果要改变这以默认行为,可以在创建context的时候指定属性preserveDrawingBuffer为true。不过,这一改动可能会引起明显的性能下降。

Reference

真心感谢,这强大开放的网络世界,可以查阅各种资料和学习各种知识。

彩蛋

自己的一点感悟,当人们在接触一样新技术时候,定会感到很多的疑惑。而消除疑惑的一种很好的方式就是:了解其基本原理,弄清其体系结构,熟悉其常用业务流程。更进一步的,如果想成为专家,那就研读其使用手册。

Fork me on GitHub