WebGL 簡單範例

image上一篇文章,算是很簡略地介紹了 WebGL 以及目前能讓 WebGL 正常運作的瀏覽器;而這一篇,就是來寫一個最簡單的 WebGL 程式了∼在這個例子裡,就是單純地在黑底的框框內,畫出一個白色的三角型(如右圖);基本上是以類似 2D 的模式在畫,也不會考慮到 3D 的投影。

不過要先強調的是,要寫 WebGL 的話,基本上應該要了解:

  1. JavaScript 與網頁的 DOM 的操作
  2. OpenGL ES 2.0 的程式架構
    如果對 OpenGL ES 2.0 完全沒碰過的話,至少也要對 OpenGL 3.0 或是 OpenGL 2.x GLSL 有概念才行。

如果都沒有的話,建議先去稍微研究一下這些東西,對於學習 WebGL 會比較好。

而對於一個 WebGL 的程式,Heresy 會把他分為三個部份:

  1. HTML 部分,包含要繪製內容的 canvas 元件
  2. WebGL 的 JavaScript 程式部分
  3. 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);
}

在這段程式裡,主要的動作是:

  1. 透過 getAttribLocation() 取得 g_ShaderProgram 這個 shader program 中,名稱為「aVertexPosition」的 attribute 位址(vertex shader 的)
  2. 使用 enableVertexAttribArray() 來 enable g_VertexPositionAttribute 這個 vertex attribute array
  3. 使用 createBuffer() 建立一個新的 buffer
  4. 使用 bindBuffer() 將 g_VertexPositionBuffer 設定為目前使用的 buffer
  5. 建立三個 vertex 的位置資訊,分別為 (0, 0.8, 0)、(-0.8, -0.8, 0)、(0.8, -0.8, 0)
  6. 將 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 檔。

參考資料:

10 thoughts on “WebGL 簡單範例”

  1. 不好意思打擾您了!想請問您, 要發展webGL除了需要有支援的瀏覽器來看結果之外, 還需要額外安裝其他的東西嗎?因為我下載了您的範例html檔後丟進google chrome內, 似乎沒有範例內的三角形顯示出來只有上方的 A simple webgl sample字樣及連結出現還煩請您提供解答, 謝謝

  2. to Yves 您好,這是因為 WebGL 的語法有做過修改了。請將程式碼裡的 WebGLFloatArray 修改為 Float32Array,這樣應該就可以了。

  3. 非常感謝您確實修改後就能夠正確運行!不過WebGL_Box.html也會出現一些error如果要進行修改而執行, 是否要針對新的語法進行瞭解?那如何能夠取得新語法資訊?

  4. to Yves 建議請參考 WebGL 官方的資料https://www.khronos.org/registry/webgl/specs/1.0/http://khronos.org/webgl/wiki/Main_Page

  5. 我写了一个小例子是画三角形和正方形的,但为什么只能看见画布,看不见图形?

  6. to changtian 由於官方有做過語法上的修改,Heresy 這邊的範例目前應該已經不適用了。建議你試試看 WebGL 官方的教學:http://www.khronos.org/webgl/wiki/Tutorial

  7. heresy 你好 ^ ^~

    我稍微研究了一下 WebGL,它本身似乎沒有辦法執行太過複雜的行為,是否需要搭配 CGI 或是其他的方式來實現應用

  8. to CROMA

    WebGL 本身只是用來做繪圖的,所以其他功能都必須要使用別的東西來輔助。
    至於要使用 server-side 的動態網頁、或是 client-side 的 java script 就是要看需求了。

發佈回覆給「CROMA」的留言 取消回覆

發佈留言必須填寫的電子郵件地址不會公開。