1

我的 OpenGL 渲染器中有一个相机对象。它工作正常。但是,我需要将其作为父节点的父节点,以便父节点可以像在 Adob​​e AfterEffects 中使用 Null 对象那样操作相机。如果您不熟悉 AE,那么这里是它的工作原理。Null Object 是一个空容器。如果相机是它的父对象并且对象本身位于目标位置,那么当空对象旋转时,在目标上具有其兴趣点(又名lookAt)的相机将围绕目标定向。这是问题的核心。在我的实现中,当我旋转将相机作为子相机并位于目标位置的父级时,相机不会保持锁定在父级的位置,但它的外观方向也会发生变化。这是描述该问题的屏幕截图: 在此处输入图像描述

左边的截图是错误的行为:相机的父级在中心,但相机的方向是旋转而不是相机。在右侧的屏幕截图中,它应该是这样以及它在 AE 中的工作方式:旋转 Null 对象会围绕空对象中心轴旋转相机。我确信我在这里做了一些愚蠢的错误矩阵顺序的事情。所以这是我在代码中的做法:

我像这样计算相机的lookAt矩阵:

 public void lookAt(float eyeX, float eyeY, float eyeZ, float centerX, float centerY, float centerZ, Vec3 upPt) {

    _eye.set(eyeX, eyeY, eyeZ);
    _center.set(centerX, centerY, centerZ);
    _up = upPt;

    _direction = Vec3.sub(_center, _eye).normalize();

    _viewMatr = Glm.lookAt(_eye, _center, _up);

    _transform.setModel(Mat4.mul(rotMat, _viewMatr));
///rotMat is rotation matrix cached from rotation method call.

 }

Glm:lookAt 是来自 C++ GLM 数学库的端口,它看起来像这样:

 public static Mat4 lookAt(Vec3 eye, Vec3 center, Vec3 up) {
    Vec3 f = normalize(Vec3.sub(center, eye));
    Vec3 u = normalize(up);
    Vec3 s = normalize(cross(f, u));
    u = cross(s, f);

    Mat4 result = new Mat4(1.0f);
    result.set(0, 0, s.x);
    result.set(1, 0, s.y);
    result.set(2, 0, s.z);
    result.set(0, 1, u.x);
    result.set(1, 1, u.y);
    result.set(2, 1, u.z);
    result.set(0, 2, -f.x);
    result.set(1, 2, -f.y);
    result.set(2, 2, -f.z);

    return translate(result, new Vec3(-eye.x,-eye.y,-eye.z));
  }

这是第一部分,我在其中创建相机“模型”矩阵。下一步是创建世界矩阵,同时考虑到相机的父节点:

  Mat4 world=   Mat4.mul( this.getTransform().parentMatr, this.getTransform().getModel());


    this.getTransform().setView(world);

稍后在管道中,每个将要渲染的几何对象访问相机的视图矩阵(我刚刚使用 setView 设置),并计算模型、视图、投影矩阵,然后将其发送到顶点着色器。

奇怪的是,如果我在将世界矩阵传递给setView()方法之前反转世界矩阵并且不否定 GLM 中的眼睛矢量,那么它可以工作!但在这种情况下,它在相机没有父级的模式下不起作用。请不要建议我使用 OpenGL 4.0 Core 的基于固定管道的解决方案。

4

1 回答 1

1

对于数学,请阅读以下内容。对于更实用的方法,请直接走到最后。

您面临的问题是,仿射变换矩阵有效地描述了另一个笛卡尔坐标系中的笛卡尔坐标系。因此,通过将它们链接在一起,您总是相对于父级工作。现在,如果您将“相机”(OpenGL 当然没有相机,但为了本文的目的,让我们假设 alookAt是相机)作为某个坐标系的父级,则所有变换都相对于该坐标系。因此, AlookAt在该局部坐标系的框架中运行。

让我们在数学上分解一下:

有一个全局坐标空间,我们称之为世界。由于世界是一种中心轴心点,因此不会对世界空间中的坐标应用任何变换,即变换是恒等式,或者简称为 I。

通过将整个世界移动到另一个位置来实现“相机”。在 OpenGL 中没有相机,但视点始终位于原点= (0,0,0)。这可以描述为将整个世界转换为一个位置,以便视点最终位于原点。假设 V^-1 描述了在世界原点的相机到所需位置的变换,然后是 V^-1 的倒数,即

V^-1^-1 = V

是视图变换。现在在我们的例子中,具有相对变换 L 的相机是其他对象的父对象,该变换由 F 描述,因此总视图变换由

V^-1 = F·L

L 是变换lookAt产生的倒数。现在问题来了:lookAt在空间 F 内操作,这意味着传递给它的所有向量都必须与 F 相关。所以你实际上也为了这个工作而反转了 F,所以

V^-1 = (L^-1 · F^-1)^-1

现在假设您有另一个超出 M 范围的对象,您想查看它。它由相对于世界的 G 来描述。要查看这个对象,我们必须知道它相对于 F 的位置。很容易。我们先从G“前进”到世界,然后“后退”到G,即(F^-1)^-1·G=F·G

假设对象 G 以原点为中心,您必须评估 F · G · (0,0,0,1),归结为用 F 转换 G 的第 4 列。结果就是你应该使用的结果的目标位置lookAt。这就是我将对象矩阵放入其中的意思。


然而,整个方法可能会因复杂的转换链而中断。我建议一些更简单的方法:只需将相机和目标对象的位置转换为世界坐标并将其应用于lookAt世界空间。

于 2012-12-26T18:09:32.317 回答