Renderer

Flying Camera

The flying camera is almost a must for this renderer. We want to be able to fly around the scene to explore the different objects from different angles and from different perspectives.

Mouse and Keyboard Control

First we need to be able to capture and process user input. Luckily, GLFW provides us with some event listeners that we can use to capture mouse movement and keyboard presses that we will use to move the camera around.

void MouseScrollCallback(GLFWwindow *Window, double OffsetX, double OffsetY);
void ProcessInput(context *Context);

glfwSetScrollCallback(Context.Window, MouseScrollCallback);


while (!glfwWindowShouldClose(Context.Window)) {
    ...
    ProcessInput(&Context);
    ...

    glfwSwapBuffers(Context.Window);
    glfwPollEvents();
}

// Process All Input: query GLFW whether relevant keys are pressed/released this
// frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
void ProcessInput(context *Context) {
    if (glfwGetKey(Context->Window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
        glfwSetWindowShouldClose(Context->Window, true);
    }

    if (glfwGetKey(Context->Window, GLFW_KEY_W) == GLFW_PRESS) {
        Camera_ProcessKeyboard(&Context->Camera, FORWARD, Context->DeltaTime);
    }

    if (glfwGetKey(Context->Window, GLFW_KEY_S) == GLFW_PRESS) {
        Camera_ProcessKeyboard(&Context->Camera, BACKWARD, Context->DeltaTime);
    }

    if (glfwGetKey(Context->Window, GLFW_KEY_A) == GLFW_PRESS) {
        Camera_ProcessKeyboard(&Context->Camera, LEFT, Context->DeltaTime);
    }

    if (glfwGetKey(Context->Window, GLFW_KEY_D) == GLFW_PRESS) {
        Camera_ProcessKeyboard(&Context->Camera, RIGHT, Context->DeltaTime);
    }

    if (glfwGetKey(Context->Window, GLFW_KEY_SPACE) == GLFW_PRESS) {
        Camera_ProcessKeyboard(&Context->Camera, UP, Context->DeltaTime);
    }

    if (glfwGetKey(Context->Window, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS) {
        Camera_ProcessKeyboard(&Context->Camera, DOWN, Context->DeltaTime);
    }

    if (glfwGetMouseButton(Context->Window, GLFW_MOUSE_BUTTON_LEFT) ==
        GLFW_PRESS) {
        int WindowWidth;
        int WindowHeight;
        glfwGetWindowSize(Context->Window, &WindowWidth, &WindowHeight);
        float CenterX = WindowWidth / 2.0f;
        float CenterY = WindowHeight / 2.0f;

        // INFO: Hide the mouse cursor on left mouse button click
        glfwSetInputMode(Context->Window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);

        if (Context->FirstClick) {
            // Center the cursor and reset the last mouse position
            glfwSetCursorPos(Context->Window, CenterX, CenterY);
            Context->LastX = CenterX;
            Context->LastY = CenterY;
            Context->FirstClick = false;
        }

        double MouseX;
        double MouseY;
        glfwGetCursorPos(Context->Window, &MouseX, &MouseY);

        float OffsetX = (float)(MouseX)-Context->LastX;
        float OffsetY = Context->LastY - (float)(MouseY);

        Context->LastX = MouseX;
        Context->LastY = MouseY;

        Camera_ProcessMouseMovement(&Context->Camera, OffsetX, OffsetY, true);

        // Reset the mouse position and the last mouse position
        // to calculate the next frame's delta
        glfwSetCursorPos(Context->Window, CenterX, CenterY);
        Context->LastX = CenterX;
        Context->LastY = CenterY;
    } else if (glfwGetMouseButton(Context->Window, GLFW_MOUSE_BUTTON_LEFT) ==
               GLFW_RELEASE) {
        glfwSetInputMode(Context->Window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
        Context->FirstClick = true;
    }
}

void MouseScrollCallback(GLFWwindow *Window, double OffsetX, double OffsetY) {
    Camera_ProcessMouseScroll(&Context.Camera, static_cast<float>(OffsetY));
}

We use the ProcessInput method every frame to update the camera state.

Camera

We now need a camera entity that moves around. The camera will be used as the view matrix for the shaders to transform everything to the point of view of the camera. This is why we need a handy function called Camera_GetViewMatrix.

#ifndef CAMERA_H_
#define CAMERA_H_

#include <glad/glad.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

// Defines several possible options for camera movement. Used as abstraction to
// stay away from window-system specific input methods
enum camera_movement { FORWARD, BACKWARD, LEFT, RIGHT, UP, DOWN };

// Default camera values
const float YAW = -90.0f;
const float PITCH = 0.0f;
const float SPEED = 2.5f;
const float SENSITIVITY = 0.1f;
const float ZOOM = 45.0f;

typedef struct camera {
    // camera Attributes
    glm::vec3 Position;
    glm::vec3 Front;
    glm::vec3 Up;
    glm::vec3 Right;
    glm::vec3 WorldUp;
    // euler Angles
    float Yaw;
    float Pitch;
    // camera options
    float MovementSpeed;
    float MouseSensitivity;
    float Zoom;
} camera;

camera Camera_Create(glm::vec3 Position, glm::vec3 Up, float Yaw, float Pitch);
glm::mat4 Camera_GetViewMatrix(const camera &Camera);
// processes input received from any keyboard-like input system. Accepts input
// parameter in the form of camera defined ENUM (to abstract it from windowing
// systems)
void Camera_ProcessKeyboard(camera *Camera, camera_movement Direction,
                            float DeltaTime);
// processes input received from a mouse input system. Expects the offset
// value in both the x and y direction.
void Camera_ProcessMouseMovement(camera *Camera, float OffsetX, float OffsetY,
                                 GLboolean ConstrainPitch);
void Camera_ProcessMouseScroll(camera *Camera, float OffsetY);
#endif

Here are the implementations of the camera functions:

#include "camera.h"

static void Camera_UpdateVectors(camera *Camera);

camera Camera_Create(glm::vec3 Position, glm::vec3 Up, float Yaw, float Pitch) {
    camera Camera = {};
    Camera.Front = glm::vec3(0.0f, 0.0f, -1.0f);
    Camera.MovementSpeed = SPEED;
    Camera.MouseSensitivity = SENSITIVITY;
    Camera.Zoom = ZOOM;
    Camera.Position = Position;
    Camera.WorldUp = Up;
    Camera.Yaw = Yaw;
    Camera.Pitch = Pitch;

    Camera_UpdateVectors(&Camera);

    return Camera;
}

glm::mat4 Camera_GetViewMatrix(const camera &Camera) {
    return glm::lookAt(Camera.Position, Camera.Position + Camera.Front,
                       Camera.Up);
}

void Camera_ProcessKeyboard(camera *Camera, camera_movement Direction,
                            float DeltaTime) {
    float Velocity = Camera->MovementSpeed * DeltaTime;

    if (Direction == FORWARD) {
        Camera->Position += Camera->Front * Velocity;
    }
    if (Direction == BACKWARD) {
        Camera->Position -= Camera->Front * Velocity;
    }
    if (Direction == LEFT) {
        Camera->Position -= Camera->Right * Velocity;
    }
    if (Direction == RIGHT) {
        Camera->Position += Camera->Right * Velocity;
    }
    if (Direction == UP) {
        Camera->Position += Camera->Up * Velocity;
    }
    if (Direction == DOWN) {
        Camera->Position -= Camera->Up * Velocity;
    }
}

void Camera_ProcessMouseMovement(camera *Camera, float OffsetX, float OffsetY,
                                 GLboolean ConstrainPitch) {
    OffsetX *= Camera->MouseSensitivity;
    OffsetY *= Camera->MouseSensitivity;

    Camera->Yaw += OffsetX;
    Camera->Pitch += OffsetY;

    // make sure that when pitch is out of bounds, screen doesn't get flipped
    if (ConstrainPitch) {
        if (Camera->Pitch > 89.0f) {
            Camera->Pitch = 89.0f;
        }
        if (Camera->Pitch < -89.0f) {
            Camera->Pitch = -89.0f;
        }
    }

    Camera_UpdateVectors(Camera);
}

void Camera_ProcessMouseScroll(camera *Camera, float OffsetY) {
    Camera->Zoom -= (float)OffsetY;
    if (Camera->Zoom < 1.0f) {
        Camera->Zoom = 1.0f;
    }
    if (Camera->Zoom > 45.0f) {
        Camera->Zoom = 45.0f;
    }
}

static void Camera_UpdateVectors(camera *Camera) {
    // calculate the new Front vector
    glm::vec3 Front;
    Front.x = cos(glm::radians(Camera->Yaw)) * cos(glm::radians(Camera->Pitch));
    Front.y = sin(glm::radians(Camera->Pitch));
    Front.z = sin(glm::radians(Camera->Yaw)) * cos(glm::radians(Camera->Pitch));
    Camera->Front = glm::normalize(Front);

    // also re-calculate the Right and Up vector
    // normalize the vectors, because their length gets
    // closer to 0 the more you look up or down which
    // results in slower movement.
    Camera->Right = glm::normalize(glm::cross(Camera->Front, Camera->WorldUp));
    Camera->Up = glm::normalize(glm::cross(Camera->Right, Camera->Front));
}

Perspective Camera

For the 3D engine we will use a perspective view using the camera to create the projection matrix.

glm::mat4 View = Camera_GetViewMatrix(Camera);
glm::mat4 Projection = glm::perspective(
    glm::radians(Camera.Zoom), ScreenWidth / ScreenHeight, 0.1f, 100.0f);

We then can set these matrices values’ uniforms for the shaders.

void Renderer_SetCameraUniforms(const renderer &Renderer, const camera &Camera,
                                float ScreenWidth, float ScreenHeight) {
    glm::mat4 View = Camera_GetViewMatrix(Camera);
    glm::mat4 Projection = glm::perspective(
        glm::radians(Camera.Zoom), ScreenWidth / ScreenHeight, 0.1f, 100.0f);

    // Set view & projection for normal shader
    glUseProgram(Renderer.ShaderProgram.ID);
    glUniformMatrix4fv(Renderer.ShaderProgram.Uniforms.ViewUniformLoc, 1,
                       GL_FALSE, glm::value_ptr(View));
    glUniformMatrix4fv(Renderer.ShaderProgram.Uniforms.ProjectionUniformLoc, 1,
                       GL_FALSE, glm::value_ptr(Projection));

    glUniform3fv(Renderer.ShaderProgram.Uniforms.ViewPositionUniformLoc, 1,
                 glm::value_ptr(Camera.Position));

    // Other Camera/View Related Uniforms
    // ...
}

Now that we have a flying camera we can move around the scene and see the objects from different perspectives.

Squared Wave SVG