剪切变换矩阵(高级)
注意
如果您只是在寻找如何使用转换矩阵,请随时跳过本节。本节探讨了转换矩阵的一个不常用的方面,以建立对它们的理解。
您可能已经注意到,变换比上述动作的组合具有更大的自由度。2D变换矩阵的基础在两个Vector2值中具有四个总数,而旋转值和比例尺Vector2仅具有3个数。缺少自由度的高级概念称为剪切。
通常,您将始终使基本向量彼此垂直。但是,剪切在某些情况下可能很有用,了解剪切可以帮助您了解变换的工作方式。
为了直观地显示外观,让我们在Godot徽标上覆盖一个网格:
该网格上的每个点都是通过将基本向量相加而获得的。右下角是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”,以将其与父节点区分开。这么多的数字可能看起来有点让人不知所措,但是请记住,每个数字显示两次(在箭头旁边以及在矩阵中),并且几乎有一半的数字为零。
此处进行的唯一转换是父节点的比例为(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”相同):
Transform2D ti = Transform.AffineInverse();
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中平移,旋转,缩放和剪切工作方式的所有概念都相同。要缩放,我们将每个分量乘以;要旋转,我们更改每个基本向量所指向的位置;翻译,我们操纵原点;为了剪切,我们将基本向量更改为非垂直。
如果您愿意,最好尝试一下变换以了解它们的工作原理。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