我有一个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。:考虑到表现会很棒。
答案 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;
现在,由于对父变换属性(本地或世界)的更改会影响子变换的世界属性,因此我需要更新子变换的世界属性。特别是,我需要更新子变换的scale
和position
属性(在此特定操作中旋转不受影响)。我们可以这样做:
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)
同样,没有必要查找父变换的世界范围。这可以根据子变换的世界尺度和局部尺度来计算。
在这个例子中,我处理了父变换规模的变化。尽管计算结果不同,但相同的原则适用于父母的位置和轮换的变化。