在以前的文章裡,不管是繪製圖形,繪製點亦或者是改變色值,所有的內容都是靜態的。

webgl 裡,圖形的運動分為 平移、旋轉、縮放 三種類型。

接下來,我們會從零基礎開始,一點一點來深入瞭解圖形如何進行運動。

首先來從零開始瞭解下圖形的平移

1. 圖形平移

首先我們來看如何實現圖形的平移操作。

平移的操作就是將圖形的原始座標加上對應的移動距離。首先來看下平移的實現

const vertexShaderSource = "" +
"attribute vec4 apos;" + // 定義一個座標
"uniform float x;" + // 處理 x 軸移動
"uniform float y;" + // 處理 y 軸移動
"void main(){" +
" gl_Position.x = apos.x + x;" +
" gl_Position.y = apos.y + y;" +
" gl_Position.z = 0.0;" + // z軸固定
" gl_Position.w = 1.0;" +
"}";
const fragmentShaderSource = "" +
"void main(){" +
" gl_FragColor = vec4(1.0,0.0,0.0,1.0);" +
"}"; // initShader已經實現了很多次,本次就不再贅述了
const program = initShader(gl,vertexShaderSource,fragmentShaderSource); const buffer = gl.createBuffer();
const data = new Float32Array([
0.0,0.0,
-0.5,-0.5,
0.5,-0.5,
]); gl.bindBuffer(gl.ARRAY_BUFFER,buffer);
gl.bufferData(gl.ARRAY_BUFFER,data,gl.STATIC_DRAW); const aposlocation = gl.getAttribLocation(program,'apos');
const xlocation = gl.getUniformLocation(program,'x');
const ylocation = gl.getUniformLocation(program,'y'); gl.vertexAttribPointer(aposlocation,2,gl.FLOAT,false,0,0);
gl.enableVertexAttribArray(aposlocation); let x = 0.0;
let y = 0.0;
function run () {
gl.uniform1f(xlocation,x += 0.01);
gl.uniform1f(ylocation,y += 0.01); gl.drawArrays(gl.TRIANGLES,0,3);
// 使用此方法實現一個動畫
requestAnimationFrame(run)
}
run()

解釋:

  • 首先宣告一個變數 x 和變數 y ,用來處理 x軸 和 y軸 的座標。這裡使用的是 uniform 變數,因為平移的操作對於圖形上的所有頂點都有影響。
  • 通過 gl_Position.[xyzw] 來分別設定 x、y、z、w 的值。用於改變圖形位置。
  • 使用 gl.uniform1f 來為 x 和 y 賦值
  • 使用 requestAnimationFrame 實現一個緩動動畫。方便觀察效果。
  • 其他的操作,緩衝區,繪製,賦值,啟用,

可以看到,這樣處理圖形移動的話很好理解,但是因為一個移動,我們聲明瞭兩個 uniform 變數來實現。並且分開設定的 xyz 座標,非常的不方便。

所以,在處理webgl變換(平移、縮放、旋轉)的時候,通常使用矩陣來實現。接下來就來看看,如何使用矩陣實現圖形的平移。

2. 平移矩陣

推導平移矩陣的步驟:

  • 獲取平移前後的圖形座標(三維)
  • 計算平移前後的差值
  • 帶入到平移矩陣
  • 處理圖形頂點
  • 獲得平移後的圖形

2.1 平移矩陣的推導

首先讓我們來看一幅圖片。

這幅圖片的意義就是我們將橙色的三角形移動到藍色虛線三角形處。

移動之後的藍色虛線三角形的三個座標分別為

  • x’ = x + x1
  • y' = y + y1
  • z' = z + z1
  • w=1 齊次座標為1

2.2 獲得平移矩陣

webgl 中,通常使用矩陣來實現圖形變換。下面我們來看看矩陣如何表示。

左側是平移之前的原始座標,中間的是一個平移矩陣,經過兩者相乘,可以得到一個平移之後的座標。

現在我們來看下平移矩陣如何計算得出

首先通過上述圖片中的矩陣我們來得到幾個方程式。用左側的列分別乘矩陣的行,可以得到一下公式

  • ax + by + cz + w = x'
  • ex + fy + gz + h = y'
  • ix + jy + kz + l = z'
  • mx + ny + oz + p = w'

公式合併:

第一節 裡的四個方程式和第二節裡的四個方程式合併,可以得到如下結果:

  • ax + by + cz + w = x + x1':只有當 a = 1,b = c = 0, w = x1 的時候,等式左右兩邊成立
  • ex + fy + gz + h = y + y1':只有當 f = 1, e = g = 0, h = y1 的時候,等式左右兩邊成立
  • ix + jy + kz + l = z + z1':只有當 k = 1,i = j = 0, l = z1 的時候,等式左右兩邊成立
  • mx + ny + oz + p = 1':只有當 m = n = o = 0, p = 1 的時候,等式左右兩邊成立

經過上述方程式,可以得到一個平移的矩陣:

| 1 0 0 x |

| 0 1 0 y |

| 0 0 1 z |

| 0 0 0 1 |

之後將平移矩陣和原始座標相乘,就可以得到平移之後的座標。

