參考:《Webgl Programe Guide》
參考:http://eux.baidu.com/blog/fe/832
(Owed by: 春夜喜雨 http://blog.csdn.net/chunyexiyu)
生成正投影的矩陣是相對比較好理解的一個矩陣,整理對它的推理過程的理解,另外最下面有js的代碼實現,參考自: webgl programe guide
預置條件
預置條件,正投影的視口範圍:
left=-30, righ=30, bottom=-20, top=20, near = 0, far = 10
// Specify the viewing volume
projMatrix.setOrtho(-30.0, 30.0, -20.0, 20.0, 0, 10);
場景分析
- 投影矩陣的使用位於視圖矩陣之後,視圖矩陣處理後的結果是:相機位置位於(0,0,0)位置,相機朝向-z軸,相機的上方向爲+y軸,右方向爲+x軸。
- 我們已知, OpenGL投影后的屏幕Clip Space可見座標保留範圍是(-1,-1,-1)—(1,1,1), 取值範圍外的會被認爲超出屏幕範圍, 不會展示到屏幕上,也就是看不見了。
- 另外正投影的效果是,定義一個長方體範圍,該範圍內的可見,輸出到屏幕中。
實現
綜合上面的點,我們需要把長方體的範圍影和可見範圍對應,讓長方體的範圍正好是可見範圍,從而達到屏幕上展示範圍爲長方體範圍,達成正投影的設置範圍效果。
1. SCALE變換:
要做到該點,首先長方體的left-right需要影射到範圍(-1,1), bottom-top需要影射到範圍(-1,1),near-far也需要影射到-1,1範圍中;
所以需要先使用縮放矩陣進行處理:
z軸和x/y軸不同,z軸可見範圍都在負軸上,但我們習慣所使用都是正值,把-z軸做爲相機正向,所以多一個負號來首先修正。
如下圖示:
2. 位移變換:
經過scale處理之後, x/y座標系上:
-
x軸在(right-left)/2的對稱範圍內取值(-1,1);
-
y軸在(top-bottom)/2對稱範圍內取值(-1,1)
-
z軸在以z=0點的對稱範圍(far-near)/2內取值(1,-1),相機方向前(-z方向)的爲正值,相機後(+z方向)的爲負值。
-
x/y軸考慮abs(right)與abs(left),abs(top)與abs(bottom)不一定相等,所以取值範圍可能會有偏差;絕對值相等時不需要修正,不相等時需要修正。
修正時,採用影射後中心點的偏差量對值進行修正。
對於x軸,(right+left)/2是原來的中心點,乘映射係數 2/(right-left),影射後就是(right+left)/(right-left),把該值作爲修正量從x中減掉;
所以x軸修正量是:-(right+left)/(right-left)
同理y軸修正量是:-(top+bottom)/(top-bottom) -
z軸是一定需要修正的,z=0之後是作爲相機後面的,肯定是不能被看見的,可見的範圍需要調整到在(-far, -near)之間,也就是對稱中心移到範圍中心去;
所以同x,y軸的修正算法一樣,把(-far, -near)中心點與原點的偏差減去掉。
對於z軸,(-far + (-near))/2是原範圍的中心點,乘影射係數-2/(far-near),映射後是(far+near)/(far-near),把該值作爲修正量從z中減去掉;
所以z軸修正量是:-(far+near)/(far-near) -
z軸演算以下,最開始的例子裏far=10, near=0,帶入公式;
使用z=0, 計算影射值 0 * -2/(far-near) +(-(far+near)/(far-near)) = 0 * -2/10 -1 = -1
使用z=-10,計算影射值 -10 * -2/(far-near) +(-(far+near)/(far-near)) = -10 * -2 / 10 - 1 = 1
可以看出,可見範圍near=0(z=0)映射到-1,far=10(z=-10)映射到1,near->far被影射到了(-1,1)的範圍內
3. 與Direct3D不同:
爲什麼會與Direct3D不同呢,因爲ClipSpace的不一致,Direct3D使用的z的空間範圍是(0,1)
所以變換時,對於z的變換就很不一樣了,只需要把可見的z範圍變換到(0,1)空間即可。
scale時把near,far長度影射到(0,1)的範圍內,再把near的位置進行偏移修正,最終表達(0,1)範圍。
webgl代碼實現
參考自: webgl programe guide
/**
* Set the orthographic projection matrix.
* @param left The coordinate of the left of clipping plane.
* @param right The coordinate of the right of clipping plane.
* @param bottom The coordinate of the bottom of clipping plane.
* @param top The coordinate of the top top clipping plane.
* @param near The distances to the nearer depth clipping plane. This value is minus if the plane is to be behind the viewer.
* @param far The distances to the farther depth clipping plane. This value is minus if the plane is to be behind the viewer.
* @return this
*/
Matrix4.prototype.setOrtho = function(left, right, bottom, top, near, far) {
var e, rw, rh, rd;
if (left === right || bottom === top || near === far) {
throw 'null frustum';
}
rw = 1 / (right - left);
rh = 1 / (top - bottom);
rd = 1 / (far - near);
e = this.elements;
e[0] = 2 * rw;
e[1] = 0;
e[2] = 0;
e[3] = 0;
e[4] = 0;
e[5] = 2 * rh;
e[6] = 0;
e[7] = 0;
e[8] = 0;
e[9] = 0;
e[10] = -2 * rd;
e[11] = 0;
e[12] = -(right + left) * rw;
e[13] = -(top + bottom) * rh;
e[14] = -(far + near) * rd;
e[15] = 1;
return this;
};
(Owed by: 春夜喜雨 http://blog.csdn.net/chunyexiyu)