cod*_*edd 19 opengl 3d graphics transformation matrix
我目前正在处理分层场景图中的节点,并且我难以相对于特定的转换空间(例如父节点)正确地转换/旋转节点.
如何在场景图中相对于其父节点正确平移/旋转节点?
考虑以下水分子图(没有连接线)用于场景节点的父/子结构,其中O xygen原子是父节点,2 H ydrogen原子是子节点.
如果您抓住父O xygen原子并翻译结构,您希望H ydrogen儿童跟随并保持与父母相同的相对位置.如果你抓住一个孩子H原子并翻译它,那么只有孩子会受到影响.这通常是它目前的工作方式.当O原子被翻译时,H原子会随着它自动移动,正如层次图所预期的那样.
然而,当翻译父母时,孩子们最终还会积累额外的翻译,这实际上会导致孩子们在同一个方向"翻译两次"并远离他们的父母而不是保持相同的相对距离.
如果您抓住父O节点并旋转它,您希望子节点H节点也旋转,但在轨道中,因为旋转正由父节点执行.这按预期工作.
但是,如果你抓住一个子H节点并告诉它相对于它的父节点旋转,我预计只有孩子会以同样的方式绕其父节点绕轨道运行,但这不会发生.相反,孩子在其当前位置以更快的速度(例如,相对于其自身局部空间旋转的两倍)在其自身轴上旋转.
我真的希望这个描述足够公平,但如果不是,请告诉我,我会根据需要澄清.
我使用4×4 列优先矩阵(即Matrix4)和列向量(即Vector3,Vector4).
下面不正确的逻辑是我最接近正确的行为.请注意,我选择使用Java的像语法,运算符重载,使数学更容易在这里找到.当我以为我已经把它想出来的时候,我尝试了不同的东西,但我真的没有.
translate(Vector3 tv /* translation vector */, TransformSpace relativeTo):
switch (relativeTo):
case LOCAL:
localTranslation = localTranslation * TranslationMatrix4(tv);
break;
case PARENT:
if parentNode != null:
localTranslation = parentNode.worldTranslation * localTranslation * TranslationMatrix4(tv);
else:
localTranslation = localTranslation * TranslationMatrix4(tv);
break;
case WORLD:
localTranslation = localTranslation * TranslationMatrix4(tv);
break;
Run Code Online (Sandbox Code Playgroud)
rotate(Angle angle, Vector3 axis, TransformSpace relativeTo):
switch (relativeTo):
case LOCAL:
localRotation = localRotation * RotationMatrix4(angle, axis);
break;
case PARENT:
if parentNode != null:
localRotation = parentNode.worldRotation * localRotation * RotationMatrix4(angle, axis);
else:
localRotation = localRotation * RotationMatrix4(angle, axis);
break;
case WORLD:
localRotation = localRotation * RotationMatrix4(angle, axis);
break;
Run Code Online (Sandbox Code Playgroud)
为了完整起见,this节点的世界变换计算如下:
if parentNode != null:
worldTranslation = parent.worldTranslation * localTranslation;
worldRotation = parent.worldRotation * localRotation;
worldScale = parent.worldScale * localScale;
else:
worldTranslation = localTranslation;
worldRotation = localRotation;
worldScale = localScale;
Run Code Online (Sandbox Code Playgroud)
此外,Node的完整/累积转换为this:
Matrix4 fullTransform():
Matrix4 localXform = worldTranslation * worldRotation * worldScale;
if parentNode != null:
return parent.fullTransform * localXform;
return localXform;
Run Code Online (Sandbox Code Playgroud)
当请求将节点的变换发送到OpenGL着色器均匀时,使用fullTransform矩阵.
Run Code Online (Sandbox Code Playgroud)worldTranslation = parentNode.worldTranslation * localTranslation; worldRotation = parentNode.worldRotation * localRotation; worldScale = parentNode.worldScale * localScale;
这不是连续变换的积累如何工作.如果你考虑它,它显而易见的原因.
假设您有两个节点:父节点和子节点.父母具有围绕Z轴的90度逆时针局部旋转.孩子的X轴偏移+5.那么,逆时针旋转应该使它在Y轴上有+5,是的(假设是右手坐标系)?
但事实并非如此.你localTranslation是永远不会影响任何形式的轮换.
所有转换都是如此.翻译受到影响只不被鳞片或旋转由翻译.轮换不受翻译的影响.等等.
这就是你的代码所要做的,而不是你应该怎么做.
保持矩阵的组件分解是个好主意.也就是说,具有单独的平移,旋转和缩放(TRS)组件是一个好主意.它使得以正确的顺序应用连续的局部变换变得更容易.
现在,将组件保持为矩阵是错误的,因为它确实没有意义,浪费时间和空间没有真正的理由.翻译只是一个vec3,并且通过存储13个其他组件没有任何好处.当您在本地累积翻译时,只需添加它们即可.
但是,当您需要累积节点的最终矩阵时,您需要将每个TRS分解转换为其自己的局部矩阵,然后将其转换为父级的整体转换,而不是父级的单个TRS组件.也就是说,您需要在本地组合单独的转换,然后将它们与父转换矩阵相乘.在伪代码中:
function AccumRotation(parentTM)
local localMatrix = TranslationMat(localTranslation) * RotationMat(localRotation) * ScaleMat(localScale)
local fullMatrix = parentTM * localMatrix
for each child
child.AccumRotation(fullMatrix)
end
end
Run Code Online (Sandbox Code Playgroud)
每个父母都将自己的累积轮换传递给孩子.根节点被赋予一个单位矩阵.
现在,TRS分解完全没问题,但只有在处理局部变换时才有效.也就是说,相对于父级的转换.如果要在其本地空间中旋转对象,请将四元数应用于其方向.
但是,在非本地空间中进行转换完全是另一回事.例如,如果您希望将世界空间中的转换应用于对其应用了一系列任意转换的对象......这是一项非常重要的任务.实际上,这是一项简单的任务:计算对象的世界空间矩阵,然后在其左侧应用平移矩阵,然后使用父对象的世界空间矩阵的逆来计算与父对象的相对转换.
function TranslateWorld(transVec)
local parentMat = this->parent ? this->parent.ComputeTransform() : IdentityMatrix
local localMat = this->ComputeLocalTransform()
local offsetMat = TranslationMat(localTranslation)
local myMat = parentMat.Inverse() * offsetMat * parentMat * localMat
end
Run Code Online (Sandbox Code Playgroud)
P -1 O P事物的含义实际上是一种常见的结构.它意味着将一般转变转变O为空间P.因此,它将世界偏移转换为父矩阵的空间.然后我们将其应用于我们的本地转型.
myMat现在包含一个变换矩阵,当乘以父变换时,它将被应用transVec,就像它在世界空间中一样.这就是你想要的.
问题是,myMat是一个矩阵,而不是一个TRS分解.你如何回到TRS分解?嗯......这需要非常重要的矩阵数学.它需要做一些叫做奇异值分解的东西.即使在实施丑陋的数学运算之后,SVD也会失败.可以使用不可分解的矩阵.
在我写的场景图系统中,我创建了一个特殊的类,它实际上是TRS分解与它所代表的矩阵之间的联合.您可以查询它是否已分解,如果是,您可以修改TRS组件.但是一旦你试图直接为它分配一个4x4矩阵值,它就变成了一个组合矩阵,你不能再应用局部分解变换了.我甚至都没有尝试过实施SVD.
哦,你可以累积矩阵.但是,任意变换的连续累积将不会产生与分解的组件修改相同的结果.如果要在不影响先前翻译的情况下影响旋转,则只能在类处于分解状态时执行此操作.
在任何情况下,您的代码都有一些正确的想法,但也有一些非常不正确的想法.您需要确定具有TRS分解的重要性与能够应用非本地变换的重要性.
我发现尼可波拉斯的回答有些帮助,尽管仍有一些细节我不太清楚。但这个回答帮助我认识到我正在解决的问题的重要性,所以我决定简化事情。
Node.TransformSpace为了简化问题,我删除了它。所有转换现在都相对于父级Node空间应用,并且一切都按预期进行。我打算在让事情开始工作后执行的数据结构更改(例如,替换简单向量的本地平移/缩放矩阵)现在也已到位。
更新后的数学摘要如下。
A的位置现在由一个对象Node表示,并且是按需构建的(见下文)。Vector3Matrix4
void translate(Vector3 tv /*, TransformSpace relativeTo */):
localPosition += tv;
Run Code Online (Sandbox Code Playgroud)
旋转现在包含在 a 中Matrix3,即 3x3 矩阵。
void rotate(Angle angle, Vector3 axis /*, TransformSpace relativeTo */):
localRotation *= RotationMatrix3(angle, axis);
Run Code Online (Sandbox Code Playgroud)
在验证我的四元数 <=> 矩阵转换正确之后,我仍然计划稍后查看四元数。
与 aNode的位置一样,缩放现在也是一个Vector3对象:
void scale(Vector3 sv):
localScale *= sv;
Run Code Online (Sandbox Code Playgroud)
以下内容更新 aNode的世界相对于其父级的变换Node(如果有)。这里的一个问题通过删除与父级完整转换的不必要的连接得到解决(请参阅原始帖子)。
void updateTransforms():
if parentNode != null:
worldRotation = parent.worldRotation * localRotation;
worldScale = parent.worldScale * localScale;
worldPosition = parent.worldPosition + parent.worldRotation * (parent.worldScale * localPosition);
else:
derivedPosition = relativePosition;
derivedRotation = relativeRotation;
derivedScale = relativeScale;
Matrix4 t, r, s;
// cache local/world transforms
t = TranslationMatrix4(localPosition);
r = RotationMatrix4(localRotation);
s = ScalingMatrix4(localScale);
localTransform = t * r * s;
t = TranslationMatrix4(worldPosition);
r = RotationMatrix4(worldRotation);
s = ScalingMatrix4(worldScale);
worldTransform = t * r * s;
Run Code Online (Sandbox Code Playgroud)