天天看點

遊戲開發中的矩陣與變換(03)

剪切變換矩陣(進階)

注意

如果您隻是在尋找如何使用轉換矩陣,請随時跳過本節。本節探讨了轉換矩陣的一個不常用的方面,以建立對它們的了解。

您可能已經注意到,變換比上述動作的組合具有更大的自由度。2D變換矩陣的基礎在兩個Vector2值中具有四個總數,而旋轉值和比例尺Vector2僅具有3個數。缺少自由度的進階概念稱為剪切。

通常,您将始終使基本向量彼此垂直。但是,剪切在某些情況下可能很有用,了解剪切可以幫助您了解變換的工作方式。

為了直覺地顯示外觀,讓我們在Godot徽标上覆寫一個網格:

遊戲開發中的矩陣與變換(03)

該網格上的每個點都是通過将基本向量相加而獲得的。右下角是X + Y,而右上角是X-Y。如果更改基本矢量,則整個網格将随之移動,因為網格是由基本矢量組成的。無論我們對基本矢量進行什麼更改,目前網格上所有平行的線都将保持平行。

例如,我們将Y設定為(1,1):

Transform2D t = Transform2D.Identity;

// Shear by setting Y to (1, 1)

t.y = Vector2.One;

Transform = t; // Change the node's transform to what we just calculated.

您無法在編輯器中設定Transform2D的原始值,是以,如果要剪切對象,則必須使用代碼。

由于矢量不再垂直,是以已剪切了對象。網格的底部中心相對于其自身為(0,1),現在位于世界位置(1,1)。

對象内的坐标在紋理中稱為UV坐标,是以在此我們借用該術語。為了從相對位置找到世界位置,公式為U * X + V * Y,其中U和V是數字,X和Y是基向量。

網格的右下角始終位于(1,1)的UV位置,位于(2,1)的世界位置,該位置由X * 1 + Y * 1計算得出,即( 1,0)+(1,1)或(1 + 1,0 + 1)或(2,1)。這與我們對圖像右下角位置的觀察相符。

同樣,網格的右上角始終位于(1,-1)的UV位置,位于(0,-1)的世界位置,該位置是根據X * 1 + Y *- 1,即(1,0)-(1,1)或(1-1,0-1)或(0,-1)。這與我們對圖像右上角的位置的觀察相符。

希望您現在完全了解了變換矩陣如何影響對象,以及基矢量之間的關系以及對象的“ UV”或“坐标内”如何改變其世界位置。

在Godot中,所有變換數學都是相對于父節點完成的。當我們提到“世界位置”時,如果節點具有父級,則它将相對于節點的父級。

如果您需要其他說明,則應該檢視3Blue1Brown的有關線性變換的精彩視訊:

https://www.youtube.com/watch?v=kYB8IZa5AuE

轉換的實際應用

在實際項目中,通常将通過使多個Node2D或Spatial 節點彼此父代來處理轉換中的轉換。

但是,有時手動計算我們需要的值非常有用。我們将介紹如何使用Transform2D或 Transform手動計算節點的變換。

在轉換之間轉換位置

在許多情況下,您想在轉換中進行位置轉換。例如,如果您有一個相對于玩家的位置并想找到世界(父母相對)位置,或者您有一個世界位置并且想知道它相對于玩家的位置。

我們可以使用“ xform”方法找到相對于玩家的矢量在世界空間中的定義:

// World space vector 100 units below the player.

GD.Print(Transform.Xform(new Vector2(0, 100)));

我們可以使用“ xform_inv”方法來查找相對于玩家定義的世界空間位置:

// Where is (0, 100) relative to the player?

GD.Print(Transform.XformInv(new Vector2(0, 100)));

如果事先知道變換位于(0,0),則可以改用“ basis_xform”或“ basis_xform_inv”方法,這些方法将跳過翻譯。

相對于自身移動對象

一種常見的操作(尤其是在3D遊戲中)是相對于自身移動對象。例如,在第一人稱射擊遊戲中,您希望當按時角色向前移動(-Z軸)W。

由于基本向量是相對于父對象的方向,而原點向量是相對于父對象的位置,是以我們可以簡單地添加多個基本向量來相對于自身移動對象。

此代碼将一個對象向右移動100個機關:

Transform2D t = Transform;

t.origin += t.x * 100;

Transform = t;

要在3D中移動,您需要将“ x”替換為“ basis.x”。

在實際項目中,您可以在3D中使用translate_object_local或在2D中使用move_local_x和move_local_y。

将變換應用于變換

關于轉換最重要的事情之一是如何一起使用其中的幾個轉換。父節點的變換會影響其所有子節點。讓我們剖析一個例子。

在此圖像中,子節點在元件名稱之後帶有“ 2”,以将其與父節點區分開。這麼多的數字可能看起來有點讓人不知所措,但是請記住,每個數字顯示兩次(在箭頭旁邊以及在矩陣中),并且幾乎有一半的數字為零。

遊戲開發中的矩陣與變換(03)

此處進行的唯一轉換是父節點的比例為(2,1),子節點的比例為(0.5,0.5),兩個節點的位置都被賦予了位置。

