3D 矩阵:绝对到相对转换,反之亦然

Posted

技术标签:

【中文标题】3D 矩阵:绝对到相对转换,反之亦然【英文标题】:3D matrices: absolute to relative and vice-versa transformations 【发布时间】:2014-12-18 16:23:17 【问题描述】:

我有一个 OpenGL C# 项目,我想提供像 Unity3D 游戏引擎这样的功能。

简介: 我有 Transform 类,它为着色器提供转换矩阵。每个变换都可以有父变换。计算最终变换矩阵的代码如下所示:

public Vector3 LocalPosition  get; set; 
public Quaternion LocalRotation  get; set; 
public Vector3 LocalScale  get; set; 

public Matrix GetModelMatrix() 
    Matrix result;
    if(HasParent)
        result = Parent.GetModelMatrix();
    else
        result = Matrix.CreateIdentity();

    ApplyLocalTransformations(result);
    return result;    

private void ApplyLocalTransform(Matrix matrix)

    matrix.Translate(LocalPosition);
    matrix.Rotate(LocalRotation);
    matrix.Scale(LocalScale);

如您所见,LocalPosition、LocalScale 和 LocalRotation 是相对于父级的转换。 此代码运行良好。

问题: 我想再添加 3 个属性(你好 Unity3D):

public Vector3 AbsolutePosition  get; set; 
public Quaternion AbsoluteRotation  get; set; 
public Vector3 AbsoluteScale  get; set; 

我希望能够获取和设置子变换的绝对变换。在设置绝对值时,本地应该不断更新,反之亦然。

示例: 我们的父节点位于 (1, 1, 1) 位置,子节点的 LocalPosition = (0, 0, 0),有了这些信息,我们可以计算子节点的 AbsolutePosition = (1, 1 , 1)。 现在我们设置孩子的AbsolutePosition = (0, 0, 0)。它的 LocalPosition 现在将是 = (-1, -1, -1)。 这是一个非常简单的例子,在实际场景中我们必须考虑父母的缩放和旋转来计算位置。

如何计算绝对位置和局部位置我有一个想法: 我可以从转换矩阵中取出最后一列,它将是我的 AbsolutePosition。要获得 LocalPosition,我可以从 AbsolutePosition 中减去父变换矩阵的最后一列。但是旋转和缩放背后的数学对我来说仍然不清楚。

问题: 你能帮我计算局部和绝对位置、旋转和比例的算法吗?

P.S.:考虑到性能会很棒。

【问题讨论】:

【参考方案1】:

我已经处理了这个确切的问题。解决它的方法不止一种,所以我只会给你我想出的解决方案。简而言之,我将位置、旋转和比例存储在本地和世界坐标中。然后我计算 deltas 以便我可以将在一个坐标空间中所做的更改应用到另一个。

最后,我使用事件将增量广播到所有下降的游戏对象。事件不是绝对必要的。您可以递归地在下降的游戏对象的变换组件上调用一些函数,以便将增量应用到游戏对象树中。

此时最好举个例子,所以看看这个用于变换本地位置的setter方法(我从very small game that I worked on中提取):

void Transform::localPosition(const Vector3& localPosition)

    const Vector3 delta = localPosition - m_localPosition;
    m_localPosition = localPosition; // Set local position.
    m_position += delta; // Update world position.

    // Now broadcast the delta to all descended game objects.

所以这是微不足道的。世界位置的设置器类似:

void Transform::position(const Vector3& position)

    const Vector3 delta = position - m_position;
    m_position = position; // Set world position.
    m_localPosition += delta; // Update local position.

    // Now broadcast the delta to all descended game objects.

旋转原理同理:

void Transform::localRotation(const Quaternion& localRotation)

    const Quaternion delta = m_localRotation.inverse() * localRotation;
    m_localRotation = localRotation; // Set the local orientation.
    m_rotation = delta * m_rotation; // Update the world orientation.

    // Now broadcast the delta to all descended game objects.


void Transform::rotation(const Quaternion& rotation)

    const Quaternion delta = m_rotation.inverse() * rotation;
    m_rotation = rotation; // Set the world orientation.
    m_localRotation = delta * m_localRotation; // Update the local orientation.

    // Now broadcast the delta to all descended game objects.

最后是缩放:

void Transform::localScale(const Vector3& scale)

    const Vector3 delta = scale - m_localScale;
    m_localScale = scale; // Set the local scale.
    m_scale += delta; // Update the world scale.

    // Now broadcast the delta to all descended game objects.


void Transform::scale(const Vector3& scale)

    const Vector3 delta = scale - m_scale;
    m_scale = scale; // Set the world scale.
    m_localScale += delta; // Update the local scale.

    // Now broadcast the delta to all descended game objects.

我不确定您如何从性能角度改进这一点。计算和应用增量相对便宜(肯定比分解转换矩阵便宜得多)。

