WebGL 範例:旋轉立體方塊

在上一篇的《WebGL 簡單範例》裡,已經示範了如何用 WebGL 來畫出一個平面的三角形;實際上,該範例主要是要展示如何建立基本的 WebGL 環境,在 render 的部分並沒有真正考慮到 3D 空間、而是以 2D 的方式來畫的,所以應該也是最簡單的 WebGL 例子了∼

image 在知道了 WebGL 最基本的操作後,接下來就是要真的來畫一個 3D 的物體了!這邊會以《WebGL 簡單範例》的程式來做改寫,並以 WebGL Wiki 的 Spinning Box Tutorial 為主要參考資料;不過由於 Heresy 在這邊主要只是先加入 3D 環境的矩陣計算,會先忽略掉 texture(貼圖)和 lighting(打光)的部分,所以結果看起來會和 WebGL 的範例差滿多的(會更接近該教學頁面最下方、沒有貼圖但是有 lighting 版本的範例)。

以官方的 Spinning Box 範例來說,他使用了兩個屬於 Apple 的 JavaScript 函式庫,來簡化 WebGL 的程式開發。其中一個是 utils3d.js(網址),他的功能包含了 WebGL 的初始化、Shader 的讀取與建立、texture / object 的讀取或產生;而另一個則是 CanvasMatrix.js(網址),提供了 WebGL 一般會用到矩陣計算功能。

而 Heresy 在這邊只會用到 CanvasMatrix.js 來做矩陣計算,並不會用到 utils3d.js 的函式;如果連 CanvasMatrix.js 都不想用的話,就是要自己寫相關的矩陣計算演算法了∼

接下來還是看程式的部分吧∼這個 WebGL 程式在 HTML 的部分,基本上是和上一個範例完全相同,所以就不贅述了。而主程式 RunWebGL() 的部分,內容則如下:

function RunWebGL()
{
  // try to get WebGL Context
  try
  {
    var canvas = document.getElementById( "canvas_object" );
    gWGL = canvas.getContext( "experimental-webgl" );
  }
  catch( e )
  {
    alert( "WebGL 初始化失敗" );
    return;
  }

  // setup viewport
  gWGL.viewport( 0, 0, canvas.width, canvas.height );

  // set clear color and depth
  gWGL.clearColor( 0, 0, 0, 1 );
  gWGL.clearDepth( 10000 );

  // set OpenGL ES
  gWGL.enable( gWGL.DEPTH_TEST );
  gWGL.enable( gWGL.BLEND );
  gWGL.blendFunc( gWGL.SRC_ALPHA, gWGL.ONE_MINUS_SRC_ALPHA );

  // create shader and data
  CreateShader();
  CreateData();

  // get the matrix location in shader
  gWGL.u_mvpMatrixLoc = gWGL.getUniformLocation( gWGL.ShaderProgram, "u_Matrix");
  gWGL.mvpMatrix = new CanvasMatrix4();

  // set projection matrix
  gWGL.ProjectionMatrix = new CanvasMatrix4();
  gWGL.ProjectionMatrix.lookat( 0, 0, 7, 0, 0, 0, 0, 1, 0 );
  gWGL.ProjectionMatrix.perspective( 30, canvas.width / canvas.height, 1, 10000 );

  // main loop
  setInterval( display, 30 );
}

這部分的程式和之前的差異,就是上方標記成黃色的區塊,主要是:

  • 加入 depth 以及 blending 的參數設定。
  • 取得 shader 程式中 matrix 的位址
  • 加入了 gWGL.ProjectionMatrix、也就是 projection matrix 的計算。這邊是設定 look at 以及 perspective 投影。

而 vertex shader 主要就是加入對於每個 vertex 的投影計算了∼除了加入 u_Matrix 這個 uniform 變數外,vertex attribute 也多加了 color 的部分;不過由於這個範例沒有考率 lighting,所以也不需要各 vertex 的 normal、也沒有對 color 進行計算。而這部分的程式則是變成:

<script id="vs_02" type="x-shader/x-vertex">
  uniform mat4 u_Matrix;

  attribute vec4 vPosition;
  attribute vec4 vColor;

  varying vec4 v_Color;

  void main()
  {
    gl_Position = u_Matrix * vPosition;
    v_Color = vColor;
  }
</script>

Fragment shader 的部分,則是將 vertex shader 計算(實際上也沒算就是了)完的顏色直接輸出。

<script id="fs_02" type="x-shader/x-fragment">
  varying vec4 v_Color;

  void main()
  {
    gl_FragColor = v_Color;
  }
</script>

在完成 shader 程式之後,則是 CreateData()、產生所需要的方塊的部分:

