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.
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.