Renaissance der Weltraumspiele – Teil 2

OpenGL: Prozedurale Erzeugung von Sonnensystemen
Kommentare

„Und sie bewegt sich doch!“ – das Thema des folgenden Beitrags steht ganz im Zeichen großer Gelehrter wie Galileo Galilei, Nikolaus Kopernikus, Johannes Kepler und Isaac Newton, die mit Fug und Recht als Väter unseres modernen heliozentrischen Weltbilds gehandelt werden. Nachdem wir uns in früheren Artikeln bereits mit atmosphärischen Streuprozessen sowie mit der prozeduralen Erzeugung und Darstellung von Gasgiganten und Gesteinsplaneten beschäftigt haben, gehen wir heute einen Schritt weiter und erschaffen nichts Geringeres als ein vollständiges Sonnensystem.

„Der Weltraum, unendliche Weiten…“ – der weltberühmte Star-Trek-Leitspruch beschreibt wohl am besten, worauf es bei einem guten Weltraumspiel ankommt: „In einer fernen Zukunft“ bereisen wir mit gigantischen Raumschiffen das noch unerforschte All und entdecken dabei „viele Lichtjahre von der Erde entfernt fremde Welten, unbekannte Lebensformen und neue Zivilisationen“.

Im Jahr 1993 erschien mit Frontier Elite 2 das erste Spiel, das mit einer für die damaligen technischen Möglichkeiten unglaublich realistischen Weltraumsimulation zu beeindrucken wusste. Als Spieler kann man mit verschiedenen Raumschiffen unsere Heimatgalaxie bereisen und dabei eine unglaubliche Anzahl (viele Milliarden!) größtenteils prozedural erzeugter bewohnter wie unbewohnter Sonnensysteme erkunden. Es ist möglich, auf Planeten, Monden oder Raumstationen zu landen. Man kann sich zwei verfeindeten interstellaren Imperien anschließen, Handel treiben und muss sich gegen finstere Piraten zur Wehr setzen. Auch wenn Elite 2 alles andere als perfekt ist – die realistische Flugphysik wirkt sich beispielsweise nachteilig auf die Raumkämpfe aus, und der Handel wird spätestens dann uninteressant, wenn man das größte Raumschiff und die stärksten Waffen besitzt –, kann das Spiel als ein Paradebeispiel für die so genannte „Fünf-Minuten-Regel“ des Game-Designs angesehen werden. Diese Regel besagt, dass sich normalerweise innerhalb der ersten fünf Spielminuten entscheidet, ob ein Spieler eine emotionale Bindung zu einem Spiel aufbauen kann oder eben nicht. Diese auch als „Wow!-Effekt“ bekannte Reaktion wurde im Fall von Elite 2 zum einen durch das Setting und zum anderen durch die Ereignisse während der ersten Flugminuten ausgelöst. Man befindet sich zu Beginn des Spiels neun Lichtjahre von der Erde entfernt im Ross-154-System, im Sirocco-Raumhafen auf einem Mond namens Merlin und kann am Himmel den Saturn-ähnlichen Gasgiganten Aster erkennen, den der Mond umkreist. Da man bisher in keinem anderen Spiel echte Planetensysteme erkunden konnte, fühlt man sich als Spieler praktisch dazu aufgefordert, nach dem Start des eigenen Raumschiffs zunächst einmal einen kurzen Abstecher zum benachbarten Gasriesen zu unternehmen. Während dieser Reise hat man seine helle Freude an der realistischen Flugphysik und lernt mal ganz nebenbei die Auswirkungen eines so genannten Swing-by-Manövers kennen, das zum ersten Mal bei den Flügen der Voyager-Sonden zu den äußeren Planeten des Sonnensystems zum Einsatz kam. Zur Erklärung: Bei einem solchen Flugmanöver nutzt man die Schwerkraft und Bahngeschwindigkeit eines Planeten, um die Flugrichtung eines Raumschiffs oder einer Sonde zu ändern, wobei sich die Geschwindigkeit in Abhängigkeit von der Anflugrichtung entweder vergrößert oder verringert. Obwohl die Grafik von 1993 natürlich längst nicht mehr zeitgemäß ist, hat das damals gewählte Setting bis heute nichts von seiner Faszination verloren. Flüge durch die Atmosphäre eines Mondes mit Blick auf den gigantischen, von Ringen umgebenen Nachbarplaneten (Abb. 1 und 2), orbitale Sonnenauf- und -untergänge (Abb. 3 und 4) sowie weitläufige Asteroidenfelder (Abb. 5 und 6) sind auch heute noch beliebte Motive auf den Screenshots moderner Weltraumspiele.

Info
Die kompletten Quellcodes zu diesem Artikel finden Sie auf der Landingpage zur Entwickler Magazin Ausgabe 6.2013 unter dem Reiter „Quellcode“.

Abb. 1: Atmosphärenflug (1)

Abb. 2: Atmosphärenflug (2)

Abb. 3: Orbitaler Sonnenauf- bzw. -untergang (1)

Abb. 4: Orbitaler Sonnenauf- bzw. -untergang (2)

Abb. 5: Planetenringe (1)

Abb. 6: Planetenringe (2)

 

Aufmacherbild: Space sunrise of an alien system. von Shutterstock / Urheberrecht: AstroStar

[ header = Seite 2: Prozedurale Erzeugung eines Sonnensystems ]

Prozedurale Erzeugung eines Sonnensystems

Abb. 7: Prozedurales Sonnensystem (1)

Abb. 8: Prozedurales Sonnensystem (2)

Abb. 9: Eigenschaften einer Ellipse

Wegen der schieren Größe der Spielewelt bietet es sich bei Weltraumspielen geradezu an, die überwiegende Anzahl aller für den Spieler erreichbaren Sonnensysteme prozedural zu generieren (Abb. 7 und 8). Lediglich unser Heimatsystem, die Heimatsysteme der übrigen galaktischen Zivilisationen sowie alle für den Storyverlauf unverzichtbaren Systeme müssen von Hand definiert werden.
Die Charakteristika eines Planetensystems hängen in erster Linie von der Größe und Strahlungsintensität seines Zentralgestirns ab. In unseren Spieleprototypen berücksichtigen wir bei der prozeduralen Erzeugung der frei erkundbaren Spielegalaxie die nachfolgenden Sternenklassen:

• Blauer Stern (Spektralklasse A)
• Oranger Stern (Spektralklasse K)
• Roter Zwerg (Spektralklasse M)
• Roter Riese (Spektralklasse M)
• Gelber Stern (Spektralklasse G)
• Weißer Stern (Spektralklasse F)
• Blauer Riese (Spektralklasse B)
• Blauer Überriese (Spektralklasse O)

Mit welcher Häufigkeit die jeweiligen Sternentypen anzutreffen sind, obliegt der Verantwortung des Spieledesigners. Obwohl unsere Milchstraße beispielsweise mehrheitlich aus roten Zwergen besteht, werden die meisten von uns sicher eine größere Anzahl von gelben – sonnenähnlichen – Sternen bevorzugen.
In Anlehnung an die Größe unseres eigenen Heimatsystems legen wir für unsere prozeduralen Systeme eine Obergrenze von zehn Planeten fest und definieren für jede mögliche Umlaufbahn (Orbit) eine minimale und maximale Distanz zum Zentralstern. Die Planeten selbst werden bei der Generierung eines neuen Sonnensystems zunächst am sonnennächsten Punkt (Periapsis) ihres Orbits positioniert (Abb. 9). Wie groß nun die Wahrscheinlichkeit ist, auf einer dieser Orbitalbahnen tatsächlich einen Planeten eines bestimmten Typs anzutreffen, hängt zum einen von der Orbitaldistanz und zum anderen von den physikalischen Eigenschaften des jeweiligen Sterns ab:

• Erdähnliche Planeten und Wasserwelten finden sich nur in der so genannten habitablen Zone eines Sonnensystems. Im Unterschied zu einem gelben Stern sind dies bei einem Roten Zwerg die Orbitalbahnen in unmittelbarer Sonnennähe.
• In größerer Entfernung zu einem Stern sind vermehrt Eisplaneten und Gasriesen anzutreffen.
• Planeten mit dichter toxischer Atmosphäre (starker Treibhauseffekt) oder Wüstenwelten finden sich üblicherweise an der Grenze der habitablen Zone in geringerer Distanz zur Sonne als erdähnliche Planeten.

Mögliche Planetentypen (entweder mit oder ohne Planetenringe):

• Gasgigant
• Wasserplanet
• Wüstenplanet
• Marsähnlicher Planet
• Planet mit dichter toxischer Atmosphäre (z. B. die Venus)
• Erdähnlicher Planet
• Felsenplanet (Barren Rock)
• Eisplanet
• Vulkanplanet

Zusätzlich zu den Planeten können sich in einem Sonnensystem bis zu zwanzig Monde befinden. Die Wahrscheinlichkeit, dass ein Planet von mindestens einem Mond umkreist wird, ist von Planetentyp zu Planetentyp unterschiedlich. Ähnlich wie bei den Planetenbahnen legen wir für alle möglichen Mondorbits jeweils unterschiedliche minimale und maximale Distanzen zum zugehörigen Planeten fest und positionieren die einzelnen Monde auf den ihnen zugewiesenen Umlaufbahnen am planetennächsten Punkt (Periapsis). Obwohl im aktuellen Spieleprototyp ein Planet maximal zwei Monde besitzt (erdähnlicher Planet: max. ein Mond, Gasgigant: max. zwei Monde usw.), lassen sich jedem Planeten theoretisch bis zu zehn Monde zuordnen.

Mögliche Mondtypen:

• Wassermond
• Wüstenmond
• Marsähnlicher Mond
• Mond mit dichter toxischer Atmosphäre
• Erdähnlicher Mond
• Felsenmond (z. B. unser Erdmond)
• Eismond
• Vulkanmond

Die Oberflächenbeschaffenheit der einzelnen Monde ähnelt denen der Planeten. Während Felsenmonde (wie unser Erdmond) besonders häufig anzutreffen sind, finden sich erdähnliche Monde, wenn überhaupt, nur in wenigen Sonnensystemen:

• Erdähnliche Monde und Wassermonde befinden sich nur in der habitablen (lebensfreundlichen) Zone eines Sonnensystems.
• Felsenmonde, Vulkanmonde, marsähnliche Monde sowie Monde mit dichter toxischer Atmosphäre befinden sich überall im Sonnensystem.
• Eismonde finden sich hauptsächlich jenseits der habitablen Zone in großer Entfernung vom Zentralgestirn (z. B. Jupitermond Europa)
• Wüstenmonde finden sich an der Grenze der habitablen Zone in geringerer Entfernung zur Sonne als erdähnliche Monde.

Die Radien, Massen, Rotationsachsen und -perioden sowie die Bahnparameter aller Planeten und Monde werden innerhalb der vom Game-Designer vorgegebenen Grenzen nach dem Zufallsprinzip festgelegt. Für die Charakterisierung einer Umlaufbahn benötigen wir die folgenden drei Parameter:

• Die Form einer Umlaufbahn wird durch die Exzentrizität beschrieben. Mit zunehmender Exzentrizität wird eine einstmals kreisförmige Umlaufbahn immer elliptischer.
• Die räumliche Lage einer Umlaufbahn wird mithilfe der Orbitalachse beschrieben. Die Orbitalachse ist senkrecht zu der Ebene orientiert, in der sich ein Himmelskörper bewegt.
• Der sonnennächste bzw. planetennächste Punkt einer Umlaufbahn wird nach dem Zufallsprinzip festgelegt.

Um die Größe der einzelnen Planeten und Monde variieren zu können, definieren wir zunächst für jeden Typus einen minimalen und einen maximalen Skalierungsfaktor. Unter Berücksichtigung dieser beiden Parameter können wir im Anschluss daran einen zufallsbasierten Skalierungsfaktor (Radius) ermitteln und eine dazu passende Masse gemäß der nachfolgenden Beziehung berechnen:

Gesamtmasse = Standardmasse(Planet bzw. Mond)*Skalierungfaktor³

