在上一篇文章,算是很簡略地介紹了 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() 取得 g_ShaderProgram 這個 shader program 中,名稱為「aVertexPosition」的 attribute 位址(vertex shader 的)
- 使用 enableVertexAttribArray() 來 enable g_VertexPositionAttribute 這個 vertex attribute array
- 使用 createBuffer() 建立一個新的 buffer
- 使用 bindBuffer() 將 g_VertexPositionBuffer 設定為目前使用的 buffer
- 建立三個 vertex 的位置資訊,分別為 (0, 0.8, 0)、(-0.8, -0.8, 0)、(0.8, -0.8, 0)
- 將 vertices 的資料,寫到目前 bind 的 buffer 裡(也就是 g_VertexPositionBuffer)
而在這些需要的資料都建立完成後,就是 RunWebGL() 的最後,執行 main loop 的部分了∼
setInterval( display, 30 );
這行程式基本上就是透過 JavaScript 的計時器,每 30ms 去執行一次 display() 這個函式,來做畫面的定時更新了;display() 的程式碼內容,則如下所列:
function display()
{
g_WebGLContext.clear( g_WebGLContext.COLOR_BUFFER_BIT );
g_WebGLContext.bindBuffer( g_WebGLContext.ARRAY_BUFFER, g_VertexPositionBuffer );
g_WebGLContext.vertexAttribPointer( g_VertexPositionAttribute, 3, g_WebGLContext.FLOAT, false, 0, 0 );
g_WebGLContext.drawArrays( g_WebGLContext.TRIANGLES, 0, 3 );
}
而內容的話,主要就是先將現有畫面的 color 都清除後,再重新畫上的 vertex array 了∼
到了這邊,最簡單的 WebGL 程式也算完成了∼原始碼的部分,可以到這裡下載,基本上因為都寫在一起,所以也就只有一個 HTML 檔。
參考資料:
- Learning WebGL:《WebGL Lesson 1 – A triangle and a square》
不好意思打擾您了!想請問您, 要發展webGL除了需要有支援的瀏覽器來看結果之外, 還需要額外安裝其他的東西嗎?因為我下載了您的範例html檔後丟進google chrome內, 似乎沒有範例內的三角形顯示出來只有上方的 A simple webgl sample字樣及連結出現還煩請您提供解答, 謝謝
to Yves 您好,這是因為 WebGL 的語法有做過修改了。請將程式碼裡的 WebGLFloatArray 修改為 Float32Array,這樣應該就可以了。
非常感謝您確實修改後就能夠正確運行!不過WebGL_Box.html也會出現一些error如果要進行修改而執行, 是否要針對新的語法進行瞭解?那如何能夠取得新語法資訊?
to Yves 建議請參考 WebGL 官方的資料https://www.khronos.org/registry/webgl/specs/1.0/http://khronos.org/webgl/wiki/Main_Page
感謝您!請持續發表各類技術好文!謝謝
我写了一个小例子是画三角形和正方形的,但为什么只能看见画布,看不见图形?
to changtian 由於官方有做過語法上的修改,Heresy 這邊的範例目前應該已經不適用了。建議你試試看 WebGL 官方的教學:http://www.khronos.org/webgl/wiki/Tutorial
to heresy好的,正在看了,谢谢
heresy 你好 ^ ^~
我稍微研究了一下 WebGL,它本身似乎沒有辦法執行太過複雜的行為,是否需要搭配 CGI 或是其他的方式來實現應用
to CROMA
WebGL 本身只是用來做繪圖的,所以其他功能都必須要使用別的東西來輔助。
至於要使用 server-side 的動態網頁、或是 client-side 的 java script 就是要看需求了。