function CreateData()
{
  gWGL.RenderObject = {};

  // create vertex
  gWGL.enableVertexAttribArray( gWGL.getAttribLocation( gWGL.ShaderProgram, "vPosition" ) );
  gWGL.RenderObject.oVertex = gWGL.createBuffer();
  gWGL.bindBuffer( gWGL.ARRAY_BUFFER, gWGL.RenderObject.oVertex );
  var vertices = new WebGLFloatArray(
        [  1, 1, 1,  -1, 1, 1,  -1,-1, 1,   1,-1, 1,    // v0-v1-v2-v3 front
           1, 1, 1,   1,-1, 1,   1,-1,-1,   1, 1,-1,    // v0-v3-v4-v5 right
           1, 1, 1,   1, 1,-1,  -1, 1,-1,  -1, 1, 1,    // v0-v5-v6-v1 top
          -1, 1, 1,  -1, 1,-1,  -1,-1,-1,  -1,-1, 1,    // v1-v6-v7-v2 left
          -1,-1,-1,   1,-1,-1,   1,-1, 1,  -1,-1, 1,    // v7-v4-v3-v2 bottom
           1,-1,-1,  -1,-1,-1,  -1, 1,-1,   1, 1,-1 ]   // v4-v7-v6-v5 back
        );
  gWGL.bufferData( gWGL.ARRAY_BUFFER, vertices, gWGL.STATIC_DRAW );
  gWGL.vertexAttribPointer( 0, 3, gWGL.FLOAT, false, 0, 0 );

  // create color
  gWGL.enableVertexAttribArray( gWGL.getAttribLocation( gWGL.ShaderProgram, "vColor" ) );
  gWGL.RenderObject.oColor = gWGL.createBuffer();
  gWGL.bindBuffer( gWGL.ARRAY_BUFFER, gWGL.RenderObject.oColor );
  var colors = new WebGLUnsignedByteArray(
        [  0, 0, 1, 1,   0, 0, 1, 1,   0, 0, 1, 1,   0, 0, 1, 1,     // v0-v1-v2-v3 front
           1, 0, 0, 1,   1, 0, 0, 1,   1, 0, 0, 1,   1, 0, 0, 1,     // v0-v3-v4-v5 right
           0, 1, 0, 1,   0, 1, 0, 1,   0, 1, 0, 1,   0, 1, 0, 1,     // v0-v5-v6-v1 top
           1, 1, 0, 1,   1, 1, 0, 1,   1, 1, 0, 1,   1, 1, 0, 1,     // v1-v6-v7-v2 left
           1, 0, 1, 1,   1, 0, 1, 1,   1, 0, 1, 1,   1, 0, 1, 1,     // v7-v4-v3-v2 bottom
           0, 1, 1, 1,   0, 1, 1, 1,   0, 1, 1, 1,   0, 1, 1, 1 ]    // v4-v7-v6-v5 back
        );
  gWGL.bufferData( gWGL.ARRAY_BUFFER, colors, gWGL.STATIC_DRAW );
  gWGL.vertexAttribPointer( 1, 4, gWGL.UNSIGNED_BYTE, false, 0, 0 );

  // create index
  gWGL.RenderObject.oIndex = gWGL.createBuffer();
  gWGL.bindBuffer( gWGL.ELEMENT_ARRAY_BUFFER, gWGL.RenderObject.oIndex );
  var indices = new WebGLUnsignedByteArray(
        [  0, 1, 2,   0, 2, 3,    // front
           4, 5, 6,   4, 6, 7,    // right
           8, 9,10,   8,10,11,    // top
          12,13,14,  12,14,15,    // left
          16,17,18,  16,18,19,    // bottom
          20,21,22,  20,22,23 ]   // back
        );
  gWGL.bufferData( gWGL.ELEMENT_ARRAY_BUFFER, indices, gWGL.STATIC_DRAW );
  gWGL.RenderObject.numIndices = indices.length;
}

這部分的程式主要分成三段,除了上一個範例裡有的 vertex 的位置資料外,還要另外產生顏色資料以及 index array 的資料;而同時,也要將這些資料傳到 GPU 裡。

最後,則是 display() 的部分了∼

function display()
{
  // Clear the canvas
  gWGL.clear( gWGL.COLOR_BUFFER_BIT | gWGL.DEPTH_BUFFER_BIT );

  // Compute a model/view matrix.
  gWGL.mvpMatrix.makeIdentity();
  gWGL.mvpMatrix.rotate( currentAngle, 0, 1, 0 );
  gWGL.mvpMatrix.rotate( 20, 1, 0, 0 );

  // Coompute the model-view * projection matrix
  gWGL.mvpMatrix.multRight( gWGL.ProjectionMatrix );

  // pass the matrix into shader
  gWGL.uniformMatrix4fv( gWGL.u_mvpMatrixLoc, false, gWGL.mvpMatrix.getAsWebGLFloatArray() );

  // Draw the cube
  gWGL.drawElements( gWGL.TRIANGLES, gWGL.RenderObject.numIndices, gWGL.UNSIGNED_BYTE, 0 );

  // Finish up.
  gWGL.flush();

  // update angle
  currentAngle  = incAngle;
  if( currentAngle > 360 )
    currentAngle -= 360;
}

這部分主要的差異,就是多加入了當下的 model view matrix 和 projecttion matrix 的計算、並將計算後的結果傳入 vertex shader,接著再透過 drawElements() 將 index array 形式的方塊資料畫出來了。

而另外為了讓這個方塊可以旋轉,所以在最後還要加上旋轉角度的計算;這邊的做法是每畫一次後,就會把現在的角度(currentAngle)再加上 incAngle,然後用以計算下一次的 model view matrix。

最後的結果網頁,可以在這邊下載

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *