问题
I've spent a couple weeks on this issue and can't seem to find a proper solution and need some advice.
I'm working on creating a Camera class using LWJGL/Java, and am using Quaternions to handle bearing (yaw), pitch and roll rotations. I'd like this camera to handle all 6 degrees of movement in 3D space, and roll. Bearing, Pitch and Roll are all quaternions. I multiply them into a 'change' quaternion, and create a translation matrix from that. I put that in a float buffer, and multiply the modelview matrix by my buffer containing the rotation matrix.
I can get the bearing and pitch rotations to work properly, but when I implement roll, I'm running into issues. Mainly, rotating around the Z-axis (rolling) doesn't seem to work. When ever I "roll" the camera, it seems to roll around the global Z axis instead of the local camera direction axis. I can usually get 2 of the 3 to work depending on the order I multiply the quaternions, but I can't get them working together.
Since they all work independently, I'm assuming there's something wrong with my orientation method where I combine them and build a rotation matrix. I'm having problems pasting the whole class in, so here are the methods and declarations relating to rotation:
private final static float DEGTORAD = (float)(Math.PI/180);
//Eye - position of the camera in the 3D world.
private Vector3f eye;
//Camera axis vectors, calculated each time reorient() is called.
//Initialized to global x, y, and z axis initially.
private Vector3f up;
private Vector3f right;
private Vector3f direction;
//Angles of rotation (in degrees)
private float pitchAngle;
private float bearingAngle;
private float rollAngle;
private Quaternion pitch;
private Quaternion bearing;
private Quaternion roll;
private FloatBuffer viewMatrixBuffer = BufferUtils.createFloatBuffer(16);
private Quaternion currentOrientation;
...
/**
* Change the bearing (yaw)
* @param bearing delta in degrees
*/
public void bearing(float bearingDelta){
bearingAngle += bearingDelta;
if(bearingAngle > 360){
bearingAngle -= 360;
}else if(bearingAngle < 0){
bearingAngle += 360;
}
bearing.setFromAxisAngle(new Vector4f(0f, 1f, 0f, bearingAngle * DEGTORAD));
bearing.normalise();
}
/**
* Change the pitch
* @param pitch delta in degrees
*/
public void pitch(float pitchDelta){
pitchAngle += pitchDelta;
if(pitchAngle > 360){
pitchAngle -= 360;
}else if(pitchAngle < 0){
pitchAngle += 360;
}
pitch.setFromAxisAngle(new Vector4f(1f, 0f, 0f, pitchAngle * DEGTORAD));
pitch.normalise();
}
/**
* @param initialRoll
*/
public void roll(float initialRoll) {
rollAngle += initialRoll;
if(rollAngle > 360){
rollAngle -= 360;
}else if(rollAngle < 0){
rollAngle += 360;
}
roll.setFromAxisAngle(new Vector4f(0, 0, 1, rollAngle * DEGTORAD));
roll.normalise();
}
/**
* Change direction to focus on a certain point in the world
* @param eye
*/
public void lookThrough(){
reorient();
GL11.glMultMatrix(viewMatrixBuffer);
}
public void reorient(){
//Multiply in order: bearing, pitch, roll. Non-commutative!
Quaternion change = new Quaternion();
Quaternion.mul(bearing, pitch, change);
Quaternion.mul(roll, change, change);
// orient the camera...
Matrix4f rotationMatrix = getRotationMatrix(change);
//Get the looking direction
direction.x = rotationMatrix.m20;
direction.y = rotationMatrix.m21;
direction.z = rotationMatrix.m22;
//Set the position
rotationMatrix.m30 = eye.x;
rotationMatrix.m31 = eye.y;
rotationMatrix.m32 = eye.z;
rotationMatrix.m33 = 1;
rotationMatrix.invert();
rotationMatrix.store(viewMatrixBuffer);
viewMatrixBuffer.rewind();
Vector3f.cross(new Vector3f(0,1,0), direction, null).normalise(right);
Vector3f.cross(right, direction, null).normalise(up);
}
Vector3f, Quaternion, and Matrix4f are all LWJGL classes, not custom made.
So my question is, given 3 Quaternions representing Bearing, Pitch and Roll, how do I modify the ModelView matrix to accurately represent these rotations?
EDIT: I feel that this is very close. See the Gist link in RiverC's comment. After rotating so many degrees, the view jumps around a lot before coming back to normal when rolling. The gist of it is there, but it's still slightly off.
回答1:
You're doing the multiplications in the wrong order.
For two rotations q1 and q2, if q2 is to follow q1 (since rotations are generally non-communitive) you multiply q2*q1.
In a gimbal style system such as controls for a FPS, the priority order is always yaw, pitch, roll. This would suggest the following math:
roll * pitch * yaw
As a java point, I would suggest not creating new Quaternions for every update but anyway
change = Quaternion.mul(Quaternion.mul(roll, pitch, change), yaw, change);
If you look at the code, the third Quaternion will just be overwritten with the result, so no need to reset it or recreate it each frame / update.
This rotational order thing is confusing, but if you look at the Wiki page on Quaternions, it's the general rule.
回答2:
I know this is old, but allow me a guess anyway. The problem is exactly what you said yourself:
When ever I "roll" the camera, it seems to roll around the global Z axis instead of the local camera direction axis.
It does so because you asked it to roll around vector (0,0,1), that is, the global Z axis.
This is what unit quaternions do: they rotate a vector (here a set of vectors, your rotation matrix) around an axis specified by their imaginary vectorial part (x,y,z) by some angle function of the w scalar (w = cos(angle/2)).
If I understand what you are trying to do, that is rolling your camera as when tilting your head from left to right, then you should build a roll quaternion around your direction vector:
roll.setFromAxisAngle(new Vector4f(direction.x, direction.y, direction.z, rollAngle * DEGTORAD));
I'm assuming your direction vector is normalized, or that LWJGL knows what to do with a non-unitary axis vector when calling setFromAxisAngle.
来源:https://stackoverflow.com/questions/7772160/lwjgl-problems-implementing-roll-in-a-6dof-camera-using-quaternions-and-a-tr