上一篇文章:WebGL圖形編程實戰【2】:動態著色 × 紋理貼圖技術揭秘
倉庫地址:github…、gitee…
矩陣操控
矩陣變換
回到前面關于平移縮放、旋轉的例子當中,我們是通過改變傳遞進去的xy的值來改變的。
在進行基礎變換的時候,涉及到多個變量且變化頻率高,在實際的webgl應用開發過程中,其復雜程度更令人發指,故引入了數學工具—矩陣。(具有規律性的二維數組)計算機實際上是一個固執的老頑童,它最喜歡有規律性質的東西,所以這樣一拍即合,計算機技術與數學理論達成情人關系(webgl內置了矩陣系統)。本堂課的內容就是將變換過程轉換成矩陣進行表示。
矩陣動畫推演
下面先展示了在數學方面上對xyz軸的數據進行相對應的變化,而后是在矩陣層面上變化的值
平移
x1 = x + Tx;
y1 = y + Ty;
z1 = z + Tz;
旋轉
x1 = x * cos(angle) - y * sin(angle);
y1 = y * cos(angle) + x * sin(angle);
縮放
x1 = x * Sx;
y1 = y * Sy;
z1 = z * Sz;
矩陣運算案例
加(減)法
只有同型矩陣之間才可以進行加(減)法運算,將兩個矩陣相同位置的元相加即可,m行n列的兩個矩陣相加(減)后得到一個新的m行n列矩陣,例如
1,2,3,4 + 3,4,5,6 = 4,6,8,10
2,3,4,5 + 2,3,4,5 = 4,6,8,10
數乘
數乘即將矩陣乘以一個常量,矩陣中的每個元都與這個常量相乘,例如
1,2,3 * 3 = 3,6,9
乘法
兩個矩陣的乘法僅當第一個矩陣的列數和另一個矩陣的行數相等時才能定義
1,2,3 * 3,4 = 1*3+2*2+3*3 1*4+2*3+3*4 = 16 22
2,3,4 * 2,3 = 2*3+3*2+4*3 2*4+3*3+4*4 = 24 333,4 =
glMatrix 常用API
glMatrix API 官網…
補充:在使用glMatrix-0.9.6.min.js和npm上的glMatrix.js,API是有一定區別的,下面是用最新的glMatrix
創建矩陣
返回類型是Float32Array,矩陣的元素個數是16,也就是一個4x4的矩陣。
const matrix = mat4.create();
投影矩陣
生成具有給定邊界的透視投影矩陣。far傳遞null/undefined/no值將生成無限投影矩陣。
mat4.perspective(out, fovy, aspect, near, far);
mat4.perspective(matrix, 45, 4 / 3, 1, 100);
名稱 | 類型 | 描述 |
---|---|---|
out | mat4 | mat4截頭體矩陣將被寫入 |
fovy | number | 垂直視場(弧度) |
aspect | number | 寬高比。通常視口寬度/高度 |
near | number | 截頭體的近界 |
far | number | 截頭體的遠邊界,可以為null或Infinity |
矩陣相乘
將兩個mat 4相乘,參數一為目標矩陣,參數二三為要相乘的矩陣。
const matrix = mat4.create();
const matrix1 = mat4.create();
let target = [];
mat4.multiply(target, matrix, matrix1);
單位矩陣
將一個矩陣設置為單位矩陣。單位矩陣是一個4x4的矩陣,其元素值都為0,除了主對角線元素值都為1。
mat4.identity(matrix);
矩陣變化(平移、旋轉、縮放)
平移縮放旋轉都傳遞了兩個矩陣參數,其中第一個參數是目標矩陣,第二個參數是變化矩陣(不做變換就和第一個傳一樣的值)。第三個參數是變化參數(平移的xyz值、縮放的xyz值、旋轉的弧度和xyz值)。
mat4.translate(matrix, matrix, [10, 10, 10]);
mat4.scale(matrix, matrix, [1, 2, 1]);
mat4.rotate(matrix, matrix, 45, [0, 0, 1]);
WebGL+矩陣變化
整體邏輯如下mermaid圖
修改著色器,添加一個中間矩陣,然后把中間矩陣傳遞給著色器。版本為0.9.6
const vertexString = `attribute vec4 a_position;uniform mat4 u_formMatrix;void main(){gl_Position = u_formMatrix * a_position;gl_PointSize = 40.0;}`;
在js當中通過glMatrix.js進行矩陣變換,然后用webGL的uniformMatrix4fv方法傳遞給著色器。
uniformMatrix4fv 為 uniform 變量指定矩陣值
- 參數一:是指定待修改 uniform 變量的存儲位置
- 參數二:指定是否轉置矩陣
- 參數三:序列值
function animate() {const middleMat4 = mat4.create();mat4.identity(middleMat4);mat4.translate(middleMat4, [0, 0.5, 0]);mat4.rotate(middleMat4, 0.5 * Math.PI, [0, 0, 1]);mat4.scale(middleMat4, [0.5, 0.5, 0.5]);let uniformMatrix = webGL.getUniformLocation(program, 'u_formMatrix');webGL.uniformMatrix4fv(uniformMatrix, false, middleMat4);
}
案例:WebGL時鐘效果
和上面webGL+矩陣變化的代碼一樣,在頂點著色器當中傳入一個u_formMatrix用來計算,隨后在initBuffer當中重新設置頂點坐標用來繪制三角帶,如下
let triangleArray = [0, -0.1, 0, 1.0,0, 0.4, 0, 1.0,0.01, 0.4, 0, 1.0,0.01, -0.1, 0, 1.0
];
webGL.drawArrays(webGL.TRIANGLE_FAN, 0, 4);
之后就是矩陣變換的代碼,用rotate選擇的方法去改變矩陣,以秒鐘為例,那就是一秒鐘走2*Math.PI弧度除以60,這樣一分鐘60秒剛好一圈,那么代碼就是這樣實現的。
const second = new Date().getSeconds();
const rotate = 2 * Math.PI / 60 * second;
const middleMat4 = mat4.create();
mat4.identity(middleMat4);
mat4.rotate(middleMat4, -rotate, [0, 0, 1]);
let uniformMatrix = webGL.getUniformLocation(program, 'u_formMatrix');
webGL.uniformMatrix4fv(uniformMatrix, false, middleMat4);
隨后分鐘小時的代碼就一樣了,分鐘和秒鐘的計算是一樣的,時針就是將60換成12即可。最后就是添加一個setInterval每隔一秒調用一次。注意:在這里繪制的時候,只需要在秒針繪制的時機先clear一遍,分針和時針的時候直接調用drawArray繪制即可,不用再次clear
完整代碼地址:https://github.com/lizuoqun/visualThree/blob/main/webGL/animate/clockTriangle.html
三維世界
視點 & 視線
觀察者的位置就是視點,從視點出發,觀察者能看到的就是視線。
調整視口觀察三維對象
前面的案例研究了xy軸的二維平面對象,而三維就是給z設置了對應的值,而改變觀察的方向就能看到不同的結果。這里以三角形繪制為例,參考前面的代碼實現,先繪制三個三角形,并且修改其z軸的值,在三個不同的平面上
let triangleArray = [0.0, 0.5, -0.4, 1.0,-0.5, -0.5, -0.4, 1.0,0.5, -0.5, -0.4, 1.0,0.5, 0.4, -0.2, 1.0,-0.5, 0.4, -0.2, 1.0,0.0, -0.6, -0.2, 1.0,0.0, 0.4, 0.0, 1,-0.4, -0.4, 0.0, 1,0.4, -0.4, 0.0, 1
];
給每一個層級的三角形設置一下不同的顏色,上面可以區分有三個層級,分別位于z的-0.4、-0.2、0.0三個位置上,修改這個數組對象,就代表著一個點對象有八個數值,分別是x、y、z、1、r、g、b、a,將顏色值和點綁定在一起
let triangleArray = [0.0, 0.5, -0.4, 1.0, 0.4, 1.0, 0.4, 1,-0.5, -0.5, -0.4, 1.0, 0.4, 1.0, 0.4, 1,0.5, -0.5, -0.4, 1.0, 0.4, 1.0, 0.4, 1,0.5, 0.4, -0.2, 1.0, 1.0, 0.4, 0.4, 1,-0.5, 0.4, -0.2, 1.0, 1.0, 0.4, 0.4, 1,0.0, -0.6, -0.2, 1.0, 1.0, 0.4, 0.4, 1,0.0, 0.4, 0.0, 1, 0.4, 0.4, 1.0, 1,-0.4, -0.4, 0.0, 1, 0.4, 0.4, 1.0, 1,0.4, -0.4, 0.0, 1, 0.4, 0.4, 1.0, 1
];
修改著色器代碼,這里使用varying變量,先將顏色值傳遞給頂點著色器,再透傳給片元著色器中,根據varying變量的值,設置顏色。
// 頂點著色器
const vertexString = `attribute vec4 a_position;attribute vec4 a_color;varying vec4 color;void main(){gl_Position = a_position;color = a_color;}`;// 片元著色器
const fragmentString = `precision mediump float;varying vec4 color;void main(){gl_FragColor = color;}`;
進行賦值,在js當中通過vertexAttribPointer將顏色值傳遞給著色器當中的a_color變量,同時因為數組的內容改了,之前是4個數據一個點現在是8個數據一個點,所以設置點的代碼也要調整
// 設置點坐標
webGL.vertexAttribPointer(aPosition, 4, webGL.FLOAT, false, 8 * 4, 0);
// 調整不同層級三角形的顏色
let aColor = webGL.getAttribLocation(program, 'a_color');
webGL.enableVertexAttribArray(aColor);
webGL.vertexAttribPointer(aColor, 4, webGL.FLOAT, false, 8 * 4, 4 * 4);
改變視角:在頂點著色器當中添加u_formMatrix,使用lookAt方法設置視點,并傳遞給著色器中
let modelView = mat4.create();
mat4.identity(modelView);
modelView = mat4.lookAt(modelView, [0, -0.5, 0.2], [0, 0, 0], [0, 1, 0]);
let uniformMatrix = webGL.getUniformLocation(program, 'u_formMatrix');
webGL.uniformMatrix4fv(uniformMatrix, false, modelView);
lookAt(out, eye, center, up): 使用給定的眼睛位置、焦點和上方向軸生成注視矩陣
參數說明
名稱 | 類型 | 描述 |
---|---|---|
out | mat4 | 截頭體矩陣將被寫入 |
eye | ReadonlyVec3 | 視口位置 |
center | ReadonlyVec3 | 觀看者正在觀看的點 |
up | ReadonlyVec3 | 指定上方向 |
疊加矩陣變化
可以再創建一個新的矩陣進行旋轉90度,之后將視口矩陣和旋轉矩陣乘積重新賦值也就完成了疊加矩陣變化。
let ModelMatrix = mat4.create();
mat4.identity(ModelMatrix);
mat4.rotate(ModelMatrix, ModelMatrix, Math.PI / 2, [0, 0, 1]);let ViewMatrix = mat4.create();
mat4.identity(ViewMatrix);
ViewMatrix = mat4.lookAt(ViewMatrix, [0, 0, 0.3], [0, 0, 0], [0, 1, 0]);
let mvMatrix = mat4.create();
mat4.multiply(mvMatrix, ViewMatrix, ModelMatrix);
// 最后將這個進行賦值
let uniformMatrix = webGL.getUniformLocation(program, 'u_formMatrix');
webGL.uniformMatrix4fv(uniformMatrix, false, mvMatrix);
原本都是正向上的三角形就變成了橫的了
可視范圍(正射投影)
在上一個案例當中,當視點在極右或極左的位置時,三角形會缺少一部分。原因是沒有指定可視范圍,即實際觀察得到的區域邊界
兩類常用的可視空間:
- 長方體可視空間,也稱盒狀空間,由正射投影產生
- 四棱錐/金字塔可視空間,由透視投影產生
可視空間由前后兩個矩形表面確定,分別稱近裁剪面(near)和遠裁剪面(far)
改變視口可視域:ortho(out, left, right, bottom, top, near, far):生成具有給定邊界的正交投影矩陣
名稱 | 類型 | 描述 |
---|---|---|
out | mat4 | 輸出矩陣 |
left | number | 截頭體的左邊界 |
right | number | 右邊界 |
bottom | number | 底邊界 |
top | number | 上邊界 |
near | number | 近 |
far | number | 遠 |
改變視口可視域,通過ortho方法設置可視域范圍,這樣他的坐標系取值就變成了canvas的坐標系,繪制圖形的坐標值也要進行相對應的調整
let ProjMatrix = mat4.create();
mat4.identity(ProjMatrix);
mat4.ortho(ProjMatrix, -100, 100, -100, 100, near, far); //修改可視域范圍let uniformMatrix = webGL.getUniformLocation(program, 'u_formMatrix');
webGL.uniformMatrix4fv(uniformMatrix, false, ProjMatrix);
可視空間(透視投影)
在正射投影的可視空間中,不管三角形與視點的距離是遠是近,它有多大,那么畫出來就有多大。為了打破這條限制,使用透視投影可視空間,它將使場景具有深度性
設置透視投影:perspective(out, fovy, aspect, near, far):生成具有給定邊界的透視投影矩陣
名稱 | 類型 | 描述 |
---|---|---|
out | mat4 | 輸出矩陣 |
fovy | number | 垂直方向的視野角度(上截面與下截面的角度) |
aspect | number | 縱橫比(寬高比) |
near | number | 近 |
far | number | 遠 |
創建一個透視投影矩陣,并賦值給uniformMatrix,去修改傳入的角度的時候可以觀察到變化
let ProjMatrix = mat4.create();
mat4.identity(ProjMatrix);
//角度小,看到的物體大,角度大,看到的物體小。
mat4.perspective(ProjMatrix, 160 * Math.PI / 180, 1, 1, 100); //修改可視域范圍
正射投影和透視投影的區別
- 在透視投影下,產生的三維場景看上去更是有深度感,更加自然,因為我們平時觀察真實世界用的也是透視投影。在大多數情況下,比如三維射擊類游戲中,我們都應當采用透視投影。
- 正射投影的好處是用戶可以方便地比較場景中物體( 比如兩個原子的模型)
的大小,這是因為物體看上去的大小與其所在的位置沒有關系。在建筑平面圖等技術繪圖的相關場合,應當使用這種投影。