在過去的章節(jié)里我們將 F 移動(dòng)到截錐的前面,因?yàn)?makePerspective 函數(shù)從原點(diǎn)(0,0,0)度量它,并且截錐的對(duì)象從 -zNear 到 -zFar 都在它前面。
視點(diǎn)前面移動(dòng)的物體似乎沒有正確的方式去做嗎?在現(xiàn)實(shí)世界中,你通常會(huì)移動(dòng)你的相機(jī)來給建筑物拍照。
將攝像機(jī)移動(dòng)到對(duì)象前
你通常不會(huì)將建筑移動(dòng)到攝像機(jī)前。
將對(duì)象移動(dòng)到攝像機(jī)前
但在我們最后一篇文章中,我們提出了一個(gè)投影,這就需要物體在 Z 軸的原點(diǎn)前面。為了實(shí)現(xiàn)它,我們想做的是把攝像機(jī)移動(dòng)到原點(diǎn),然后把所有的其它物體都移動(dòng)恰當(dāng)?shù)木嚯x,所以它相對(duì)于攝像機(jī)仍然是在同一個(gè)地方。
將對(duì)象移動(dòng)到視圖
我們需要有效地將現(xiàn)實(shí)中的物體移動(dòng)到攝像機(jī)的前面。能達(dá)到這個(gè)目的的最簡(jiǎn)單的方法是使用“逆”矩陣。一般情況下的逆矩陣的計(jì)算是復(fù)雜的,但從概念上講,它是容易的。逆是你用來作為其他數(shù)值的對(duì)立的值。例如,123 的是相反數(shù)是 -123。縮放比例為5的規(guī)模矩陣的逆是 1/5 或 0.2。在 X 域旋轉(zhuǎn) 30° 的矩陣的逆是一個(gè)在 X 域旋轉(zhuǎn) -30° 的矩陣。
直到現(xiàn)在我們已經(jīng)使用了平移,旋轉(zhuǎn)和縮放來影響我們的 'F' 的位置和方向。把所有的矩陣相乘后,我們有一個(gè)單一的矩陣,表示如何將 “F” 以我們希望的大小和方向從原點(diǎn)移動(dòng)到相應(yīng)位置。使用攝像機(jī)我們可以做相同的事情。一旦我們的矩陣告訴我們?nèi)绾螐脑c(diǎn)到我們想要的位置移動(dòng)和旋轉(zhuǎn)攝像機(jī),我們就可以計(jì)算它的逆,它將給我們一個(gè)矩陣來告訴我們?nèi)绾我苿?dòng)和旋轉(zhuǎn)其它一切物體的相對(duì)數(shù)量,這將有效地使攝像機(jī)在點(diǎn)(0,0,0),并且我們已經(jīng)將一切物體移動(dòng)到它的前面。
讓我們做一個(gè)有一圈 'F' 的三維場(chǎng)景,就像上面的圖表那樣。
下面是實(shí)現(xiàn)代碼。
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);
}
就在我們計(jì)算出我們的投影矩陣之后,我們就可以計(jì)算出一個(gè)就像上面的圖表中顯示的那樣圍繞 ‘F’ 旋轉(zhuǎn)的攝像機(jī)。
// Compute the camera's matrix
var cameraMatrix = makeTranslation(0, 0, radius * 1.5);
cameraMatrix = matrixMultiply(
cameraMatrix, makeYRotation(cameraAngleRadians));
然后,我們根據(jù)相機(jī)矩陣計(jì)算“視圖矩陣”?!耙晥D矩陣”是將一切物體移動(dòng)到攝像機(jī)相反的位置,這有效地使攝像機(jī)相對(duì)于一切物體就像在原點(diǎn)(0,0,0)。
// Make a view matrix from the camera matrix.
var viewMatrix = makeInverse(cameraMatrix);
最后我們需要應(yīng)用視圖矩陣來計(jì)算每個(gè) ‘F’ 的矩陣
// Multiply the matrices.
var matrix = translationMatrix;
matrix = matrixMultiply(matrix, viewMatrix); // <=-- added
matrix = matrixMultiply(matrix, projectionMatrix);
一個(gè)攝像機(jī)可以繞著一圈 “F”。拖動(dòng) cameraAngle 滑塊來移動(dòng)攝像機(jī)。
這一切都很好,但使用旋轉(zhuǎn)和平移來移動(dòng)一個(gè)攝像頭到你想要的地方,并且指向你想看到的地方并不總是很容易。例如如果我們想要攝像機(jī)總是指向特定的 ‘F’ 就要進(jìn)行一些非常復(fù)雜的數(shù)學(xué)計(jì)算來決定當(dāng)攝像機(jī)繞 ‘F’ 圈旋轉(zhuǎn)的時(shí)候如何旋轉(zhuǎn)攝像機(jī)來指向那個(gè) ‘F’。
幸運(yùn)的是,有一個(gè)更容易的方式。我們可以決定攝像機(jī)在我們想要的地方并且可以決定它指向什么,然后計(jì)算矩陣,這個(gè)矩陣可以將把攝像機(jī)放到那里?;诰仃嚨墓ぷ髟磉@非常容易實(shí)現(xiàn)。
首先,我們需要知道我們想要攝像機(jī)在什么位置。我們將稱之為 CameraPosition。然后我們需要了解我們看過去或瞄準(zhǔn)的物體的位置。我們將把它稱為 target。如果我們將 CameraPosition 減去 target 我們將得到一個(gè)向量,它指向從攝像頭獲取目標(biāo)的方向。讓我們稱它為 zAxis。因?yàn)槲覀冎罃z像機(jī)指向 -Z 方向,我們可以從另一方向做減法 cameraPosition - target。我們將結(jié)果規(guī)范化,并直接復(fù)制到 z 區(qū)域矩陣。
+----+----+----+----+
| | | | |
+----+----+----+----+
| | | | |
+----+----+----+----+
| Zx | Zy | Zz | |
+----+----+----+----+
| | | | |
+----+----+----+----+
這部分矩陣表示的是 Z 軸。在這種情況下,是攝像機(jī)的 Z 軸。一個(gè)向量的標(biāo)準(zhǔn)化意味著它代表了 1.0。如果你回到二維旋轉(zhuǎn)的文章,在哪里我們談到了如何與單位圓以及二維旋轉(zhuǎn),在三維中我們需要單位球面和一個(gè)歸一化的向量來代表在單位球面上一點(diǎn)。
雖然沒有足夠的信息。只是一個(gè)單一的向量給我們一個(gè)點(diǎn)的單位范圍內(nèi),但從這一點(diǎn)到東方的東西?我們需要把矩陣的其他部分填好。特別的 X 軸和 Y 軸類零件。我們知道這 3 個(gè)部分是相互垂直的。我們也知道,“一般”我們不把相機(jī)指向。因?yàn)椋绻覀冎滥膫€(gè)方向是向上的,在這種情況下(0,1,0),我們可以使用一種叫做“跨產(chǎn)品和“計(jì)算 X 軸和 Y 軸的矩陣。
我不知道叉乘的數(shù)學(xué)意義是什么,但我知道將兩個(gè)單位向量叉乘后可以得到一個(gè)和它們都垂直的向量。 換句話說,如果你有一個(gè)向量指向東南方,一個(gè)向量指向上方, 叉乘后會(huì)得到一個(gè)指向西南方或東北方的矢量,因?yàn)檫@兩個(gè)矢量都和東南方和上方垂直。 相乘的順序不同的到結(jié)果相反。
現(xiàn)在,我們有 xAxis,我們可以通過 zAxis 和 xAxis 得到攝像機(jī)的 yAxis
現(xiàn)在我們所要做的就是將 3 個(gè)軸插入一個(gè)矩陣。這使得矩陣可以指向物體,從 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
+----+----+----+----+
下面是用來計(jì)算 2 個(gè)向量的交叉乘積的代碼。
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]];
}
這是減去兩個(gè)向量的代碼。
function subtractVectors(a, b) {
return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
}
這里是規(guī)范化一個(gè)向量(使其成為一個(gè)單位向量)的代碼。
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];
}
}
下面是計(jì)算一個(gè) "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];
}
這是我們?nèi)绾问褂盟鼇硎瓜鄼C(jī)隨著我們移動(dòng)它指向在一個(gè)特定的 ‘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);
...
下面是結(jié)果。
拖動(dòng)滑塊,注意到相機(jī)追蹤一個(gè) ‘F’。
請(qǐng)注意,您可以不只對(duì)攝像機(jī)使用 “l(fā)ookAt” 函數(shù)。共同的用途是使一個(gè)人物的頭跟著某人。使小塔瞄準(zhǔn)一個(gè)目標(biāo)。使對(duì)象遵循一個(gè)路徑。你計(jì)算目標(biāo)的路徑。然后你計(jì)算出目標(biāo)在未來幾分鐘在路徑的什么地方。把這兩個(gè)值放進(jìn)你的 lookAt 函數(shù),你會(huì)得到一個(gè)矩陣,使你的對(duì)象跟著路徑并且朝向路徑。
更多建議: