Rotation of View Camera around X Axis produces extremely weird behavior

Started by Xirema, June 26, 2015, 14:28:20

Previous topic - Next topic

Xirema

I'm getting extremely inconsistent behavior when modifying my view matrix. When I try to rotate the camera around a fixed point, everything seems to render fine when rotating around the Y or Z axis', but when I rotate around the X Axis, the objects recede into the distance and eventually vanish.

So I'm defining my Projection, View, and Model matrices like so:

//I'm using the JOML library for Matrices/Vectors: http://forum.lwjgl.org/index.php?topic=5695.0
//https://github.com/JOML-CI/JOML
projection.setPerspective(45, (float)aspectRatio, 0.1f, 1000);

eye.set((float)(90*sin(rotation[1])+xCenter),(float)(90*sin(rotation[0])+yCenter),(float)(90*cos(rotation[0])*cos(rotation[1])+zCenter));
center.set((float)(0+xCenter),(float)(0+yCenter),(float)(0+zCenter));
up.set((float)sin(rotation[2]),(float)cos(rotation[2]),0);

view.setLookAt(eye, center, up);
model.identity();
model.translate((float)x,(float)y,(float)z);
model.scale((float)r);


My shader, for reference:

//VERTEX SHADER
#version 400 core
layout(location = 0) in vec3 vertex;
uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;

out float shading;

void main() {
	vec4 v = vec4(vertex, 1.0);
	gl_Position = projection * view * model * v;
	shading = (v.y+1)/2;
}

//FRAGMENT SHADER
#version 400 core
uniform vec4 orbColor;

in float shading;

out vec3 color;

void main() {
	color = orbColor.xyz*(0.3 + shading*0.7);
}


When I view the Render without any rotations, everything looks fine (or at least exactly as I expected it to look):



If I rotate around the Y Axis, it also looks correct:



And rotations around the Z Axis also look correct:



But when I rotate around the X-Axis, the objects seem to recede into the distance.



And if I rotate all the way to 90 degrees, the objects vanish entirely.



The Y-rotation is most interesting to me: I don't understand why the Y-rotation produces the correct results, but the X rotation does not: what's different between the two, and what do I need to do to fix this issue?

Kai

First, thanks for giving JOML a try! :)

You seem to be doing the euler rotation computations all by yourself, which with JOML luckily is unnecessary ;). The reason why it is not working for you, is that you are not applying all euler rotations to all vector components.
But to make things short, using JOML it is plain simple to do such an "arcball" camera (which you seem to be trying to do yourself with custom euler rotations), like so.

Just use the following for your view/camera matrix:

float distance = 90.0f;
Matrix4f view = new Matrix4f();
...
view.translation(0, 0, -distance)
    .rotateX(degreesX)
    .rotateY(degreesY)
    .translate(-center.x, -center.y, -center.z);


The above will initialize your "view" matrix by setting it 'distance' units away from the center and rotating arbitrarily around the center, while keeping the camera's 'up' vector to be co-planar to the plane defined by 'direction' (center - eye) and (0, 1, 0).
You see that it actually does not use lookAt, since it computes its orthonormal basis from simple rotations. LookAt is not needed there.

Please update to the latest JOML head before using the above function, since Matrix4f.translation had an issue which is solved now.

You can also have a look at the side-project joml-camera, which already implements an arcball camera. You can also see that camera in action in this demo in the joml-lwjgl3-demos repository.

I know, that some kind of math tutorial for 3D transformations would be some vital thing to have. I will put that on the agenda for JOML. Even though that does not apply to JOML alone (because JOML just enables Java users with some interface to use those math functions), I guess JOML users would benefit from that.

Xirema

Hmm. Using the code you provided without updating JOML created some interesting graphical glitches.  ;D

This works perfectly, after I added .rotateZ(float) to the conversions:

view.identity();
view.translation(0, 0, -90)
.rotateX((float)toDegrees(rotation[0]))
.rotateY((float)toDegrees(rotation[1]))
.rotateZ((float)toDegrees(rotation[2]))
.translate((float)-xCenter, (float)-yCenter, (float)-zCenter);


Your comment about the eye needing to be coplanar to the direction makes sense; now only if even a SINGLE resource I studied had explained that to me....................  >:(


Kai

Glad it works for you now! :)
Ah, you also wanted the camera to rotate about itself (its own z-axis), which I guess is called "rolling" in aviation :).
Yes, then you need rotateZ.
I was just assuming that you wanted an arcball camera which does not rotate about its own view axis, but tries to keep its own 'up' vector to be coplanar to the plane spanned by 'direction' and (0, 1, 0). And with those, only two rotation angles are needed.

EDIT: Also note that when using rotateZ like you did, this transformation will be applied first! So your camera would now rotate about the world's Z axis and not about its own local frame of reference after the X- and Y-rotations are being applied. If you want the camera to always "tilt" to the same side regardless of the other rotations, you need to apply rotateZ as first rotation after the initial translation.
There is also an inherent problem with euler angles, if you apply all rotations about all axes, since then any two rotations will be in the local frame of the previous rotation, which in some cases can cause to the popular "gimbal lock" problem.
You might want to have a look at the Quaternionf class to avoid that.

EDIT 2: Also you do not have to identity() the matrix if you use translation() afterwards. translation() will reset the matrix to a purely translation matrix. The method translate() (note the verb-form of the method) on the other hand will apply a translation to an existing matrix, and that therefore would need an identity() if you do not want that.

Kai

Hey Xirema,

the observed behaviour from your original code could actually have been caused by a bug (https://github.com/JOML-CI/JOML/issues/26) in JOML after all!
Sorry about that!

The setLookAt and all other lookAt and lookAlong methods actually did have an issue, which was very hard to track down, but is now fixed.
If you could try out your original code with the latest JOML head, that'd be really great!

Xirema

Quote from: Kai on June 26, 2015, 21:52:11
Hey Xirema,

the observed behaviour from your original code could actually have been caused by a bug (https://github.com/JOML-CI/JOML/issues/26) in JOML after all!
Sorry about that!

The setLookAt and all other lookAt and lookAlong methods actually did have an issue, which was very hard to track down, but is now fixed.
If you could try out your original code with the latest JOML head, that'd be really great!

My original code works /better/ after the fix, but not still the way I intended it to work (which is much more correct with the code you gave me).

The main thing that happens is that when the scene is rotated past 90 degrees around the x axis, the image suddenly inverts itself. I don't think that's a problem with your code, though: I could probably fix that by making the up-vector reorient itself, but honestly, that's a lot of work finding a solution for a problem I already have a better solution to.  ;D

Quote from: Kai on June 26, 2015, 16:13:25
EDIT: Also note that when using rotateZ like you did, this transformation will be applied first! So your camera would now rotate about the world's Z axis and not about its own local frame of reference after the X- and Y-rotations are being applied. If you want the camera to always "tilt" to the same side regardless of the other rotations, you need to apply rotateZ as first rotation after the initial translation.
There is also an inherent problem with euler angles, if you apply all rotations about all axes, since then any two rotations will be in the local frame of the previous rotation, which in some cases can cause to the popular "gimbal lock" problem.
You might want to have a look at the Quaternionf class to avoid that.

Good to know. For my specific use-case, the current code is fine.