Der gigantische Asteroidengürtel zwischen den Planeten Mars und Jupiter zählt mit Sicherheit zu den faszinierendsten Reisezielen in unserem Sonnensystem. Auch wenn Asteroidenfelder im Stil von „Star Wars – Das Imperium schlägt zurück“ nur wenig mit der Realität zu tun haben, gelten sie dennoch in Filmen und Spielen als perfekte Orte für Weltraumschlachten und Verfolgungsjagden. Augenblicklich bewegen sich in unseren prozedural erzeugten Systemen bis zu 2 000 Asteroiden auf elliptischen Bahnen um die Sonne (solare Asteroiden) – gleichmäßig verteilt auf zehn Asteroidengürtel mit jeweils 200 Asteroiden pro Gürtel. Ähnlich wie bei den Planetenbahnen legen wir für jeden Gürtel eine minimale und maximale orbitale Distanz zur Sonne fest, wobei wir die einzelnen Gürtel aus optischen Gründen gemäß dem nachfolgenden Schema gruppieren:

• SolarAsteroidBeltOrbitMinDistance: 2.4, 2.5,   7.6, 7.7, 7.8,   12.25, 12.35, 12.45,   16.25, 16.35
• SolarAsteroidBeltOrbitMaxDistance: 2.4, 2.5,   7.6, 7.7, 7.8,   12.25, 12.35, 12.45,   16.25, 16.35

Die Breite, Form und räumliche Orientierung eines Asteroidengürtels lassen sich mithilfe der nachfolgenden drei Parameter variieren:

• Halbdurchmesser (je größer der Halbdurchmesser, umso scheibenförmiger der Gürtel)
• Achsenvarianz (0°: ringförmige Anordnung der Asteroiden, 90°: schalenförmige Anordnung der Asteroiden)
• Orbitalachse

Zusätzlich zu den solaren Asteroiden umrunden bis zu 1 500 Asteroiden einen von Ringen umgebenen Planeten (planetare Asteroiden). Obwohl sich die einzelnen Asteroiden gleichmäßig auf fünf Asteroidengürtel verteilen (300 Asteroiden pro Gürtel), legen wir für die einzelnen Gürtel im Unterschied zu den solaren Asteroiden keine separaten Orbitaldistanzen fest. Jeder Gürtel erstreckt sich stattdessen über das komplette Ringsystem und trägt somit zur Verdichtung des Asteroidenfelds bei (Abb. 4, 5 und 6).

Die Rotationsachse und -geschwindigkeit sowie die Masse und Bahnexzentrizität der einzelnen Asteroiden werden nach dem Zufallsprinzip festgelegt, wobei die Rotationsachse anders als bei den Planeten und Monden beliebig orientiert sei kann.

[ header = Seite 3: Sonnensystemphysik ]

Sonnensystemphysik

Im Rahmen der für ein Computerspiel erforderlichen Genauigkeit lassen sich die Bewegungen der Planeten, Monde und Asteroiden mit verhältnismäßig einfachen mathematischen Mitteln ohne großen Rechenaufwand simulieren. Relativistische Effekte können wir getrost vernachlässigen; ebenso die Ausdehnung der einzelnen Himmelskörper und die Tatsache, dass wir es genau genommen mit einem so genannten N-Körper-Problem zu tun haben, bei dem sich alle Himmelskörper gegenseitig beeinflussen. Hier nun alle Vereinfachungen im Überblick:

• Alle Himmelskörper werden wie Punktmassen behandelt.
• Planeten und solare Asteroiden bewegen sich im Gravitationsfeld der Sonne.
• Monde und planetare Asteroiden bewegen sich im Gravitationsfeld des zugehörigen Planeten.
• Alle weiteren Kräfte zwischen den Himmelskörpern werden vernachlässigt.

Um die Position und Geschwindigkeit eines Himmelskörpers zu einem bestimmten Zeitpunkt berechnen zu können, müssen wir uns zunächst noch einmal das zweite Newtonsche Axiom in Erinnerung rufen, das uns allen aus der Schulzeit eigentlich noch bestens vertraut sein sollte:

Kraft = Masse mal Beschleunigung: F = m * a

Kennt man alle auf einen Körper einwirkenden Kräfte, können wir seine Beschleunigung wie folgt berechnen:

Beschleunigung = Kraft durch Masse: a = F / m

Im Rahmen unserer vereinfachten Simulation müssen wir für einen Planeten lediglich die von der Sonne und für einen Mond lediglich die von einem Planeten verursachte Gravitationskraft berücksichtigen (Listing 3):

• Für einen Planeten gilt: F = Kraftrichtung * Gravitationskonstante * Masse (Planet) * Masse (Sonne) / Abstand²
• Für einen Mond gilt: F = Kraftrichtung * Gravitationskonstante * Masse (Mond) * Masse (Planet) / Abstand²

Die von der Gravitationskraft verursachte Beschleunigung berechnet sich wie folgt:

• Für einen Planeten gilt: a = Beschleunigungsrichtung * Gravitationskonstante * Masse (Sonne) / Abstand²
• Für einen Mond gilt: a = Beschleunigungsrichtung * Gravitationskonstante * Masse (Planet) / Abstand²

Unter Berücksichtigung der Beschleunigung lässt sich nun für jeden Zeitpunkt die aktuelle Geschwindigkeit und Position eines Körpers mithilfe eines geeigneten numerischen Integrationsverfahrens ermitteln. Da die Genauigkeit der Berechnungen in einem Spiel lediglich zweitrangig ist, wäre hierfür die einfachste Methode – das Eulersche Polygonzugverfahren – im Prinzip vollkommen ausreichend:

Geschwindigkeit (neu) = Geschwindigkeit (alt) + momentane Beschleunigung mal Zeitspanne: v(t+dt) = v(t) + a(t) * dt

Position (neu) = Position (alt) + momentane Geschwindigkeit mal Zeitspanne: s(t+dt) = s(t) + v(t+dt) * dt

Problematisch wird es jedoch, wenn man, wie in den Abbildungen 7 und 8 gezeigt, versucht, die Umlaufbahnen der Planeten und Monde anhand ihrer Orbitalparameter zu visualisieren. Da sich die Zeitspanne zwischen zwei Simulationsschritten nicht beliebig verkleinern lässt, werden mit zunehmender Spieldauer die Abweichungen zwischen den berechneten Positionen und der zugehörigen Orbitalkurve immer größer. Reduzieren lassen sich diese Ungenauigkeiten nur, indem man zum einen auf ein genaueres Integrationsverfahren (Velocity-Verlet) zurückgreift und darüber hinaus die Berechnungen mit doppelter Genauigkeit (64 Bit) durchführt. Vergleichen wir einmal beide Verfahren miteinander:

• Euler-Verfahren:
Beschleunigung = Kraft(Zeit, Position) / Masse
Geschwindigkeit += Zeitspanne * Beschleunigung
Position += Zeitspanne * Geschwindigkeit
• Velocity-Verlet-Verfahren:
Beschleunigung = Kraft(Zeit, Position) / Masse
Position += Zeitspanne * (Geschwindigkeit + Zeitspanne * Beschleunigung/2)
Zeit += Zeitspanne
neueBeschleunigung = Kraft(Zeit, Position) / Masse
Geschwindigkeit += Zeitspanne * (Beschleunigung + neueBeschleunigung)/2

Da im Unterschied zu den Planeten und Monden für Asteroiden eine Rechengenauigkeit von 32 Bit vollkommen ausreichend ist und der Einsatz des Velocity-Verlet-Verfahrens letztlich nur eine Verschwendung von Rechenleistung darstellt, nutzen wir bei der Handhabung der einzelnen Himmelskörper zwei Klassen anstelle von nur einer:

• Sonne, Planeten und Monde: CSolarSystemPhysicsObject_DoublePrecision-Klasse (64-Bit-Genauigkeit, Velocity-Verlet-Integration, siehe Listing 1)
• Asteroiden: CSolarSystemPhysicsObject-Klasse (32-Bit-Genauigkeit, Euler-Integration, siehe Listing 3)

Mathematisch gesehen handelt es sich bei einer physikalischen Bewegungssimulation um ein Anfangswertproblem, das die genaue Kenntnis der Position und Geschwindigkeit eines Himmelskörpers bei Spiel- bzw. Simulationsbeginn zwingend voraussetzt. Die Anfangsgeschwindigkeit muss bei der Initialisierung jedoch nicht explizit bekannt sein, sondern kann stattdessen, wie in Listing 2 gezeigt, mithilfe der so genannten Vis-Viva-Gleichung berechnet werden. Was die Anfangsposition betrifft, sind im Prinzip alle Punkte auf der Umlaufbahn gleich gut geeignet; es bietet sich jedoch an, bei der Initialisierung lediglich die beiden in Abbildung 9 skizzierten charakteristischen Orbitalpositionen zu berücksichtigen – also entweder den Punkt mit der größten oder den Punkt mit der kleinsten Entfernung zum jeweiligen Gravitationszentrum (Apoapsis bzw. Periapsis). Die Initialisierung eines Himmelskörpers in der Periapsis-Distanz können Sie anhand von Listing 2 nachvollziehen.

Schematische Darstellung der Umlaufbahnen

Hand aufs Herz: Würde man Sie zu den Herausforderungen und Problemen befragen, die im Verlauf der Entwicklung eines Weltraumspiels zu bewältigen sind, dann wäre die Visualisierung von Orbitalbahnen sicher eines der letzten Dinge, die Ihnen dabei in den Sinn kämen – oder etwa nicht?
Und in der Tat, die Darstellung von kreisförmigen Linienzügen ist wirklich nicht weiter schwierig; kompliziert wird die Sache jedoch bei elliptischen Umlaufbahnen, da die Berechnung der für das Rendering erforderlichen Transformationsmatrix nicht ganz einfach ist. Die gesuchte Matrix ist nicht nur für die Skalierung, Ausrichtung und Positionierung verantwortlich, sondern muss darüber hinaus auch den im Vertex-Buffer gespeicherten kreisförmigen Linienzug in einen elliptischen transformieren. Fassen wir einmal zusammen, was es bei der Konstruktion der gesuchten Matrix alles zu beachten gibt:

• Im Unterschied zu einer Kreisbahn befindet sich die Sonne bei einer elliptischen Umlaufbahn, wie in Abbildung 9 skizziert, nicht im Zentrum der Ellipse, sondern in einem der beiden Brennpunkte. Um zu verhindern, dass sich beim Rendern der Umlaufbahn die Sonne plötzlich im Ellipsenzentrum wiederfindet, müssen wir den Abstand zwischen Brennpunkt und Mittelpunkt im Rahmen einer Positionskorrektur berücksichtigen.
• Der Differenzvektor zwischen Ellipsen-Brennpunkt und -Mittelpunkt beschreibt die räumliche Orientierung der Ellipse in Richtung der großen Ellipsen-Halbachse.
• Der zweite Orientierungsvektor, der die räumliche Orientierung der Ellipse in Richtung der kleinen Halbachse beschreibt, berechnet sich als Kreuzprodukt aus der Orbitalachse (dritter Orientierungsvektor) und dem ersten Orientierungsvektor.
• Damit die Ellipse im korrekten Maßstab dargestellt wird, reskalieren wir die ersten beiden Orientierungsvektoren und verwenden in diesem Zusammenhang die Länge der großen Halbachse (semiMajorAxisDistance) als Betrag für Orientierungsvektor 1 (Perpendicular1) und die Länge der kleinen Halbachse (semiMinorAxisDistance) als Betrag für Orientierungsvektor 2 (Perpendicular2).

In unserem Spieleprototyp erfolgt die schematische Darstellung der Umlaufbahnen in drei Schritten mithilfe der in Listing 4 skizzierten COrbitalCurve-Klasse:

• Shader-Programm aktivieren, Vertex und Index-Buffer auswählen (Listing 5)
• Nacheinander alle Umlaufbahnen rendern (Listing 6)
• Shader-Programm deaktivieren (Listing 7)

[ header = Seite 4: Effiziente Darstellung von Asteroidenfeldern ]

Effiziente Darstellung von Asteroidenfeldern

