到目前為止,在設置座標資訊時,總是基於裁剪空間而設置,然而這並不方便,你也許會想要自訂一個空間,例如一個 X、Y、Z 分別為 100、100、100 的空間,原點位於空間的中心,三個軸各被畫分為 100 個單位,物件的大小以每個單位來設置,然後,有個方式可以自動將你自訂的空間投影片裁剪空間。
其實在〈使用 attribute 變數〉做過類似的事,只不過那時是從 Canvas 繪圖座標投影至裁剪空間,因為當時是 2D 投影至 3D,將 Z 設為 0 罷了,現在我們來將之擴充為 3D 投影至 3D,並且可以任意設置邊界。
首先定義你的自訂空間需要的幾個值,left
是左邊界值,right
是右邊界值,top
是上邊界值,bottom
是下邊界值,near
是近面距離,far
是遠面距離:
接下來,找出自訂空間的中心點:
midX = (left + right) / 2
midY = (top + bottom) / 2
midZ = (near + far) / 2
在自訂空間中的一點 (x, y, z),相對於空間的中心點之座標為:
x' = x - midX
y' = y - midY
z' = z - midZ
可以用矩陣的方式將之表達出來:
為了要能對應至裁剪空間 -1.0 ~ 1.0 的範圍,必須對座標進行縮放:
裁剪空間 x = sX * x' = 2 / (right - left) * x'
裁剪空間 y = sY * y' = 2 / (top - bottom) * y'
裁剪空間 z = sZ * z' = 2 / (far - near) * z'
也就是使用矩陣表示的話會是:
矩陣計算後,將全部算式展開結果,就可以得到正交投影(Orthographic Projection)矩陣:
因此,可以在 mat4
中加上個正交投影矩陣的方法實作:
const mat4 = {
....
ortho(left, right, top, bottom, near, far) {
const rl = 1 / (right - left);
const tb = 1 / (top - bottom);
const fn = 1 / (far - near);
return [
2 * rl, 0, 0, 0,
0, 2 * tb, 0, 0,
0, 0, 2 * fn, 0,
-(left + right) * rl, -(top + bottom) * tb, -(far + near) * fn, 1
];
}
};
這個正交投影矩陣要怎麼用呢?首先,來改一下著色器:
<script id="vertex-shader" type="x-shader/x-vertex">
uniform mat4 projection;
uniform mat4 transformation;
attribute vec3 position;
attribute vec4 color;
varying vec4 fColor;
void main(void) {
gl_Position = projection * transformation * vec4(position.x, position.y, position.z, 1.0);
fColor = color;
}
</script>
<script id="fragment-shader" type="x-shader/x-fragment">
precision mediump float;
varying vec4 fColor;
void main(void) {
gl_FragColor = fColor;
}
</script>
其中有個 projection
將用來設置投影矩陣,在這邊注意到,不需要接受 aspect
了,因為投影矩陣的計算中,會包含寬高比的計算,最主要的是投影矩陣的設置:
renderer.uniformMatrix4fv('projection',
mat4.ortho(
-canvas.clientWidth / 2, // 左邊界
canvas.clientWidth / 2, // 右邊界
canvas.clientHeight / 2, // 上邊界
-canvas.clientHeight / 2, // 下邊界
0.1, // 近面
canvas.clientWidth // 遠面
)
);
因為使用了 Canvas 的顯示寬高作為邊界資訊,這表示幾何物件的設置,可以用寬高作為基準,例如設置邊長 100 的立方體:
const geometry = new CubeGeometry(100);
繪製時的方式是類似的,只不過因為近面被設為 0.1,遠面被設為 Canvas 的寬度,記得將立方體移至深度為 canvas.clientWidth / 2
的位置:
let zRotation = mat4.create();
function drawCube() {
zRotation = mat4.zRotate(zRotation, 0.025);
let transformation = mat4.translate(zRotation, canvas.clientWidth / 8, 0, canvas.clientWidth / 2);
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);
}
drawCube();
其他部份與〈後乘?右乘?〉中的範例大致相同,你可以看看範例網頁,效果是相同的。