I've developed a simple (Third-Person) Perspective Camera with Orbit controls.
However, my implementation doesn't handle the case where if the pitch goes over 90º it flips upside down. From what I've learned, the reason behind it has to do with using the Look-At function.
My questions are:
- Is the problem really the Look-at function? How would I fix this, without restricting the camera's pitch?
- I want to develop a camera class that is control agnostic (can be fly, orbit, ...) and therefore not take into account that it's first/third-person. Are there methods on how to develop such a camera abstraction?
- I also want it to be able to roll, can the advised solution for the last question support it?
- I haven't delved into quaternions yet, is the solution based on them? If so, are there also limitations to an implementation like that?
I'm basically looking for theory on developing proper robust cameras (with as few limitations as possible), so if you could point me into the right direction it would be nice, but a full answer is also very much appreciated. Code is not essential, but it would be also very helpful to see a proper (yet simple) implementation.
My camera implementation (in Vulkan):
Camera.hpp:
#pragma once
#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <GLFW/glfw3.h>
class CameraControl;
class Camera
{
private:
glm::vec3 position = glm::vec3(0.0f, 0.0f, 1.0f);
glm::vec3 target = glm::vec3(0.0f, 0.0f, 0.0f);
glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f);
float fov = glm::radians(45.0f);
float aspectRatio = 1.0f;
float near = 0.1f;
float far = 1000.0f;
glm::mat4 view;
glm::mat4 projection;
private:
void updateViewMatrix();
void updateProjectionMatrix();
public:
CameraControl *control;
public:
Camera();
~Camera();
void setPosition(glm::vec3 position);
void setTarget(glm::vec3 target);
void setPerspective(float fov, float aspectRatio, float near, float far);
glm::mat4 inline getView() { return view; };
glm::mat4 inline getProjection() { return projection; };
glm::vec3 inline getPosition() { return position; };
glm::vec3 inline getTarget() { return target; };
};
class CameraControl {
public:
float orbitSensitivity = 0.2f;
float zoomSensitivity = 0.0025f;
private:
Camera *camera;
bool firstMouse;
float lastMouseX;
float lastMouseY;
float distance;
float pitch;
float yaw;
public:
CameraControl(Camera *camera);
void handleInput(GLFWwindow *window);
};
Camera.cpp:
#include "Camera.hpp"
Camera::Camera() {
control = new CameraControl(this);
updateViewMatrix();
updateProjectionMatrix();
}
Camera::~Camera() {
delete control;
}
void Camera::setPosition(glm::vec3 position) {
this->position = position;
updateViewMatrix();
}
void Camera::setTarget(glm::vec3 target) {
this->target = target;
updateViewMatrix();
}
void Camera::setPerspective(float fov, float aspectRatio, float near, float far) {
this->fov = fov;
this->aspectRatio = aspectRatio;
this->near = near;
this->far = far;
updateProjectionMatrix();
}
void Camera::updateViewMatrix() {
view = glm::lookAt(position, target, up);
}
void Camera::updateProjectionMatrix() {
projection = glm::perspective(fov, aspectRatio, near, far);
projection[1][1] *= -1; // Vulkan coordinate system fix
}
CameraControl::CameraControl(Camera* camera) {
this->camera = camera;
firstMouse = true;
distance = 1.0f;
pitch = 0.0f;
yaw = 180.0f;
}
void CameraControl::handleInput(GLFWwindow *window) {
double xpos, ypos;
glfwGetCursorPos(window, &xpos, &ypos);
if (firstMouse) {
lastMouseX = xpos;
lastMouseY = ypos;
firstMouse = false;
}
float xoffset = xpos - lastMouseX;
float yoffset = lastMouseY - ypos;
lastMouseX = xpos;
lastMouseY = ypos;
if(glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT)) {
pitch -= yoffset * orbitSensitivity;
yaw -= xoffset * orbitSensitivity;
}
if(glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_RIGHT)) {
distance += yoffset * zoomSensitivity;
distance = glm::max(distance, 0.0f);
}
float verticalDistance = distance * glm::sin(glm::radians(pitch));
float horizontalDistance = distance * glm::cos(glm::radians(pitch));
glm::vec3 position;
position.x = camera->getTarget().x - horizontalDistance * glm::sin(glm::radians(yaw));
position.y = camera->getTarget().y + verticalDistance;
position.z = camera->getTarget().z - horizontalDistance * glm::cos(glm::radians(yaw));
camera->setPosition(position);
}
Thank you for your attention! Cheers!