Als 3-D-Programmierer sucht man eigentlich immer nach neuen Mitteln und Wegen, um auch das letzte Quäntchen Geschwindigkeit aus einer Grafikanwendung, einem Spiel oder einer Engine herauszupressen. Obwohl bekanntlich viele Wege nach Rom führen, lassen sich die wichtigsten Ansatzpunkte für die Implementierung eines performanten Render-Schemas dennoch kurz und knapp in einem einfachen Satz zusammenfassen: Minimiere die Anzahl der Render-Aufrufe (Draw Calls) und vermeide unnötige Textur- sowie Shader-Wechsel.
Was die Darstellung der Asteroidenfelder betrifft, so haben wir zwar Glück, dass sich ein Großteil der maximal 2 000 solaren und 15 000 planetaren Asteroiden (zehn Planeten mit jeweils 1 500 Asteroiden) normalerweise nicht im Blickfeld des Spielers befindet; ohne Einsatz moderner effizienter Render-Techniken wäre unser Spieleprototyp dennoch praktisch unspielbar.
Zur Vermeidung unnötiger Texturwechsel stehen uns die mit der OpenGL-Spezifikation 3.0 eingeführten Texture-Array-Objekte zur Verfügung. Da die Auswahl der in einem solchen Array gespeicherten Texturen innerhalb eines Shader-Programms erfolgt, kann folglich auf viele der ansonsten aus dem Hauptprogramm heraus initiierten Texturwechsel verzichtet werden. Betrachten wir hierzu ein kleines Beispiel:

uniform sampler2DArray SurfaceTextureArray;
[...]
vec4 Color = texture(SurfaceTextureArray, vec3(TexCoord.st, TextureID));

Die Anzahl der notwendigen Draw Calls lässt sich mithilfe des im Rahmen der OpenGL-Spezifikation 3.1 eingeführten Geometry Instancings verringern. Eine größere Anzahl von 3-D-Objekten mit gleicher Geometrie (Instanzen) kann dank dieser Methode mit einem einzigen Render-Aufruf dargestellt werden, wobei uns in Abhängigkeit von der Anzahl der darzustellenden 3-D-Instanzen zwei unterschiedliche Instancing-Techniken zur Verfügung stehen:

• Uniform Buffer Instancing für die Darstellung einer verhältnismäßig kleinen Anzahl von Instanzen (Beispiel: Asteroidenfelder)
• Texture Buffer Instancing für die Darstellung einer sehr großen Anzahl von Instanzen (Beispiele: Sterne und Nebelwolken einer Galaxie, Terrain Tiles)

Schauen wir uns einmal an, wie der Zugriff auf die in einem Uniform-Buffer gespeicherten Asteroiden-Transformationsmatrizen erfolgt:

layout(shared) uniform instanceData
{
vec4 matCameraWorldArrayColumn[256]; // 64 (Instanzen) * 4
};

void main()
{
int Index = 4*gl_InstanceID;

mat4 matCameraWorld = mat4(matCameraWorldArrayColumn[Index],
                           matCameraWorldArrayColumn[Index+1],
                           matCameraWorldArrayColumn[Index+2],
                           matCameraWorldArrayColumn[Index+3]);
[...]
}

Den praktischen Einsatz von Uniform-Buffer- und Texture-Array-Objekten können Sie anhand des in unserem Spieleprototyp zum Einsatz kommenden Grafikframeworks sowie anhand der in Listing 8 skizzierten Asteroiden-Render-Funktion nachvollziehen:

• Im ersten Schritt werden alle instanzenübergreifenden Render-Parameter (Texture Arrays, Beleuchtungs- und Oberflächenparameter sowie die View-Projection-Matrix) an das aktivierte Shader-Programm übergeben.
• Da sich die Formen aller Asteroiden von einem würfelförmigen 3-D-Modell ableiten, benötigen wir insgesamt sechs Render-Durchgänge – einen pro würfelbasierter Fläche.
• Im zweiten Schritt werden alle nicht sichtbaren Asteroiden sowie alle Asteroiden des falschen Geometrietyps (falsches 3-D-Modell) aussortiert. Beachten Sie in diesem Zusammenhang, dass beim Geometry Instancing allen Instanzen dasselbe 3-D-Modell zugrunde liegen muss.
• Im dritten Schritt werden die einzelnen Asteroiden individuell skaliert und die Instanzdaten (Position, Skalierung, Orientierung, Textur-ID) so lange an ein so genanntes Instanzenmanagerobjekt (CMeshInstanceManager) weitergeleitet, bis schließlich die maximal mögliche Anzahl der pro Render-Aufruf darstellbaren Instanzen erreicht ist.
• Im vierten Schritt kopieren wir die vom Instanzenmanagerobjekt verwalteten Daten in das beim Geometry Instancing zum Einsatz kommende Uniform-Buffer-Objekt und führen den Draw Call aus.
• Im letzten Schritt werden die noch verbliebenen Instanzen mit einem einzigen Render-Aufruf dargestellt.

