WebGL 3D 透視

2023-09-19 10:06 更新

WebGL 3D 透視

在上一篇文章中,我們就學(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é),攝像機。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號