Simple OpenGL Keyboard and Mouse FPS Controls

Note: This was written in January 2011 – I just never posted it, but I’d already uploaded the video to YouTube and someone asked for the code, so here it is, in all its fixed-pipeline glory ;)

Update – September 2013: I took these camera controls and wrapped them up into a Camera class in a later post which you can find here: https://r3dux.org/2012/12/a-c-camera-class-for-simple-opengl-fps-controls/. When I did this I wasn’t used to GLM (the OpenGL Mathematics library) so I just rolled my own Vec3 class – you can happily substitute glm::vec3’s if you’d like, and in fact I’d recommend it. Cheers!


I’m working on my OpenGL skills (or lack thereof) at the moment, and wanted to implement some 3D movement controls kinda of like a FPS with clipping off, so I read some chapters of the hallowed OpenGL SuperBible and did some googling, where I came across Swiftless‘ camera tutorials (Part 1, Part 2, Part 3) which gave me a really good start (Thank you, Swiftless!) on how to manipulate the ModelView matrix so we can move around a 3D scene, only it wasn’t quite perfect…

Strange things would happen like you’d look vertically downwards (i.e. directly down the negative Y axis), then you’d push forward – and yeah, you’d move “down”, but you’d also move “forward” at the same time (oh, and I’m putting things like “down” and “forward” in quotes because these concepts are all relative to your viewing orientation – not because I’m trying to be “sarcastic” or anything =P)

Anyways, I had a play with it and sorted it out after spending some time looking at the graphs for trigonometric functions and doing a little bit of off-setting and range-limiting as required. Check it out:

It actually looks quite a lot better running live than in the video due to mis-matched frame-capture rates and the like, but you get the idea =D

Full source code is available after the jump.

Cheers!

Source Code:
Note: Requires the following libraries: GL, GLEE, GLFW, freeGLUT (or GLUT). Also, this code contains a wrong-headed amount of global variables and isn’t in the slightest bit object-oriented. Feel free to encapsulate the camera class and all related functions at your leisure ;)

#include <iostream>
 
#include <GLee.h>         // No need to link to GL/gl.h
#include <GL/glfw.h>      // Include OpenGL Framework library
#include <GL/freeglut.h>  // Include FreeGLUT so we can easily draw spheres and calculate our viewing frustrum
#include <math.h>         // Used only for sin() and cos() functions
 
using namespace std;
 
const float TO_RADS = 3.141592654f / 180.0f; // The value of 1 degree in radians
 
GLint windowWidth  = 800;                    // Width of our window
GLint windowHeight = 600;                    // Heightof our window
 
GLint midWindowX = windowWidth  / 2;         // Middle of the window horizontally
GLint midWindowY = windowHeight / 2;         // Middle of the window vertically
 
GLfloat fieldOfView = 45.0f;                 // Define our field of view (i.e. how quickly foreshortening occurs)
GLfloat near        = 1.0f;                  // The near (Z Axis) point of our viewing frustrum (default 1.0f)
GLfloat far         = 1500.0f;               // The far  (Z Axis) point of our viewing frustrum (default 1500.0f)
 
// Camera rotation
GLfloat camXRot = 0.0f;
GLfloat camYRot = 0.0f;
GLfloat camZRot = 0.0f;
 
// Camera position
GLfloat camXPos = 0.0f;
GLfloat camYPos = 0.0f;
GLfloat camZPos = 0.0f;
 
// Camera movement speed
GLfloat camXSpeed = 0.0f;
GLfloat camYSpeed = 0.0f;
GLfloat camZSpeed = 0.0f;
 
GLint frameCount = 0; // How many frames have we drawn?
 
// Location of the sun (i.e. how far deep into the screen is it?)
GLfloat sunZLocation = -300.0f;
 
// How many segments make up our sphere around the latutude and longitude of the sphere
// The higher the number, the closer an approximation to a sphere we get! Try low numbers to see how bad it looks!
int sphereLatitudalSegments  = 100;
int sphereLongitudalSegments = 100;
 
// "Earth" details
GLbyte earthColour[]       = { 0, 0, 255 };
GLfloat earthOrbitDistance = 100.0f;
GLfloat earthOrbitSpeed    = 0.5f;
GLfloat earthRot           = 0.0f;
 
// "Moon" details
GLbyte moonColour[]        = { 255, 0, 0 };
GLfloat moonOrbitDistance  = 30.0f;
GLfloat moonOrbitSpeed     = 1.5f;
GLfloat moonRot            = 0.0f;
 
// Set the light source location to be the same as the sun position
// Don't forget that the position is a FOUR COMPONENT VECTOR (with the last component as w) if you omit the last component expect to get NO LIGHT!!!
// Learnt that one the hard way... =P
GLfloat  lightPos[] = { 0.0f, 0.0f, -300.0f, 1.0f };
 
// How fast we move (higher values mean we move and strafe faster)
GLfloat movementSpeedFactor = 3.0f;
 
// Hoding any keys down?
bool holdingForward     = false;
bool holdingBackward    = false;
bool holdingLeftStrafe  = false;
bool holdingRightStrafe = false;
 
// Function to convert degrees to radians
float toRads(const float &theAngleInDegrees)
{
    return theAngleInDegrees * TO_RADS;
}
 
// Function to check if OpenGL is having issues - pass it a unique string of some kind to track down where in the code it's moaning
void checkGLError(const char * errorLocation)
{
    unsigned int gle = glGetError();
 
    if (gle != GL_NO_ERROR)
    {
        cout << "GL Error discovered from caller " << errorLocation << ": ";
 
        switch (gle)
        {
        case GL_INVALID_ENUM:
            cout << "Invalid enum." << endl;
            break;
 
        case GL_INVALID_VALUE:
            cout << "Invalid value.\n";
            break;
 
        case GL_INVALID_OPERATION:
            cout << "Invalid Operation.\n";
            break;
 
        case GL_STACK_OVERFLOW:
            cout << "Stack overflow.\n";
            break;
 
        case GL_STACK_UNDERFLOW:
            cout << "Stack underflow.\n";
            break;
 
        case GL_OUT_OF_MEMORY:
            cout << "Out of memory.\n";
            break;
        default:
            cout << "Unknown error! Enum code is: " << gle << endl;
            break;
 
        } // End of switch
 
    } // End of if error detected
 
} // End of chechGLError function
 
void initGL()
{
    // ----- GLFW Settings -----
 
    glfwDisable(GLFW_MOUSE_CURSOR); // Hide the mouse cursor
 
    // ----- Window and Projection Settings -----
 
    // Set the window title
    glfwSetWindowTitle("Solar System FPS Controls | r3dux.org");
 
    // Setup our viewport to be the entire size of the window
    glViewport(0, 0, (GLsizei)windowWidth, (GLsizei)windowHeight);
 
    // Change to the projection matrix, reset the matrix and set up our projection
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
 
    // The following code is a fancy bit of math that is eqivilant to calling:
    // gluPerspective(fieldOfView/2.0f, width/height , near, far);
    // We do it this way simply to avoid requiring glu.h
    GLfloat aspectRatio = (windowWidth > windowHeight)? float(windowWidth)/float(windowHeight) : float(windowHeight)/float(windowWidth);
    GLfloat fH = tan( float(fieldOfView / 360.0f * 3.14159f) ) * near;
    GLfloat fW = fH * aspectRatio;
    glFrustum(-fW, fW, -fH, fH, near, far);
 
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
 
    // ----- OpenGL settings -----
 
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Set out clear colour to black, full alpha
 
    glfwSwapInterval(1);        // Lock to vertical sync of monitor (normally 60Hz, so 60fps)
 
    glShadeModel(GL_SMOOTH);    // Enable (gouraud) shading
 
    glEnable(GL_DEPTH_TEST);    // Enable depth testing
 
    glClearDepth(1.0f);         // Clear the entire depth of the depth buffer
 
    glDepthFunc(GL_LEQUAL);		// Set our depth function to overwrite if new value less than or equal to current value
 
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Ask for nicest perspective correction
 
    glEnable(GL_CULL_FACE); // Do not draw polygons facing away from us
 
    glLineWidth(2.0f);			// Set a 'chunky' line width
 
    // ---- Set up OpenGL lighting -----
 
    // Enable lighting
    glEnable(GL_LIGHTING);
 
    // Ambient, diffuse and specular lighting values (note that these are ALL FOUR COMPONENT VECTORS!)
    GLfloat  ambientLight[] = { 0.2f, 0.2f, 0.2f, 1.0f };
    GLfloat  diffuseLight[] = { 0.7f, 0.7f, 0.7f, 1.0f };
    GLfloat specularLight[] = { 1.0f, 1.0f, 1.0f, 1.0f };
 
    GLint specularMagnitude = 64; // Define how "tight" our specular highlights are (larger number = smaller specular highlight). The valid range is is 1 to 128
 
    // Setup and enable light 0
    glLightfv(GL_LIGHT0, GL_POSITION, lightPos);          // Specify the position of the light
    glLightfv(GL_LIGHT0, GL_AMBIENT,  ambientLight);      // Specify ambient light properties
    glLightfv(GL_LIGHT0, GL_DIFFUSE,  diffuseLight);      // Specify diffuse light properties
    glLightfv(GL_LIGHT0, GL_SPECULAR, specularLight);     // Specify specular light properties
    glEnable(GL_LIGHT0);
 
    // Enable colour tracking of materials
    glEnable(GL_COLOR_MATERIAL);
 
    // Define the shininess of the material we'll use to draw things
    GLfloat materialSpecularReflectance[] = { 1.0f, 1.0f, 1.0f, 1.0f };
 
    // Set Material properties to follow glColor values
    glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
 
    // Use our shiny material and magnitude
    glMaterialfv(GL_FRONT, GL_SPECULAR, materialSpecularReflectance);
    glMateriali(GL_FRONT, GL_SHININESS, specularMagnitude);
 
    // Check for any OpenGL errors (providing the location we called the function from)
    checkGLError("InitGL");
}
 
// Function to move the camera the amount we've calculated in the calculateCameraMovement function
void moveCamera()
{
    camXPos += camXSpeed;
    camYPos += camYSpeed;
    camZPos += camZSpeed;
}
 
// Function to deal with mouse position changes, called whenever the mouse cursorm moves
void handleMouseMove(int mouseX, int mouseY)
{
    GLfloat vertMouseSensitivity  = 10.0f;
    GLfloat horizMouseSensitivity = 10.0f;
 
    //cout << "Mouse cursor is at position (" << mouseX << ", " << mouseY << endl;
 
    int horizMovement = mouseX - midWindowX;
    int vertMovement  = mouseY - midWindowY;
 
    camXRot += vertMovement / vertMouseSensitivity;
    camYRot += horizMovement / horizMouseSensitivity;
 
    // Control looking up and down with the mouse forward/back movement
    // Limit loking up to vertically up
    if (camXRot < -90.0f)
    {
        camXRot = -90.0f;
    }
 
    // Limit looking down to vertically down
    if (camXRot > 90.0f)
    {
        camXRot = 90.0f;
    }
 
    // Looking left and right. Keep the angles in the range -180.0f (anticlockwise turn looking behind) to 180.0f (clockwise turn looking behind)
    if (camYRot < -180.0f)
    {
        camYRot += 360.0f;
    }
 
    if (camYRot > 180.0f)
    {
        camYRot -= 360.0f;
    }
 
    // Reset the mouse position to the centre of the window each frame
    glfwSetMousePos(midWindowX, midWindowY);
}
 
// Function to calculate which direction we need to move the camera and by what amount
void calculateCameraMovement()
{
    // Break up our movement into components along the X, Y and Z axis
    float camMovementXComponent = 0.0f;
    float camMovementYComponent = 0.0f;
    float camMovementZComponent = 0.0f;
 
    if (holdingForward == true)
    {
        // Control X-Axis movement
        float pitchFactor = cos(toRads(camXRot));
        camMovementXComponent += ( movementSpeedFactor * float(sin(toRads(camYRot))) ) * pitchFactor;
 
        // Control Y-Axis movement
        camMovementYComponent += movementSpeedFactor * float(sin(toRads(camXRot))) * -1.0f;
 
        // Control Z-Axis movement
        float yawFactor = float(cos(toRads(camXRot)));
        camMovementZComponent += ( movementSpeedFactor * float(cos(toRads(camYRot))) * -1.0f ) * yawFactor;
    }
 
    if (holdingBackward == true)
    {
        // Control X-Axis movement
        float pitchFactor = cos(toRads(camXRot));
        camMovementXComponent += ( movementSpeedFactor * float(sin(toRads(camYRot))) * -1.0f) * pitchFactor;
 
        // Control Y-Axis movement
        camMovementYComponent += movementSpeedFactor * float(sin(toRads(camXRot)));
 
        // Control Z-Axis movement
        float yawFactor = float(cos(toRads(camXRot)));
        camMovementZComponent += ( movementSpeedFactor * float(cos(toRads(camYRot))) ) * yawFactor;
    }
 
    if (holdingLeftStrafe == true)
    {
        // Calculate our Y-Axis rotation in radians once here because we use it twice
        float yRotRad = toRads(camYRot);
 
        camMovementXComponent += -movementSpeedFactor * float(cos(yRotRad));
        camMovementZComponent += -movementSpeedFactor * float(sin(yRotRad));
    }
 
    if (holdingRightStrafe == true)
    {
        // Calculate our Y-Axis rotation in radians once here because we use it twice
        float yRotRad = toRads(camYRot);
 
        camMovementXComponent += movementSpeedFactor * float(cos(yRotRad));
        camMovementZComponent += movementSpeedFactor * float(sin(yRotRad));
    }
 
    // After combining our movements for any & all keys pressed, assign them to our camera speed along the given axis
    camXSpeed = camMovementXComponent;
    camYSpeed = camMovementYComponent;
    camZSpeed = camMovementZComponent;
 
    // Cap the speeds to our movementSpeedFactor (otherwise going forward and strafing at an angle is twice as fast as just going forward!)
    // X Speed cap
    if (camXSpeed > movementSpeedFactor)
    {
        //cout << "high capping X speed to: " << movementSpeedFactor << endl;
        camXSpeed = movementSpeedFactor;
    }
    if (camXSpeed < -movementSpeedFactor)
    {
        //cout << "low capping X speed to: " << movementSpeedFactor << endl;
        camXSpeed = -movementSpeedFactor;
    }
 
    // Y Speed cap
    if (camYSpeed > movementSpeedFactor)
    {
        //cout << "low capping Y speed to: " << movementSpeedFactor << endl;
        camYSpeed = movementSpeedFactor;
    }
    if (camYSpeed < -movementSpeedFactor)
    {
        //cout << "high capping Y speed to: " << movementSpeedFactor << endl;
        camYSpeed = -movementSpeedFactor;
    }
 
    // Z Speed cap
    if (camZSpeed > movementSpeedFactor)
    {
        //cout << "high capping Z speed to: " << movementSpeedFactor << endl;
        camZSpeed = movementSpeedFactor;
    }
    if (camZSpeed < -movementSpeedFactor)
    {
        //cout << "low capping Z speed to: " << movementSpeedFactor << endl;
        camZSpeed = -movementSpeedFactor;
    }
}
 
// Function to set flags according to which keys are pressed or released
void handleKeypress(int theKey, int theAction)
{
    // If a key is pressed, toggle the relevant key-press flag
    if (theAction == GLFW_PRESS)
    {
 
        switch(theKey)
        {
        case 'W':
            holdingForward = true;
            break;
 
        case 'S':
            holdingBackward = true;
            break;
 
        case 'A':
            holdingLeftStrafe = true;
            break;
 
        case 'D':
            holdingRightStrafe = true;
            break;
 
        default:
            // Do nothing...
            break;
        }
    }
    else // If a key is released, toggle the relevant key-release flag
    {
        switch(theKey)
        {
        case 'W':
            holdingForward = false;
            break;
 
        case 'S':
            holdingBackward = false;
            break;
 
        case 'A':
            holdingLeftStrafe = false;
            break;
 
        case 'D':
            holdingRightStrafe = false;
            break;
 
        default:
            // Do nothing...
            break;
        }
    }
}
 
// Function to draw a grid of lines
void drawGround()
{
    GLfloat extent      = 600.0f; // How far on the Z-Axis and X-Axis the ground extends
    GLfloat stepSize    = 20.0f;  // The size of the separation between points
    GLfloat groundLevel = -50.0f;   // Where on the Y-Axis the ground is drawn
 
    // Set colour to white
    glColor3ub(255, 255, 255);
 
    // Draw our ground grid
    glBegin(GL_LINES);
    for (GLint loop = -extent; loop < extent; loop += stepSize)
    {
        // Draw lines along Z-Axis
        glVertex3f(loop, groundLevel,  extent);
        glVertex3f(loop, groundLevel, -extent);
 
        // Draw lines across X-Axis
        glVertex3f(-extent, groundLevel, loop);
        glVertex3f(extent,  groundLevel, loop);
    }
    glEnd();
 
}
 
// Function to draw our spheres and position the light source
void drawScene()
{
    // Clear the screen and depth buffer
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
    // Reset the matrix
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
 
    // Move the camera to our location in space
    glRotatef(camXRot, 1.0f, 0.0f, 0.0f);        // Rotate our camera on the x-axis (looking up and down)
    glRotatef(camYRot, 0.0f, 1.0f, 0.0f);        // Rotate our camera on the  y-axis (looking left and right)
    glTranslatef(-camXPos,-camYPos,-camZPos);    // Translate the modelviewm matrix to the position of our camera
 
    // Draw the lower ground-grid
    drawGround();
 
    // Draw the upper ground-grid, keeping a copy of our current matrix on the stack before we translate it
    glPushMatrix();
 
    glTranslatef(0.0f, 200.0f, 0.0f);
 
    drawGround();
 
    glPopMatrix();
 
    // Move everything "into" the screen (i.e. move 300 units along the Z-axis into the screen) so that all positions are now relative to the location of the sun
    glTranslatef(0.0f, 0.0f, sunZLocation);
 
    // Draw the sun (disable lighting so it's always drawn as bright as possible regardless of any lighting going on)
    glColor3ub(255, 255, 0);
    glDisable(GL_LIGHTING);
    glutSolidSphere(15.0f, sphereLatitudalSegments, sphereLongitudalSegments);
    glEnable(GL_LIGHTING);
 
    // Define our light position
    // *** IMPORTANT! *** A light position takes a FOUR component vector! The last component is w! If you leave off the last component, you get NO LIGHT!!!
    GLfloat newLightPos[] = { 0.0f, 0.0f, 0.0f, 1.0f };
 
    glLightfv(GL_LIGHT0, GL_POSITION, newLightPos);  // Place the light where the sun is!
 
    // Rotate the coordinate system around the y-axis and then translate to shift outwards
    glRotatef(earthRot, 0.0f, 1.0f, 0.0f);
    glTranslatef(earthOrbitDistance, 0.0f, 0.0f);
 
    // Draw the "earth"
    glColor3ub(earthColour[0], earthColour[1], earthColour[2]);
    glutSolidSphere(15.0f, sphereLatitudalSegments, sphereLongitudalSegments);
 
    // Rotate from earth-based coordinates and translate out again
    glRotatef(moonRot, 0.0f, 1.0f, 0.0f);
    glTranslatef(moonOrbitDistance, 0.0f, 0.0f);
 
    // Keep the "moon" rotation within a sensible range
    moonRot += moonOrbitSpeed;
    if (moonRot > 360.0f)
    {
        moonRot -= 360.0f;
    };
 
    // Draw the moon
    glColor3ub(moonColour[0], moonColour[1], moonColour[2]);
    glutSolidSphere(6.0f, sphereLatitudalSegments, sphereLongitudalSegments);
 
    // Step earth orbit but keep within a sensible range
    earthRot += earthOrbitSpeed;
    if (earthRot > 360.0f)
    {
        earthRot -= 360.0f;
    }
 
    // ----- Stop Drawing Stuff! ------
 
    glfwSwapBuffers(); // Swap the buffers to display the scene (so we don't have to watch it being drawn!)
}
 
// Fire it up...
int main(int argc, char **argv)
{
    cout << "Controls: Use WSAD and the mouse to move around!" << endl;
 
    // Frame counter and window settings variables
    int	redBits    = 8, greenBits = 8,    blueBits    = 8;
    int alphaBits  = 8, depthBits = 24,   stencilBits = 0;
 
    // Flag to keep our main loop running
    bool running = true;
 
    // ----- Intialiase FreeGLUT -----
 
    // Note: We're only using freeGLUT to draw some spheres, so if you modify the code to not include any calls
    // to glutSolidSphere, then you don't need this, the header or the lib...
    glutInit(&argc, argv);
 
    // Initialise GLFW
    glfwInit();
 
    // Ask for 4x AntiAliasing (this doesn't mean we'll get it - it'll work only if the GLX_ARB_multisample extension is available)
    // Note: Hints must be provided BEFORE the window is opened! But we can't query for it with GLEE until the window is opened! Catch 22!
    glfwOpenWindowHint(GLFW_FSAA_SAMPLES, 4);
 
    // Create a window
    if(!glfwOpenWindow(windowWidth, windowHeight, redBits, greenBits, blueBits, alphaBits, depthBits, stencilBits, GLFW_WINDOW))
    {
        cout << "Failed to open window!" << endl;
        glfwTerminate();
        return 0;
    }
 
    // ----- Initialise GLEE -----
 
    // Initialise GLee once we've got a rendering context
    // Note: We don't really have to do this because it's called automatically, but if we do it - we KNOW it's been called!
    GLeeInit();
 
    // Check for the OpenGL extension which allows us to use vsync
    if (GLEE_GLX_SGI_swap_control)
    {
        cout << "Extension found: GLX_SGI_swap_control (vsync can be used)." << endl;
        glfwSwapInterval(1);
    }
    else
    {
        cout << "Extension NOT found: GLX_SGI_swap_control (vsync cannot be used)." << endl;
        glfwSwapInterval(0);
    }
 
    // Check for the OpenGL extension which allows us to use antialiasing
    if (GLEE_ARB_multitexture)
    {
        cout << "Extension found: GLX_ARB_multitexture (anti-aliasing can be used)." << endl;
 
        // If the extension's available, we likely got anti-aliasing, so disable line smoothing as it comes free with the AA
        glDisable(GL_LINE_SMOOTH);
    }
    else
    {
        cout << "Extension NOT found: GLX_ARB_multitexture (anti-aliasing cannot be used)." << endl;
 
        // If the extention's not available, turn on line smoothing
        glEnable(GL_LINE_SMOOTH);
    }
 
    // Set the mouse cursor to the centre of our window
    glfwSetMousePos(midWindowX, midWindowY);
 
    // Call our initGL function to set up our OpenGL options
    initGL();
 
    // Specify the function which should execute when a key is pressed or released
    glfwSetKeyCallback(handleKeypress);
 
    // Specify the function which should execute when the mouse is moved
    glfwSetMousePosCallback(handleMouseMove);
 
    while (running == true)
    {
        // Draw our scene
        drawScene();
 
        // Calculate our camera movement
        calculateCameraMovement();
 
        // Move our camera
        moveCamera();
 
        // Increase our frame counter
        frameCount++;
 
        // Check for any OpenGL errors (providing the location we called the function from)
        checkGLError("Main loop");
 
        // exit if ESC was pressed or window was closed
        running = !glfwGetKey(GLFW_KEY_ESC) && glfwGetWindowParam(GLFW_OPENED);
    }
 
    // Clean up GLFW and exit
    glfwTerminate();
 
    return 0;
}

53 thoughts on “Simple OpenGL Keyboard and Mouse FPS Controls”

  1. Hi,

    Thanks for the code. I used your keyboard and mouse input code in my program but the mouse is stationary in the middle of the screen and won’t move way up or down. Nothing happens when I press the keyboard too. Any pointers as to what I might be doing wrong and how I can tweak your code to work for me?

    1. It sounds like something’s off with the mouse and keyboard callback registration, which should occur with these lines in the main:

      // Specify the function which should execute when a key is pressed or released
      glfwSetKeyCallback(handleKeypress);
       
      // Specify the function which should execute when the mouse is moved
      glfwSetMousePosCallback(handleMouseMove);

      The mouse cursor is supposed to be in the dead-centre of the screen and invisible, because the way we handle mouse movement is to calc how far from the centre of the screen the mouse has moved per frame (i.e. + or – horizontally (x-axis) and + or – vertically (y-axis) ), and then modify the ModelView matrix accordingly so you actually “look” in different directions. The mouse cursor then gets set back to the dead-centre of the window after each frame where we detect some cursor movement.

      I’ve dug out some example code from when I was learning keyboard and mouse handlers for you. Once you can get those to work, you should be fine for the FPS controls:
      GLFWKeyboardHandlerTest.zip
      GLFWMouseHandlerTest.zip

      Hope this helps.

      Update: I should add that as far as I’m concerned these mouse and keyboard handlers work perfectly. I just don’t know if they’ll work for you. They should, but who knows… Could be something specific to your setup causing issues.

      1. Thank you so much for your prompt response and detailed explanations. It helps me learn more. Thank you. I’ll take a look at the sample codes and revisit this code again. Hopefully, I will be able to sort it out. Thanks again.

        1. Hi,
          I managed to get the keyboard code working in my program. As it turns out, there were some changes I needed to make in my code which I over looked. I’m working on the mouse code now but I have a question. I know you can use both glut and glfw functions in one program. That is how mine is. Is it possible for me to use glut functions for the keyboard and mouse interactions instead of glfw functions? Will my program work or I have to stick with glfw functions?

          1. I can’t say I’ve tried it, but you should be able to use GLUT keyboard & mouse handlers without any issues – certainly sounds reasonable. Give it a whirl! =D

    1. I can give you my Linux Code::Blocks project if that’s what you mean, but the OpenGL library (libGL.so etc) comes as part of the graphics driver package.

      If you really want the C::B project let me know and I’ll upload it.

  2. @r3dux, I wonder about glfwSwapInterval() (apparently I just see that there’re just two options to set 1, 0 not merely a number or some other values, Am i right about this ?) whether how can we’re sure it will be lock down to 60Hz or 60 fps. I believe it’s too much for the system with this restriction. If I want the game to run only at 30fps (i don’t think it will relate with Hz no more, 30Hz no good for monitor), how can I do that ?

    I found Glut really flexible on this as its underlying system won’t strict automatically to some setting of fps.

    1. When glfwSwapInterval is enabled (1), then drawing is locked to the vertical refresh rate of your screen, whether that’s 50Hz, 60Hz, 75Hz, 85H or whatever. So if your program draws a frame, then it has time to spare before the next frame, it’ll just wait.

      When glfwSwapInterval is disabled (0), then the program will run as fast as it possibly can – once the program has drawn a frame, it moves right onto to the next frame without worrying about weather drawing at this point in the display’s refresh might cause page-tearing or anything.

      These are your two built-in options.

      The third option, which we need to implement ourselves, can be to lock our program to a specified refresh rate by working out how many milliseconds we want each frame to take (i.e. 60fps = 1000 / 60 = 16.6667ms), and then in our main loop we:

      1. Get the current system time (frameStart time)
      2. Do stuff and draw the frame,
      3. Get the current system time (frameEnd time),
      4. Calculate the frameDraw time (which is frameEnd – frameStart),
      5. If the frameDraw time is > 16.667ms draw the next frame, otherwise, if the frameDraw time is < 16.667ms, sleep for (16.667 - frameDraw time)ms

      To do this, you can use code like this:

      // Frame limiting vars
      int    desiredFPS = 60;
      double msPerFrame = 1000 / desiredFPS;
      double frameStartTime, frameEndTime, frameDrawTime;
       
       
      // ***** Everything below this line goes inside the main loop *****
      frameStartTime = glfwGetTime();
       
      // Do any calculations and draw the frame here
       
      // Lock to ~60fps
      frameEndTime = glfwGetTime();
      frameDrawTime = frameEndTime - frameStartTime;
      if (frameDrawTime < msPerFrame)
      {
      	glfwSleep(msPerFrame - frameDrawTime);
      }

      Hope this helps.

  3. Firstly – thanks for this post, it’s helped me understand a lot of what’s involved :)

    However, as Glee no longer seems to be available, how would I go about running this code without it?

    Any help would be greatly appreciated!

    1. You’re welcome =D

      GLee’s still alive and kicking as far as I’m aware – you can get it from the GLee site at: http://elf-stone.com/glee.php

      I actually stopped using GLee a little while after this as some aspects were a little bit buggy and instead changed to GLEW (GL Extension Wrangler), which you can find at: http://glew.sourceforge.net/. There are examples on how to use it on the site, or if you wanted you could take a look at any of the projects I’ve used GLEW in (http://r3dux.org/tag/glew/), for example this one: http://r3dux.org/2010/11/2d-c-openglglfw-basecode/ (take a look near the bottom of the post).

      Hope this helps!

      1. Thanks again for your reply. I ended up comment out some code and things are running (for now!) so happy days :)

        I’m now attempting to implement a simple ray picking system into my game. I’m following this guide: http://schabby.de/picking-opengl-ray-tracing/

        “Note that cameraLookAt is the 3D point where the camera looks at (as used on glLookat), cameraPosition is the current position of the camera in world space and cameraUp is the up vector of the camera.”

        I know what to use as cameraPosition, but what variables in your code should I use for the camera’s focus? (cameraLookAt)?

        Thanks again!

  4. #include // No need to link to GL/gl.h
    #include // Include OpenGL Framework library
    #include // Include FreeGLUT so we can easily draw spheres and calculate our viewing frustrum

    Can I ask where can i get this library?

      1. ya~ i look up to the internet and add some header files already, then i got a new problem :

        1>d:\my documents\documents\visual studio 2010\projects\hw3\hw3\counter_strike\counter_strike\main.cpp(612): error C2065: ‘GLEE_GLX_SGI_swap_control’ : undeclared identifier

        if (GLEE_GLX_SGI_swap_control)
        {
        cout << "Extension found: GLX_SGI_swap_control (vsync can be used)." << endl;
        glfwSwapInterval(1);
        }
        else
        {
        cout << "Extension NOT found: GLX_SGI_swap_control (vsync cannot be used)." << endl;
        glfwSwapInterval(0);
        }

        what is the problem?\

        tks for helping

        1. All that code’s doing is checking whether there’s an extension available which will allow us to use vsync, and then setting vsync to be on or off. I wouldn’t worry about it – just remove the code entirely, or just call glfwSwapInterval(0); or gfwSwapInterval(1); to try to disable/enable vsync to your chosen setting.

          Also, I probably wouldn’t use GLee, so just strip out everything GLee related (just the stuff in the GLee init section). If you really need OpenGL extensions, which this code doesn’t, use GLEW instead.

  5. Hello again,

    Got ray picking working in the end :)

    A (hopefully more simple!) question: I’m making a simple portal remake using your FPS controls as above. Lets say portal “a” is the one you walk into, and “b” is the one you walk out of. I’ve drawn 4 walls in my game. I’ve attached a value to each as a “centerPoint”. When I emerge from portal b, I set the rotation of the camera to the same rotation as the nearest wall (really the nearest centerPoint). However, the limit of the camera to 180 and -180 seems to be affecting this and it doesn’t work sometimes.

    I would really appreciate it if you had the time to get back to me.

    Thanks again for putting your time into this site!

    1. Sorry for the delay in getting back to you – eventful weekend (unfortunately not in a good way).

      Okay, so we’re setting camera rotations (really, the camera yaw around the Y axis) – so when you hit one portal and are teleported to the location of the linked portal, you’ll want to set the camera position to be the target portal coordinates, and the cam rotation to be the rotation of the portal (assuming the front of the portal is “facing outwards” – otherwise add or remove 180 degrees to the portal rotation and set as the new cam rotation).

      You say that it “doesn’t work sometimes” – like how? What happens? Post some snippets of code if it’ll help.

      To be honest, the camera controls as implemented were never meant to be used for anything too flash, which is why they weren’t encapsulated into a class or anything. But I knackered my foot last Friday so had to take it easy for the day, which gave me a change to write the FPS controls as a proper Camera class. I’ll post that up shortly – maybe it’ll help.

      1. Thanks again for your reply. Sorry to hear your weekend didn’t go too well!

        My wall class constructor is: (xPos,yPos,zPos,xRot,yRot,zRot,width,height). I have 4 walls:
        //”Front” wall (in front of camera at spawn)
        -600.0f , -50.0f, -600.0f, 0.0f, 0.0f,0.0f, 1200.0f,200.0f
        //”Back” Wall
        600.0f, -50.0f, 600.0f, 0.0f, 180, 0.0f, 1200.0f,200.0f
        //”Left” Wall
        -600.0f, -50.0f, 600.0f, 0.0f, 90, 0.0f, 1200.0f,200.0f
        //”Right” Wall
        600.0f, -50.0f, -600.0f, 0.0f, 270, 0.0f, 1200.0f,200.0f

        Player walks into portalA. The following code is executed (pseudo-code for the sake of your eyes!):
        camPos = portalB.pos;
        camRot = nearestWall(portalB.pos).rot //NearestWall returns the nearest Wall object to portalB, and the rotation of this wall is assigned to the camera.

        This works fine for “left” and “right” walls, but portals on the “front” and “back” walls don’t function correctly (my camera ends up staring 180 degrees the wrong way)

        Thanks again, I look forward to the Camera class :)

        1. Murphy’s law – I have solved the problem after all :) Here is what I used:

          camPos = portalB.pos;
          Vector3f newRot = nearestWall(portalB).rot;
          if (newRot.y == 180.0){
          newRot.y = 360.0;
          }
          if (newRot.y == 0.0){
          newRot.y = 180.0;
          }
          camRot = newRot;

    1. Thanks!

      The source highlighting plugin I use updated not so long ago and it’s become sluggish, but I don’t see how you can struggle to copy and paste a few pages of text, let along needing regex to do it. This is actually a bug in the Firefox rendering engine (as there’s no such slowdown in Chrome or IE), but as I wasn’t particularly keen on the colour-banding anyway I’ve added some CSS to disable it.

      Also, “#sthash.XSmg9l2h.dpuf”? This doesn’t appear anywhere on this page, or on the YouTube video page – so I can only imagine it must be something to do with your machine.

      Hope you found the code useful, even if it might have been a pain to copy and paste.

      1. Oh wow, I see what you mean now… I just went to copy and paste a snippet of code from an article I wrote ages back and I got the exact same stupid-ass behavior putting all the code on the same line and with that #sthash.blah.dpuf stuff…

        Turns out the ShareThis plugin is trying to keep track of all text copied and pasted from the site – which is fucked up and bullshit. Disabled.
        Source: http://www.mybloggingtricks.com/2013/03/fix-for-text-ending-with-sthash.html

        Sorry about that, I had no idea a plugin update had introduced that new ‘feature’.

  6. Hey, Im having trouble when running this code. Everything works fine except mouse handling. When I move with my mouse to rotate the camera scene is rendered somehow laggy, its not smooth as in the video. Do you have any idea why?

    1. Not really, no – it should be nice and smooth. Maybe try tweaking the vertMouseSensitivity and horizMouseSensitivity values in the handleMouseMove function and see if that helps (higher values mean less sensitive – so you have to move the mouse a greater distance to look around). If that doesn’t work try modifying the following code:

      camXRot += vertMovement / vertMouseSensitivity;
      camYRot += horizMovement / horizMouseSensitivity;

      To read:

      camXRot += (float)vertMovement / (float)vertMouseSensitivity;
      camYRot += (float)horizMovement / (float)horizMouseSensitivity;

      Maybe that’ll help…

      1. That did not help. The problem is that if I move my mouse as fast as I usually do, scene renders strangely. It looks like camera does not move continuously and its movement is snatchy. But if I move mouse really slow and sensitive theres no problem. Well except the fact that its annoying and view angle is changin slow.

  7. Hi there! Just wanted to pop in and say *thank you* for this example, it was precisely what I needed to get my bearings with cameras. Keep up the great work!

    1. Glad you found it useful!

      GLFW 2 was the most recent version when I wrote this a few years back, GLFW3 is now the current version so I’ve ported it to that. Can post if you’re interested.

      Also, if you port to GLUT feel free to sling a link here in case it’ll help out anyone looking for simple camera controls w/ GLUT.

      Cheers! =D

  8. > Can post if you’re interested.

    Yes, that could be useful, thanks. What I attached in mouse.cpp would actually be enough, don’t really care about the planets that much, simplicity is more important here.

    1. GLFW3 mouse and keyboard handling example:

      /***
      Project: GLFW3 Test
      Version: 0.2
      Author : r3dux
      Date   : 02/02/2014
      Purpose: Demonstration of using GLFW3 mouse and keyboard input handling
      ***/
       
      #include <iostream>
       
      // Define that we're using the static version of GLEW (glew32s) so it gets built into our final executable
      // NOTE: This MUST be defined before importing GLEW!
      #define GLEW_STATIC
      #include <GL/glew.h>                    // Include the GL Extension Wrangler
      #include <GL/glfw3.h>                   // Include GL Framework (NOTE: This pulls in GL.h for us)
       
      // Include the GL Mathematics library
      #include "glm/glm.hpp"
      #include "glm/gtc/matrix_transform.hpp" // Needed for the perspective() method
      #include "glm/gtc/type_ptr.hpp"         // Needed for the value_ptr() method
       
      #include "Shader.hpp"           // Include our custom Shader helper class
      #include "ShaderProgram.hpp"    // Include our custom ShaderProgram helper class
      #include "ShaderTools.h"        // Include our custom ShaderTools helper class
      #include "Camera.h"             // Include our custom Camera class
      #include "Model.hpp"            // Include our custom Model loader class
       
      using std::cout;
      using std::endl;
      using glm::vec3;
      using glm::vec4;
      using glm::mat4;
      using glm::mat3;
       
      // -------------- Global variables ---------------
       
      // Window settings
      GLsizei windowWidth    = 800;
      GLsizei windowHeight   = 600;
       
      // Projection settings
      float vertFieldOfView  = 45.0f;
      float nearClipDistance = 1.0f;
      float farClipDistance  = 400.0f;
       
      // Misc
      int  frameCount        = 0;    // How many frames we've drawn
       
      // FPS Manager. Params: Lock to 60fps, update fps stats every second, title
      int     frameRate = 60;
      double  deltaTime = 0.0;
       
      // Shaders
      ShaderProgram *shaderProgram;  // Create a pointer to a shader program
      GLuint         vertexArrayId;  // Create an unsigned integer to hold the ID of a vertex array object
       
      // Matricies
      mat4 pMatrix;      // Projection matrix i.e. the matrix used to perform the 3D to 2D conversion
      mat4 vMatrix;      // View  matrix i.e. the camera's coordinate system
      mat4 mMatrix;      // Model matrix i.e. the model's coordinate system
      mat3 normalMatrix; // Normal matrix i.e. which way is "up" in relation to our camera
       
      // Define our axes to rotate around as vec3s
      const vec3 xAxis = vec3(1.0f, 0.0f, 0.0f); // Positive x-axis points directly to the right
      const vec3 yAxis = vec3(0.0f, 1.0f, 0.0f); // Positive y-axis points directly up
      const vec3 zAxis = vec3(0.0f, 0.0f, 1.0f); // Positive z-axis points directly out of the screen
       
      // Camera. Params: location, rotation, window width & height
      Camera camera(vec3(0.0f, 0.0f, 8.0f), vec3(0.0), windowWidth, windowHeight);
       
      // Create a model object
      Model model;
       
      // Callback function to resize the window and set the viewport to the correct size
      void resizeWindow(GLFWwindow *window, GLsizei newWidth, GLsizei newHeight)
      {
          // Keep track of the new width and height of the window
          windowWidth  = float(newWidth);
          windowHeight = float(newHeight);
       
          // Update the midpoint location in the camera class because it uses these values, too
          camera.updateWindowMidpoint(windowWidth, windowHeight);
       
          // Viewport is the entire window (at its new size)
          glViewport( 0, 0, windowWidth, windowHeight);
      }
       
       
      // Function to pass-through any keypresses to our Camera class
      static void handleKeypress(GLFWwindow* window, int key, int scancode, int action, int mods)
      {
          // User hit ESC? Set the window to close
          if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
          {
              glfwSetWindowShouldClose(window, GL_TRUE);
          }
          else
          {
              camera.handleKeypress(key, action);
          }
      }
       
      // Function to pass-through any mouse movements to our Camera class
      void handleMouseMove(GLFWwindow *window, GLdouble mouseX, GLdouble mouseY)
      {
          camera.handleMouseMove(window, mouseX, mouseY);
      }
       
      // Function to set up our OpenGL rendering context
      void initGL(GLFWwindow *window)
      {
          // -------------- Initialise GLEW ---------------
          // Note: We MUST have an OpenGL rendering context open to initialise GLEW successfully!
       
          GLenum err = glewInit();
          if (GLEW_OK != err)
          {
              cout << "GLEW error: " << glewGetErrorString(err) << endl;
              glfwTerminate();
          }
          cout << "GLEW intialised successfully. Using GLEW version: " << glewGetString(GLEW_VERSION) << endl << endl;
       
          // -------------- Setup OpenGL Options ---------------
       
          glViewport( 0, 0, GLsizei(windowWidth), GLsizei(windowHeight) ); // Viewport is entire window
          glClearColor(0.0f, 0.0f, 0.0f, 1.0f);                            // Clear to black with full alpha
          glfwSwapInterval(1);                                             // Lock to vSync
          glEnable(GL_DEPTH_TEST);                                         // Enable depth testing
          glDepthFunc(GL_LEQUAL);                                          // Specify depth testing function
          glfwSetInputMode(window, GLFW_CURSOR_DISABLED, GL_TRUE);         // Hide the mouse cursor
          glEnable(GL_CULL_FACE);                                          // Enable face culling
          glCullFace(GL_BACK);                                             // Cull back faces of polygons
          glFrontFace(GL_CCW);                                             // Counter-clockwise winding indicates a forward facing polygon
       
          // -------------- Setup callback functions ---------------
       
          glfwSetWindowSizeCallback(window, resizeWindow);             // Register window resize functiom
          glfwSetKeyCallback(window, handleKeypress);                  // Register keyboard handler function
          glfwSetCursorPos(window, windowWidth / 2, windowHeight / 2); // Move the mouse cursor to the centre of the window
          glfwSetCursorPosCallback(window, handleMouseMove);           // Register mouse movement handler function
       
          checkGLError("InitGL");
      }
       
      // Function to create a valid shader program
      void initShader()
      {
          // NOTE: Shaders CANNOT be created without a valid rendering context as glCreateShader will segfault.
       
          // Create/load/compile our vertex shader
          Shader vertexShader(GL_VERTEX_SHADER);
          vertexShader.loadFromFile("MyVertexShader.vert");
          vertexShader.compile();
       
          // Create/load/compile our fragment shader
          Shader fragmentShader(GL_FRAGMENT_SHADER);
          fragmentShader.loadFromFile("MyFragmentShader.frag");
          fragmentShader.compile();
       
          // Create our shader program, attach the shaders and link the program
          shaderProgram = new ShaderProgram();
          shaderProgram->attachShader(&vertexShader);
          shaderProgram->attachShader(&fragmentShader);
          shaderProgram->linkProgram();
      }
       
      // Function to perform our drawing
      void drawFrame()
      {
          // ----- Matrix operations -----
       
          // Reset our View matrix
          vMatrix = mat4(1.0f);
       
          // Move the camera
          camera.move(1.0f/60.0f);
       
          // Perform camera rotation
          vMatrix = glm::rotate(vMatrix, camera.getXRotation(), xAxis);
          vMatrix = glm::rotate(vMatrix, camera.getYRotation(), yAxis);
          //vMatrix = glm::rotate(vMatrix, camera.getZRotation(), zAxis); // Commented out because we aren't actually rotating on the z-axis in this project!
       
          // Translate to our camera position before drawing our geometry
          vMatrix = glm::translate(vMatrix, vec3(-camera.getXPosition(), -camera.getYPosition(), -camera.getZPosition() ) );
       
          // ----- Drawing operations -----
       
          // Specify our shader program and bind to our vertex buffer object
          shaderProgram->use();
          glBindVertexArray(vertexArrayId);
       
          // Specify non-changing uniforms
          glUniformMatrix4fv( shaderProgram->uniform("pMatrix"),  1, GL_FALSE, glm::value_ptr(pMatrix) );
          glUniformMatrix4fv( shaderProgram->uniform("vMatrix"),  1, GL_FALSE, glm::value_ptr(vMatrix) );
       
          // Reset the ModelView matrix
          mMatrix = mat4(1.0f);
          mMatrix = glm::rotate(mMatrix, float(frameCount), yAxis);
       
          // Calculate the normal matrix as the inverse transpose of the Model matrix
          normalMatrix = glm::transpose( glm::inverse( mat3(mMatrix) ) );
       
          glUniformMatrix4fv( shaderProgram->uniform("mMatrix"),      1, GL_FALSE, glm::value_ptr(mMatrix) );
          glUniformMatrix3fv( shaderProgram->uniform("normalMatrix"), 1, GL_FALSE, glm::value_ptr(normalMatrix) );
       
          // Draw our model
          // Params: Primitive type, number of elements in the data set (i.e number of VALUES - not faces!) to draw, the data type of the index data, the element to start at
          glDrawArrays(GL_TRIANGLES, 0, model.getNumVertices() );
       
          // Unbind from our vertex array objext and disable our shader program
          glBindVertexArray(0);
          shaderProgram->disable();
      }
       
      int main()
      {
          // -------------- Initialise GLFW3 ---------------
       
          if (!glfwInit())
          {
              cout << "glfwInit failed!" << endl;
              exit(-1);
          }
       
          // -------------- Specify Window Hints ---------------
          /*** NOTE: Window hints must be provided AFTER initialising GLEW but BEFORE we have opened a rendering context!
       
          Hint                          Default Value             Valid Values
          -------------------------------------------------------------------------------------------------------------------------------------
          GLFW_RESIZABLE                GL_TRUE 	                GL_TRUE or GL_FALSE
          GLFW_VISIBLE                  GL_TRUE 	                GL_TRUE or GL_FALSE
          GLFW_DECORATED                GL_TRUE 	                GL_TRUE or GL_FALSE
          GLFW_RED_BITS 	              8                         0 to INT_MAX
          GLFW_GREEN_BITS               8                         0 to INT_MAX
          GLFW_BLUE_BITS 	              8 	                    0 to INT_MAX
          GLFW_ALPHA_BITS 	          8 	                    0 to INT_MAX
          GLFW_DEPTH_BITS 	          24 	                    0 to INT_MAX
          GLFW_STENCIL_BITS 	          8 	                    0 to INT_MAX
          GLFW_ACCUM_RED_BITS 	      0 	                    0 to INT_MAX
          GLFW_ACCUM_GREEN_BITS   	  0 	                    0 to INT_MAX
          GLFW_ACCUM_BLUE_BITS 	      0 	                    0 to INT_MAX
          GLFW_ACCUM_ALPHA_BITS   	  0 	                    0 to INT_MAX
          GLFW_AUX_BUFFERS 	          0 	                    0 to INT_MAX
          GLFW_SAMPLES 	              0 	                    0 to INT_MAX
          GLFW_REFRESH_RATE 	          0 	                    0 to INT_MAX
          GLFW_STEREO 	              GL_FALSE 	                GL_TRUE or GL_FALSE
          GLFW_SRGB_CAPABLE 	          GL_FALSE 	                GL_TRUE or GL_FALSE
          GLFW_CLIENT_API 	          GLFW_OPENGL_API 	        GLFW_OPENGL_API or GLFW_OPENGL_ES_API
          GLFW_CONTEXT_VERSION_MAJOR 	  1 	                    Any valid major version number of the chosen client API
          GLFW_CONTEXT_VERSION_MINOR 	  0 	                    Any valid minor version number of the chosen client API
          GLFW_CONTEXT_ROBUSTNESS 	  GLFW_NO_ROBUSTNESS 	    GLFW_NO_ROBUSTNESS, GLFW_NO_RESET_NOTIFICATION or GLFW_LOSE_CONTEXT_ON_RESET
          GLFW_OPENGL_FORWARD_COMPAT 	  GL_FALSE 	                GL_TRUE or GL_FALSE
          GLFW_OPENGL_DEBUG_CONTEXT 	  GL_FALSE 	                GL_TRUE or GL_FALSE
          GLFW_OPENGL_PROFILE 	      GLFW_OPENGL_ANY_PROFILE 	GLFW_OPENGL_ANY_PROFILE, GLFW_OPENGL_COMPAT_PROFILE or GLFW_OPENGL_CORE_PROFILE
          */
          // Create a window
          GLFWwindow* window = glfwCreateWindow(GLsizei(windowWidth), GLsizei(windowHeight), "Phong Shading with FPS Camera Controls", NULL, NULL);
       
          if (!window)
          {
              glfwTerminate();
              exit(-1);
          }
       
          // Make the current OpenGL context active
          glfwMakeContextCurrent(window);
       
          // -------------- Set up our OpenGL settings ---------------
       
          initGL(window);
       
          // -------------- Set up our model ---------------
       
          model.load("cow-with-normals.obj");
       
          // -------------- Set up our GLSL shader ---------------
       
          // Perform shader loading, compilation and linking
          initShader();
       
          // Add the shader attributes
          shaderProgram->addAttribute("vVertex");
          shaderProgram->addAttribute("vNormal");
       
          // Add the shader uniforms
          shaderProgram->addUniform("pMatrix");
          shaderProgram->addUniform("vMatrix");
          shaderProgram->addUniform("mMatrix");
          shaderProgram->addUniform("normalMatrix");
       
          // ------------------ Set up our matricies ---------------------------
       
          // Specify the projection matrix
          pMatrix = glm::perspective(vertFieldOfView, GLfloat(windowWidth) / GLfloat(windowHeight), nearClipDistance, farClipDistance);
       
          // Reset the model and view matrices to identity ( like glLoadIdentity() )
          mMatrix = mat4(1.0f);
          vMatrix = mat4(1.0f);
       
          // ------------------ Setup our buffers and attributes ---------------------------
       
          // Generate an ID for the vertex array object
          glGenVertexArrays(1, &vertexArrayId);
       
          cout << "Our vertex array object ID is: " << vertexArrayId << endl;
       
          // Bind the vertex array object to store all the settings for buffers and vertex attributes we create
          glBindVertexArray(vertexArrayId);
       
          // ------------------ Set up our verticies ---------------------------
       
          // Define a custom data type called Attributes
          const int VERTEX_COMPONENTS  = 3; // x, y and z
       
          // ... then bind it to a array buffer and place the data in the buffer, then finally ...
          GLuint vertexBufferId;
          glGenBuffers(1, &vertexBufferId);
          glBindBuffer(GL_ARRAY_BUFFER, vertexBufferId);
          glBufferData(GL_ARRAY_BUFFER, model.getVertexDataSizeBytes(), model.getVertexData(), GL_STATIC_DRAW);
       
          // ... set up the attribute pointer ...
          glVertexAttribPointer(
              shaderProgram->attribute("vVertex"),  // Attribute location
              VERTEX_COMPONENTS,                    // Number of elements per vertex, here (x,y,z), so 3
              GL_FLOAT,                             // Data type of each element
              GL_FALSE,                             // Normalised?
              0,                                    // Stride
              0                                     // Offset
          );
       
          // ... and enable the attribute.
          glEnableVertexAttribArray( shaderProgram->attribute("vVertex") );
       
          // ------------------ Set up normal attribute ---------------------------
       
          GLuint normalBufferId;
          glGenBuffers(1, &normalBufferId);
          glBindBuffer(GL_ARRAY_BUFFER, normalBufferId);
          glBufferData(GL_ARRAY_BUFFER, model.getNormalDataSizeBytes(), model.getNormalData(), GL_DYNAMIC_DRAW);
       
          // Set up the attribute pointer and data properties. Params: location, number of components, data type, normalised?, stride, offset
          glVertexAttribPointer(
              shaderProgram->attribute("vNormal"),  // Attribute location
              VERTEX_COMPONENTS,                    // Number of elements per vertex, here (x, y, z), so 3
              GL_FLOAT,                             // Data type of each element
              GL_FALSE,                             // Normalised?
              0,                                    // Stride is zero
              0                                     // Offset is 0
          );
       
          // Enable the colour attribute
          glEnableVertexAttribArray( shaderProgram->attribute("vNormal") );
       
          // Unbind our Vertex Array object - all the buffer and attribute settings above will be associated with our VAO!
          glBindVertexArray(0);
       
          // ------------------ Main Loop ---------------------------
       
          while (!glfwWindowShouldClose(window))
          {
              frameCount++;
       
              glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
       
              drawFrame();
       
              // Swap the buffers on this window and poll for events
              glfwSwapBuffers(window);
              glfwPollEvents();
          }
       
          // Delete the vertex array object and shader program
          glDeleteVertexArrays(1, &vertexArrayId);
          delete shaderProgram;
       
          // Check the final error state and exit
          // NOTE: This MUST be called while we still have a valid rendering context (i.e. before we call glfwTerminate() )
          checkGLError("End");
          glfwDestroyWindow(window);
          glfwTerminate();
       
          return 0;
      }
  9. Hi,

    I use your code exactly the same way but when I move it moves only so far. It turns out that

    CamXPosition += CamXSpeed;
    CamYPosition += CamYSpeed;
    CamZPosition += CamZSpeed;

    is failed for me.

  10. Really tight coding, great job. Am I missing a repository somewhere? Where are these?:

    #include “Shader.hpp”
    #include “ShaderProgram.hpp”
    #include “ShaderTools.h”
    #include “Camera.h”
    #include “Model.hpp”

        1. Okay, I’ve put something together.

          It’s not great, and I would happily spend a week or two refactoring and adding in all manner of things if I had the time (I actually do really miss graphics programming) – but anyways, have a crack at this and see how you get on:

          https://r3dux.org/files/GLFW3_Basecode_2018_01.7z

          The projects are both for Code::Blocks (17.12 on Windows, 16.01 on Linux). GLFW, GLEW and GLM have all been updated to the latest versions on Windows – as before, the Linux project just looks for your own native versions of the libs.

          This code contains a WaveFront .OBJ model loader and a few object files. It loads a cow and spins it about, change the filename to load other stuff.

          There’s only a single point light shining on the cow. The location is hard-coded in the vertex shader, and the colour is hard-coded in the fragment shader. Yes, this is weak – but I don’t have the time this evening to change it to uniforms – consider it an exercise for the reader ;-) The model itself doesn’t have a colour, so the light colour and model colour aren’t blended – you just get the reflected light colour.

          Also, I may have stuffed up the calculation for the light stuff – that is, if you changed the model’s ModelView matrix the light might move with it. If so I’m sorry – I’m really out of touch with all this at the moment, and when I was on-point with it I didn’t document it all properly so I end up looking at old projects where I’ve fixed or not-fixed issues and I don’t recall which is which!

          My advice for learning graphic programming would be to get a copy of the OpenGL SuperBible (I wouldn’t even worry about Vulkan just yet – everything you learn about OpenGL will conceptually translate over) and plow through that. Then when you actually want to understand what it all means hit up some other OpenGL books (like some of these).

          I wrote a course on computer graphics a couple of years back which covers things from the very basics of drawing by arrays and elements to texture mapping, bump/displacement mapping, shadow mapping and things. If you’d like a copy of the resources then I can upload them for you next week.

          Cheers!

          1. Actually – yup, that light calculation is off because the specular highlight changes as you look up and down from the exact same location (which shouldn’t happen).

            Not a big issue to fix – will get onto it on Sunday.

            Update: Fixed it, added materials and better Phong lighting (previous was Gouraud, or at least Gouraud-esque). Will update source over the weekend.

            Update 2: Here’s the updated project – GLFW3_Basecode_2018_01_Fix1.

  11. Thanks for your time, looking good so far. Only issues(but maybe more in store: ( ) are:

    C:/minGW/mingw64/x86_64-w64-mingw32/include/glm/gtx/dual_quaternion.hpp:24:3: error: #error “GLM: GLM_GTX_dual_quaternion is an experimental extension and may change in the future. Use #define GLM_ENABLE_EXPERIMENTAL before including it, if you really want to use it.”
    # error “GLM: GLM_GTX_dual_quaternion is an experimental extension and may change in the future. Use #define GLM_ENABLE_EXPERIMENTAL before including it, if you really want to use it.”
    ^~~~~
    In file included from ..\src\Window.h:21:0,
    from ..\src\Window.cpp:1:
    C:/minGW/mingw64/x86_64-w64-mingw32/include/glm/gtx/string_cast.hpp:27:3: error: #error “GLM: GLM_GTX_string_cast is an experimental extension and may change in the future. Use #define GLM_ENABLE_EXPERIMENTAL before including it, if you really want to use it.”
    # error “GLM: GLM_GTX_string_cast is an experimental extension and may change in the future. Use #define GLM_ENABLE_EXPERIMENTAL before including it, if you really want to use it.”
    ^~~~~
    I can research it myself, but I am betting you know how this should be handled.

    1. Hmm… why did you place glm inside the mingw compiler folder? i.e.
      C:/minGW/mingw64/x86_64-w64-mingw32/include/glm/gtx/…

      The project as provided comes with the latest versions of glm, glfw3 and glew and the Win32 Code::Blocks project is configured to work with those versions.

      Whenever I’ve seen that complaint about GLM_ENABLE_EXPERIMENTAL I’ve just put the define line in the main.cpp before importing any glm functionality and it’s worked just fine. Am at a bit of a loss as to why you’d get these errors when it works just fine for me – are you pointing at an older version of glm or something?

      Also, if you try and post some code type stuff and it doesn’t come out properly just wrap it in some <pre> tags.

  12. Success! Thank you! I used the Eclipse Oxygen IDE to compile with MinGW-64 the program in 64bits, using 64bit versions of the library files. I think the MinGW came with the GLM installation. I don’t remember installing it.

    Writing the #define GLM_ENABLE_EXPERIMENTAL in Window.h and commenting out the GLM code in main.cpp was the key for successfully launching the program. Using Eclipse rather than CodeBlocks might be why, I don’t know. But since main.cpp included Window.h it was still able to access GLM.

    Amazing graphics. Please post your editor/preferred source for model creation.

  13. I prefer using 3ds Max for model creation. It’s free if you’re a student (see: https://www.autodesk.com/education/free-software/featured) – but if not it’s kinda expensive.

    You can always use Blender for free though. The interface is completely different to max, but it’s perfectly use-able (just takes a little getting used to).

    If you create your own models keep in mind that the Model class doesn’t presently support texture coordinates (although you could add them), and that the models must be exported as triangles (not quads) in WaveFront .OBJ format.

Leave a Reply to Annette Cancel reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.