In diesem Kapitel konzentrieren wir uns darauf unsere Erde gut in Szene zu setzen. Das heißt wir räumen die Szene etwas auf und fügen der Erde Animationen hinzu, so dass sie sich abhängig von Tageszeit und Monat passend zur Sonne dreht. So entsteht die Illusion von echten Jahreszeiten und Sonnenständen. Außerdem platzieren wir einen kleinen Satelliten im Orbit.
Skalierungsmatrix
Wir benötigen die Möglichkeit Objekte zu skalieren. Hierfür implementieren wir eine Skalierungsmatrix.
struct Matrix4
{
...
static Matrix4 scale(double a)
{
Matrix4 m = {
a, 0, 0, 0,
0, a, 0, 0,
0, 0, a, 0,
0, 0, 0, 1
};
return m;
}
...
};
Anschließend rufen wir diese Funktion in einer neuen Funktion setScale() innerhalb der Mesh-Klasse auf.
mesh.h
class Mesh
{
public:
...
void setScale(const double scale);
protected:
...
Matrix4 scale = Matrix4::scale(1.0);
};
mesh.cpp
void Mesh::setScale(const double scale)
{
this->scale = Matrix4::scale(scale);
}
Mit dieser Änderung können wir Modelle nun in verschiedenen Größen darstellen. Damit wir dies aber auch sehen muss die Skalierungsmatrix beim Rendern auch verwendet werden.
mesh.cpp
void Mesh::render() const
{
Matrix4 worldMatrix = position * rotation * scale;
...
}
Neue Klasse Simulation
In der Klasse Simulation soll alles stattfinden, was mit dem Bewegen der geladenen Meshes zu tun hat. Dazu legen wir eine neue Datei simulation.cpp und simulation.h an.
Wenn Sie ein Computerspiel entwickeln möchten, dann wäre diese Klasse die richtige Stelle für die Logik und Physik, aber auch für das Anwenden von Benutzereingaben auf die virtuelle Welt.
simulation.h
#pragma once
#include "mesh.h"
#include <memory>
class Simulation
{
public:
Simulation(const std::shared_ptr<Mesh> &earth, const std::shared_ptr<Mesh> &satellite);
void update();
private:
void updateEarthRotation(double time);
void updateSatellitePosition(double time);
std::shared_ptr<Mesh> earth;
std::shared_ptr<Mesh> satellite;
};
simulation.cpp
#include "simulation.h"
#include <chrono>
Simulation::Simulation(const std::shared_ptr<Mesh> &earth, const std::shared_ptr<Mesh> &satellite)
{
this->earth = earth;
this->satellite = satellite;
}
void Simulation::update()
{
auto time = std::chrono::system_clock::now();
auto timeSinceEpoch = time.time_since_epoch();
double secondsSinceEpoch = std::chrono::duration<double>(timeSinceEpoch).count();
updateEarthRotation(secondsSinceEpoch);
updateSatellitePosition(secondsSinceEpoch);
}
...
Mithilfe des Wertes in secondsSinceEpoch erhalten wir die aktuelle Uhrzeit. Damit sind wir in der Lage innerhalb unserer beiden Funktionen die Rotation der Erde und die Position des Satelliten zu bestimmen. Dies geschieht in jedem Frame.
Ekliptik und Rotation der Erde
Als nächstes wollen wir die Drehung und Neigung der Erde zur Sonne berechnen. Wir könnten dies entweder durch eine tatsächliche Bewegung des Planeten entlang seiner Bahn erreichen oder wir vereinfachen die Logik soweit, dass die Erde still steht und sich lediglich dreht und neigt. Letzteres erscheint für unseren konkreten Anwendungsfall praktikabler. Die erste Komponente unserer Animation ist die Tageszeit. Wir wollen, dass unsere Erde in 24 Stunden exakt einmal um Ihre eigene Achse rotiert. Als Orientierungspunkt für die Ausrichtung der Erde legen wir fest, dass jeden Tag um 12 Uhr mittags in der UTC-Zeitzone die Sonne im Zenit über dem Nullmeridian steht. Die zweite Komponente ist die Neigung der Erde gegen die Ebene der Ekliptik. Die Erde bewegt sich auf einer elliptischen Bahn um die Sonne. Wir vereinfachen diese Bahn zu einer Kreisbahn und nutzen die Cosinus Funktion zum Berechnen der Neigung. Wir wissen, dass am 21. Dezember die Südhalbkugel am stärksten zur Sonne geneigt ist und am 21.06. die Nordhalbkugel die größte Sonneneinstrahlung erfährt.
simulation.cpp
void Simulation::updateEarthRotation(double time)
{
double timeOfDay = std::fmod(time, 86400);
double earthRotation = timeOfDay / 86400.0 * deg2rad(360);
int timeOfYear = std::fmod(time + 864000.0, 31557600);
double earthEcliptic = cos(timeOfYear / 31557600.0 * deg2rad(360)) * deg2rad(-23.4);
earth->setRotation(Vector3(earthEcliptic, earthRotation, 0));
}
Umlaufbahn eines Satelliten
Um die Szene ein bisschen aufzulockern wollen wir einen Satelliten um die Erde kreisen lassen. Was eignet sich dafür besser als ein riesiger THM Würfel? Wir verkleinern unseren Würfel, auf eine Größe von 50x50x50 Kilometer und platzieren ihn in einer Umlaufbaum um den Äquator in einer Höhe von 400km. Das entspricht in etwa der Höher der Internationalen Raumstation. In dieser Höhe dauert ein Umlauf etwa 90 Minuten.
simulation.cpp
void Simulation::updateSatellitePosition(double time)
{
double scale = 25.0 / 6370.0;
double orbitTime = 5400.0;
double orbitProgress = std::fmod(time, orbitTime);
double orbitRadius = 6770.0 / 6370.0;
Vector4 position = Vector4(0.0, 0.0, orbitRadius, 1.0);
Matrix4 orbit = Matrix4::rotateX(deg2rad(45)) * Matrix4::rotateY(orbitProgress / orbitTime * deg2rad(360.0));
position = orbit * position;
double tumbleTime = 60.0;
double tumbleProgress = std::fmod(time, tumbleTime);
double tumble = tumbleProgress / tumbleTime * deg2rad(360.0);
satellite->setScale(scale);
satellite->setPosition(position.xyz());
satellite->setRotation(Vector3(tumble, tumble, tumble));
}
Verwendung der neuen Klasse im Renderer
Im Renderer werden nun nur noch Erde und Satellit mit ihren Texturen geladen. Die beiden Meshes übergeben wir dem Konstruktor von Simulation. In der Hauptschleife wird unsere Szene pro Frame geupdatet.
renderer.cpp
namespace Colors
{
...
Color black = Color(0.0, 0.0, 0.0, 1.0);
}
renderer.cpp
void Renderer::start()
{
auto earthTexture = std::make_shared<Texture>("textures/earth_diffuse.jpg");
auto satelliteTexture = std::make_shared<Texture>("textures/thm2k.png");
auto earth = std::make_shared<Sphere>(earthTexture);
auto satellite = std::make_shared<Cube>(satelliteTexture);
satellite->setScale(0.01);
satellite->setMaterial(Colors::black, Colors::black, Colors::white, Colors::black, 0.0f);
Scene foreground;
foreground.addMesh(earth);
foreground.addMesh(satellite);
Simulation simulation(earth, satellite);
setViewportSize();
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
Vector4 lightPosition(0, 0, 50000, 0);
foreground.setLight(lightPosition, Colors::sunLight, Colors::ambientLight, Colors::white);
while (!glfwWindowShouldClose(window))
{
simulation.update();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
foreground.render(activeCamera);
glfwSwapBuffers(window);
glfwPollEvents();
printFps();
if (resized)
{
resized = false;
setViewportSize();
}
}
}
Versuchen Sie nachzuvollziehen, was hier im Einzelnen passiert. Eine Neuerung führen wir auch mit den Materialeigenschaften des Satelliten ein. Können Sie erklären was wir da machen?
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.