1. 程式人生 > >【筆記】《WebGL程式設計指南》學習-第4章高階變換與動畫基礎(1-平移然後旋轉))

【筆記】《WebGL程式設計指南》學習-第4章高階變換與動畫基礎(1-平移然後旋轉))

目標:實現三角形的 先旋轉再平移
結果:
這裡寫圖片描述

本節要使用一個專為本書編寫的矩陣函式庫。有了矩陣函式庫,進行如“平移,然後旋轉”這種複合的變換就很簡單了。

矩陣變換庫:cuon-matrix.js

在 OpenGL 中,我們無需手動指定變換矩陣的每個元素,因為 OpenGL 提供了一系列有用的函式來幫助我們穿件變換矩陣。比如,通過呼叫 glTranslate ()函式並傳入在X、Y、Z軸上的平移的距離就可以建立一個平移矩陣。

這裡寫圖片描述

遺憾的是,WebGL 沒有提供類似的矩陣函式,如果想要使用他們,你就得自己編寫,或者使用其他人已經編寫好的。因為矩陣函式非常有用,所以書的作者專門編寫了一個JS函式庫 cuon-matrix.js。該函式允許你通過Open GL 中類似的方法建立變換矩陣。雖然這個庫是專門為本書編寫的,你也可以在自己的程式中使用它。

Matrix4 是矩陣庫提供的新型別,顧名思義,Matrix4 物件表示一個 4x4 的矩陣。該物件內部使用型別化陣列 Floated2Array 來儲存矩陣的元素。

我們來利用 Matrix4 和其相關的方法來把 RotatedTriangles_Matrix 程式重寫一遍,找找使用這個矩陣函式庫的感覺。

示例程式

這個示例程式相比於第3章中的 RotatedTriangle_Matrix.js,唯一的改動發生在新步驟上:建立變換矩陣,並將變換矩陣傳給頂點著色器。

在 RotatedTriangle_Matrix.js 中,我們是這樣創鍵變換矩陣的:

var radian = Math
.PI * ANGLE / 180.0; // Convert to radians var cosB = Math.cos(radian), sinB = Math.sin(radian); var xformMatrix = new Float32Array([ cosB, sinB, 0.0, 0.0, -sinB, cosB, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ]);

在本例中,你需要利用一個 Matrix 物件比呼叫其setRotate()方法計算出旋轉矩陣來重寫這部分:

  var ANGLE = 90.0;
    //為旋轉矩陣建立 Matrix4 物件
var xformMatrix = new Matrix4(); //將 xformMatrix 設定為旋轉矩陣 xformMatrix.setRotate(ANGLE, 0, 0, 1); //將旋轉矩陣傳輸給頂點著色器 var u_xformMatrix = gl.getUniformLocation(gl.program, 'u_xformMatrix'); if(u_xformMatrix < 0){ console.log("Failed to get the storage location of u_xformMatrix"); return; } gl.uniformMatrix4fv(u_xformMatrix, false, xformMatrix.elements);

可見,建立變換矩陣然後傳給 uniform 變數的基本步驟,在這兩個示例程式中是相同的:使用 new 操作符新建一個 Matrix4 物件,就像我們使用 new 操作符建立一個 Array 物件或 Date 物件一樣。我們新建了一個 Matrxi4物件 xformMatrix,並呼叫其 setRotate()方法把自身設為計算出的旋轉矩陣。

setRotate()函式接受的引數是旋轉角(角度制而非弧度制)和旋轉軸(x, y, z)。旋轉軸(x, y, z)表示旋轉是繞著從原點(0, 0, 0)指向(x, y, z)的軸進行的。第3章說過,如果旋轉角度值是正值,那麼旋轉就是逆時針方向的。本例中的旋轉是繞Z軸進行的,所以旋轉軸設為(0, 0, 1):

    xformMatrix.setRotate(ANGLE, 0, 0, 1);

類似的,如果是繞 X 軸旋轉的,那麼旋轉軸的三個分量x = 1, y = 0, z = 0;如果是繞 Y 軸旋轉的,則 x = 0, y = 1, z = 0。一旦你在 xformMatrix 變數中設定好了旋轉矩陣,剩下的任務只是用相同的 gl.uniformMatrix4fv()方法將旋轉矩陣傳入頂點著色器。注意,你不能將 Matrix4 物件直接作為最後一個引數傳入,因為該方法的最後一個引數必須是型別化陣列。你應當使用 Matrix4 物件的 elements 屬性訪問儲存矩陣元素的型別化陣列:

gl.uniformMatrix4fv(u_xformMatrix, false, xformMatrix.elements);

Matrix4 物件所支援的方法和屬性如下表所示:

這裡寫圖片描述
這裡寫圖片描述

從上表中科建,Matrix4 物件有兩種方法:一種方法的名稱中含有字首 set,另一種則不含。包含 set 字首的方法會根據引數計算出變換矩陣,然後將變換矩陣寫入到自身中;而不含 set 字首的方法,會先根據引數計算出變換矩陣,然後將自身與剛剛計算得到的變換矩陣相乘,然後把最終得到的記過再寫入到 Matrix4 物件中。

如上表所示, Matrix4 物件的方法十分強大且靈活。更重要的是,有了這些函式,進行變換就會變得輕而易舉。比如,如果本例中你不希望對三金星進行旋轉,而希望對它進行平移,你只需要重寫一下內容:

xformMatrix.setTranslate(0.5, 0.5, 0.0);

複合變換

現在,你對 Matrix4 物件應該有了基本的瞭解,下面就來看看如何利用 Matrix4 將兩次變換組合起來,即進行一次平移,再進行一次旋轉。

