寫在前面
[上一篇筆記](/articles/glsl-es(opengl-es 著色器語言)-webgl 筆記 9/) 中,我們全面了解了 GLSL ES 的語法,必須掌握的 WebGL 基礎知識已經基本都有了(也就是說 API 沒了..),接下來的內容基本都是數學,而變換只是其中最淺顯的一部分
一.變換的數學原理
一句兩句說不清楚,例如 translate(平移)就是給圖像每一個點的坐標的各個分量添上一個 delta,點 (0, 3, 2) 沿 y 軸正方向移動 2 個單位長度,結果是 (0, 5, 2)。原理就是這麼簡單,說不清楚是因為牽扯到公式推導和矩陣表示,如下:
x' = x + deltaX; how? why? |1 0 0 tx|
y' = y + deltaY; ------------> |0 1 0 ty|
z' = z + deltaZ; |0 0 1 tz|
|0 0 0 1 |
三維坐標為什麼需要用 4x4 矩陣表示 translate 變換?這樣做有什麼好處?那 rotate 和 scale 呢?
感興趣可以自行摸索(搜索),完整過程是公式推導->齊次坐標->矩陣乘法->變換矩陣->CTM,理解了 CTM 後,就完全理解變換的數學原理了
P.S.太數學的東西暫時不想寫,但早晚會寫。所以這裡不展開變換矩陣,以後再說
二.WebGL 變換矩陣
變換矩陣不都長一個樣嗎?不不不,上一篇筆記中說了,GLSL ES 中的矩陣是列主序的,比如 translate 矩陣就得是這個樣子:
Matrix4.prototype.setTranslate = function(x, y, z) {
var e = this.elements;
e[0] = 1; e[4] = 0; e[8] = 0; e[12] = x;
e[1] = 0; e[5] = 1; e[9] = 0; e[13] = y;
e[2] = 0; e[6] = 0; e[10] = 1; e[14] = z;
e[3] = 0; e[7] = 0; e[11] = 0; e[15] = 1;
return this;
};
P.S.以上代碼摘自《WebGL 編程指南》源碼 cuon-matrix.js,API 風格用著很順手,所以..以後就直接用了
注意:WebGl 和 OpenGL 一樣,矩陣元素是按列主序存儲的,其實 CSS 中 transform: matrix(a,b,c,d,e,f) 也是按列主序的,但線性代數裡面一般習慣按行主序
三.矩陣庫
一個簡單的矩陣庫是必須的,至少要支持 3 種基本變換和矩陣乘法,當然最好有一套友好的(用著舒服的)API
《WebGL 編程指南》提供的矩陣庫 cuon-matrix.js 就不錯,小巧只提供最基本的功能,如果覺得不好用可以去找找其它矩陣庫,但不建議使用 Three.js,因為功能多而全,不利於我們深入了解 WebGL
以後的例子將使用 cuon-matrix.js,具體用法如下:
// 創建 4x4 矩陣,默認填充成單位陣
var m4 = new Matrix4();
// m4.elements === [
// 1, 0, 0, 0,
// 0, 1, 0, 0,
// 0, 0, 1, 0,
// 0, 0, 0, 1]
// 填充為平移矩陣,參數為 deltaX, deltaY, deltaZ
m4.setTranslate(0.25, 0.25, 0.0);
// m4.elements === [
// 1, 0, 0, 0,
// 0, 1, 0, 0,
// 0, 0, 1, 0,
// 0.25, 0.25, 0, 1]
// 在平移的基礎上旋轉
m4.rotate(30, 0, 0, 1); // 逆時針 30 度,(0, 0, 1) 是旋轉軸 z 軸
// m4.elements === [
// 0.8660253882408142, 0.5, 0, 0,
// -0.5, 0.8660253882408142, 0, 0,
// 0, 0, 1, 0,
// 0.25, 0.25, 0, 1]
// ...
// setXXX 就是填充
// xxx 就是求 CTM,即 m4 = m4 x new Matrix4().setXXX
也有很方便很實用的功能,比如:
// 複製
var newM4 = new Matrix4(oldM4);
// 矩陣乘法,乘積放在 m4_1 中
m4_1.multiply(m4_2);
// 矩陣轉置
m4.transpose();
四.基本變換
變換是對圖像上的每一個點的坐標做同樣的運算,因為片元是根據頂點生成的,所以我們只需要對頂點做運算,也就是說只需要修改頂點著色器,如下:
// 頂點著色器源程序
var vsSrc = 'attribute vec4 a_Position;' +
'uniform mat4 u_transformMatrix;' +
'void main() {' +
'gl_Position = u_transformMatrix * a_Position;' + // 設置坐標
// 一點小把戲,為了把變換前的三角形顯示出來
'if (u_transformMatrix == mat4(0.0)) {gl_Position = a_Position;}' +
'}';
我們聲明瞭矩陣類型 uniform 變量 u_transformMatrix,用來接受變換矩陣,接下來要把值傳進去
首先要有一個變換矩陣,現做一個:
// 變換
var transformMatrix = new Matrix4();
// 先平移
transformMatrix.setTranslate(0.25, 0.25, 0.0); // 向右上平移
// 再旋轉
transformMatrix.rotate(30, 0, 0, 1); // 逆時針 30 度,(0, 0, 1) 是旋轉軸 z 軸
// 再縮放
transformMatrix.scale(0.5, 0.5, 1); // x, y 縮放一半,z 不變
這就折騰出了一個很複雜的變換矩陣,然後想辦法賦值給 uniform 變量,如下:
// 把變換矩陣傳遞給頂點著色器
var u_transformMatrix = gl.getUniformLocation(glUtil.program, 'u_transformMatrix');
gl.uniformMatrix4fv(u_transformMatrix, false, transformMatrix.elements);
給 mat 類型著色器變量賦值用到了 gl.uniformMatrix4fv,參數含義如下:
gl.uniformMatrix4fv(location, transpose, array)
---
transpose 在 WebGL 中只能為 false,表示矩陣是不是轉置矩陣,WebGL 沒有提供矩陣轉置的方法
array 類型化數組,4x4 矩陣按列主序存放
最後再 draw 出來,就完成了
五.動畫
動畫就是不斷擦除重繪產生的視覺效果,比如旋轉就是通過旋轉角度不斷遞增繪製出來的,要保證流暢就要保證角度均勻遞增。那麼實現動畫的關鍵變成了保證均勻變化,而單純的 setInterval 就不行,所以需要加入時間控制,具體如下:
// 變換
var angle = 0;
var ROTATE_SPEED = 60; // 60 度/秒
var transformMatrix = new Matrix4();
var now;
var lastTime;
var delta = 0;
var u_transformMatrix = gl.getUniformLocation(glUtil.program, 'u_transformMatrix');
// draw
function draw() {
now = Date.now();
delta = (now - lastTime) * ROTATE_SPEED / 1000;
// 角度遞增
angle += delta;
angle %= 360;
// console.log(angle);
// 旋轉
transformMatrix.setRotate(angle, 0.0, 0.0, 1.0);
// 把旋轉矩陣傳遞給頂點著色器
gl.uniformMatrix4fv(u_transformMatrix, false, transformMatrix.elements);
// clear
gl.clear(gl.COLOR_BUFFER_BIT);
// 繪製變換後的三角形
gl.drawArrays(gl.TRIANGLES, 0, arrVtx.length / 2);
// 記錄時間
lastTime = now;
// 延遲遞歸
window.requestAnimationFrame(draw);
}
// 動畫
window.requestAnimationFrame(draw);
lastTime = Date.now();
旋轉速度是 度/秒,下一次繪製時轉過了多少度是根據時間算的,這樣就保證了旋轉角度均勻變化
注意:requestAnimationFrame 類似於 setTimeout/setInterval,區別是 requestAnimationFrame只在標籤頁處於激活狀態時才會生效,與 setTimeout/setInterval 不同,性能也要更高一些
P.S.在本例中當然看不出標籤頁非激活狀態動畫停止,因為我們是根據時間計算的角度,本來中間停了一段,但恢復激活的時候旋轉了一個大角度,把中間停的部分都追上了,css3 動畫內部原理也是這樣的:
CSS3 動畫在 Tab 切換回來的時候,動畫表現並不暫停;通過 Chrome frames 工具測試發現,Tab 切換之後,計算渲染繪製都停止,Tab 切換回來時似乎通過內置 JS 計算了動畫位置實現重繪,造成動畫不暫停的感覺
P.S.摘自 CSS3 動畫那麼強,requestAnimationFrame 還有毛線用? « 張鑫旭 - 鑫空間 - 鑫生活,關於 requestAnimationFrame 的更多信息可以查看這篇文章,很有價值
六.DEMO
包含上述代碼的完整的例子,請查看:
在 console 可以看到每一步變換後矩陣的值
七.總結
變換矩陣也叫模型矩陣,3D 中必不可少的 mvp 就是(Model Matrix、View Matrix、Projection Matrix)
至此,WebGL 2D 就結束了,仔細想想好像也沒學到什麼,大部分 API 都在 3D 那邊嗎?不是,大部分常用 API 我們都用過了,剩下的除了一點點有用的就只有一些很高級的功能(比如動態紋理)了,3D 場景幾乎全都是用矩陣"搓"出來的,沒什麼可用 API 了,沒有類似於 Camera.setPosition(0, 0, 3) 這樣的神奇方法
視角是什麼,是矩陣,光照是什麼,矩陣,那陰影呢,矩陣,霧化呢,說了都是矩陣。。。
參考資料
- WebGL 編程指南》
暫無評論,快來發表你的看法吧