在上一篇文章中,我們就學(xué)習(xí)過如何做三維,但三維沒有任何透視。它是利用一個所謂的“正交”的觀點,它固然有其用途,但這通常不是人們說 “3D” 時他們想要的。
相反,我們需要補充透視。只不過什么是透視?它基本上是一種事物越遠(yuǎn)顯得更小的特征。
看上面的例子,我們看到越遠(yuǎn)的東西被畫得越小。鑒于我們目前樣品的一個讓較遠(yuǎn)的物體顯得更小簡單的方法就是將 clipspace X 和 Y 除以 Z。
可以這樣想:如果你有一條從(10,15)到 (20,15) 的線段,10個單位長。在我們目前的樣本中,它將繪制 10 像素長。但是如果我們除以 Z,例如例子中如果是 Z 是 1
10 / 1 = 10
20 / 1 = 20
abs(10-20) = 10
這將是 10 像素,如果 Z 是 2,則有
10 / 2 = 5
20 / 2 = 10
abs(5 - 10) = 5
5 像素長。如果 Z = 3,則有
10 / 3 = 3.333
20 / 3 = 6.666
abs(3.333 - 6.666) = 3.333
你可以看到,隨著 Z 的增加,隨著它變得越來越遠(yuǎn),我們最終會把它畫得更小。如果我們在 clipspace 中除,我們可能得到更好的結(jié)果,因為 Z 將是一個較小的數(shù)字(-1 到 +1)。如果在除之前我們加一個 fudgeFactor 乘以 Z,對于一個給定的距離我們可以調(diào)整事物多小。
讓我們嘗試一下。首先讓我們在乘以我們的 “fudgefactor” 后改變頂點著色器除以 Z 。
<script id="2d-vertex-shader" type="x-shader/x-vertex">
...
uniform float u_fudgeFactor;
...
void main() {
// Multiply the position by the matrix.
vec4 position = u_matrix * a_position;
// Adjust the z to divide by
float zToDivideBy = 1.0 + position.z * u_fudgeFactor;
// Divide x and y by z.
gl_Position = vec4(position.xy / zToDivideBy, position.zw);
}
</script>
注意,因為在 clipspace 中 Z 從 -1 到 +1,我加 1 得到 zToDivideBy 從 0 到 +2 * fudgeFactor
我們也需要更新代碼,讓我們設(shè)置 fudgeFactor。
...
var fudgeLocation = gl.getUniformLocation(program, "u_fudgeFactor");
...
var fudgeFactor = 1;
...
function drawScene() {
...
// Set the fudgeFactor
gl.uniform1f(fudgeLocation, fudgeFactor);
// Draw the geometry.
gl.drawArrays(gl.TRIANGLES, 0, 16 * 6);
下面是結(jié)果。
如果沒有明確的把 “fudgefactor” 從 1 變化到 0 來看事物看起來像什么樣子在我們除以 Z 之前。
WebGL 在我們的頂點著色器中把 X,Y,Z,W 值分配給 gl_Position 并且自動除以 W。
我們可以證明通過改變著色這很容易實現(xiàn),而不是自己做除法,在 gl_Position.w 中加 zToDivideBy 。
<script id="3d-vertex-shader" type="x-shader/x-vertex">
...
uniform float u_fudgeFactor;
...
void main() {
// Multiply the position by the matrix.
vec4 position = u_matrix * a_position;
// Adjust the z to divide by
float zToDivideBy = 1.0 + position.z * u_fudgeFactor;
// Divide x, y and z by zToDivideBy
gl_Position = vec4(position.xyz, zToDivideBy);
}
</script>
看看這是完全相同的。
為什么有這樣一個事實: WebGL 自動除以 W ?因為現(xiàn)在,使用更多維的矩陣,我們可以使用另一個矩陣復(fù)制 z 到 w。
矩陣如下
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 1,
0, 0, 0, 0,
將復(fù)制 z 到 w.你可以看看這些列如下
x_out = x_in * 1 +
y_in * 0 +
z_in * 0 +
w_in * 0 ;
y_out = x_in * 0 +
y_in * 1 +
z_in * 0 +
w_in * 0 ;
z_out = x_in * 0 +
y_in * 0 +
z_in * 1 +
w_in * 0 ;
w_out = x_in * 0 +
y_in * 0 +
z_in * 1 +
w_in * 0 ;
簡化后如下
x_out = x_in;
y_out = y_in;
z_out = z_in;
w_out = z_in;
我們可以加 1 我們之前用的這個矩陣,因為我們知道 w_in 總是 1.0。
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 1,
0, 0, 0, 1,
這將改變 W 計算如下
w_out = x_in * 0 +
y_in * 0 +
z_in * 1 +
w_in * 1 ;
因為我們知道 w_in = 1.0 所以就有
w_out = z_in + 1;
最后我們可以將 fudgeFactor 加到矩陣,矩陣如下
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, fudgeFactor,
0, 0, 0, 1,
這意味著
w_out = x_in * 0 +
y_in * 0 +
z_in * fudgeFactor +
w_in * 1 ;
簡化后如下
w_out = z_in * fudgeFactor + 1;
所以,讓我們再次修改程序只使用矩陣?! ?/p>
首先讓我們放回頂點著色器。這很簡單
<script id="2d-vertex-shader" type="x-shader/x-vertex">
uniform mat4 u_matrix;
void main() {
// Multiply the position by the matrix.
gl_Position = u_matrix * a_position;
...
}
</script>
接下來讓我們做一個函數(shù)使 Z - > W 矩陣。
function makeZToWMatrix(fudgeFactor) {
return [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, fudgeFactor,
0, 0, 0, 1,
];
}
我們將更改代碼,以使用它。
...
// Compute the matrices
var zToWMatrix =
makeZToWMatrix(fudgeFactor);
...
// Multiply the matrices.
var matrix = matrixMultiply(scaleMatrix, rotationZMatrix);
matrix = matrixMultiply(matrix, rotationYMatrix);
matrix = matrixMultiply(matrix, rotationXMatrix);
matrix = matrixMultiply(matrix, translationMatrix);
matrix = matrixMultiply(matrix, projectionMatrix);
matrix = matrixMultiply(matrix, zToWMatrix);
...
注意,這一次也是完全相同的。
以上基本上是向你們展示,除以 Z 給了我們透視圖,WebGL 方便地為我們除以 Z?! ?/p>
但是仍然有一些問題。例如如果你設(shè)置 Z 到 -100 左右,你會看到類似下面的動畫。
發(fā)生了什么事?為什么 F 消失得很早?就像 WebGL 剪輯 X 和 Y 或+ 1 到 - 1 它也剪輯 Z。這里看到的就是 Z<-1 的地方。
我可以詳細(xì)了解如何解決它,但你可以以我們做二維投影相同的方式來得到它。我們需要利用 Z,添加一些數(shù)量和測量一定量,我們可以做任何我們想要得到的 -1 到 1 的映射范圍。
真正酷的事情是所有這些步驟可以在 1 個矩陣內(nèi)完成。甚至更好的,我們來決定一個 fieldOfView 而不是一個 fudgeFactor,并且計算正確的值來做這件事。
這里有一個函數(shù)來生成矩陣。
function makePerspective(fieldOfViewInRadians, aspect, near, far) {
var f = Math.tan(Math.PI * 0.5 - 0.5 * fieldOfViewInRadians);
var rangeInv = 1.0 / (near - far);
return [
f / aspect, 0, 0, 0,
0, f, 0, 0,
0, 0, (near + far) * rangeInv, -1,
0, 0, near * far * rangeInv * 2, 0
];
};
這個矩陣將為我們做所有的轉(zhuǎn)換。它將調(diào)整單位,所以他們在clipspace 中它會做數(shù)學(xué)運算,因此我們可以通過角度選擇視野,它會讓我們選擇我們的 z-clipping 空間。它假定有一個 eye 或 camera 在原點(0,0,0)并且給定一個 zNear 和 fieldOfView 計算它需要什么,因此在 zNear 的物體在 z = - 1 結(jié)束以及在 zNear 的物體它們在中心以上或以下半個 fieldOfView,分別在 y = - 1 和 y = 1 結(jié)束。計算 X 所使用的只是乘以傳入的 aspect。我們通常將此設(shè)置為顯示區(qū)域的 width / height。最后,它計算出在 Z 區(qū)域物體的規(guī)模,因此在 zFar 的物體在 z = 1 處結(jié)束。
下面是動作矩陣的圖。
形狀像四面錐的立方體旋轉(zhuǎn)稱為“截錐”。矩陣在截錐內(nèi)占空間并且轉(zhuǎn)換到 clipspace。zNear 定義夾在前面的物體,zfar定義夾在后面的物體。設(shè)置 zNear 為23你會看到旋轉(zhuǎn)的立方體的前面得到裁剪。設(shè)置 zFar 為24你會看到立方體的后面得到剪輯。
只剩下一個問題。這個矩陣假定有一個視角在 0,0,0 并假定它在Z軸負(fù)方向,Y的正方向。我們的矩陣到目前為止已經(jīng)以不同的方式解決問題。為了使它工作,我們需要我們的對象在前面的視圖。
我們可以通過移動我們的 F 做到。 我們在(45,150,0)繪圖。讓我們將它移到(0,150,- 360)
現(xiàn)在,要想使用它,我們只需要用對 makePerspective 的調(diào)用取代對make2DProjection 舊的調(diào)用
var aspect = canvas.clientWidth / canvas.clientHeight;
var projectionMatrix =
makePerspective(fieldOfViewRadians, aspect, 1, 2000);
var translationMatrix =
makeTranslation(translation[0], translation[1], translation[2]);
var rotationXMatrix = makeXRotation(rotation[0]);
var rotationYMatrix = makeYRotation(rotation[1]);
var rotationZMatrix = makeZRotation(rotation[2]);
var scaleMatrix = makeScale(scale[0], scale[1], scale[2]);
結(jié)果如下
我們回到了一個矩陣乘法,我們得到兩個領(lǐng)域的視圖,我們可以選擇我們的 z 空間。受篇幅限制我們沒有做。下一節(jié),攝像機。
更多建議: