版主
☯ 博 丽 不 是 灵 梦 ☯
- 经验
- 7178
- 硬币
- 1220 枚
|
本帖最后由 囿里有条小咸鱼 于 2024-12-30 18:44 编辑
本贴需要你有一定的Godot基础和最最基础的线代知识。如果你没用过godot,或者没法把godot里的缩放和变换矩阵联系在一起,再或者你连线性变换都没接触过,请先看本贴
众所周知,Godot里Node2D的scale属性允许你对一个物件沿物体的x轴和y轴进行缩放,但这个scale既可以是正值,也可以是负值。正值的情况我们都不陌生,就是沿某个方向放大或者缩小嘛,但如果是个负值,情况就比较复杂了。
- 如果scale.x是负值,那么此时物体就相当于先沿其x轴进行了翻转,然后再缩放|scale.x|倍(写成代码的话应该是abs(scale.x))。
- 如果scale.y是负值,则与scale.x为负值的情况同理,但此时物体是沿y轴翻转而不是沿x轴翻转。
- 如果scale.x和scale.y都是负值,那么此时物体就相当于旋转了180°。
如果你还知道Godot里父级Node2D的变换会被其所有子级Node2D继承的话,那么我只需要翻转这个父级Node2D,其子级Node2D不也就跟着翻转了吗?确实如此,对于沿X轴翻转,你可以这样弄:
- scale.x = -1.0 #这里我取了-1.0是为了方便研究。当然,只要是负值,就一定会沿X轴翻转。
复制代码 没错,就是这么简单,但……真的就这么简单吗?
越是简单的东西,其背后的水一定不会很浅。Godot亦是如此,有一天,当你突然想要获取这个对象的全局缩放的时候:
纳尼?!我记得我明明设置的是(-1, 1)啊(scale默认是(1, 1),而你刚才把scale.x设为了-1,所以这里写的是(-1, 1)),为啥获取一下全局缩放可就出幺蛾子了?!考虑到全局缩放也会影响缩放,你又手抖地尝试着打印了下scale:
幸好scale是正确地打印出来了。你突然又想到,缩放是变换(这里严格来说应该是仿射变换,因为godot的变换有平移操作)的一种操作,考虑到scale没有受到影响,于是好奇的你想赶紧打印了transform.get_scale()看看:
你:WTF……
好家伙,连transform都不给面子了是吧。但这也触发了你的思考:为啥scale就没事,而global_scale和transform.get_scale()就受到了影响呢?于是,你尝试看了看api里有关scale的解释:
注意:2D 中,变换矩阵是无法分解出负数的 X 缩放的。由于 Godot 中使用变换矩阵来表示缩放,X 轴上的负数缩放在分解后会变为 Y 轴的负数缩放和一次 180 度的旋转。
你一定想知道为什么不能分解出负X缩放吧,这其实跟背后的数学原理有一定的关系。但由于我数学水平实在有限,我就用比较平白的话来解释一下吧:
变换矩阵也是矩阵,而矩阵又跟行列式有所联系。对于二维线性空间,一个二阶矩阵的行列式具有以下判别功能:
- 当行列式为0时,表明该矩阵是线性相关的,即该矩阵的两个基向量要么为零向量,要么互为共线向量,且该矩阵不可逆
- 当行列式为负值时,表面该矩阵存在反射变换。反射变换说白了就是有一个基向量发生了翻转的变换。
行列式为0时的变换矩阵在本贴里是没有讨论意义的,我们就直接忽略,来看行列式为负值的情况,这也是研究负X缩放无法分解的关键。当我们尝试将scale.x设置为-1.0时,本质上就是把变换矩阵里的第一列向量乘上了这个值,假设该经该变换后变换矩阵的基如下表示:
| -1 0 | | 0 1 | 可以看到,此时该变换矩阵的x轴基向量已经指向了x轴负方向,我们把它行列式算一下。二阶行列式的计算方法很简单,左上右下减去右上左下(交叉相乘相减),结果是-1,显然小于0,说明该矩阵存在一个反射变换。虽然我们现在可以很当然的肯定这个变换是沿x轴翻转的,但如果要是其他人看到了,也可能会说:不不不,这显然是沿y轴翻转的,然后又旋转了180°而已罢了。没错,如果你好奇地再把全局旋转和transform.get_rotation()打印一下的话你会发现打印出来的的的确确是一个180°的弧度值。回归正题,由于不同的人对这个翻转矩阵的翻转轴有不同的看法,就导致我们很难甚至几乎无法直接从变换里精确分解出我们想要的结果。那么,既然结果要不到,那我们总不能搁这儿吵吵怎么翻转吧。Godot最终还是选择了将行列式为负值的缩放变换分解为x轴缩放为正,y轴缩放为负的变换。至于要不要旋转180°嘛,就要从这个旋转是怎么获得的来说起了。其实说来也简单,godot直接以变换矩阵的x轴基向量为参考,对其求atan2,你要问我啥是atan2的话,你可以理解为就是输入一个y输入一个x然后求角度的函数。至少,如果你一开始设置的scale.y为-1而scale.x为1的话,那么你所获取的global_rotation和transform.get_rotation()也不会有影响。
说完了2D,好奇的你又想看看3D里会是什么情况,由于三维空间里多了一个轴,因此其行列式为负时就更难判断到底是哪个基向量发生了翻转了。Godot对Node3D的基变换为负的情况处理的也很干脆简单:
注意:3D 中,变换矩阵是无法分解出正负混合的缩放的。由于 Godot 中使用变换矩阵来表示缩放,得到的缩放值要么全正、要么全负。
假设你执意要把其中一个或两个轴的scale设为负值,那么就会产生以下结果:
- 如果有一个轴为负值,则所有轴上的scale均为负值,并且还会沿该轴产生一个180°的旋转
- 如果有两个轴为负值,则所有轴上的scale均为正值,并且会沿唯一为正值的轴产生一个180°的旋转
至于为什么获取scale不会受影响,我查了下源代码,发现Node2D里的scale是单独存放的,并且在获取的时候是直接取这个值,而不是通过transform.get_scale()来获取的,所以直接通过scale获取该物体的缩放是没有问题的。但是Node3D就不一样了,虽然跟Node2D一样,也是单独存的一个scale变量,但是在获取scale的时候还顺便更新了下物体的缩放和旋转,而更新缩放和旋转的函数正好涉及到那个单独存放的scale,因此Node3D里直接获取scale也会出问题。
至此,你也许也弄明白了为啥负缩放会产生这么个诡异的结果,但看完了整个过程,你也理解了其实这一点也不诡异。有时候,当你不理解一件事情的时候,去深挖它背后的原理,你也许也就理解了。不过,我开这个帖的目的不是劝大家不要用负数scale,而是要意识到使用负数scale之后会产生的副作用,在实际生产开发的过程中要尽量避免该副作用所带来的影响。
哦忘记说了,如果scale分量全负的话,通过global_scale(global_transform.get_scale())和transform.get_scale()所获取的scale分量都是正的,没有负值!
|
|