3. 矩陣實戰

來看看使用矩陣如何處理圖形的平移。

第一步,建立著色器原始碼
const vertexShaderSource = "" +
"attribute vec4 apos;" +
"uniform mat4 mat;" + // 建立一個 uniform 變數,代表平移矩陣
"void main(){" +
" gl_Position = mat * apos;" + // 矩陣與原始座標相乘
"}";
const fragmentShaderSource = "" +
"void main(){" +
" gl_FragColor = vec4(1.0,0.0,0.0,1.0);" +
"}";
第二步,建立平移矩陣
let Tx = 0.1;    //x座標的位置
let Ty = 0.1; //y座標的位置
let Tz = 0.0; //z座標的位置
let Tw = 1.0; //差值
const mat = new Float32Array([
1.0,0.0,0.0,0.0,
0.0,1.0,0.0,0.0,
0.0,0.0,1.0,0.0,
Tx,Ty,Tz,Tw,
]);

這裡可以看到,使用的矩陣和我們推匯出來的矩陣不太一樣,推導的平移矩陣裡 xyzw 位於矩陣的右側,現在是位於矩陣的底部,這是為什麼呢?

這是因為在 webgl 中,矩陣的使用需要按照 左上右下 的對角線做一次翻轉。所以使用的矩陣,xyzw 位於底部

第三步,繪製一個三角形
const program = initShader(gl,vertexShaderSource,fragmentShaderSource);
const aposlocation = gl.getAttribLocation(program,'apos');
const data = new Float32Array([
0.0,0.0,
-.3,-.3,
.3,-.3
]); const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER,buffer);
gl.bufferData(gl.ARRAY_BUFFER,data,gl.STATIC_DRAW); gl.vertexAttribPointer(aposlocation,2,gl.FLOAT,false,0,0);
gl.enableVertexAttribArray(aposlocation); gl.drawArrays(gl.TRIANGLES,0,3); // 第五步的時候會重寫
第四步,獲取矩陣變數,給矩陣賦值
const matlocation = gl.getUniformLocation(program,'mat');
gl.uniformMatrix4fv(matlocation,false,mat);

這裡使用 gl.uniformMatrix4fv 來給矩陣賦值。

第五步,新增緩動動畫
function run () {
Tx += 0.01
Ty += 0.01
const mat = new Float32Array([
1.0,0.0,0.0,0.0,
0.0,1.0,0.0,0.0,
0.0,0.0,1.0,0.0,
Tx,Ty,Tz,Tw,
]);
gl.uniformMatrix4fv(matlocation,false,mat);
gl.drawArrays(gl.TRIANGLES,0,3); // 使用此方法實現一個動畫
requestAnimationFrame(run)
}
run()

4. 完整程式碼

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<canvas id="webgl" width="500" height="500"></canvas>
<script>
const gl = document.getElementById('webgl').getContext('webgl');
const vertexShaderSource = "" +
"attribute vec4 apos;" +
"uniform mat4 mat;" +
"void main(){" +
" gl_Position = mat * apos;" +
"}";
const fragmentShaderSource = "" +
"void main(){" +
" gl_FragColor = vec4(1.0,0.0,0.0,1.0);" +
"}"; const program = initShader(gl,vertexShaderSource,fragmentShaderSource);
const aposlocation = gl.getAttribLocation(program,'apos');
const matlocation = gl.getUniformLocation(program,'mat'); const data = new Float32Array([
0.0,0.0,
-.3,-.3,
.3,-.3
]);
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER,buffer);
gl.bufferData(gl.ARRAY_BUFFER,data,gl.STATIC_DRAW); gl.vertexAttribPointer(aposlocation,2,gl.FLOAT,false,0,0);
gl.enableVertexAttribArray(aposlocation); let Tx = 0.1; //x座標的位置
let Ty = 0.1; //y座標的位置
let Tz = 0.0; //z座標的位置
let Tw = 1.0; //差值
function run () {
Tx += 0.01
Ty += 0.01
const mat = new Float32Array([
1.0,0.0,0.0,0.0,
0.0,1.0,0.0,0.0,
0.0,0.0,1.0,0.0,
Tx,Ty,Tz,Tw,
]);
gl.uniformMatrix4fv(matlocation,false,mat);
gl.drawArrays(gl.TRIANGLES,0,3); // 使用此方法實現一個動畫
requestAnimationFrame(run)
}
run()
function initShader(gl,vertexShaderSource,fragmentShaderSource){
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(vertexShader,vertexShaderSource);
gl.shaderSource(fragmentShader,fragmentShaderSource); gl.compileShader(vertexShader);
gl.compileShader(fragmentShader); const program = gl.createProgram(); gl.attachShader(program,vertexShader);
gl.attachShader(program,fragmentShader) gl.linkProgram(program);
gl.useProgram(program);
return program;
}
</script>
</body>
</html>

至此,通過矩陣控制圖形移動就全部實現完成了。

今天的分享就到這兒了,

Bye~


更多精彩內容,定製禮品圖書贈送,高薪職位內推,微信搜尋關注“豆皮範兒”