1
votes

I have a camera object in my OpenGL renderer. It works fine. However I need to parent it to a parent node so that the parent can manipulate camera as it is done in Adobe AfterEffects with Null Object. If you are not familiar with AE, so here is how it works. Null Object is an empty container. If the camera is parented to it and the object itself is located at the target position then the camera, having its point of interest (aka lookAt) at the target would orient around the target when null object is rotated. That is the core of the problem.In my implementation, when I rotate the parent, which has the camera as child and is located at target's position, the camera doesn't remain locked at the parent's position but its lookAt direction changes too. Here is the screenshot depicting the issue: enter image description here

At the left screenshot is the wrong behavior:Camera's parent is at the center but the camera's direction is rotating instead of the camera. At the right screenshot that is how it should be and how it works in AE: rotating the Null Object rotates the camera around the null object center axis. I am sure I do some stupid wrong matrix order thing here. So here is how I do in the code:

I calculate camera's lookAt matrix like this:

 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.

 }

The Glm:lookAt is the port from C++ GLM math lib and it looks like this:

 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));
  }

So that is the first part , where I create camera "model" matrix.Nex step is create the world matrix taking into account camera's parent node:

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


    this.getTransform().setView(world);

Later in the pipeline, view matrix of the camera (which I just have set with setView) is accessed by each geometry object which going to be rendered, and model, view, projection matrix is calculated and then sent to vertex shader.

The strange thing is, if I invert world matrix before passing it into setView() method and don't negate eye vector in GLM, then it works!But in such a case it doesn't work in a mode where the camera is not parented. Please don't suggest Fixed pipeline based solutions I work with OpenGL 4.0 Core.

1

1 Answers

1
votes

For the math read the following. For a more practical approach, go right to the end.

The problem you are facing is, that affine transformation matrices effectively describe a cartesian coordinate systems within another cartesian coordinate system. So by chaining them together, you're always working relative to the parent. Now if you parent a "camera" (OpenGL has no camera of course, but for the sake of this text let's assume that a lookAt is a camera) to some coordinate system, all the transformations are relative to this coordinate system. A lookAt hence operates in the frame of this local coordinate system.

Let's break this down mathematically:

There is a global coordinate space, we call the world. Since the world is kind of the central pivot point no transformations is applied to coordinates in world space, i.e. the transform is identity, or simply I.

A "camera" is implemented by moving the whole world into another position. In OpenGL there's no camera, but the viewpoint is always at the origin = (0,0,0). This can be described as a transformation of the whole world into a position so that the viewpoint ends up in the origin. Let's say V^-1 describes the transformation of a camera at the origin of the world to the desired position, then the inverse of V^-1, i.e.

V^-1^-1 = V

is the view transformation. Now in our case the camera with relative transformation L is parented to some other object, which transformation is described by, let's say F, So the total view transformation is described by

V^-1 = F · L

L is the inverse of the very transform lookAt produces. Now here's the problem: lookAt operates within the space F, which means that all the vectors passed to it must be relative to F. So you've actually invert F as well for this wo work, so

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

Now let's say you've got another object outside of the scope of M, which you'd like to look at. It's described by, let's say G relative to the world. To look at this object we have to know it's position with respect to F. Easy enough. We first "forward" go from G to world and then "backward" to G, i.e. (F^-1)^-1 · G = F · G

Assuming that object G is centered around the origin, you've to evaluate F · G · (0,0,0,1), which boils down to transforming the 4th column of G with F. The result is what you should use as target position for the lookAt. That's what I meant with put the objects matrix into it.


However this whole approach may break with complicated transformation chains. I suggest something far simpler: Just transform the camera's and target object's positions into world coordinates and apply the lookAt on the world space.