【发布时间】:2016-12-14 00:27:49
【问题描述】:
简短版 (TL;DR)
我有一个Camera 附加到一个SceneNode 并且只要SceneNode 的旋转/轴与世界对齐,运动就可以正常工作。但是,当一个对象旋转“看”到不同的方向并被告知要“向前”移动时,它不会沿着新的“向前”方向移动。相反,它会继续沿其在应用旋转之前所面对的方向移动。
细节和例子
我有一个场景图来管理 3D 场景。该图是SceneNode 对象的树,它们知道它们相对于其父对象和世界的转换。
根据 TL;DR; sn-p,假设您有一个零旋转的cameraNode(例如朝北),然后将cameraNode 围绕+Y“上”轴向左旋转90度,即让它看起来向西。到目前为止一切正常。如果您现在尝试将cameraNode“向前”移动到现在向西移动,那么cameraNode 会移动,就好像“向前”仍然朝北一样。
简而言之,它的移动就好像它从来没有被旋转过一样。
下面的代码显示了我最近尝试的内容以及我(当前)在缩小最可能与问题相关的区域范围内的最佳猜测。
相关SceneNode成员
SceneNode 实现具有以下字段(仅显示与此问题相关的字段):
class GenericSceneNode implements SceneNode {
// this node's parent; always null for the root scene node in the graph
private SceneNode parentNode;
// transforms are relative to a parent scene node, if any
private Vector3 relativePosition = Vector3f.createZeroVector();
private Matrix3 relativeRotation = Matrix3f.createIdentityMatrix();
private Vector3 relativeScale = Vector3f.createFrom(1f, 1f, 1f);
// transforms are derived by combining transforms from all parents;
// these are relative to the world --in world space
private Vector3 derivedPosition = Vector3f.createZeroVector();
private Matrix3 derivedRotation = Matrix3f.createIdentityMatrix();
private Vector3 derivedScale = Vector3f.createFrom(1f, 1f, 1f);
// ...
}
将Camera 添加到场景仅意味着它附加到图表中的SceneNode。由于Camera 没有自己的位置/旋转信息,客户端只需处理Camera 所附加的SceneNode 即可。
除了这个问题中提到的问题,其他一切似乎都按预期工作。
SceneNode翻译
将节点平移到特定方向的数学很简单,基本上可以归结为:
currentPosition = currentPosition + normalizedDirectionVector * offset;
SceneNode 实现如下:
@Override
public void moveForward(float offset) {
translate(getDerivedForwardAxis().mult(-offset));
}
@Override
public void moveBackward(float offset) {
translate(getDerivedForwardAxis().mult(offset));
}
@Override
public void moveLeft(float offset) {
translate(getDerivedRightAxis().mult(-offset));
}
@Override
public void moveRight(float offset) {
translate(getDerivedRightAxis().mult(offset));
}
@Override
public void moveUp(float offset) {
translate(getDerivedUpAxis().mult(offset));
}
@Override
public void moveDown(float offset) {
translate(getDerivedUpAxis().mult(-offset));
}
@Override
public void translate(Vector3 tv) {
relativePosition = relativePosition.add(tv);
isOutOfDate = true;
}
除此问题中提到的问题外,其他情况均符合预期。
SceneNode 旋转
客户端应用旋转cameraNode如下:
final Angle rotationAngle = new Degreef(-90f);
// ...
cameraNode.yaw(rotationAngle);
SceneNode 的实现也相当简单:
@Override
public void yaw(Angle angle) {
// FIXME?: rotate(angle, getDerivedUpAxis()) accumulates other rotations
rotate(angle, Vector3f.createUnitVectorY());
}
@Override
public void rotate(Angle angle, Vector3 axis) {
relativeRotation = relativeRotation.rotate(angle, axis);
isOutOfDate = true;
}
旋转的数学/代码封装在一个 3x3 矩阵对象中。请注意,在测试期间,您可以看到场景围绕相机旋转,因此确实应用了旋转,这让我更加困惑这个问题。
方向向量
方向向量只是从派生的 3x3 旋转矩阵中提取的列,相对于世界:
@Override
public Vector3 getDerivedRightAxis() {
return derivedRotation.column(0);
}
@Override
public Vector3 getDerivedUpAxis() {
return derivedRotation.column(1);
}
@Override
public Vector3 getDerivedForwardAxis() {
return derivedRotation.column(2);
}
计算派生变换
如果相关,这就是 parentNode 转换组合以计算 this 实例的派生转换的方式:
private void updateDerivedTransforms() {
if (parentNode != null) {
/**
* derivedRotation = parent.derivedRotation * relativeRotation
* derivedScale = parent.derivedScale * relativeScale
* derivedPosition = parent.derivedPosition + parent.derivedRotation * (parent.derivedScale * relativePosition)
*/
derivedRotation = parentNode.getDerivedRotation().mult(relativeRotation);
derivedScale = parentNode.getDerivedScale().mult(relativeScale);
Vector3 scaledPosition = parentNode.getDerivedScale().mult(relativePosition);
derivedPosition = parentNode.getDerivedPosition().add(parentNode.getDerivedRotation().mult(scaledPosition));
} else {
derivedPosition = relativePosition;
derivedRotation = relativeRotation;
derivedScale = relativeScale;
}
Matrix4 t, r, s;
t = Matrix4f.createTranslationFrom(relativePosition);
r = Matrix4f.createFrom(relativeRotation);
s = Matrix4f.createScalingFrom(relativeScale);
relativeTransform = t.mult(r).mult(s);
t = Matrix4f.createTranslationFrom(derivedPosition);
r = Matrix4f.createFrom(derivedRotation);
s = Matrix4f.createScalingFrom(derivedScale);
derivedTransform = t.mult(r).mult(s);
}
这用于通过场景图传播变换,以便子 SceneNodes 可以将其父变换考虑在内。
其他/相关问题
在发布此问题之前的最后约 3 周内,我已经在 SO 内部和外部完成了多个答案(例如 here、here、here 和 here 等)。显然,虽然相关,但它们对我的情况确实没有帮助。
评论中的问题解答
您确定在计算
derivedTransform时您父母的derivedTransform已经计算了吗?
是的,父 SceneNode 总是在更新子之前更新。 update 的逻辑是:
@Override
public void update(boolean updateChildren, boolean parentHasChanged) {
boolean updateRequired = parentHasChanged || isOutOfDate;
// update this node's transforms before updating children
if (updateRequired)
updateFromParent();
if (updateChildren)
for (Node n : childNodesMap.values())
n.update(updateChildren, updateRequired);
emitNodeUpdated(this);
}
@Override
public void updateFromParent() {
updateDerivedTransforms(); // implementation above
isOutOfDate = false;
}
这部分调用了上一节中的私有方法。
【问题讨论】:
-
您使用的是哪个版本的 OpenGL?
-
@FrancisCugler 我正在使用 OpenGL 4.5。你需要我发布着色器吗?
-
否;只是想知道您使用的是现代 OpenGL 还是旧的 1.0 API 调用。我不熟悉 Java,但我一直在使用 c++ 中的 OpenGL。我知道 OpenGL 没有相机对象,您必须创建一个并将其附加到场景中。然后,一旦你渲染它,它的移动和旋转取决于你定义的坐标系的惯用手。您的 MVP(模型视图投影)矩阵计算很重要;旋转平移和缩放的顺序也很重要。
-
@FrancisCugler 我明白了。
Camera和SceneNode对象是我定义的。即使您不熟悉 Java,您也熟悉 C++ 和 OpenGL,因此您可能已经了解上述代码的语义(无运算符重载)。您可能仍然能够注意到我错过的一些东西(例如数学或其他东西)。如果您希望我添加更多信息,请告诉我具体内容。 -
您是否尝试过单独渲染相机而不是将其节点嵌套在场景节点下?