對於要繪製的對象,先平移再將之旋轉,與先旋轉將再之平移,結果是不同的:
因此,為了要能達到想要的轉換結果,就要留意轉換操作的順序,例如,在〈轉換矩陣〉中最後的範例,想要看到方塊逆時針轉動,必須先平移方塊,再將方塊繞著 Z 軸轉動,範例的程式片段是這麼撰寫的:
...
transformation = mat4.translate(transformation, 0.25, 0, 0); // 先平移
let i = 0;
function drawCube() {
i++;
renderer.uniformMatrix4fv('transformation',
mat4.zRotate(transformation, 0.025 * i) // 再旋轉
);
...
}
不過,程式碼可以使用這樣的順序來撰寫,是因為 translate
、scale
、xRotate
、yRotate
、zRotate
是這麼撰寫的:
translate(m, tx, ty, tz) {
return this.multiply(this.translation(tx, ty, tz), m);
},
scale(m, sx, sy, sz) {
return this.multiply(this.scaling(sx, sy, sz), m);
},
xRotate(m, radians) {
return this.multiply(this.xRotation(radians), m);
},
yRotate(m, radians) {
return this.multiply(this.yRotation(radians), m);
},
zRotate: function(m, radians) {
return this.multiply(this.zRotation(radians), m);
}
也就是,對於一個舊有的轉換矩陣 m
,想要進行轉換操作時,是將下一個轉換操作矩陣乘上 m
,這種寫法稱為前乘(Pre-Multiplication)或左乘(Left-Multiplication),也就是下個轉換操作矩陣在前,或者說是左邊。
這麼做的話,如果程式碼撰寫時是 translate
後 zRotate
,就會如上頭的說明的方式進行,然而,前乘並不是 OpenGL 的慣例,因為 WebGL 衍生自 OpenGL,WebGL 的矩陣處理程式庫,實際上也不會是採取前乘的方式。
WebGL 的矩陣處理程式庫,慣例上會依循 OpenGL,也就是採用後乘(Post-Multiplication)或右乘(Right-Multiplication),也就是對於一個舊有的轉換矩陣 m
,想要進行轉換操作時,是將下一個轉換操作矩陣會是放在後面,或者說是右邊。
由於矩陣乘法具有結合律,就數學公式本身的演算來說,例如 S * R * T * V
,前乘或後乘,不過是從後面往前算,或者是從前面往後算,結果都是相同的。
只不過,撰寫程式碼時總究需要個前後順序,而採用前乘時,操作頂點的順序正好符合程式碼上呼叫函式的順序,如〈轉換矩陣〉中的範例就是如此。
然而,如果打算採用後乘的慣例,translate
、scale
、xRotate
、yRotate
、zRotate
就必須改寫為:
translate(m, tx, ty, tz) {
return this.multiply(m, this.translation(tx, ty, tz));
},
scale(m, sx, sy, sz) {
return this.multiply(m, this.scaling(sx, sy, sz));
},
xRotate(m, radians) {
return this.multiply(m, this.xRotation(radians));
},
yRotate(m, radians) {
return this.multiply(m, this.yRotation(radians));
},
zRotate: function(m, radians) {
return this.multiply(m, this.zRotation(radians));
}
然而,這會造成一個結果,操作頂點的順序與程式碼撰寫上的順序是反過來的,例如想要完成同樣是逆時針轉動的話,就必須寫為:
let zRotation = mat4.create();
function drawCube() {
zRotation = mat4.zRotate(zRotation, 0.025);
let transformation = mat4.translate(zRotation, 0.25, 0, 0);
transformation = mat4.xRotate(transformation, 0.5);
transformation = mat4.yRotate(transformation, 0.5);
renderer.uniformMatrix4fv('transformation', transformation);
renderer.clear();
renderer.bindBuffer(GL.ARRAY_BUFFER, vertBuffer);
renderer.bufferSubData(GL.ARRAY_BUFFER, 0, geometry.verteices);
renderer.render(cube);
requestAnimationFrame(drawCube);
}
可以點選完整的範例網頁來看看結果,乍看之下撰寫程式碼時,順序似乎很不直覺,其實這是觀點不同,這時在建立轉換矩陣的過程,心智模型應想著數學公式上的右乘順序,不是頂點操作順序,也就是說,心裡想的要是 Rz * T * Rx * Ry
,而程式碼的順序對照的是由左至右的順序。
(某些程度上,感覺這就像是函數式設計,畢竟 3D 運算就是數學,你要想著這邊要完成一個什麼轉換矩陣,而不是如何對頂點做操作,也就是函數式設計上常說的,宣告 What,而不是編寫 How。)
當然,若你堅持前乘而不是後乘,也不能說錯,只不過不符合 OpenGL/WebGL 的慣例下,在必須與其他工具或程式庫結合時,而它們採用的是後乘的慣例,會遇上些困擾就是了。