In diesem Kapitel liegt der Fokus auf dem Hintergrund der Szene. Wir laden eine Sky Box und platzieren eine Sonne. Damit das funktioniert müssen wir einiges beachten denn weder die Sterne noch die Sonne sollen beim "zoomen" ihre Position verändern.

Einführung

Eine Sky Box basiert auf dem Prinzip des Cube Mappings. In Computerspielen wird dieses Verfahren genutzt um Teile der weit entfernten Umgebung des Spielers mittels einer speziellen Projektionstechnik auf einen Würfel zu projizieren. Auf diese Weise können komplexe Landschaften oder weit entfernte Objekte angezeigt werden ohne die tatsächliche Geometrie dieser Objekte laden zu müssen. In unserem Anwendungsfall nutzen wir es als einfache Möglichkeit die Sterne zu rendern.

Texturen laden

  • Laden Sie sich diese Star Map Textur herunter und speichern Sie die Datei als Computergrafik/textures/cubemap8k.jpg
  • Zum Testen der Implementierung und zum besseren Verständnis können Sie die Cube Map Referenz nutzen.

Cube Map Mesh erzeugen

Zuerst erzeugen wir uns die nötige Geometrie um eine Cube Map zu rendern. Im Prinzip handelt es sich dabei um einen einfachen Würfel der jedoch von innen betrachtet wird und deshalb die Normalenvektoren invertiert hat. Ebenfalls hat die Cube Map andere Texturkoordinaten als der Würfel den wir bisher verwendet haben.

Hier der Würfel für die Cube Map, links ohne Backface Culling und rechts mit.

Erstellen Sie eine neue Klasse Skybox.

skybox.h

#pragma once #include "mesh.h" class Skybox : public Mesh { public: Skybox(std::shared_ptr<Texture> &texture); };

skybox.cpp

#include "skybox.h" Skybox::Skybox(std::shared_ptr<Texture> &texture) : Mesh(texture) { vertices.reserve(24); // +y vertices.push_back(Vertex({1, 1, -1}, {0, -1, 0}, {2 / 3.f, 1 / 2.f})); vertices.push_back(Vertex({1, 1, 1}, {0, -1, 0}, {2 / 3.f, 2 / 2.f})); vertices.push_back(Vertex({-1, 1, 1}, {0, -1, 0}, {1 / 3.f, 2 / 2.f})); vertices.push_back(Vertex({-1, 1, -1}, {0, -1, 0}, {1 / 3.f, 1 / 2.f})); // +z vertices.push_back(Vertex({1, -1, 1}, {0, 0, -1}, {2 / 3.f, 1 / 2.f})); vertices.push_back(Vertex({-1, -1, 1}, {0, 0, -1}, {3 / 3.f, 1 / 2.f})); vertices.push_back(Vertex({-1, 1, 1}, {0, 0, -1}, {3 / 3.f, 2 / 2.f})); vertices.push_back(Vertex({1, 1, 1}, {0, 0, -1}, {2 / 3.f, 2 / 2.f})); // -x vertices.push_back(Vertex({-1, -1, 1}, {1, 0, 0}, {0 / 3.f, 0 / 2.f})); vertices.push_back(Vertex({-1, -1, -1}, {1, 0, 0}, {1 / 3.f, 0 / 2.f})); vertices.push_back(Vertex({-1, 1, -1}, {1, 0, 0}, {1 / 3.f, 1 / 2.f})); vertices.push_back(Vertex({-1, 1, 1}, {1, 0, 0}, {0 / 3.f, 1 / 2.f})); // -y vertices.push_back(Vertex({-1, -1, -1}, {0, 1, 0}, {1 / 3.f, 1 / 2.f})); vertices.push_back(Vertex({-1, -1, 1}, {0, 1, 0}, {1 / 3.f, 0 / 2.f})); vertices.push_back(Vertex({1, -1, 1}, {0, 1, 0}, {2 / 3.f, 0 / 2.f})); vertices.push_back(Vertex({1, -1, -1}, {0, 1, 0}, {2 / 3.f, 1 / 2.f})); // +x vertices.push_back(Vertex({1, -1, -1}, {-1, 0, 0}, {0 / 3.f, 1 / 2.f})); vertices.push_back(Vertex({1, -1, 1}, {-1, 0, 0}, {1 / 3.f, 1 / 2.f})); vertices.push_back(Vertex({1, 1, 1}, {-1, 0, 0}, {1 / 3.f, 2 / 2.f})); vertices.push_back(Vertex({1, 1, -1}, {-1, 0, 0}, {0 / 3.f, 2 / 2.f})); // -z vertices.push_back(Vertex({-1, -1, -1}, {0, 0, 1}, {2 / 3.f, 0 / 2.f})); vertices.push_back(Vertex({1, -1, -1}, {0, 0, 1}, {3 / 3.f, 0 / 2.f})); vertices.push_back(Vertex({1, 1, -1}, {0, 0, 1}, {3 / 3.f, 1 / 2.f})); vertices.push_back(Vertex({-1, 1, -1}, {0, 0, 1}, {2 / 3.f, 1 / 2.f})); }

