鍍金池/ 教程/ HTML/ WebGL 3D 透視
WebGL 文本 HTML
WebGL 文本 Canvas 2D
WebGL 2D 圖像旋轉(zhuǎn)
WebGL 圖像處理(續(xù))
WebGL 2D 矩陣
WebGL 繪制多個東西
WebGL 圖像處理
WebGL 2D 圖像轉(zhuǎn)換
WebGL 3D 透視
WebGL 是如何工作的
WebGL 文本 紋理
WebGL 2D 圖像伸縮
WebGL 場景圖
WebGL 3D 攝像機(jī)
WebGL 文本 使用字符紋理
WebGL 正交 3D
WebGL 基本原理
WebGL - 更少的代碼,更多的樂趣
WebGL 著色器和 GLSL

WebGL 3D 透視

在上一篇文章中,我們就學(xué)習(xí)過如何做三維,但三維沒有任何透視。它是利用一個所謂的“正交”的觀點,它固然有其用途,但這通常不是人們說 “3D” 時他們想要的。

相反,我們需要補充透視。只不過什么是透視?它基本上是一種事物越遠(yuǎn)顯得更小的特征。

http://wiki.jikexueyuan.com/project/webgl/images/perspective-example.svg" alt="" />

看上面的例子,我們看到越遠(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 之前。

http://wiki.jikexueyuan.com/project/webgl/images/orthographic-vs-perspective.png" alt="" />

WebGL 在我們的頂點著色器中把 X,Y,Z,W 值分配給 gl_Position 并且自動除以 W。

我們可以證明通過改變著色這很容易實現(xiàn),而不是自己做除法,在 gl_Position.w 中加 zToDivideBy

<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, 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。    

但是仍然有一些問題。例如如果你設(shè)置 Z 到 -100 左右,你會看到類似下面的動畫。

http://wiki.jikexueyuan.com/project/webgl/images/z-clipping.gif" alt="" />

發(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é),攝像機(jī)。