© DrHitch/Shutterstock.com
Erfolgreiche Spieleentwicklung

2 OpenGL - Fortgeschrittene Grafikprogrammierung


Im zweiten Teil dieses shortcuts befassen wir uns mit einigen fortgeschrittenen Themen der Grafikprogrammierung, die uns als Grundlage für die Entwicklung eines einfachen Frameworks für 3-D-Anwendungen dienen werden. Hierzu zählen das Ressourcenmanagement, die Verwendung von Frame-Buffer- und Uniform-Buffer-Objekten, Deferred Lighting im Rahmen des Post-Processings sowie Geometry Instancing.

2.1 Grafik-Frameworks

Die Entwicklung kommerzieller Spieletitel wird stetig aufwändiger und kostspieliger, was nicht zuletzt an der rasanten technischen Entwicklung moderner Computer-Hardware liegt. Die Spielewelten müssen heutzutage nicht nur optisch so realistisch wie möglich erscheinen, auch die Interaktionen der Spieleobjekte untereinander und mit dem Spieler müssen möglichst glaubhaft simuliert werden. Selbst an der Hobby- und Nachwuchsszene ist diese Entwicklung nicht spurlos vorbeigegangen. Verglichen mit der Anzahl der in den diversen Onlinecommunities aktiven Mitglieder, sind die Resultate in Form von downloadbaren Spielen und 3-D-Anwendungen erschreckend dürftig. Nichts schadet der Motivation mehr als unfertige abgebrochene Projekte. Man sollte daher die eigene Messlatte zu Beginn nicht allzu hoch anlegen und sich zunächst auf die Programmierung kleinerer 3-D-Anwendungen konzentrieren, um sich in die verschiedenen Gebiete der Grafikprogrammierung einzuarbeiten. Parallel dazu bietet sich die Entwicklung eines eigenen Frameworks an, damit man bisher Geleistetes schnell und unkompliziert in späteren Projekten und Spielen wiederverwenden kann. Im Unterschied zu einer Game Engine handelt es sich bei einem Framework um ein schrittweise erweiterbares Programmiergerüst, das die Erstellung neuer Anwendungen vereinfachen soll. Die Programmbeispiele im Downloadbereich unter www.entwickler-press.de/spieleentwickeln [9] nutzen die folgenden Funktionalitäten des beigelegten (in der Entwicklung befindlichen) Grafik-Frameworks:

  • Routinen zum Initialisieren und Beenden einer OpenGL-Anwendung
  • die Mathematik-Bibliothek
  • die Texturverwaltung (Laden und Freigeben von Texturen, mehrfaches Laden von Texturen wird verhindert)
  • die Mesh-Verwaltung (Laden und Freigeben von Vertex- und Indexdaten, mehrfaches Laden von Mesh-Daten wird verhindert)
  • das GLSL Shader Framework (Schnittstelle zwischen der 3-D-Anwendung und den verwendeten Shader-Programmen)
  • das Post Processing Framework
  • animierbare 3-D-Modelle (Skelett-Animation)
  • den Rendering Manager (erleichtert den Einsatz von Geometry Instancing)
  • sowie das Partikelsystem-Framework

2.2 Ressourcenmanagement

Das Ressourcenmanagement zählt zu den zentralen Aufgaben eines 3-D-Grafik-Frameworks. Während sich die Shader-Programme mehr oder weniger unkompliziert verwalten lassen (Shader-Programme werden für gewöhnlich bei Programmstart geladen und bei Programmende wieder freigegeben), gestaltet sich das Management von Textur- und Mesh-Ressourcen ungleich komplizierter. Im Zuge der Darstellung von detailreichen und weitläufigen Spielewelten müssen kontinuierlich die aktuell benötigten 3-D-Daten in den Grafikspeicher geladen und nicht mehr benötigte Daten aus dem Grafikspeicher entfernt werden (Streaming). Für die Gesamtheit aller Daten steht in der Regel nicht genügend Speicherplatz zur Verfügung. Darüber hinaus gilt es zu verhindern, dass die von unterschiedlichen 3-D-Modellen gemeinsam genutzten Texturen und Mesh-Daten mehrfach in den Grafikspeicher kopiert werden. An einen Textur- bzw. Mesh-Manager sind daher die folgenden Anforderungen zu stellen:

  • Verhindern, dass eine Ressource mehrfach geladen wird
  • Freigabe einer Ressource, nachdem sichergestellt ist, dass diese nicht mehr anderweitig benötigt wird
  • Handhabung statischer Ressourcen (diese verbleiben ihre gesamte Lebensdauer über im Grafikspeicher)
  • Handhabung dynamischer Ressourcen (diese lassen sich im Hintergrund dynamisch initialisieren und entladen)

Im Gegensatz zu statischen Ressourcen ist die Initialisierung dynamischer Ressourcen ungleich komplizierter, denn der Spielfluss sollte möglichst nicht durch unnötige Ladezeiten unterbrochen bzw. durch Einbrüche der Frame-Rate gestört werden.

  • Schritt 1: Lesen der Daten von der Festplatte, Weiterverarbeitung der Daten falls notwendig (z. B. Berechnung einer Normal Map aus einer Height Map, Berechnung von Vertexnormalen, usw.). Da Festplattenzugriffe generell sehr langsam sind und auch die Weiterverarbeitung der Daten einiges an Rechenzeit erfordert, sollten die Arbeiten in einen separaten Thread ausgelagert werden.
  • Schritt 2: Kopieren der Ressourcendaten in den Grafikspeicher (schnell im Vergleich zu Schritt 1; dennoch sollten größere Datenmengen schrittweise kopiert werden)

Bei der Überwachung von Ressourcen kommen sogenannte Referenzzähler zum Einsatz. Ein Zählerstand von 0 zeigt an, dass die betreffende Ressource momentan nicht verwendet wird. Bei Anforderung einer Ressource wird der Zähler um den Wert 1 inkrementiert; zudem erfolgt bei der ersten Anforderung ihre Initialisierung. Bei Freigabe einer Ressource wird der Zählerstand um den Wert 1 dekrementiert. Wird eine Ressource schließlich nicht mehr verwendet, dann steht der Referenzzähler wieder bei 0, und die Ressource kann zerstört werden.

Anhand des Ressourcennamens lässt sich überprüfen, ob eine Ressource bereits geladen wurde. Ein Vergleich des Namens einer neu zu initialisierenden Ressource mit den Namen von bereits zuvor initialisierten Ressourcen ist jedoch nicht besonders performant. Als schnellere Alternative bietet sich der Einsatz von eindeutigen ID-Nummern (ResourceID) an, die man jeder Textur bzw. jedem Mesh zuvor zuordnen muss. Vor dem Anlegen einer neuen Ressource wird dann lediglich überprüft, ob bereits zuvor eine Ressource mit identischer ID geladen wurde.

Verschaffen wir uns zunächst anhand von Listing 2.1 eine Übersicht über die vom Framework bereitgestellten Funktionen für die Texturverwaltung.

void Init_TextureManager(long numTexturesMax);
void Delete_TextureManager(void);
// Durch Vergleich der ResourceID mit bereits verwendeten
// ...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Gewinner-Teams

Wir bieten Lizenz-Lösungen für Teams jeder Größe: Finden Sie heraus, welche Lösung am besten zu Ihnen passt.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang