ModernGL Camera
This is an update to my previous OpenGL camera class. It uses GLM for all math operations and is better suited for use with modern opengl code based on shaders and such.
Code is also availible here:
https://github.com/hmazhar/moderngl_camera
Initialize the camera
The camera requires:
- a mode, Either FREE or ORTHO
- a position
- a point to look at
- clipping information
- the field of View
Camera camera;
camera.SetMode(FREE); //Two Modes FREE and ORTHO
camera.SetPosition(glm::vec3(0, 0, -10));
camera.SetLookAt(glm::vec3(0, 0, 0));
camera.SetClipping(.01, 500);
camera.SetFOV(45);
Base camera class
/*
camera.h
OpenGL Camera Code
Capable of 2 modes, orthogonal, and free
Quaternion camera code adapted from: http://hamelot.co.uk/visualization/opengl-camera/
Written by Hammad Mazhar
*/
#ifndef CAMERA_H
#define CAMERA_H
#include <GL/freeglut.h>
#include <glm/glm.hpp>
#include <glm/gtx/transform.hpp>
#include <glm/gtc/quaternion.hpp>
#include <glm/gtx/quaternion.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
enum CameraType {
ORTHO, FREE
};
enum CameraDirection {
UP, DOWN, LEFT, RIGHT, FORWARD, BACK
};
class Camera {
public:
Camera();
~Camera();
void Reset();
//This function updates the camera
//Depending on the current camera mode, the projection and viewport matricies are computed
//Then the position and location of the camera is updated
void Update();
//Given a specific moving direction, the camera will be moved in the appropriate direction
//For a spherical camera this will be around the look_at point
//For a free camera a delta will be computed for the direction of movement.
void Move(CameraDirection dir);
//Change the pitch (up, down) for the free camera
void ChangePitch(float degrees);
//Change heading (left, right) for the free camera
void ChangeHeading(float degrees);
//Change the heading and pitch of the camera based on the 2d movement of the mouse
void Move2D(int x, int y);
//Setting Functions
//Changes the camera mode, only three valid modes, Ortho, Free, and Spherical
void SetMode(CameraType cam_mode);
//Set the position of the camera
void SetPosition(glm::vec3 pos);
//Set's the look at point for the camera
void SetLookAt(glm::vec3 pos);
//Changes the Field of View (FOV) for the camera
void SetFOV(double fov);
//Change the viewport location and size
void SetViewport(int loc_x, int loc_y, int width, int height);
//Change the clipping distance for the camera
void SetClipping(double near_clip_distance, double far_clip_distance);
void SetDistance(double cam_dist);
void SetPos(int button, int state, int x, int y);
//Getting Functions
CameraType GetMode();
void GetViewport(int &loc_x, int &loc_y, int &width, int &height);
void GetMatricies(glm::mat4 &P, glm::mat4 &V, glm::mat4 &M);
CameraType camera_mode;
int viewport_x;
int viewport_y;
int window_width;
int window_height;
double aspect;
double field_of_view;
double near_clip;
double far_clip;
float camera_scale;
float camera_heading;
float camera_pitch;
float max_pitch_rate;
float max_heading_rate;
bool move_camera;
glm::vec3 camera_position;
glm::vec3 camera_position_delta;
glm::vec3 camera_look_at;
glm::vec3 camera_direction;
glm::vec3 camera_up;
glm::quat rotation_quaternion;
glm::vec3 mouse_position;
glm::mat4 projection;
glm::mat4 view;
glm::mat4 model;
glm::mat4 MVP;
};
#endif
//camera.cpp
#include "camera.h"
using namespace std;
Camera::Camera() {
camera_mode = FREE;
camera_up = glm::vec3(0, 1, 0);
field_of_view = 45;
rotation_quaternion = glm::quat(1, 0, 0, 0);
camera_position_delta = glm::vec3(0, 0, 0);
camera_scale = .5f;
max_pitch_rate = 5;
max_heading_rate = 5;
move_camera = false;
}
Camera::~Camera() {
}
void Camera::Reset() {
camera_up = glm::vec3(0, 1, 0);
}
void Camera::Update() {
camera_direction = glm::normalize(camera_look_at - camera_position);
//need to set the matrix state. this is only important because lighting doesn't work if this isn't done
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glViewport(viewport_x, viewport_y, window_width, window_height);
if (camera_mode == ORTHO) {
//our projection matrix will be an orthogonal one in this case
//if the values are not floating point, this command does not work properly
//need to multiply by aspect!!! (otherise will not scale properly)
projection = glm::ortho(-1.5f * float(aspect), 1.5f * float(aspect), -1.5f, 1.5f, -10.0f, 10.f);
} else if (camera_mode == FREE) {
projection = glm::perspective(field_of_view, aspect, near_clip, far_clip);
//detmine axis for pitch rotation
glm::vec3 axis = glm::cross(camera_direction, camera_up);
//compute quaternion for pitch based on the camera pitch angle
glm::quat pitch_quat = glm::angleAxis(camera_pitch, axis);
//determine heading quaternion from the camera up vector and the heading angle
glm::quat heading_quat = glm::angleAxis(camera_heading, camera_up);
//add the two quaternions
glm::quat temp = glm::cross(pitch_quat, heading_quat);
temp = glm::normalize(temp);
//update the direction from the quaternion
camera_direction = glm::rotate(temp, camera_direction);
//add the camera delta
camera_position += camera_position_delta;
//set the look at to be infront of the camera
camera_look_at = camera_position + camera_direction * 1.0f;
//damping for smooth camera
camera_heading *= .5;
camera_pitch *= .5;
camera_position_delta = camera_position_delta * .8f;
}
//compute the MVP
view = glm::lookAt(camera_position, camera_look_at, camera_up);
model = glm::mat4(1.0f);
MVP = projection * view * model;
glLoadMatrixf(glm::value_ptr(MVP));
}
//Setting Functions
void Camera::SetMode(CameraType cam_mode) {
camera_mode = cam_mode;
camera_up = glm::vec3(0, 1, 0);
rotation_quaternion = glm::quat(1, 0, 0, 0);
}
void Camera::SetPosition(glm::vec3 pos) {
camera_position = pos;
}
void Camera::SetLookAt(glm::vec3 pos) {
camera_look_at = pos;
}
void Camera::SetFOV(double fov) {
field_of_view = fov;
}
void Camera::SetViewport(int loc_x, int loc_y, int width, int height) {
viewport_x = loc_x;
viewport_y = loc_y;
window_width = width;
window_height = height;
//need to use doubles division here, it will not work otherwise and it is possible to get a zero aspect ratio with integer rounding
aspect = double(width) / double(height);
;
}
void Camera::SetClipping(double near_clip_distance, double far_clip_distance) {
near_clip = near_clip_distance;
far_clip = far_clip_distance;
}
void Camera::Move(CameraDirection dir) {
if (camera_mode == FREE) {
switch (dir) {
case UP:
camera_position_delta += camera_up * camera_scale;
break;
case DOWN:
camera_position_delta -= camera_up * camera_scale;
break;
case LEFT:
camera_position_delta -= glm::cross(camera_direction, camera_up) * camera_scale;
break;
case RIGHT:
camera_position_delta += glm::cross(camera_direction, camera_up) * camera_scale;
break;
case FORWARD:
camera_position_delta += camera_direction * camera_scale;
break;
case BACK:
camera_position_delta -= camera_direction * camera_scale;
break;
}
}
}
void Camera::ChangePitch(float degrees) {
//Check bounds with the max pitch rate so that we aren't moving too fast
if (degrees < -max_pitch_rate) {
degrees = -max_pitch_rate;
} else if (degrees > max_pitch_rate) {
degrees = max_pitch_rate;
}
camera_pitch += degrees;
//Check bounds for the camera pitch
if (camera_pitch > 360.0f) {
camera_pitch -= 360.0f;
} else if (camera_pitch < -360.0f) {
camera_pitch += 360.0f;
}
}
void Camera::ChangeHeading(float degrees) {
//Check bounds with the max heading rate so that we aren't moving too fast
if (degrees < -max_heading_rate) {
degrees = -max_heading_rate;
} else if (degrees > max_heading_rate) {
degrees = max_heading_rate;
}
//This controls how the heading is changed if the camera is pointed straight up or down
//The heading delta direction changes
if (camera_pitch > 90 && camera_pitch < 270 || (camera_pitch < -90 && camera_pitch > -270)) {
camera_heading -= degrees;
} else {
camera_heading += degrees;
}
//Check bounds for the camera heading
if (camera_heading > 360.0f) {
camera_heading -= 360.0f;
} else if (camera_heading < -360.0f) {
camera_heading += 360.0f;
}
}
void Camera::Move2D(int x, int y) {
//compute the mouse delta from the previous mouse position
glm::vec3 mouse_delta = mouse_position - glm::vec3(x, y, 0);
//if the camera is moving, meaning that the mouse was clicked and dragged, change the pitch and heading
if (move_camera) {
ChangeHeading(.08f * mouse_delta.x);
ChangePitch(.08f * mouse_delta.y);
}
mouse_position = glm::vec3(x, y, 0);
}
void Camera::SetPos(int button, int state, int x, int y) {
if (button == 3 && state == GLUT_DOWN) {
camera_position_delta += camera_up * .05f;
} else if (button == 4 && state == GLUT_DOWN) {
camera_position_delta -= camera_up * .05f;
} else if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) {
move_camera = true;
} else if (button == GLUT_LEFT_BUTTON && state == GLUT_UP) {
move_camera = false;
}
mouse_position = glm::vec3(x, y, 0);
}
CameraType Camera::GetMode() {
return camera_mode;
}
void Camera::GetViewport(int &loc_x, int &loc_y, int &width, int &height) {
loc_x = viewport_x;
loc_y = viewport_y;
width = window_width;
height = window_height;
}
void Camera::GetMatricies(glm::mat4 &P, glm::mat4 &V, glm::mat4 &M) {
P = projection;
V = view;
M = model;
}
Using with GLUT
In order to use this code with the GLUT library a few more functions are required, it is assumed that camera is a Camera object.
//Camera camera
void CallBackKeyboardFunc(unsigned char key, int x, int y)
{
switch (key) {
case 'w':
camera.Move(FORWARD);
break;
case 'a':
camera.Move(LEFT);
break;
case 's':
camera.Move(BACK);
break;
case 'd':
camera.Move(RIGHT);
break;
case 'q':
camera.Move(DOWN);
break;
case 'e':
camera.Move(UP);
break;
}
}
void CallBackMouseFunc(int button, int state, int x, int y)
{
camera.SetPos(button, state, x, y);
}
void CallBackMotionFunc(int x, int y)
{
camera.Move2D(x, y);
}