本文大綱
- 矩陣和線性變換是什麼?
- webgl如何實現縮放和旋轉?
- 平移不是線性變換,那該怎麼辦?
- webgl如何實現平移?
今天的主菜是“矩陣”
在上一篇中我們已經實現了使用webgl繪製圖形這個小目標《前端圖形學從入門到放棄》001 畫一個三角形
今天我們來探討一個新的話題矩陣
我們都知道空間中的點我們可以用向量表示,例如二維平面中的點(1,1)就表示第一象限的點:
而多個點就能組成圖形,這也是上一篇文章中我們說過的。
實際生產中這些圖形往往並不會固定在畫面中不懂,例如我們可以對圖形進行旋轉,縮放,移動。
實際上這個過程就是將圖形的頂點組進行了旋轉,縮放,移動,成為了新的頂點組,再由新的頂點組繪製成新的圖形。
例如我們要將由點A(0,0),B(1,0),C(0,1)組成的三角形放大一倍,那麼我們很容易知道放大後的點ÂḂĆ的座標
Âx = Ax2 = 02 = 0
Ây = Ay2 = 02 = 0
Ḃx = Bx2 = 12 = 2
Ḃy = By2 = 02 = 0
Ćx = Cx2 = 02 = 0
Ćy = Cy2 = 12 = 2
數學家嫌這一番操作太過麻煩,而點又是可以寫成向量形式的,要是能把操作簡化成Â = M*A的形式就再好不過了,於是
⎡ 2 0 ⎤
 = ⎪ ⎪ * A