顯然,示例中包含了以下兩種變換:

  1. 將三角形沿著 X 軸平移一段距離。
  2. 在此基礎上,旋轉三角形。

這裡寫圖片描述

講解了這麼多,我們可以先寫下第1條中的座標方程式。

這裡寫圖片描述

然後對<平移後的座標>進行旋轉

這裡寫圖片描述

當然你也可以分佈計算這兩個等式,但更好的方法是,將等式1代入到等式2中,把兩個等式組合起來:

這裡寫圖片描述

這裡

這裡寫圖片描述

等於

這裡寫圖片描述

最後,我們可以在JS中計算<旋轉矩陣>x<平移矩陣>,然後將得到的矩陣傳入頂點著色器。像這樣,我們就可以把多個變換複合起來了。一個模型可能經過了多次變換,將這些變換全部複合成一個等效的變換,就得到了 模型變換,或稱 建模變換,相應地,模型變換的矩陣稱為 模型矩陣

再來複習一下矩陣的乘法:
這裡寫圖片描述

如上所示,將兩個 3x3 矩陣 A 與 B 相乘的結果如下:

這裡寫圖片描述

上式是兩個 3x3 矩陣相乘的結果,實際用到的模型矩陣是 4x4 的矩陣。然後要注意,矩陣相乘的次序很重要,A*B 的結果並不一定等於 B*A。

如你所料,cuon-matrix.js 中的 Matrix4 物件支援矩陣乘法。下面就來看一下如何使用 Matrix4 物件進行矩陣乘法,從而將多個變換複合起來,實現先平移,然後旋轉。

RotatedTranslatedTriangle.js

//頂點著色器程式
var VSHADER_SOURCE =
    'attribute vec4 a_Position;'+
    'uniform mat4 u_ModelMatrix;'+
    'void main(){'+
    'gl_Position = u_ModelMatrix * a_Position;'+
    '}';

//片元著色器程式
var FSHADER_SOURCE=
    'void main(){'+
    'gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);'+
    '}';

function main() {
    //獲取canvas元素
    var canvas = document.getElementById("webgl");
    if(!canvas){
        console.log("Failed to retrieve the <canvas> element");
        return;
    }

    //獲取WebGL繪圖上下文
    var gl = getWebGLContext(canvas);
    if(!gl){
        console.log("Failed to get the rendering context for WebGL");
        return;
    }

    //初始化著色器
    if(!initShaders(gl,VSHADER_SOURCE,FSHADER_SOURCE)){
        console.log("Failed to initialize shaders.");
        return;
    }

    //設定頂點位置
    var n = initVertexBuffers(gl);
    if (n < 0) {
        console.log('Failed to set the positions of the vertices');
        return;
    }

    //計算模型矩陣
    var ANGLE = 60.0; //旋轉角
    var Tx = 0.5; //平移距離
    //為旋轉矩陣建立 Matrix4 物件
    var modelMatrix = new Matrix4();
    modelMatrix.setRotate(ANGLE, 0, 0, 1);
    modelMatrix.translate(Tx, 0, 0);

    //將旋轉矩陣傳輸給頂點著色器
    var u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix');
    if(u_ModelMatrix < 0){
        console.log("Failed to get the storage location of u_ModelMatrix");
        return;
    }

    gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);

    //指定清空<canvas>顏色
    gl.clearColor(0.0, 0.0, 0.0, 1.0);

    //清空<canvas>
    gl.clear(gl.COLOR_BUFFER_BIT);

    //繪製三個點
    gl.drawArrays(gl.TRIANGLES, 0, n);
}

function initVertexBuffers(gl) {
    var vertices = new Float32Array([
        0.0, 0.3, -0.3, -0.3, 0.3, -0.3
    ]);
    var n=3; //點的個數

    //建立緩衝區物件
    var vertexBuffer = gl.createBuffer();
    if(!vertexBuffer){
        console.log("Failed to create thie buffer object");
        return -1;
    }

    //將緩衝區物件儲存到目標上
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);

    //向快取物件寫入資料
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

    var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
    if(a_Position < 0){
        console.log("Failed to get the storage location of a_Position");
        return -1;
    }

    //將緩衝區物件分配給a_Postion變數
    gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);

    //連線a_Postion變數與分配給它的緩衝區物件
    gl.enableVertexAttribArray(a_Position);

    return n;
}

最關鍵的兩行,我們計算了<旋轉矩陣>x<平移矩陣>:

modelMatrix.setRotate(ANGLE, 0, 0, 1);
    modelMatrix.translate(Tx, 0, 0);

我們首先呼叫了包含 set 字首的方法 setRotate(),傳入的引數用以計算旋轉矩陣,並寫入 mpdelMatrix。接下來,我們呼叫了不帶 set 字首的方法 translate(),意思就是,先計算出一個平移矩陣,然後用原先儲存在 modelMatrix 變數中的矩陣乘以這個新計算平移矩陣,將得到的結果寫回 modelMatrix中。由於在第一步之後,modelMatrix 已經包含了一個旋轉矩陣,那麼經過了這一步,modelMatrix中的矩陣就是<旋轉矩陣>x<平移矩陣>了。

你可能回注意到,“先平移後旋轉”的順序與構造模型矩陣<旋轉矩陣>x<平移矩陣>的順序是相反的,這是因為變換矩陣最終要與三角形的三個頂點的原始座標向量相乘。

最後,我們把矩形矩陣傳遞給頂點著色器中的 u_ModelMatrix 變數,並如常將平移和旋轉後的紅色三角形。

這裡寫圖片描述