转自博客:http://viml.nchc.org.tw/blog/paper_info.php?CLASS_ID=1&SUB_ID=1&PAPER_ID=166
在上一篇文章,算是很简略地介绍了 WebGL 以及目前能让 WebGL 正常运作的浏览器;而这一篇,就是来写一个最简单的 WebGL 程序了~在这个例子里,就是单纯地在黑底的框框内,画出一个白色的三角型(如右图);基本上是以类似 2D 的模式在画,也不会考虑到 3D 的投影。
不过要先强调的是,要写 WebGL 的话,基本上应该要了解:
JavaScript 与网页的 DOM 的操作
OpenGL ES 2.0 的程序架构
如果对 OpenGL ES 2.0 完全没碰过的话,至少也要对 OpenGL 3.0 或是 OpenGL 2.x + GLSL 有概念才行。
如果都没有的话,建议先去稍微研究一下这些东西,对于学习 WebGL 会比较好。
而对于一个 WebGL 的程序,Heresy 会把他分为三个部份:
HTML 部分,包含要绘制内容的 canvas 组件
WebGL 的 JavaScript 程序部分
WebGL 程序所需使用的 GLSL 程序的部分(包含 vertex shader 和 fragment shader)
首先,先讲最简单的 HTML 的部分。由于这部分除了网页本身的内容外,就只是单纯地需要额外配置一个 <canvas>,来指定 WebGL 的区域,所以其实相当地单纯。下方就是一个很简单的例子:
<body onload="RunWebGL();"> <canvas id="canvas_object" width="500" height="500"></canvas></body>这样写的话,就是会在网页里产生一个 id 是「canvas_object」、大小是 500 x 500 的画布,可以用来呈现 WebGL 的绘制结果。
另外,这个网页也会在读取完成后,就自动执行 RunWebGL() 这个 JavaScript 的函式、开始执行 WebGL 的程序;这个函式的性质接近一般 C/C++ 程序的 main(),也就是前面所提的第二部分。他的内容如下:
function RunWebGL(){
// get WebGL Context
var canvas = document.getElementById( "canvas_object" );
g_WebGLContext = canvas.getContext( "experimental-webgl" ); // setup viewport
g_WebGLContext.viewport(0, 0, canvas.width, canvas.height); // set clear color
g_WebGLContext.clearColor( 0.0, 0.0, 0.0, 1.0 ); // create shader and data
CreateShader();
CreateData(); // main loop
setInterval( drawScene, 30 );
}
而在这边比较特别的,就是在编写 WebGL 程序时,必须先透过 DOM 来取得要使用的 Canvas、并透过他产生 WebGL rendering context,也就是函式一开始标示成黄色的区块。
其中,g_WebGLContext 是一个全域变量,是用来指向 WebGL rendering context 的。而在程序上,就是先透过 DOM 找到 id 为「canvas_object」的组件,再透过 canvas 的 getContext() 来取得 WebGL 的 rendering context;不过要稍微注意的是,目前是使用「experimental-webgl」,但是以后等到 WebGL 正式版的时候,应该会改成使用「webgl」。
而由于这是一个很简单的范例,所以在取得 WebGL 的 Context 后,就只有简单的设定 viewport 和 clear color,其它都使用 OpenGL ES 的默认值,然后就开始建立所需的 shader program 和对象数据、然后进入 main loop 了~
由于 OpenGL 3.0 / OpenGL ES 2.0 基本上已经将 fixed pipeline 的东西丢了,所以连同在 WebGL 环境里,都是一定要自己编写 Vertex Shader 和 Fragment Shader 的!
而在 WebGL 里,每一个 shader 程序,都是一个独立的 <script>、藉由不同的 id 和 type 来做区隔;id 基本上就是自己取的、用来识别用的名称,而 type 则有「x-shader/x-vertex」和「x-shader/x-fragment」两种。
下面就是一个简单的 vertex shader 的范例,他基本上不会做任何坐标的转换、投影,只会直接把每一个 vertex 的位置信息转换为 vec4 的型态,继续往下传:
<script id="vs_01" type="x-shader/x-vertex">
attribute vec3 aVertexPosition;
void main(void)
{
gl_Position = vec4( aVertexPosition, 1 );
}
</script>
而下方则是一个简单的 fragment shader,他会把所有的 fragment 的颜色都填为白色:
<script id="fs_01" type="x-shader/x-fragment">
void main(void)
{ gl_FragColor = vec4( 1.0, 1.0, 1.0, 1.0 );
}
</script>
在上面这样写了之后,基本上就是写好了最单纯的 vertex shader 和 fragment shader 了。
而要使用这两个 shader 程序,在 JavaScript 程序的部分还需要先透过 DOM 的架构,把他们的程序代码读取出来成一个字符串,这部分 Heresy 是把他写成一个函式:
function getShaderSource( id )
{ // get shader script element
var shaderScript = document.getElementById( id );
if( !shaderScript ) return null; // get shader program string
var str = ""; var childNode = shaderScript.firstChild;
while( childNode )
{
if( childNode.nodeType == childNode.TEXT_NODE )
str += childNode.textContent;
childNode = childNode.nextSibling;
}
return str;}
这边的程序,会先找到指定 id 的 element,然后依序地把他里面的 text node 的数据读出来,并且累加到 str 这个字符串里,最后再传出来。
而在取得 shader 程序的程序代码之后,还需要再以 OpenGL 对 shader 的处理程序,进行编译、连结等动作;这边也就是在 RunWebGL() 中呼叫到的 CreateShader() 函式了~
1: function CreateShader()
2: {
3: // create vertex shader
4: var vsSource = getShaderSource( "vs_01" );
5: var vertexShader = g_WebGLContext.createShader( g_WebGLContext.VERTEX_SHADER );
6:
7: // compile vertex shader
8: g_WebGLContext.shaderSource( vertexShader, vsSource );
9: g_WebGLContext.compileShader( vertexShader );
10: if( !g_WebGLContext.getShaderParameter( vertexShader, g_WebGLContext.COMPILE_STATUS ) )
11: alert( g_WebGLContext.getShaderInfoLog( vertexShader ) );
12:
13: // create fragment shader
14: var fsSource = getShaderSource( "fs_01" );
15: var fragmentShader = g_WebGLContext.createShader( g_WebGLContext.FRAGMENT_SHADER );
16:
17: // compile fragment shader
18: g_WebGLContext.shaderSource( fragmentShader, fsSource );
19: g_WebGLContext.compileShader( fragmentShader );
20: if( !g_WebGLContext.getShaderParameter( fragmentShader, g_WebGLContext.COMPILE_STATUS ) )
21: alert( g_WebGLContext.getShaderInfoLog( fragmentShader ) );
22:
23: // create shader program
24: g_ShaderProgram = g_WebGLContext.createProgram();
25: g_WebGLContext.attachShader( g_ShaderProgram, vertexShader );
26: g_WebGLContext.attachShader( g_ShaderProgram, fragmentShader );
27: g_WebGLContext.linkProgram( g_ShaderProgram );
28:
29: if( !g_WebGLContext.getProgramParameter( g_ShaderProgram, g_WebGLContext.LINK_STATUS ) )
30: {
31: alert( "Shader 初始化失败" );
32: return;
33: }
34:
35: g_WebGLContext.useProgram( g_ShaderProgram );
36: }
在这个函式里,就是各别针对 vertex shader 和 fragment shader,依序地读取 shader 的程序代码、建立 shader 对象、编译 shader 了;而在这两个 shader object 都建立完成后,就再将这两个 shader object attach 到 shader program(全域变量 g_ShaderProgram)上,并且 link、使用这个包含了 vs_01 以及 fs_01 的 shader program 了。而如果都没出问题的话,在执行完 CreateShader() 后,也就完成了 WebGL 的 shader 的配置了。
接下来,就是 CreateData()、也就是建立要画的三角型的部分了;这部分也是相当简单,就是建立一个 vertex array,里面放三个只有位置信息的 vertex 了~
function CreateData()
{
g_VertexPositionAttribute = g_WebGLContext.getAttribLocation( g_ShaderProgram, "aVertexPosition" );
g_WebGLContext.enableVertexAttribArray( g_VertexPositionAttribute ); // create scene data
g_VertexPositionBuffer = g_WebGLContext.createBuffer();
g_WebGLContext.bindBuffer( g_WebGLContext.ARRAY_BUFFER, g_VertexPositionBuffer );
var vertices = [ 0.0, 0.8, 0.0, -0.8, -0.8, 0.0, 0.8, -0.8, 0.0 ];
g_WebGLContext.bufferData( g_WebGLContext.ARRAY_BUFFER, new WebGLFloatArray(vertices), g_WebGLContext.STATIC_DRAW);
}
在这段程序里,主要的动作是:
透过 getAttribLocation() 取得