Planetenringe

Zum Abschluss befassen wir uns noch mit der für Weltraumspiele so wichtigen Darstellung von Planetenringen – ein Thema, das ich Ihnen aus Platzgründen in der letzten Ausgabe (prozedurale Planeten) leider vorenthalten musste.
Bevor wir jedoch überhaupt etwas rendern können, benötigen wir zunächst einmal ein geeignetes Planetenringmodell, das sich mithilfe unseres Grafikframeworks einfach und unkompliziert erzeugen lässt:

pPlanetRingVB_IB = Load_RingMesh_And_Get_ResourceID(&PlanetRingResourceID,
                   "SimpleRingModel", 0.2f /*Innendurchmessen*/,
                   80 /*Segmente*/);

Sowohl die Geometrie des von uns verwendeten Planetenringmodells als auch die Berechnung der einzelnen Vertexpositionen sollten Sie anhand von Abbildung 10 problemlos nachvollziehen können.

Abb. 10: Planetenringe (Vertexpositionen)

Die Darstellung der einzelnen Planetenringe erfolgt unmittelbar im Anschluss an das Deferred Lighting in drei Schritten:

• Shader-Programm aktivieren, Übergabe aller erforderlichen Shader-Parameter (Listing 9)
• Nacheinander die Planetenringe aller Planeten bei aktiviertem Color Blending und deaktiviertem z-Buffer rendern (Listing 10)
• Shader-Programm deaktivieren (Listing 11)

War doch gar nicht so schwierig, oder? Nun ja, in der Praxis ist die Darstellung von Planetenringen mit deutlich mehr Komplikationen verbunden als es auf den ersten Blick den Anschein hat. Zu verdanken haben wir diese Probleme, wie so oft, dem vermaledeiten z-Buffer. Zur Vermeidung von Transparenzfehlern müssen zunächst alle undurchsichtigen Objekte (Planeten, Monde, Asteroiden usw.) gerendert werden, bevor wir im zweiten Schritt mit der Darstellung der transparenten Objekte (Wolkendecken, Planetenringe usw.) beginnen können. Rendert man die Planetenringe jedoch mit aktiviertem z-Buffer, kommt es aufgrund der zu geringen Genauigkeit der gespeicherten Tiefenwerte wieder einmal zu hässlichen z-Fighting-Artefakten. Deaktiviert man andererseits den z-Buffer, so kommt es zu Transparenzfehlern – beispielsweise wären dann auch die vom Planeten verdeckten Teile der Ringe sichtbar. Das Problem ließe sich zwar beheben, indem man mithilfe von Clipping-Operationen lediglich die Vorderseite der Planetenringe rendert; Transparenzfehler bei Objekten, die sich innerhalb des Ringsystems in der Ringebene bewegen (jedes Objekt befindet sich teils oberhalb und teils unterhalb der Ringe) lassen sich auf diese Weise jedoch nicht vermeiden.
Die Lösung all unserer Probleme können Sie anhand des in Listing 13 im Download skizzierten Planetenring-Fragment-Shader-Programms nachvollziehen. Da wir die Kameraraumpositions- und Tiefenwerte aller undurchsichtigen (opaken) Objekte standardmäßig für ihre Verwendung beim Deferred Lighting zwischenspeichern, können wir mithilfe dieser Daten die zur Vermeidung von Transparenzfehlern notwendigen Tiefenvergleiche eigenhändig durchführen und die Planetenringe deshalb mit ausgeschaltetem z-Buffer rendern.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -