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

WebGL 3D 攝像機

在過去的章節(jié)里我們將 F 移動到截錐的前面,因為 makePerspective 函數從原點(0,0,0)度量它,并且截錐的對象從 -zNear 到 -zFar 都在它前面。

視點前面移動的物體似乎沒有正確的方式去做嗎?在現實世界中,你通常會移動你的相機來給建筑物拍照。

將攝像機移動到對象前

http://wiki.jikexueyuan.com/project/webgl/images/4.png" alt="" />

你通常不會將建筑移動到攝像機前。

將對象移動到攝像機前

http://wiki.jikexueyuan.com/project/webgl/images/5.png" alt="" />

但在我們最后一篇文章中,我們提出了一個投影,這就需要物體在 Z 軸的原點前面。為了實現它,我們想做的是把攝像機移動到原點,然后把所有的其它物體都移動恰當的距離,所以它相對于攝像機仍然是在同一個地方。

將對象移動到視圖

http://wiki.jikexueyuan.com/project/webgl/images/6.png" alt="" />

我們需要有效地將現實中的物體移動到攝像機的前面。能達到這個目的的最簡單的方法是使用“逆”矩陣。一般情況下的逆矩陣的計算是復雜的,但從概念上講,它是容易的。逆是你用來作為其他數值的對立的值。例如,123 的是相反數是 -123。縮放比例為5的規(guī)模矩陣的逆是 1/5 或 0.2。在 X 域旋轉 30° 的矩陣的逆是一個在 X 域旋轉 -30° 的矩陣。

直到現在我們已經使用了平移,旋轉和縮放來影響我們的 'F' 的位置和方向。把所有的矩陣相乘后,我們有一個單一的矩陣,表示如何將 “F” 以我們希望的大小和方向從原點移動到相應位置。使用攝像機我們可以做相同的事情。一旦我們的矩陣告訴我們如何從原點到我們想要的位置移動和旋轉攝像機,我們就可以計算它的逆,它將給我們一個矩陣來告訴我們如何移動和旋轉其它一切物體的相對數量,這將有效地使攝像機在點(0,0,0),并且我們已經將一切物體移動到它的前面。

讓我們做一個有一圈 'F' 的三維場景,就像上面的圖表那樣。

下面是實現代碼。

  var numFs = 5;
  var radius = 200;

  // Compute the projection matrix
  var aspect = canvas.clientWidth / canvas.clientHeight;
  var projectionMatrix =
      makePerspective(fieldOfViewRadians, aspect, 1, 2000);

  // Draw 'F's in a circle
  for (var ii = 0; ii < numFs; ++ii) {
    var angle = ii * Math.PI * 2 / numFs;

    var x = Math.cos(angle) * radius;
    var z = Math.sin(angle) * radius;
    var translationMatrix = makeTranslation(x, 0, z);

    // Multiply the matrices.
    var matrix = translationMatrix;
    matrix = matrixMultiply(matrix, projectionMatrix);

    // Set the matrix.
    gl.uniformMatrix4fv(matrixLocation, false, matrix);

    // Draw the geometry.
    gl.drawArrays(gl.TRIANGLES, 0, 16 * 6);
  }

就在我們計算出我們的投影矩陣之后,我們就可以計算出一個就像上面的圖表中顯示的那樣圍繞 ‘F’ 旋轉的攝像機。

  // Compute the camera's matrix
  var cameraMatrix = makeTranslation(0, 0, radius * 1.5);
  cameraMatrix = matrixMultiply(
      cameraMatrix, makeYRotation(cameraAngleRadians));

然后,我們根據相機矩陣計算“視圖矩陣”?!耙晥D矩陣”是將一切物體移動到攝像機相反的位置,這有效地使攝像機相對于一切物體就像在原點(0,0,0)。

  // Make a view matrix from the camera matrix.
  var viewMatrix = makeInverse(cameraMatrix);

最后我們需要應用視圖矩陣來計算每個 ‘F’ 的矩陣

    // Multiply the matrices.
    var matrix = translationMatrix;
    matrix = matrixMultiply(matrix, viewMatrix);  // <=-- added
    matrix = matrixMultiply(matrix, projectionMatrix);

一個攝像機可以繞著一圈 “F”。拖動 cameraAngle 滑塊來移動攝像機。

這一切都很好,但使用旋轉和平移來移動一個攝像頭到你想要的地方,并且指向你想看到的地方并不總是很容易。例如如果我們想要攝像機總是指向特定的 ‘F’ 就要進行一些非常復雜的數學計算來決定當攝像機繞 ‘F’ 圈旋轉的時候如何旋轉攝像機來指向那個 ‘F’。

幸運的是,有一個更容易的方式。我們可以決定攝像機在我們想要的地方并且可以決定它指向什么,然后計算矩陣,這個矩陣可以將把攝像機放到那里?;诰仃嚨墓ぷ髟磉@非常容易實現。

首先,我們需要知道我們想要攝像機在什么位置。我們將稱之為 CameraPosition。然后我們需要了解我們看過去或瞄準的物體的位置。我們將把它稱為 target。如果我們將 CameraPosition 減去 target 我們將得到一個向量,它指向從攝像頭獲取目標的方向。讓我們稱它為 zAxis。因為我們知道攝像機指向 -Z 方向,我們可以從另一方向做減法 cameraPosition - target。我們將結果規(guī)范化,并直接復制到 z 區(qū)域矩陣。

+----+----+----+----+
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
+----+----+----+----+
| Zx | Zy | Zz |    |
+----+----+----+----+
|    |    |    |    |
+----+----+----+----+

這部分矩陣表示的是 Z 軸。在這種情況下,是攝像機的 Z 軸。一個向量的標準化意味著它代表了 1.0。如果你回到二維旋轉的文章,在哪里我們談到了如何與單位圓以及二維旋轉,在三維中我們需要單位球面和一個歸一化的向量來代表在單位球面上一點。

http://wiki.jikexueyuan.com/project/webgl/images/7.png" alt="" />

雖然沒有足夠的信息。只是一個單一的向量給我們一個點的單位范圍內,但從這一點到東方的東西?我們需要把矩陣的其他部分填好。特別的 X 軸和 Y 軸類零件。我們知道這 3 個部分是相互垂直的。我們也知道,“一般”我們不把相機指向。因為,如果我們知道哪個方向是向上的,在這種情況下(0,1,0),我們可以使用一種叫做“跨產品和“計算 X 軸和 Y 軸的矩陣。

我不知道一個跨產品意味著在數學方面。我所知道的是,如果你有 2 個單位向量和你計算的交叉產品,你會得到一個向量,是垂直于這 2 個向量。換句話說,如果你有一個向量指向東南方,和一個向量指向上,和你計算交叉產品,你會得到一個向量指向北西或北東自這2個向量,purpendicular 到東南亞和。根據你計算交叉產品的順序,你會得到相反的答案。

http://wiki.jikexueyuan.com/project/webgl/images/8.png" alt="" />

現在,我們有 xAxis,我們可以通過 zAxisxAxis 得到攝像機的 yAxis

http://wiki.jikexueyuan.com/project/webgl/images/9.png" alt="" />

現在我們所要做的就是將 3 個軸插入一個矩陣。這使得矩陣可以指向物體,從 cameraPosition 指向 target。我們只需要添加 position

+----+----+----+----+
| Xx | Xy | Xz |  0 |  <- x axis
+----+----+----+----+
| Yx | Yy | Yz |  0 |  <- y axis
+----+----+----+----+
| Zx | Zy | Zz |  0 |  <- z axis
+----+----+----+----+
| Tx | Ty | Tz |  1 |  <- camera position
+----+----+----+----+

下面是用來計算 2 個向量的交叉乘積的代碼。

function cross(a, b) {
  return [a[1] * b[2] - a[2] * b[1],
          a[2] * b[0] - a[0] * b[2],
          a[0] * b[1] - a[1] * b[0]];
}

這是減去兩個向量的代碼。

function subtractVectors(a, b) {
  return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
}

這里是規(guī)范化一個向量(使其成為一個單位向量)的代碼。

function normalize(v) {
  var length = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
  // make sure we don't divide by 0.
  if (length > 0.00001) {
    return [v[0] / length, v[1] / length, v[2] / length];
  } else {
    return [0, 0, 0];
  }
}

下面是計算一個 "lookAt" 矩陣的代碼。

function makeLookAt(cameraPosition, target, up) {
  var zAxis = normalize(
      subtractVectors(cameraPosition, target));
  var xAxis = cross(up, zAxis);
  var yAxis = cross(zAxis, xAxis);

  return [
     xAxis[0], xAxis[1], xAxis[2], 0,
     yAxis[0], yAxis[1], yAxis[2], 0,
     zAxis[0], zAxis[1], zAxis[2], 0,
     cameraPosition[0],
     cameraPosition[1],
     cameraPosition[2],
     1];
}

這是我們如何使用它來使相機隨著我們移動它指向在一個特定的 ‘F’ 的。

  ...

  // Compute the position of the first F
  var fPosition = [radius, 0, 0];

  // Use matrix math to compute a position on the circle.
  var cameraMatrix = makeTranslation(0, 50, radius * 1.5);
  cameraMatrix = matrixMultiply(
      cameraMatrix, makeYRotation(cameraAngleRadians));

  // Get the camera's postion from the matrix we computed
  cameraPosition = [
      cameraMatrix[12],
      cameraMatrix[13],
      cameraMatrix[14]];

  var up = [0, 1, 0];

  // Compute the camera's matrix using look at.
  var cameraMatrix = makeLookAt(cameraPosition, fPosition, up);

  // Make a view matrix from the camera matrix.
  var viewMatrix = makeInverse(cameraMatrix);

  ...

下面是結果。

拖動滑塊,注意到相機追蹤一個 ‘F’。

請注意,您可以不只對攝像機使用 “l(fā)ookAt” 函數。共同的用途是使一個人物的頭跟著某人。使小塔瞄準一個目標。使對象遵循一個路徑。你計算目標的路徑。然后你計算出目標在未來幾分鐘在路徑的什么地方。把這兩個值放進你的 lookAt 函數,你會得到一個矩陣,使你的對象跟著路徑并且朝向路徑。