⎣ 0 2 ⎦
真是一頓操作猛如虎,一句不懂二百五
解剖矩陣
舉證代表了一種計算,如上我們使用了一個二維矩陣
⎡ A B ⎤
⎣ C D ⎦
與一個二維向量相乘,會得到一個新的二維向量,計算公式如下
⎡ A B ⎤ ⎡x⎤ = ⎡ A*x + B*y ⎤
⎣ C D ⎦ ⎣y⎦ ⎣ C*x + D*y ⎦
當然矩陣也不僅僅可以和向量相乘也可以和舉證相乘,矩陣也不僅僅可以是22,也可以是33,更可以是n*m(n代表行數,m代表列數)。
兩個矩陣可以相乘只需要,前一個矩陣的列數和後一個矩陣的函式相等即可。
例如nm的舉證可以和ml的矩陣相乘,得到n*l的矩陣。
至於計算方法不是本文討論的內容,推薦觀看3blue1brown的視訊。
縮放矩陣 與 旋轉矩陣
而上文我們看到的矩陣
⎡ 2 0 ⎤
⎣ 0 2 ⎦
就是一個把任意點放大兩倍的矩陣,更一般的,如果可以寫出縮放矩陣(n≠0)
⎡ n 0 ⎤ ⎡x⎤ = ⎡ n*x ⎤
⎣ 0 n ⎦ ⎣y⎦ ⎣ n*y ⎦
相比於縮放還有一種操作也很高頻,那就是旋轉。前面沒有提到,矩陣的變換是線性的。什麼叫做線性?也是是說同樣的操作(放大2倍)對A點產生的效果,和對B點產生的效果(放大2倍)是一樣的。
所以對於旋轉矩陣我們也可以找到特殊的點進行求解,從而得到普遍適用的矩陣
對於x軸上的點a,旋轉ø角後,可以用下圖描述
我們就得到了二維平面上的旋轉矩陣
⎡ cosø -sinø ⎤
⎣ sinø cosø ⎦
webgl和矩陣更配喲~
下面我們把矩陣和webgl結合起來,讓《前端圖形學從入門到放棄》001 畫一個三角形中我們實現的三角形可以旋轉與縮放
首先我們在頁面上新增兩個滑塊分辨實現旋轉與縮放
<canvas id="canvas" width="1000" height="1000"></canvas>
<div id="control">
<input type="range" value=".75" min=".5" max="1" step="0.01" id="scale" value="0">
<input type="range" value="0" min="0" max="360" step="0.01" id="rotate" value="0">
</div>
由於旋轉和縮放操作僅僅影響頂點位置,下面我們之需要修改頂點著色器即可:
<script id="vertex-shader-2d" type="notjs">
attribute vec2 vertPosition;
attribute vec3 vertColor;
varying vec3 fragColor;
// 額外申明兩個矩陣用於旋轉和縮放
uniform mat2 scaleMatrix;
uniform mat2 rotateMatrix;
void main() {
fragColor = vertColor;
// 把頂點座標與矩陣相乘,得到旋轉和縮放後的新頂點,傳給gl
gl_Position = vec4(scaleMatrix*rotateMatrix*vertPosition,0.0,1.0);
}
</script>
這兩個申明的變數也要在js中取出
...
var positionAttribLocation = gl.getAttribLocation(program, 'vertPosition');
var colorAttribLocation = gl.getAttribLocation(program, 'vertColor');
var scaleMatrix = gl.getUniformLocation(program, 'scaleMatrix');
var rotateMatrix = gl.getUniformLocation(program, 'rotateMatrix');
...
由於我們期望在滑動滑塊時,頁面實時變化,因此需要一個loop函式來完成這一切:
....
gl.useProgram(program);
gl.drawArrays(gl.TRIANGLES, 0, 6);
loop(gl, rotateMatrix, scaleMatrix);
}
...
loop函式:
var scaleNode = document.querySelector("#scale");
var rotateNode = document.querySelector("#rotate");
function loop(gl, rotateMatrix, scaleMatrix) {
var angle = rotateNode.value/180*Math.PI;
var scale = scaleNode.value;
var sin = Math.sin(angle);//旋轉角度正弦值
var cos = Math.cos(angle);//旋轉角度餘弦值
var myArr = new Float32Array([cos, -sin, sin, cos,]);
var scaleArr = new Float32Array([scale, 0, 0, scale,]);
gl.uniformMatrix2fv(rotateMatrix, false, myArr);
gl.uniformMatrix2fv(scaleMatrix, false, scaleArr);
gl.clearColor(0.75, 0.85, 0.8, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
requestAnimationFrame(function () {
loop(gl, rotateMatrix, scaleMatrix);
});
gl.drawArrays(gl.TRIANGLES, 0, 3);
}
大功告成:
教練我動不了了
不知道各位看官有沒有發現,在矩陣這套線性變化下,我們沒辦法做平移操作。因為作為原點的o(0,0)不論乘以什麼矩陣,結果都還是自己。但是平移操作是日常工作中極其常見的操作,不能平移甚至無法實現拖拽!
難道圖形學之路就此gg?
但天無絕人之路,只要零點不是零點我就可以移動它,對於二維平面,我可以把它看作三維世界中一個不過原點的平面,原本的(x,y)變為(x,y,1)
此時就可以實現平移
根據上文,我們已經瞭解的矩陣知識,不難寫出
而這種通過n+1維實現了n維線性變換外加移動操作的變換,就被稱為齊次變換
。
webgl和齊次變換更配喲~
下面我們繼續改造原有的webgl程式碼!
首先我們還需要加入兩個滑塊分別控制,圖形上下和左右運動
<div id="control">
...
<input type="range" value="0" min="-0.5" max="0.5" step="0.01" id="tranX">
<input type="range" value="0" min="-.5" max=".5" step="0.01" id="tranY">
</div>
由於齊次變換將所有的矩陣都升維了,我們需要改造定點著色器。
<script id="vertex-shader-2d" type="notjs">
...
// 將原本二維矩陣定義為三維
uniform mat3 scaleMatrix;
uniform mat3 rotateMatrix;
uniform mat3 transformMatrix;
void main() {
fragColor = vertColor;
vec3 v = rotateMatrix*scaleMatrix*transformMatrix*vec3(vertPosition,1.0);
// 由於我們之需要x,y把他們取出即可
gl_Position = vec4(v.xy,0.0,1.0);
}
</script>
由於矩陣從二維變為三維,取出的變數也需要重新定義為三維:
...
var trMatrix = gl.getUniformLocation(program,'transformMatrix');
var scaleMatrix = gl.getUniformLocation(program, 'scaleMatrix');
var rotateMatrix = gl.getUniformLocation(program, 'rotateMatrix');
...
// 獲取滑塊
var tranXNode = document.querySelector("#tranX");
var tranYNode = document.querySelector("#tranY");
// 修改loop函式
...
loop(gl, rotateMatrix, scaleMatrix,trMatrix);
}
function loop(gl, rotateMatrix, scaleMatrix,trMatrix) {
...
var myArr = new Float32Array([cos, sin, 0 , -sin, cos, 0,0,0,1]);
var scaleArr = new Float32Array([scale, 0, 0,0, scale,0,0,0,1]);
var tranArr = new Float32Array([1,0,0,0,1,0,tranXNode.value,tranYNode.value,1]);
// console.log(tranXNode.value);
gl.uniformMatrix3fv(rotateMatrix, false, myArr);
gl.uniformMatrix3fv(scaleMatrix, false, scaleArr);
gl.uniformMatrix3fv(trMatrix, false, tranArr);
....
大功告成:
下期預告
我想二維的世界,大家也膩了,下篇我們將進入三維世界,並說說光線是如何影響物體的