How to correctly implement a quaternion camera in modern opengl?
I am trying to create a uvn quaternion based camera in opengl, having used a variety of tutorials listed below, and having read up on quaternions and axis angle rotation. I am left with a peculiar bug which I cannot seem to fix.
Basically the camera seems to work fine up until the camera is rotated approx 45 degrees from +z at this point tilting the camera up or down seems to tilt the camera around its target axis, turning the up vector.
By the time the camera faces along -z tilting up or down gives the illusion of the opposite, up tilts down and down tilts up.
I have seen other implementations suggesting the use of a non uvn system where quaternions are accumulated into one which describes the current orientation as a delta from some arbitrary start angle. This sounds great however I can't seem to work out exactly how I would implement this, specifically the conversion from this to a view matrix.
Elsewhere on SO I read about splitting the rotation into two quaternions that represent the yaw and pitch separately but I'm not convinced that this is the cause of the problem since in this context, correct me if I am wrong but my understanding is that the order in which you apply the two rotations does not matter.
Relevant Source Code Snippets:
Quarternion Operations
Quaternion<TValue> conjugate() const{
return Quaternion({ { -m_values[X], -m_values[Y], -m_values[Z], m_values[W] } });
};
Quaternion<TValue>& operator*=(const Quaternion<TValue>& rhs) {
TValue x, y, z, w;
w = rhs[W] * m_values[W] - rhs[X] * m_values[X] - rhs[Y] * m_values[Y] - rhs[Z] * m_values[Z];
x = rhs[W] * m_values[X] + rhs[X] * m_values[W] - rhs[Y] * m_values[Z] + rhs[Z] * m_values[Y];
y = rhs[W] * m_values[Y] + rhs[X] * m_values[Z] + rhs[Y] * m_values[W] - rhs[Z] * m_values[X];
z = rhs[W] * m_values[Z] - rhs[X] * m_values[Y] + rhs[Y] * m_values[X] + rhs[Z] * m_values[W];
m_values[X] = x;
m_values[Y] = y;
m_values[Z] = z;
m_values[W] = w;
return *this;
};
static Quaternion<TValue> rotation(Vector<3, TValue> axis, TValue angle){
float x, y, z, w;
TValue halfTheta = angle / 2.0f;
TValue sinHalfTheta = sin(halfTheta);
return Quaternion<TValue>({ { axis[X] * sinHalfTheta, axis[Y] * sinHalfTheta, axis[Z] * sinHalfTheta, cos(halfTheta) } });
};
Vector Rotation Operation
Vector<dimensions, TValue> rotate(const Vector<3, TValue> axis, float angle){
Quaternion<TValue> R = Quaternion<TValue>::rotation(axis, angle);
Quaternion<TValue> V = (*this);
Vector<dimensions, TValue> result = R * V * R.conjugate();
return result;
}
Camera Methods
Camera::Camera(Vector<2, int> windowSize, float fov, float near, float far):
m_uvn(Matrix<4, float>::identity()),
m_translation(Matrix<4, float>::identity()),
m_ar(windowSize[Dimensions::X] / (float)windowSize[Dimensions::Y]),
m_fov(fov),
m_near(near),
m_far(far),
m_position(),
m_forward({ { 0, 0, 1 } }),
m_up({ { 0, 1, 0 } })
{
setViewMatrix(Matrix<4, float>::identity());
setProjectionMatrix(Matrix<4, float>::perspective(m_ar, m_near, m_far, m_fov));
};
Matrix<4, float> Camera::getVPMatrix() const{
return m_vp;
};
const Vector<3, float> Camera::globalY = Vector<3, float>({ { 0, 1, 0 } });
void Camera::setProjectionMatrix(const Matrix<4, float> p){
m_projection = p;
m_vp = m_projection * m_view;
};
void Camera::setViewMatrix(const Matrix<4, float> v){
m_view = v;
m_vp = m_projection * m_view;
};
void Camera::setTranslationMatrix(const Matrix<4, float> t){
m_translation = t;
setViewMatrix(m_uvn * m_translation);
}
void Camera::setPosition(Vector<3, float> position){
if (position != m_position){
m_position = position;
setTranslationMatrix(Matrix<4, float>::translation(-position));
}
};
void Camera::moveForward(float ammount){
setPosition(m_position + (m_forward * ammount));
}
void Camera::moveRight(float ammount){
setPosition(m_position + (getRight() * ammount));
}
void Camera::moveUp(float ammount){
setPosition(m_position + (m_up * ammount));
}
void Camera::setLookAt(Vector<3, float> target, Vector<3, float> up){
Vector<3, float> newUp = up.normalize();
Vector<3, float> newForward = target.normalize();
if (newUp != m_up || newForward != m_forward){
m_up = newUp;
m_forward = newForward;
Vector<3, float> newLeft = getLeft();
m_up = newLeft * m_forward;
m_uvn = generateUVN();
setViewMatrix(m_uvn * m_translation);
}
};
void Camera::rotateX(float angle){
Vector<3, float> hAxis = (globalY * m_forward).normalize();
m_forward = m_forward.rotate(hAxis, angle).normalize();
m_up = (m_forward * hAxis).normalize();
m_uvn = generateUVN();
setViewMatrix(m_translation * m_uvn);
}
void Camera::rotateY(float angle){
Vector<3, float> hAxis = (globalY * m_forward).normalize();
m_forward = m_forward.rotate(globalY, angle).normalize();
m_up = (m_forward * hAxis).normalize();
m_uvn = generateUVN();
setViewMatrix(m_translation * m_uvn);
}
Vector<3, float> Camera::getRight(){
return (m_forward * m_up).normalize();
}
Vector <3, float> Camera::getLeft(){
return (m_up * m_forward).normalize();
}
};
I am guessing that the problem is in either my implementation of a quaternion or the way I am using it, but due to the complex nature of the system I cannot seem to pin down the problem any further than that. Due to the weird bugs being experienced I am unsure if there is just something wrong with the way I am trying to implement the camera?
Tutorials
Quarternion/Vector Math
上一篇: 从四元数到OpenGL旋转