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

时间:2014-12-18 16:23:17

标签: opengl matrix linear-algebra matrix-multiplication

我有一个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个属性(hello Unity3D):

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

我希望能够获得并设置子转换的绝对转换。设置绝对值Local时应该一致地更新,反之亦然。

示例:我们在父级位置(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 个答案:

答案 0 :(得分:1)

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

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

最好在这一点上给出一个例子,所以看看这个变换器本地位置的setter方法(我已从very small game that I worked on取出):< / p>

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的评论。

我有一个有一个孩子的游戏对象。我将这些游戏对象分别称为 parent child 。它们都有默认比例(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函数使用计算的delta更新转换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)

同样,没有必要查找父变换的世界范围。这可以根据子变换的世界尺度和局部尺度来计算。

在这个例子中,我处理了父变换规模的变化。尽管计算结果不同,但相同的原则适用于父母的位置和轮换的变化。