游戏开发中的矩阵与变换(03)

简介: 游戏开发中的矩阵与变换

剪切变换矩阵(高级)


注意

如果您只是在寻找如何使用转换矩阵,请随时跳过本节。本节探讨了转换矩阵的一个不常用的方面,以建立对它们的理解。

您可能已经注意到,变换比上述动作的组合具有更大的自由度。2D变换矩阵的基础在两个Vector2值中具有四个总数,而旋转值和比例尺Vector2仅具有3个数。缺少自由度的高级概念称为剪切。


通常,您将始终使基本向量彼此垂直。但是,剪切在某些情况下可能很有用,了解剪切可以帮助您了解变换的工作方式。


为了直观地显示外观,让我们在Godot徽标上覆盖一个网格:


image.png


该网格上的每个点都是通过将基本向量相加而获得的。右下角是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”,以将其与父节点区分开。这么多的数字可能看起来有点让人不知所措,但是请记住,每个数字显示两次(在箭头旁边以及在矩阵中),并且几乎有一半的数字为零。


image.png


此处进行的唯一转换是父节点的比例为(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中平移,旋转,缩放和剪切工作方式的所有概念都相同。要缩放,我们将每个分量乘以;要旋转,我们更改每个基本向量所指向的位置;翻译,我们操纵原点;为了剪切,我们将基本向量更改为非垂直。


image.png


如果您愿意,最好尝试一下变换以了解它们的工作原理。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


目录
相关文章
|
2月前
|
PHP 计算机视觉
罗德里格斯公式推导,以及如何使用cv2.Rodrigues进行旋转矩阵和旋转向量之间的相互转化
罗德里格斯公式推导,以及如何使用cv2.Rodrigues进行旋转矩阵和旋转向量之间的相互转化
83 0
|
2月前
|
Python
python实现:旋转矩阵转换为四元数
python实现:旋转矩阵转换为四元数
61 0
|
2月前
第4章-变换-4.1-基础变换
第4章-变换-4.1-基础变换
18 0
|
2月前
|
算法框架/工具
第4章-变换-4.2-特殊矩阵变换和运算
第4章-变换-4.2-特殊矩阵变换和运算
15 0
|
5月前
|
算法 图形学
【计算机图形学】实验四 二维图形的缩放、旋转,平移,组合变换
【计算机图形学】实验四 二维图形的缩放、旋转,平移,组合变换
144 2
|
前端开发 数据可视化 图形学
【数学篇】09 # 如何用仿射变换对几何图形进行坐标变换?
【数学篇】09 # 如何用仿射变换对几何图形进行坐标变换?
147 0
【数学篇】09 # 如何用仿射变换对几何图形进行坐标变换?
|
算法 图形学
【计算机图形学】实验三:二维图形变换
【计算机图形学】实验三:二维图形变换
227 0
【计算机图形学】实验三:二维图形变换
|
前端开发 数据可视化 API
【数学篇】05 # 如何用向量和坐标系描述点和线段?
【数学篇】05 # 如何用向量和坐标系描述点和线段?
186 0
【数学篇】05 # 如何用向量和坐标系描述点和线段?
|
机器学习/深度学习 算法 图形学
【计算机图形学】实验一:二维图形绘制
【计算机图形学】实验一:二维图形绘制
229 0
【计算机图形学】实验一:二维图形绘制
|
计算机视觉
数字图像处理实验(二)|图像变换{离散傅里叶变换fft2,离散余弦变换dct2、频谱平移fftshift}(附实验代码和截图)
数字图像处理实验(二)|图像变换{离散傅里叶变换fft2,离散余弦变换dct2、频谱平移fftshift}(附实验代码和截图)
401 0
数字图像处理实验(二)|图像变换{离散傅里叶变换fft2,离散余弦变换dct2、频谱平移fftshift}(附实验代码和截图)