所有子轉換都受父轉換影響。子項的比例為(0.5,0.5),是以您希望它是一個1:1比例的正方形,并且它是(但僅相對于父項)。子項的X向量最終在世界空間中為(1、0),因為它由父項的基礎向量縮放。同樣,子節點的原點向量設定為(1,1),但是由于父節點的基礎向量,實際上将其在世界空間中移動了(2,1)。

要手動計算子變換的世界空間變換,這是我們将使用的代碼:

// Set up transforms just like in the image, except make positions be 100 times bigger.
Transform2D parent = new Transform2D(2, 0, 0, 1, 100, 200);
Transform2D child = new Transform2D(0.5f, 0, 0, 0.5f, 100, 100);
// Calculate the child's world space transform
// origin = (2, 0) * 100 + (0, 1) * 100 + (100, 200)
Vector2 origin = parent.x * child.origin.x + parent.y * child.origin.y + parent.origin;
// basisX = (2, 0) * 0.5 + (0, 1) * 0 = (0.5, 0)
Vector2 basisX = parent.x * child.x.x + parent.y * child.x.y;
// basisY = (2, 0) * 0 + (0, 1) * 0.5 = (0.5, 0)
Vector2 basisY = parent.x * child.y.x + parent.y * child.y.y;
// Change the node's transform to what we just calculated.
Transform = new Transform2D(basisX, basisY, origin);      

在實際的項目中,我們可以使用*運算符将一個變換應用于另一個變換,進而找到孩子的世界變換:

// Set up transforms just like in the image, except make positions be 100 times bigger.
Transform2D parent = new Transform2D(2, 0, 0, 1, 100, 200);
Transform2D child = new Transform2D(0.5f, 0, 0, 0.5f, 100, 100);
// Change the node's transform to what would be the child's world transform.
Transform = parent * child;      

當矩陣相乘時,順序很重要!不要把它們混在一起。

最後,應用身份轉換将始終無濟于事。

如果您需要其他說明,則可以檢視3Blue1Brown的有關基質組成的出色視訊:

https://www.youtube.com/watch?v=XkY2DOUCWMU

倒置轉換矩陣

“ affine_inverse”函數傳回一個“撤消”先前轉換的轉換。在某些情況下這可能很有用,但是僅提供一些示例會更容易。

将逆變換與法向變換相乘會撤消所有變換:

Transform2D ti = Transform.AffineInverse();

Transform2D t = ti * Transform;

// The transform is the identity transform.

通過變換及其逆變換來變換位置會導緻相同位置(與“ xform_inv”相同):

Position = Transform.Xform(Position);

Position = ti.Xform(Position);

// The position is the same as before.

這一切在3D中如何運作?

轉換矩陣的一大優點是它們在2D和3D轉換之間的工作原理非常相似。上面用于2D的所有代碼和公式在3D中的工作方式相同,但有3個例外:添加了第三個軸,每個軸均為Vector3類型,并且Godot将基準與Transform分開存儲,因為數學可以變得複雜,将其分開是有意義的。

與2D相比,有關3D中平移,旋轉,縮放和剪切工作方式的所有概念都相同。要縮放,我們将每個分量乘以;要旋轉,我們更改每個基本向量所指向的位置;翻譯,我們操縱原點;為了剪切,我們将基本向量更改為非垂直。

遊戲開發中的矩陣與變換(03)

如果您願意,最好嘗試一下變換以了解它們的工作原理。Godot允許您直接從檢查器編輯3D變換矩陣。您可以下載下傳帶有彩色線條和立方體的項目,以幫助可視化2D和3D中的 基礎向量和原點:

https://github.com/godotengine/godot-demo-projects/tree/master/misc/matrix_transform

Godot

3.2的檢查器中Spatial的“矩陣”部分将矩陣換位顯示,列為水準,行為垂直。在将來的Godot版本中,可以對此進行更改以減少混亂。

您不能直接在Godot 3.2的檢查器中編輯Node2D的變換矩陣。這可能會在Godot的将來版本中更改。

如果您需要其他說明,則可以檢視3Blue1Brown關于3D線性變換的精彩視訊:

https://www.youtube.com/watch?v=rHLEWRxRGiM

表示3D旋轉(進階)

2D和3D轉換矩陣之間的最大差別在于,如何在沒有基向量的情況下自己表示旋轉。

使用2D,我們有一個簡單的方法(atan2)在轉換矩陣和角度之間切換。在3D中,我們不能簡單地将旋轉表示為一個數字。有一種稱為歐拉角的東西,可以将旋轉表示為一組3個數字,但是,它們是有限的,除了瑣碎的情況外,它不是很有用。

在3D中,我們通常不使用角度,或者使用變換基礎(在Godot中幾乎所有地方都使用過),或者使用四元數。Godot可以使用Quat結構表示四元數。我建議您完全忽略它們在背景的工作方式,因為它們非常複雜且不直覺。

但是,如果您真的必須知道它是如何工作的,則可以參考以下一些有用的資源:

https://www.youtube.com/watch?v=mvmuCPvRoWQ https://www.youtube.com/watch?v=d4EgbgTm0Bg https://eater.net/quaternions