Im Rahmen dieser Übung wollen wir eine 3rd Person Kamera Steuerung implementieren. Das heißt die Kamera hat einen unveränderlichen Zielpunkt und ihre Position kann um diesen Zielpunkt rotiert werden. Zusätzlich werden wir mit dem Mausrad oder der Scrollfunktion in der Lage sein den Abstand zum Ziel zu verändern.

Erstellen Sie eine neue Translation Unit camera.o

Beginnen wir mit einem Blick in die einfachste Version der Header Datei.

#include <GLFW/glfw3.h>

void loadCameraProjectionMatrix(float aspect);
void loadCameraViewMatrix();

Hier definieren wir zwei Methoden mit deren Hilfe wir zukünftig die Projektionsmatrix und die Viewmatrix laden. Lesen Sie hierzu auch diesen Artikel auf codinglabs.net. Hier unsere Implementierung:

#include <math.h>
#include "cgmath.h"
#include "camera.h"

static double cameraPitch = 0.0;
static double cameraYaw = 0.0;
static double cameraDistance = 3.0;

void loadCameraProjectionMatrix(float aspect)
{
    float zNear = 0.1f;
    float zFar = 100.0f;
    float fov = deg2rad(45.0f);

    float h = zNear * tanf(fov * 0.5f);
    float w = h * aspect;

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glFrustum(-w, w, -h, h, zNear, zFar);
    
}

void loadCameraViewMatrix()
{
    glMatrixMode(GL_MODELVIEW);

    glLoadIdentity();
    matrix translationMatrix = matrixTranslate(0.0f, 0.0f, -cameraDistance);
    glMultMatrixf(&translationMatrix);
    matrix pitchRotationMatrix = matrixRotateX(deg2rad(-cameraPitch));
    glMultMatrixf(&pitchRotationMatrix);
    matrix yawRotationMatrix = matrixRotateY(deg2rad(-cameraYaw));
    glMultMatrixf(&yawRotationMatrix);
}

In der Projektionsmatrix nutzen wir glFrustum um eine perspektivische Transformation zu realisieren. Zusätzlich kommt eine bisschen Pythagoras ins Spiel um aus einem FOV (=FieldOfView) die Höhe der Near Plane zu berechnen. Diese Matrix ändert sich nur, falls sich das Seitenverhältnis des Fensters ändert. Lesen Sie sich in diese Materie ein und stellen Sie sicher, dass Sie alles verstehen.

Die ViewMatrix dient nun dazu die eigentliche Bewegung der Camera umzusetzen. Sie sehen, dass wir hier mehrere Matritzen laden und nacheinander zusammen multiplizieren.

Integration in den bestehenden Code

Um von diesen beiden Matrizen Gebrauch zu machen, entfernen Sie den bisherigen Code, die die Orthogonale Projektion aus Ihrer scene.c und fügen Sie die gerade erstellen Methoden hinzu:

#include "camera.h"

...

loadCameraProjectionMatrix((float)(width / height));
loadCameraViewMatrix();

Wenn Sie alles richtig gemacht habe, dann können Sie die Anwendung jetzt starten und alles sollte exakt genauso aussehen wie vorher. Falls Ihr Rechteck verschwindet oder ein anderes Problem auftritt sollten Sie auf Fehlersuche gehen.

Mauseingaben verarbeiten

Wenn bis hierhin alles geklappt hat, können Sie nun mit der Verarbeitung der Mauseingaben weiter machen.

Hierzu erweitern wir die camera.h um 3 neue Funktionen:

void enableCameraMouseControl(GLFWwindow* window);

static void cursorPositionCallback(GLFWwindow* window, double xpos, double ypos);
static void scrollCallback(GLFWwindow* window, double xoffset, double yoffset);

Die Funktion enableCameraMouseControl wird alle Funktionen beinhalten, die wir benötigen um die Maussteuerung zu initialisieren. Die beiden anderen Funktionen sind Callbacks, die ausgelöst werden, sobald der Benutzer die Maus bewegt.

Hier die Implementierungen der Reihe nach. Zuerst definieren wir zwei statische Variablen um die jeweils letzten Koordinaten der Maus zu speichern:

static double mouseLastX = 0.0;
static double mouseLastY = 0.0;

Anschließend definieren wir ebenfalls als statische Variablen mit Geschwindigkeitsfaktoren für die Maus. Diese können Sie später anpassen falls Sie ihre Maus schneller oder langsamer machen möchten.

static double scrollSpeed = 0.1;
static double mouseSpeed = 0.05;

Hier nun die enableMouseControl:

void enableCameraMouseControl(GLFWwindow* window)
{
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
    if (glfwRawMouseMotionSupported())
    {
        glfwSetInputMode(window, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE);
    }
    glfwSetScrollCallback(window, scrollCallback);
    glfwSetCursorPosCallback(window, cursorPositionCallback);
    glfwGetCursorPos(window, &mouseLastX, &mouseLastY);
}

Haben Sie eine Ahnung was die beiden Aufrufe von glfwSetInputMode tun? Die beiden Callbacks sollten selbsterklärend sein. In der letzten Zeile werden schließlich die Koordinaten der aktuellen Mausposition gespeichert, damit unsere Kamera beim ersten Frame keinen riesen Sprung macht.

Bei jeder Bewegung der Maus wird von nun an unser cursorPositionCallback aufgerufen:

static void cursorPositionCallback(GLFWwindow* window, double xpos, double ypos)
{
    double deltaX = xpos - mouseLastX;
    mouseLastX = xpos;

    double yaw = cameraYaw - deltaX * mouseSpeed;
    
    while (yaw < 0) yaw += 360;
    while (yaw > 360) yaw -= 360;

    cameraYaw = yaw;

    double deltaY = ypos - mouseLastY;
    mouseLastY = ypos;

    double pitch = cameraPitch - deltaY * mouseSpeed;
    
    if (pitch < -90) pitch = -90;
    if (pitch > 90) pitch = 90;

    cameraPitch = pitch;
}

Und bei jeder Scrolleingabe wird unser scrollCallback aufgerufen:

static void scrollCallback(GLFWwindow* window, double xoffset, double yoffset)
{
    double distance = cameraDistance - yoffset * scrollSpeed;

    if (distance < 1.4) distance = 1.4;
    if (distance > 20.0) distance = 20.0;

    cameraDistance = distance;
}

Maussteuerung aktivieren

Damit wir etwas sehen, müssen wir nun zuletzt noch in der graphics.c unsere Maussteuerung aktivieren. Dies tun wir am besten kurz bevor unser Message Loop beginnt.

#include "camera.h"

...

enableCameraMouseControl(window);

Testen Sie ob Sie die Kamera mit der Maus steuern können.