最后,由于您正在尝试模拟 Unity,您可能想看看我的 small c++ mathematics library,它以 Unity 的数学课程为蓝本。

计算示例

所以我在原始答案中遗漏了很多细节,这似乎引起了一些混乱。我在下面提供了一个详细示例,该示例遵循使用增量(如上所述)的概念来响应 Xorza 的评论。

我有一个有一个孩子的游戏对象。我将这些游戏对象分别称为 parentchild。它们都具有默认比例 (1, 1, 1) 并位于原点 (0, 0, 0)。

请注意,Unity 的 Transform 类不允许写入 lossyScale(世界比例)属性。因此,按照 Unity 提供的行为,我将处理对父变换的 localScale 属性的修改。

首先,我打电话给parent.transform.setLocalScale(0.1, 0.1, 0.1)

setLocalScale 函数将新值写入localScale 字段,然后按如下方式计算缩放增量:

scalingDelta = newLocalScale / oldLocalScale
             = (0.1, 0.1, 0.1) / (1, 1, 1)
             = (0.1, 0.1, 0.1)

我们使用这个缩放增量来更新变换的世界 scale 属性。

scale = scalingDelta * scale;

现在,由于父变换属性(本地或世界)的更改会影响子变换的世界属性,我需要更新子变换的世界属性。特别是,我需要更新子变换的scaleposition 属性(在此特定操作中不影响旋转)。我们可以这样做:

child.transform.scale = scalingDelta * child.transform.scale
                      = (0.1, 0.1, 0.1) * (1, 1, 1)
                      = (0.1, 0.1, 0.1)

child.transform.position = parent.transform.position + scalingDelta * child.transform.localPosition
                         = (child.transform.position - child.transform.localPosition) + scalingDelta * child.transform.localPosition
                         = ((0, 0, 0) - (0, 0, 0)) + (0.1, 0.1, 0.1) * (0, 0, 0)
                         = (0, 0, 0)

请注意,如果您使用事件将增量向下传递到游戏对象树,则访问父变换的位置会很困难。但是,由于child.transform.position = parent.transforn.position + child.transform.localPosition,我们可以根据子变换的世界位置和本地位置来计算父变换的世界位置。

另外,重要的是,请注意子变换的本地属性没有改变。

其次,我打电话给child.transform.setPosition(1, 1, 1)

setPosition 函数将新值写入position,然后计算转换增量如下:

translationDelta = newPosition - oldPosition
                 = (1, 1, 1) - (0, 0, 0)
                 = (1, 1, 1)

最后,setPosition 函数使用计算出的增量更新变换的localPosition。但是,请注意,计算出的平移增量位于世界空间坐标中。所以在更新localPosition之前,我们需要做一些工作将其转换为局部空间坐标。特别是,我们需要考虑父变换的世界比例。

localPosition = localPosition + translationDelta / parent.transform.scale
              = localPosition + translationDelta / (scale / localScale)
              = localPosition + translationDelta * (localScale / scale)
              = (0, 0, 0) + (1, 1, 1) * ((1, 1, 1,) / (0.1, 0.1, 0.1))
              = (10, 10, 10)

同样,不必查找父变换的世界比例。这可以从子变换的世界比例和局部比例计算得出。

在这个例子中,我处理了父变换比例的变化。相同的原则适用于父位置和旋转的更改,尽管计算会有所不同。

【讨论】:

如何在不考虑父级旋转和缩放的情况下计算位置和本地位置?如果父母的比例是例如(0.1 0.1 0.1),那么将孩子的位置设置为(1 1 1)应该会导致localPosition =(10 10 10)类似的旋转 localPosition 是相对于父游戏对象的变换,所以我们不应该在父对象被平移、旋转或缩放时更新它。但是,世界位置position 确实需要更新。如前所述,我使用事件将增量广播到子游戏对象。所以我将position 旋转为从AncestorTransformRotated 事件收到的增量。如果没有事件,您只需执行child.transform.position = rotate(child.transform.position, parent_rotation_delta) 之类的操作。同样的想法也适用于规模。 @xorza 我的原始答案跳过了很多细节。这可能是让你感到困惑的地方。我添加了一个详细的示例,该示例使用我描述的方法计算您的示例。

以上是关于3D 矩阵:绝对到相对转换,反之亦然的主要内容,如果未能解决你的问题,请参考以下文章

将 Numpy 数组“转换”为 Matlab,反之亦然

将 vector<Point3d> 转换为大小为 (n x 3) 的 Mat,反之亦然

QT中的相对位置,绝对位置之间的转换(maptoglobal,mapfromglobal)

引用外部文件时将绝对目录转换为相对路径

使用 OpenCV 将 RGB 图像转换为 LMS,反之亦然

将相对 URL 转换为绝对 URL