Prinzipiell ist zu sagen, daß die meisten Fragen
bezüglich OpenGL, die man sich während der Programmierung
des Spiels stellen wird, sich durch eingehendes Studium des
sogenannten Red Book klären lassen
können. Es gibt aber einige Punkte, die eher systemabhängig
sind und daher nicht in der allgemeinen OpenGL-Literatur zu finden
sind, diese wollen wir hier erläutern.
Effizientes Programmieren
Es ist kein "rasend schnelles" Spiel verlangt,
Geschwindigkeit ist aber trotzdem ein wichtiger
Bewertungspunkt. Anders als beim reinen Softwarerendering geht es
jedoch nicht darum, jede Zeile Code zu optimieren oder sogar C-Code
durch Assembler-Code zu ersetzen. Normalerweise verbringt ein
OpenGL-Programm nämlich nicht die meiste Zeit im eigenen C-Code,
sondern im OpenGL-Treiber! Die wichtigste Aufgabe beim effizienten
Programmieren mit OpenGL ist daher, die richtigen
OpenGL-Befehle in der richtigen Reihenfolge zu verwenden!
Hierfür gibt es einige Richtlinien:
State-changes sind teuer: Der häufige Wechsel
zwischen verschiedenen Texturen, Materialien und vor allem Shadern
kann viel Zeit kosten - minimiert daher die Anzahl der nötigen
State Changes und vermeidet redundantes Setzen von gleichen
Zuständen.
Vertex Arrays statt glVertex: Jeden einzelnen
Eckpunkt mit einem glVertex plus
dazugehörigem glNormal etc. spezifizieren zu müssen,
kostet Rechenzeit (für die Funktionsaufrufe). Bei Vertex Arrays
gibt man stattdessen einen Zeiger auf ein Array an, in dem alle
Eckpunkte und dazugehörigen Daten schon vorbereitet stehen. Am
effektivsten ist es, die Geometrie-Daten direkt auf der GPU als
Vertex Buffer Object(VBO) zu speichern und aufzurufen.
Durch die Anforderung OpenGL 3.x in dieser LU verwenden zu müssen ergibt sich,
dass glNormal und glVertex - Befehle ohnehin nicht mehr verwendet werden dürfen.
Ihr braucht mindestens ein Vertex-Array oder auch ein VBO.
Testen, testen, testen! Ob ein Programm auf
einer bestimmten Plattform problemlos läuft ist, kann man nur
sagen, wenn man es dort ausprobiert! Befehle, die auf einer Karte
langsam sind, können auf einer anderen sehr schnell sein. Außerdem
ist es möglich, dass unterschiedliche Treiber (und sogar einzelne
Treiberversionen vom selben Hersteller) bestimmte Befehle
unterschiedlich verarbeiten. Auf ATI-Grafikkarten wird
beispielsweise GL_TEXTURE_2D für
non-power-of-2-Texturen nur teilweise unterstützt.
Moderne Graphikkarten sind oft so schnell, dass die CPU sie nicht
schnell genug mit neuen Aufgaben versorgen kann. Deshalb kostet es oft
nichts, komplexere Geometrie oder komplexere Beleuchtung zu verwenden,
da man damit sowieso nur ungenutzte Resourcen der Graphikkarte
verwendet. So sind beispielsweise Vertex Arrays bis ~200 Dreiecke rein
CPU-limitiert. Das heißt auch oft, dass es schneller ist, ein
komplexes Objekt an die Graphikkarte zu schicken, als es vorher mit
der CPU zu vereinfachen oder auszurechnen, ob es überhaupt sichtbar
ist. Wie üblich muss man das durch probieren austesten.
Objektorientiertes Programmieren und STL
Designed Euer Programm, wie man es in Software Engineering lernt,
mit objektorientierten Methoden - es zahlt sich aus!
Verwendet außerdem die Standard Template Library (STL). Sie
ist bei jedem C++ Compiler schon dabei, weil sie Teil des C++
Standards ist. Die STL enthält effiziente Implementierungen von
vielen wichtigen Datenstrukturen. Es ist heutzutage eigentlich nicht
mehr zu entschuldigen, wenn man List-, Hashtable-, Resizable Array-
oder ähnliche Klassen selbst programmiert. All dies ist in der
STL schon enthalten, viel getestet und effizient. Eine gute
Dokumentation zur STL gibt es
bei SGI, und
im 1. Repetitorium wird
auch darauf eingegangen.
Transformationen
Um in OpenGL verschiedenste Transformationen durchzuführen wurden früher Befehle,
wie glPop/PushMatrix, glLoadIdentity, glRotate(), glTranslate(), glScale() ....
verwendet. Durch die Verwendung von OpenGL 3.x fallen alle diese
Fixed-Function calls weg, d.h. ihr müsst für die Berechnung der Vertices im
Vertex-Shader sorgen. Am Besten definiert ihr uniform mat4 Matrizen im Shader,
um die View-Transformation durchzuführen. Wie man Parameter an die Shader
übergeben kann wurde euch bereits im 1.
Repetitorium
vorgeführt.
Hier sei nun eindringlich auf das Kapitel 'Viewing and Modeling
Transformations' im Redbook (Seite 104-108) hingewiesen - das
Verständnis dieser Seiten ist essentiell für ein
Verständnis jeglicher Kamera und Objektmanipulation. Für
die meisten Anwendungen eignet sich der 'Moving a Local Coordinate
System' besser - man denkt sich einfach ein Koordinatenkreuz im
Raum, das zu Beginn im Augpunkt verankert ist. Jede weitere
Transformation bewegt dieses Koordinatenkreuz, und jegliche
Geometrie wird in diesem lokalen Koordinatensystem
spezifiziert.ziert.
Kamera
In OpenGL gibt es keinen Befehl, eine 'Kamera' zu
positionieren. Die Kamera ist einfach das erste lokale
Koordinatenkreuz, von dem aus man alle anderen Transformationen
durchführt. Will man doch mit einem Weltkoordinatensystem
arbeiten, macht man sich zunutze, daß Kamerabewegungen ja
relativ zur Welt sind: ob sich die Kamera auf ein Objekt zubewegt oder
das Objekt auf die Kamera zu, macht im Ergebnis keinen
Unterschied. Deswegen ist die erste Transformation, die man im
Programm spezifiziert, die INVERSE Kameratransformation (siehe dazu
das RedBook ab Seite 113)!
Sehr einfach kann man die Position mit
der gluLookAt()-Routine spezifizieren, indem man einfach
den Blickpunkt und die Blickrichtung mit je einem einem Vektor angibt,
und zusätzlich noch einen Vektor, der 'oben' definiert
(gluLookAt() übernimmt die Berechnung der oben
angesprochenen inversen Kameratransformation, man braucht sich darum
also nicht zu kümmern).
Für die Berechnung dieser Vektoren gibt es viele Methoden. Man
kann sie z. B. als Definition für die Betrachterposition im
Programm speichern und bei Eingaben durch den Benutzer bei Bedarf
verändern. Bewegt sich z.B. ein Raumschiff mit einer gewissen
Geschwindigkeit vorwärts, addiert man den Blickrichtungsvektor -
skaliert mit der Geschwindigkeit - zum Blickpunktvektor. Bei
Rotationen wird es schon gefinkelter: Möchte man um eine
'globale' Achse im Weltkoordinatensystem rotieren, multipliziert man
den Blickrichtungsvektor mit einer Rotationsmatrix für die
jeweilige Achse. Aber Achtung: das funktioniert nur gut für
Rotationen um die senkrechte Achse! Möchte man um z.B. die
x-Achse (die parallel zum Boden liegt) rotieren, meint man damit
praktisch immer eine Rotation um die lokale x-Achse, nicht die
globale!
Deswegen ist es oft einfacher, man speichert gleich die aktuelle
Rotationsmatrix für die Betrachterposition. Bewegungen im
globalen Koordinatensystem (also um eine globale Achse) kann man dann
durch Multiplikation einer Rotationsmatrix von links, Bewegungen im
lokalen mit einer Rotationsmatrix von rechts erreichen. So erhält
man dann eine neue Kameramatrix, die man jetzt aber
mit glMultMatrix() spezifiziert. Allerdings muß man
hier wieder aufpassen, weil man ja die INVERSE Kameramatrix
benötigt. Das ist für Rotationsmatrizen aber kein Problem,
weil hier gilt, daß die Inverse gleich der Transponierten
ist.
Empfohlene Datenstrukturen
Objekt-Geometrie
Es macht Sinn, Geometrie als Indexed Face Sets zu speichern: in
verschiedenen Arrays werden Koordinaten, Normalvektoren und Texturkoordinaten
gespeichert, und ein zusätzliches Array enthält für jedes Primitiv (z.B. für
Dreiecke 3) Indizes, die die Koordinaten, Normalvektoren und Texturkoordinaten
angeben, die aus den jeweiligen Arrays verwendet werden sollen.
Generell läßt man sich beim Design von Datenstrukturen davon leiten, wie
man die Daten an OpenGL übergeben wird. Verwendet man (was sehr empfohlen ist)
Vertex Arrays, wird man die Koordinaten und Indizes schon in einer Form
speichern, daß man sie OpenGL nur noch mit dem entsprechenden Aufruf von
glVertexPointer() usw. angeben muß. Es wäre nämlich sehr aufwendig, dafür
die Daten jedesmal im Hauptspeicher verändern (verschieben, umordnen o.ä.) zu
müssen. Das bedeutet insbesondere, daß klassische zeigerbasierte
Datenstrukturen wie B-Reps oder BSP-Bäume für schnelles und effizientes
Rendering ungeeignet sind! Unabhängig davon kann es notwendig sein, für andere
Aufgaben (etwa Kollisionsbehandlung) zusätzliche Daten oder etwa die gesamten
Daten nochmals in anderer Form speichern zu müssen - das hängt dann sehr vom
jeweiligen Spiel ab.
Globale Geometrie
Wenn man eine große Welt simuliert, wird man nicht immer alle Objekte
gleichzeitig darstellen können/wollen. Man muß sich daher eine globale
Strukturierung der Szene überlegen, die erlaubt, schnell herauszufinden, welche
Teile der Szene sichtbar sind oder gezeichnet werden sollen. Die einfachste
Möglichkeit dazu ist hierarchisches View-Frustum Culling: Jeder Koten im
Szenengraph (also jedes Gruppen-Objekt und jedes eigentliche Objekt) enthält
eine Bounding Sphere. Bevor man einen Knoten rendert, testet man, ob die
Bounding Sphere überhaupt das View Frustum schneidet (dazu benötigt man
natürlich Daten über die Kameratransformation, wie etwa den Blickpunkt, die
Blickrichtung, den Kameraöffnungswinkel usw.). Dadurch können ganze Objekte
oder sogar Gruppen von Objekten, die sich nicht im Blickfeld befinden, schnell 'geculled'
werden.
Aufwendigere Methoden hängen dann immer von der Art des Spiels und der Szene
ab: für Spiele in geschlossenen Räumen kann man (wie in Quake) BSP-Bäume (als
volumetrische Unterteilung der Innenräume) generieren, die in jedem Knoten eine
Liste von sichtbaren Objekten speichert - das ist aber ziemlich aufwendig.
Alternativ kann man in Innenräumen auch auf sogenanntes Portal-Culling
zurückgreifen, welches durch die hohe Geometrieverdeckungsicher sehr effizient
ist. Eine Implementation ist allerdings nicht so einfach wie z.B. View Frustum
Culling.
Für die Übung sollt ihr View Frustum Culling (nicht unbedingt hierarchisch,
also ev. nur mit Bounding-Spheres oder Bounding-Boxes für die echten Objekte) implementieren,
aufwendigere Methoden können als Spezialeffekt Punkte bringen.
Beliebte Fehler die ihr vermeiden solltet
Die Erfahrung aus den letzten Jahren hat gezeigt, dass es einige Möglichkeiten gibt,
sich bei Programmierung und Design des Spiels zu verzetteln. Ein paar dieser Fallstricke
werden nun beschrieben und sollten tunlichst vermieden werden:
Over-Engineering
Grundsätzlich ist es gut, sich ein objekt-orientiertes Design zu überlegen und die Features
von C++ zu nutzen, da ein wohlüberlegtes System letztendlich viel Zeit und Nerven sparen kann.
Allerdings sollte man es auch nicht übertreiben. Zeit ist knapp bei dieser Übung, und wenn man
einen Großteil davon damit verbringt, ein komplexes Framework mit allen möglichen und unmöglichen
und vor allem nie benutzten Features zu implementieren statt sie für die Lösung der eigentlichen
Aufgabenstellung zu nutzen, kann man schnell in Probleme geraten. Ziel der Lehrveranstaltung ist es,
ein spaßiges Spiel zu machen, schöner Code ist nur Mittel zum Zweck!
Zu Features, die möglicherweise nett und praktisch sind, aber von uns nicht bewertet werden,
zählen Dinge wie XML-basierte Menüs oder In-Game-Kommandokonsolen mit Autovervollständigung. Auch Lauffähigkeit
auf unterschiedlichen Plattformen ist für uns nicht relevant, bisher hatte ausnahmslos jede Gruppe,
die das versucht hat, ihre Probleme damit. Konzentriert euch auf eine funktionierende Version für Windows
7 64bit,
denn das ist die Anforderung. Ihr könnt das Spiel nach der LU immer noch auf andere Plattformen portieren, falls
ihr das wollt.
Über-ambitioniertes Gamedesign
Denkt daran, dass ihr nur ein Semester Zeit habt, ein vollständiges Spiel zu programmieren und plant
das auch beim Gamedesign ein. Beispielsweise sind Multiplayer-Spiele übers Netzwerk ein enormer
Programmieraufwand, der sich bisher nur selten ausgezahlt hat. Bis man hier ein robustes,
funktionierendes System entwickelt hat, vergeht viel Zeit - in der Regel zu viel, um sich noch
ausreichend um den Rest des Spiels kümmern zu können. Außerdem bereiten derartige Spiele aufgrund
ihres Konzepts immer wieder Probleme bei der (durchaus wichtigen) Präsentation.
Natürlich soll das Spielkonzept nicht extrem simpel ausfallen, allgemein sollte man sich
jedoch gut überlegen, was man sich vornimmt, denn ein übermäßig komplexes, nicht abgeschlossenes
Projekt macht in der Regel weniger Eindruck als ein kleines, aber feines Spiel, das letztendlich
auch aufgrund seiner Komplettheit Spaß bereitet.
Ressourcen
Entwicklungsumgebung und Compiler
Für die Entwicklung empfehlen wir Visual Studio
2008. Informatik-Studenten haben über
die MSDNAA
kostenlos Zugang zur Vollversion. Außerdem lässt sich bei
Microsoft kostenlos die
Visual
Studio 2008 Express Edition herunterladen. Achtet darauf dass die
verwendeten Libraries mit dem Visual Studio 2008-Library-Format
kompatibel sind!
Content Creation
Empfohlene Tools:
CrazyBump ist ein Gratis-Tool zum Generieren von Normalmaps, Heightmaps,
Occlusionmaps und Specularmaps von Texturen und Fotos, das auch von kommerziellen Spielen verwendet wird.
Dank vielen Detail-Einstellungen und Echtzeit-Preview kann man mit dem Tool schnell und effizient sehr gute Texturen
erzeugen.
Blender ist ein Open Source-Modellierungstool, das auch Animation erlaubt.
Blender ist auf Spiele ausgerichtet, deshalb wird sogar eine Engine mitgeliefert - diese sollte
natürlich nicht verwendet werden.
Das Doom3-Modelformat (md5), für das ebenfalls Importer und
Exporter für alle gängigen Modelling-programme existieren. Im Unterschied zu MD3 erlaubt dieses Format weighted vertex skinning.
Das Format ist human-readable und damit leicht zu parsen.
FBX, das proprietäre Autodesk-Format
für 3DS Max und Maya, für das ein SDK mit Sample-Code zum Import und Export von ganzen Szenen existiert. Mit diesem Format
lassen sich damit neben einzelnen Models auch ganze Szenen oder Level importieren.
Ogre XML (und Ogre Tools).
Auch für die die Nicht-Engine-Gruppen kann man die Ogre3D Tools empfehlen.
Darunter gibt es Export-Plugins für so ziemlich jede Modellierungssoftware, die meistens in das
human-readable Ogre XML Format speichert, das sich dann leicht auslesen lässt, um Modelle in die eigene Engine zu bekommen.
Collada: Zum schnellen und unkomplizierten Laden von Collada-Dateien empfehlen wir die
FCollada-Library.
Zum Download muss man sich auf der Seite registrieren.
Milkshape 3D (ms3d)
Libraries
Bei einem Spiel fallen eine Menge von Programmieraufgaben an, die nicht direkt mit Computergraphik
oder Spieldesign zu tun haben, aber leider unerlässlich sind. Wir bieten daher hier eine Übersicht über die
wichtigsten Libraries, die einem eine Menge Programmierarbeit abnehmen können.
OpenGL Helper:
GLEW
Zusatzfunktionalität in OpenGL, die über die Core-Spezifikation
hinausgehen, wird über so genannte Extensions gelöst.
Dafür muss man mittels wglGetProcAddress() (bzw. den
Äquivalenten unter anderen Fenstersystemen) einen Funktionspointer auf
die gewünschte Extension-Funktion holen. Unter Windows braucht man
weiters aktualisierte glext.h und wglext.h Header Files. Da dieser
Prozess mühsam ist und keinen wirklichen Lehrcharakter besitzt,
empfehlen wir die Verwendung
von GLEW (GL Extension
Wrangler). Diese Library kann mit wenigen Zeilen Code alle
Extension-Funktionen verfügbar machen und spart einiges an Arbeit.
GLFW
OpenGL enthält grundsätzlich keine Funktionen, die ein passendes
Fenster im jeweiligen Fenstersystem erzeugen. Diese werden über eine
plattformabhängige Schnittstelle implementiert (WGL unter Windows, GLX
unter Unix, usw.). Damit ihr diese Funktionen nicht selbst
implementieren müsst, empfehlen wir die Verwendung
von GLFW. Der Vorteil
liegt darin, dass GLFW sich auch um die Behandlung von Tastatur-,
Maus- und Joystick-Ereignissen kümmert.
SDL
Etwas älter und mit mehr Features ausgestattet
ist SDL, eine ebenfalls
plattformunabhängige Library, die auch Sound-Unterstützung enthält,
und sehr häufig eingesetzt wird.
Image Loading:
FreeImage
Die FreeImage-Library
unterstützt alle gängigen Bildformate (inklusive HDR-Bildformaten)
DevIL
DevIL ist eine sehr
umfangreiche Library zum Laden und Speichern von Bilddateien mit
OpenGL-artiger Syntax. DevIL unterstützt alle gängigen Formate: JPEG,
BMP, TGA und PNG (für Alpha-Kanäle) und DDS. Außerdem können Mipmaps
und Animationen leicht erstellt werden. Allerdings hat die
DevIL-Library Bugs bei der Unicode-Implementation, so dass ihr immer
auf die ANSI-Funktionen zum Bildladen zurückgreifen solltet.
Auf der Homepage der Library findet ihr auch
zahlreiche Tutorials.
Für die Übung sollten allerdings die ilutGL-Funktionen
nicht verwendet werden!
Sound:
FMOD
Sehr gute Erfahrungen haben Studenten
mit FMOD gemacht, für
nicht-kommerzielle Verwendung ist die Lizenz frei und diese Library
unterstützt sogar 3D Sound, MP3 und ähnliche Features.
FMOD EX bietet sogar ein objektorientiertes Framework
an und ist damit noch einfacher zu benutzen.
OpenAL
OpenAL ist eine
plattformübergreifende Open Source Audio Library für Sound, die
ebenfalls 3D Sound unterstützt.
Collision Detection/Physik:
PhysX
Falls ihr euch nicht allzu sehr mit Kollision und Physik
auseinandersetzen wollt, kann die Verwendung einer Physik-Library
sinnvoll sein. Gute Erfahrungen haben wir mit
der nVidia
PhysX Engine gemacht.
OPCODE
Bevor man beginnt PhysX herunterzuladen und sich einmal eine Woche
mit dem Einarbeiten beschäftigt, sollte man sich überlegen, ob es
nicht ausreicht, wenn man das einfache Objekt-Verhalten selber
schreibt. Dabei kann es aber immer noch nützlich sein, wenn man
eine fertige, schnelle Kollisionserkennungs-Library verwendet. Hier
wäre OPCODE zu
empfehlen.
Vektor Mathematik:
Imath
Imath bietet eine ganze Reihe von generischen Klassen an (Vektoren,
Matrizen, Quaternionen, etc. etc) an, die einem die Arbeit sehr
erleichtern. Ihr findet die Library im ilmbase Packet von
OpenEXR: http://www.openexr.com/downloads.html
GLM
Eine Library, ähnlich in Umfang und Anwendung wie Imath,
ist GLM. Sie ist darauf
ausgelegt das Verhalten der in GLSL eingebauten Vektor und Matrix
Typen zu simulieren. Deshalb integriert sie sich
besonders gut in OpenGL Code. GLM ist eine Header-only
Library. Das heißt, dass man die Files nur im eigenen Code
includen muss, um sie zu verwenden.
OpenGL Programming Guide, Sixth Edition (das
sogenannte "Red Book") (auch die 5th, 4th, 3rd
oder 2nd Edition sind ok): Das ist eindeutig die wichtigste Quelle
für alle Fragen, die OpenGL betreffen. Dieses Buch ist für die
Laborübung unverzichtbar, es enthält genaue Anleitungen, wie man
in OpenGL die komplette Rendering-Pipeline steuern kann. Frühere
Versionen des Red Book findet man auch leicht mit Google im Netz
als PDF oder HTML. Gegen Studentenausweis ist es im
Lehrmittelzentrum etwas vergünstigt zu erhalten.
OpenGL Spezifikationen: Die verbindliche Stelle
wenn man klare Fragen hat. Die C API und GLSL haben jeweils ein
eigenes Dokument. Ihr bekommt die Spezifikationen aller Versionen
in PDF Form
hier.
Die aktuellste Version gibt es hier:
API,
GLSL
Es gibt mittlerweile eine Unmenge an Büchern zur
Spieleprogrammierung mit OpenGL. Ein paar davon gibt es bei uns in
der Bibliothek zum Anlesen.
OpenGL Spezialeffekte:
Nvidias
Developer Website enthält in den Sections "White Papers"
und "Technical Presentations" zu einigen der Spezialeffekte
sehr gute Erläuterungen.
Die "GPU
Gems"(auch online verfügbar)- und
"ShaderX"-Bücher, die
ihr in der Institutsbibliothek findet, beinhalten viele
nützliche Artikel zur effizienten Implementierung von State of
the Art-Effekten.
Die "Game
Programming Gems"-Serie enthält auch viele Tipps und
Tricks für Spieleprogrammierer. Die Bücher sind in der
Bibliothek verfügbar.
Gamedev.net:
Artikelbibliothek, sowie Community für Game Development.
Delphi OpenGL
Community: Viele Einsteigertutorials für OpenGL, jedoch
auch Artikel zu Mathematikgrundlagen, KI und vielen anderen
Spielrelevanten Themen. Sehr empfehlenswert!
GLFW macht die Initialisierung eines Fensters sehr einfach:
#include <GL/glfw.h>#include <iostream>voidrenderScene(void){static const GLfloat vertices[] = {0.000f,1.0f,0.0f,// top-0.866f,-0.5f,0.0f,// lower left0.866f,-0.5f,0.0f};// lower rightstatic const GLfloat colors[] = {1.0f,0.0f,0.0f,// Red0.0f,1.0f,0.0f,// Green0.0f,0.0f,1.0f};// Blue/* not using Shaders here, but remember NOT to use glPushMatrix(), glRotatef() etc. with OpenGL3.x*/glPushMatrix();glRotatef(glfwGetTime()*15.0,0,0,1);// Rotate around Z axisglEnable(GL_VERTEX_ARRAY);glEnable(GL_COLOR_ARRAY);glVertexPointer(3, GL_FLOAT,0, vertices);glColorPointer(3, GL_FLOAT,0, colors);glDrawArrays(GL_TRIANGLES,0,3);glDisable(GL_VERTEX_ARRAY);glDisable(GL_COLOR_ARRAY);glPopMatrix();}intmain(void){bool running =true;// Initialise GLFWglfwInit();// Open an OpenGL windowif( !glfwOpenWindow(300,300,0,0,0,0,24,8, GLFW_WINDOW)) {
std::cerr <<"Could not create window"<< std::endl;glfwTerminate();return1;}// Main loopwhile(running) {// Game logic goes here...// OpenGL rendering goes here...glClear(GL_COLOR_BUFFER_BIT);renderScene();// Swap front and back buffersglfwSwapBuffers();// Check if ESC key was pressed or window was closed
running = !glfwGetKey( GLFW_KEY_ESC ) &&glfwGetWindowParam( GLFW_OPENED );}// Close window and terminate GLFWglfwTerminate();// Exit programreturn0;}
Mit glfwGetVideoModes() kann man die vom System unterstützten
Video-Modes abfragen.
GLFWvidmode modes[100];int mode_cnt;
mode_cnt =glfwGetVideoModes(modes,100);for(int i =0; i < mode_cnt; i++) {
std::cout << modes[i].Width <<"x"<< modes[i].Height << std::endl;}
Tastatur-/Mausabfrage in GLFW
Am einfachsten realisiert man die Steuerung eines Spiels über die Tastatur.
Dazu gibt es in prinzipiell 2 Möglichkeiten (dasselbe gilt für die Maus):
Ereignis (Event)-gesteuerte Abfrage
Punktuelle Abfrage (Polling)
Tastaturabfrage
Ereignisgesteuert
Ereignisorientiertes Abfragen eignet sich für alle Tasten, die eine einmalige Wirkung haben sollen
(wie z.B. die ESC-Taste, oder die "Schuss"-Taste, oder das
Ein/Ausschalten einer Option). Dazu registriert man eigene
Callback-Funktionen, die von GLFW aufgerufen werden, wenn eine
Taste gedrückt worden ist.
GLFW stellt dafür die Funktion glfwSetKeyCallback zur
Verfügung. Eine einfache Callback-Funktion kann man z.B. so
einrichten:
Polling hingegen benötigt man für Tasten, die für die Dauer, die sie gedrückt sind, eine Wirkung haben sollen.
Das gilt z.B. für die Cursor-Tasten - hier ist es nicht korrekt, wenn sich das Raumschiff
nur ein einziges Mal vorwärts bewegt, wenn die Pfeil-hinauf-Taste
gedrückt wird!
Keyboard-polling geht in GLFW einfach mit der Funktion
glfwGetKey():
if(glfwGetKey(GLFW_KEY_UP))
std::cout <<"Up key is pressed"<< std::endl;if(glfwGetKey('T'))
std::cout <<"T key is pressed"<< std::endl;
Mausabfrage
Für die Maus hat man gleich mehrere Polling und
Callback-Funktionen, mit dem man Bewegungen vom Mauszeiger, dem
Mausrad als auch Button-Klicks abfangen und abfragen kann.
Ereignisgesteuert
void GLFWCALL myMousePosCallback(int x,int y){
std::cout <<"Mouse moved to ("<< x <<", "<< y <<")"<< std::endl;}void GLFWCALL myMouseButtonCallback(int button,int action){switch(button) {case GLFW_MOUSE_BUTTON_LEFT:
std::cout <<"Left mouse button ";break;case GLFW_MOUSE_BUTTON_RIGHT:
std::cout <<"Right mouse button ";break;case GLFW_MOUSE_BUTTON_MIDDLE:
std::cout <<"Middle mouse button ";break;default:// There are mice with more than 3 buttons!
std::cout <<"Some mouse button ";}if(action == GLFW_PRESS) {
std::cout <<"pressed."<< std::endl;}else{
std::cout <<"released."<< std::endl;}}...glfwSetMousePosCallback(myMousePosCallback);glfwSetMouseButtonCallback(myMouseButtonCallback);
Polling
int x,y;glfwGetMousePos(&x, &y)
std::cout <<"Mouse is at position ("<< x <<", "<< y <<")."<< std::endl;if(glfwGetMouseButton(GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS) {
std::cout <<"Left mouse button is pressed."<< std::endl;}else{
std::cout <<"Left mouse button is released."<< std::endl;}
Extensions laden mit GLEW
Zu Beginn des Programmes, nachdem man ein OpenGL Fenster erzeugt hat, muss GLEW initialisiert werden:
Zur Laufzeit kann man nun die Verfügbarkeit der Extensions folgendermaßen überprüfen:
if(GLEW_ARB_depth_clamp){// it is safe to use the ARB_depth_clamp here
glEnable(GL_DEPTH_CLAMP);
}
Timer für framerateunabhängige Spiellogik
GLFW stellt die Funktion double glfwGetTime() zur Verfügung,
die die seit dem Start des Programmes vergangenen Sekunden
zurückgibt.
Eine gute Zeitmessung braucht man für mehrere Dinge:
Berechnen der Framerate, um sie am Bildschirm anzuzeigen
(damit kann man die allgemeine Performance beurteilen)
Genaues Timing von bestimmten Code-Stücken
Abstimmen der Animationen und Bewegungen auf die
Framerate. Man kann die Geschwindigkeit der einzelnen Objekte
dann so berechnen, dass sie unabhängig von der Framerate
ist. Die Geschwindigkeit ist dann also wirklich in "Einheiten
pro Sekunde" und nicht "Einheiten pro Frame" bestimmbar.
Kommen in dem Stück Code, das man messen möchte, auch
OpenGL-Befehle vor, so muss man (vor allem bei
Hardware-Beschleunigung) beachten, dass OpenGL Befehle in einem
Kommando-Puffer speichert und nicht sofort abarbeitet. Um also
sicherzugehen, dass man die gesamte Zeit misst, die OpenGL zur
Ausführung benötigt, fügt man vor den Aufrufen zur Timing-Routine
jeweils ein
glFinish() ein. In der Endversion des Programmes sollten
aber keine glFinish-Befehle mehr vorkommen (außer ev. für
die Frameratenanzeige), da sie das Programm verzögern.
Noch ein Tipp: Beachtet dass Physik-Engines wie PhsyX sehr wohl
eine fixe Update-Rate pro Sekunde benötigen um korrekte Resultate zu
liefern!
Game Programming Primer
Wenn ihr noch nicht wisst wie ihr das Programmgerüst für euer Spiel
aufbauen wollt dann lest euch am besten
den Game Programming Primer
durch.