Camera anpassen

Würden wir die Skybox einfach rendern, dann würde Sie wie jedes andere Objekt in unsere 3D Szene positioniert werden. Das heißt wenn man die Kamera zoomt, wird der Sternenhimmel größer oder kleiner. Probieren Sie das gerne mal aus.

Um die Illusion zu erschaffen, dass die Sterne und auch die Sonne immer im Hintergrund sind, müssen wir die Kamera anpassen. Genauer gesagt wir benötigen eine zweite, spezielle View Matrix:

camera.h

class Camera { public: ... void loadFixedViewMatrix() const; ... };

camera.cpp

void Camera::loadFixedViewMatrix() const { glMatrixMode(GL_MODELVIEW); glLoadIdentity(); Matrix4 pitchRotation = Matrix4::rotateX(deg2rad(-pitch)); float pitchRotationF[16]; pitchRotation.toColumnMajor(pitchRotationF); glMultMatrixf(pitchRotationF); Matrix4 yawRotation = Matrix4::rotateY(deg2rad(-yaw)); float yawRotationF[16]; yawRotation.toColumnMajor(yawRotationF); glMultMatrixf(yawRotationF); }

Können Sie erklären wo der Unterschied zur normalen View Matrix liegt?

Scene anpassen

Um zu unterscheiden welche Objekte diese neue View Matrix benutzen sollen führen wir zwei entsprechende Flags in der Szene ein. So kann man verschiedenen Szenen erstellen und je nach Einstellung der Szene werden alle Objekte mit der passenden View Matrix gerendert.

scene.h

class Scene { public: ... void enableDepthIsolation(); void enableFixedPosition(); private: ... bool depthIsolation = false; bool fixedPosition = false; };

scene.cpp

void Scene::enableDepthIsolation() { depthIsolation = true; } void Scene::enableFixedPosition() { fixedPosition = true; }

Und natürlich noch der eigentliche Rendervorgang. Hier wird die jeweils passende Matrix geladen und am Ende bei Bedarf der Tiefenpuffer geleert.

scene.cpp

void Scene::render(const Camera &camera) const { if (fixedPosition) { camera.loadFixedViewMatrix(); } else { camera.loadViewMatrix(); } glEnable(GL_LIGHT1); ... glDisable(GL_LIGHT1); if (depthIsolation) { glClear(GL_DEPTH_BUFFER_BIT); } }

Renderer anpassen

In Renderer::start() erstellen wir nun eine zweite Szene background und laden dort Sterne und Sonne als neue Modelle. Anschließend rendern wir erst den Hintergrund, dann den Vordergrund.

renderer.cpp

namespace Colors { ... Color sky = Color(0.5, 0.5, 0.5, 1.0); }

renderer.cpp

void Renderer::start() { auto starTexture = std::make_shared<Texture>("textures/cubemap8k.jpg"); auto noTexture = std::shared_ptr<Texture>{}; ... auto stars = std::make_shared<Skybox>(starTexture); auto sun = std::make_shared<Sphere>(noTexture); ... stars->setScale(5); sun->setScale(0.03); sun->setPosition(Vector3(0, 0, 3)); sun->setMaterial(Colors::black, Colors::black, Colors::white, Colors::black, 0.0f); Scene background; background.addMesh(stars); background.addMesh(sun); background.enableDepthIsolation(); background.enableFixedPosition(); ... background.setLight(lightPosition, Colors::black, Colors::sky, Colors::black); while (!glfwWindowShouldClose(window)) { ... background.render(activeCamera); foreground.render(activeCamera); ... } }

Hausaufgabe

  • Bereiten Sie dieses Kapitel eigenständig bis zum nächsten Termin vor.
  • Ziehen Sie weitere Quellen zu Rate um wirklich zu verstehen was hier passiert und wozu das nötig ist.
  • Bereiten Sie sich auf das nächste Plenum vor.