Lektion 1
Willkommen bei meinen OpenGL Tutorials. Ich bin ein durchschnittlicher Typ mit einer Passion für OpenGL! Das erste Mal, dass ich was über OpenGL gehört habe, war, als 3Dfx ihren Hardwarebeschleunigten OpenGL Treiber für die Voodoo 1 Karte veröffentlichte. Ich wusste sofort, dass OpenGL etwas war, dass ich lernen musste. Unglücklicherweise war es recht hart, irgendwelche Informationen über OpenGL zu finden, weder in Büchern noch im Netz. Ich verbrachte Stunden damit, Code zum laufen zu kriegen und noch mehr Zeit damit, Leute um Hilfe per E-Mail oder im IRC anzubetteln. Ich stellte fest, dass die Leute, die OpenGL verstanden, sich selbst als Elite betrachteten und kein Interesse hatten, ihr Wissen zu teilen. SEHR frustrierend!
Ich habe diese Webseite erstellt damit Leute die OpenGL lernen wollen einen Ort haben, wo sie hinkommen können, wenn sie Hilfe benötigen. In jedem meiner Tutorials versuche ich so detailiert wie menschlich möglich zu erklären, was jede CodeZeile macht. Ich versuche meinen Code simpel zu halten (kein MFC zu lernen)! Ein absoluter Neuling sowohl in Visual C++ als auch OpenGL sollte fähig sein, durch den Code zu gehen und eine ziemlich genaue Vorstellung zu bekommen, was da vor geht. Meine Seite ist nur eine von vielen Seiten, die OpenGL Tutorials anbieten. Wenn Sie ein Hardcore-OpenGL-Programmierer sind, mag meine Seite zu einfach sein, aber wenn Sie gerade erst anfangen, denke ich, dass meine Seite recht viel zu bieten hat!
Dieses Tutorial wurde im Januar 2000 komplett neu geschrieben. Dieses Tutorial wird Ihnen beibringen, wir man ein OpenGL-Fenster initialisiert. Das Fenster kann im Fenster-Modus oder als Fullscreen laufen, jede Größe die Sie wollen, jeder Auflösung die Sie wollen und jede Farbtiefe die Sie wollen. Der Code ist sehr flexibel und kann für all Ihre OpenGL Projekte benutzt werden. Alle meine Tutorials werden auf diesem Code basieren! Ich habe den Code geschrieben, so dass er felxibel und mächtig zur selben Zeit ist. Alle Fehler werden berichtet. Es sollten keine Speicherlecks existieren und der Code ist einfach zu lesen und einfach zu modifizieren. Danke an Frederic Echols für die Modifikationen an dem Code!
Ich fange mit diesem Tutorial an, indem wir direkt mit dem Code anfangen. Das erste was wir machen müssen, ist ein Projekt in Visual C++ zu erstellen. Wenn Sie nicht wissen, wie Sie das machen müssen, sollten Sie nicht mit OpenGL anfangen, sondern erst einmal Visual C++ lernen. Der Code zum herunterladen ist Visual C++ 6.0 Code. Bei einigen Versionen von VC++ muss bool zu BOOL geändert werden, true in TRUE und false in FALSE. Mit den erwähnten Änderungen konnte ich den Code ohne Probleme auch unter Visual C++ 4.0 und 5.0 kompilieren.
Nachdem Sie eine neue Win32 Applikation (NICHT eine Konsolen-Applikation) in Visual C++ erstellt haben, müssen Sie die OpenGL Librariers hinzu linken. In Visual C++ gehen Sie auf Projekt / Einstellungen und klicken dann auf den LINK Reiter. Unter "Objekt/Bibliotheks Module" am Anfang der Zeile (noch vor kernel32.lib) fügen Sie OpenGL32.lib, GLu32.lib und GLaux.lib ein. Wenn Sie das getan haben, klicken Sie auf OK. Nun sind Sie bereit, ein OpenGL Windows-Programm zu schreiben.
Die ersten 4 Zeilen inkludieren die Header-Dateien für die jeweiligen Libraries, die wir benutzen. Die Zeilen sehen wie folgt aus:
#include <windows.h> // Header Datei für Windows
#include <gl\gl.h> // Header Datei für die OpenGL32 Library
#include <gl\glu.h> // Header Datei für die GLu32 Library
#include <gl\glaux.h> // Header Datei für die GLaux Library
Als nächstes müssen Sie alle Variablen deklarieren, die Sie in Ihrem Programm benutzen wollen. Dieses Programm wird ein leeres OpenGL Fenster erzeugen, weshalbt wir noch nicht allzu viele Variable deklarieren müssen. Die paar Variablen die wir deklarieren sind sehr wichtig und werden in jedem OpenGL Programm, welches Sie mit Hilfe dieses Codes schreiben, benutzt werden.
Die erste Zeile ist für unseren Rendering Context. Jedes OpenGL Programm ist mit einem Rendering Context verbunden. Ein Rendering Context ist das, was die OpenGL Aufrufe mit unserem Device Context (=Geräte-Kontext) verbindet. Der OpenGL Rendering Context ist als hRC definiert. Um mit Ihrem Programm in einem Fenster zu zeichnen, müssen Sie einen Device Context erstellen, was in der zweiten Zeile gemacht wird. Der Fenster Device Context ist als hDC definiert. Der DC verbindet das Fenster mit der GDI (Graphics Device Interface). Der RC verbindet OpenGL mit dem DC.
In der dritten Zeilen ist die Variable hWnd welche das Handle, das unserem Fenster von Fenster zugeordnet wird, enthält und letztendlich ist in der vierten Zeile unsere Instanz für unser Programm.
HGLRC hRC=NULL; // Permanenter Rendering Context
HDC hDC=NULL; // Privater GDI Device Context
HWND hWnd=NULL; // Enthält unser Fenster-Handle
HINSTANCE hInstance; // Enthält die Instanz der Applikation
Die folgende erste Zeile definiert ein Array, das wir benutzen werden, um Tastenanschläge der Tastatur zu überwachen. Es gibt viele Wege zu überprüfen, ob eine Taste gedrückt wurde, aber ich mache es auf diesen Weg. Es ist verlässlich und außerdem kann man so mehr als eine Taste gleichzeitig überprüfen.
Die Variable active wird benutzt, um unser Programm mitzuteilen, ob unser Fenster in die Taskbar minimiert wurde oder nicht. Wenn das Fenster minimiert wurde, können wir alles machen, vom still legen bis zum beenden des Programmes. I lege das Programm still. Auf diese Art läuft es nicht im Hintergrund weiter, wenn es minimiert ist.
Die Variable fullscreen ist ziemlich selbsterklärend. Wenn unser Programm im Fullscreen-Modus läuft, wird fullscreen auf TRUE gesetzt, wenn unser Programm im Fenster-Modus läuft, wird fullscreen auf FALSE gesetzt. Es ist wichtig diese Variable global zu machen, so dass jede Prozedur weiß, ob das Programm im Fullscreen-Modus läuft oder nicht.
bool keys[256]; // Array das für die Tastatur Routine verwendet wird
bool active=TRUE; // Fenster Aktiv Flag standardmäßig auf TRUE gesetzt
bool fullscreen=TRUE; // Fullscreen Flag ist standardmäßig auf TRUE gesetzt
Nun müssen wir WndProc() definieren. Der Grund dafür ist, dass CreateGLWindow() WndProc() benutzt, aber WndProc() erst nach CreateGLWindow() kommt. In C müssen wir, wenn wir Zugriff auf eine Prozedur oder eine Code-Sektion haben wollen, die nach der Code-Sektion kommt, in der wir uns gerade befinden, die Code-Sektion auf die wir zugreifen wollen, am Anfang unseres Programms deklarieren. Deshalb definieren wir WndProc() mit der folgenden Zeile, so dass CreateGLWindow() gebrauch von WndProc() machen kann.
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // Deklaration für WndProc
Die Aufgabe des nächsten Code-Abschnitts ist es, die Größe der OpenGL Szene neu zu setzen, wann immer sich das Fenster (angenommen, Sie verwenden ein Fenster statt des Fullscreen-Modus) in seiner Größe ändert. Selbst wenn Sie die Größe des Fensters nicht änderen können (wenn Sie sich zum Beispiel im Fullscreen-Modus befinden), wird diese Routine zumindest einmal am Anfang des Programms aufgerufen, wenn unsere Perspektiven-Ansicht das erste Mal gesetzt wird. Die Größe der OpenGL Szene wird basierend auf der Breite und Höhe des Fensters angezeigt, indem die Szene angezeigt wird.
GLvoid ReSizeGLScene(GLsizei width, GLsizei height) // verändert die Größe und initialisiert das GL-Fenster
{
if (height==0) // Verhindere eine Division durch 0, indem
{
height=1; // die Höhe auf 1 gesetzt wird
}
glViewport(0, 0, width, height); // Resette die aktuelle Ansicht (Viewport)
Die folgenden Zeilen intialisieren den Screen für unsere Perspektive. Das bedeutet, dass Dinge in der Ferne kleiner werden. Das erzeugt ein realistisches Aussehen der Szene. Die Perspektive wird mit einem 45 Grad Sichtwinkel, basierend auf der Fenster Breite und Höhe berechnet. Die 0.1f, 100.0f ist der Start- und Endpunkt für die Tiefe der Szene, wie weit wir in den Bildschirm hinein zeichnen können.
glMatrixMode(GL_PROJECTION) indiziert, dass die nächsten beiden 2 Zeilen Code die Projektionsmatrix beeinflussen. Die Projektionsmatrix is verantwortlich für eine zusätzliche Perspektive unserer Szene. glLoadIdentity() gleicht einem Reset. Der Befehl stellt den original Status der ausgewählte Matrix wieder her. Nachdem glLoadIdentity() aufgerufen wurde, setzen wir unsere Perspektive für unsere Szene. glMatrixMode(GL_MODELVIEW) indiziert, dass jede Transformation die Modelview-Matrix beeinflusst. Die Modelview-Matrix enthält Informationen über unser Objekt. Als letztes resetten wir die Modelview-Matrix. Machen Sie sich nichts draus, wenn Sie das nicht verstanden haben, ich werde es in späteren Tutorials weiter erklären. Nur damit man weiß, dass es gemacht werden MUSS, wenn Sie eine schöne perspektivische Szene haben wollen.
glMatrixMode(GL_PROJECTION); // wähle die Projektions-Matrix aus
glLoadIdentity(); // Resette die Projektions-Matrix
// berechne das Seitenverhältnis des Fensters
gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f);
glMatrixMode(GL_MODELVIEW); // Wähle die Modelview Matrix aus
glLoadIdentity(); // Resette die Modelview Matrix
}
Im nächsten Code-Abschnitt machen wir alles für die Initialisierung für OpenGL. Wir geben an, zu welcher Farbe der Screen gelöscht werden soll, wir schalten den Depth-Buffer an, aktivieren weiches Shading (smooth shading), etc. Diese Routine wird nicht aufgerufen, bis das OpenGL-Fenster erzeugt wurde. Diese Prozedur liefert zwar einen Wert zurück, da unsere Initialisierung nicht so komplex ist, kümmern wir uns um diesen Wert erst mal nicht.
int InitGL(GLvoid) // Der ganze Setup Kram für OpenGL kommt hier rein
{
Die nächste Zeile aktiviert Smooth Shading. Smooth Shading blendet Farben sehr schön über ein Polygon und erzeugt auch 'weiches' Licht. Ich werde Smooth Shading in einem anderen Tutorial näher erklären.
glShadeModel(GL_SMOOTH); // aktiviert weiches Shading (Smooth Shading)
Die folgende Zeile setzt die Farbe des Screens, wenn er gelöscht wird. Wenn Sie nicht wissen, wie Farben funktionieren, erkläre ich es hier kurz. Die Farbwerte bewegen sich zwischen 0.0f und 1.0f. 0.0f ist das Dunkelste und 1.0f das Hellste. Der erste Parameter von glClearColor ist die Rot-Intensität, der zweite Parameter ist für Grün und der dritte für Blau. Je höher die Zahl ist, desto heller wird die entsprechende Farbe sein. Die letzte Zahl ist der Alpha-Wert. Wenn wir den Bildschirm löschen, müssen wir uns nicht um den 4ten Parameter kümmern. Wir lassen ihn erstmal auf 0.0f. Ich werde seinen Gebrauch in einem anderen Tutorial beschreiben.
Sie erzeugen verschiedene Farben, indem Sie die drei Gruindfarben mischen (rot, grün, blau). Ich hoffe, Sie haben die Grundfarben in der Schule gelernt. So, wenn Sie jetzt glClearColor(0.0f,0.0f,1.0f,0.0f) hätten, würden Sie den Bildschirm auf ein helles Blau löschen. Wenn Sie glClearColor(0.5f,0.0f,0.0f,0.0f) hätten, würden Sie auf ein mittleres Rot löschen. Nicht hell (1.0f) und auch nicht dunkel (0.0f). Um einen weißen Hintergrund zu erhalten, würden Sie alle Farben auf den höchst möglichen Wert setzen (1.0f). Um einen schwarzen Hintergrund zu erhalten, würden Sie alle Farben auf den niedrigst möglichen Wert setzen (0.0f).
glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // Schwarzer Hintergrund
Die nächsten drei Zeilen haben etwas mit dem Depth Buffer zu tun. Stellen Sie sich den Depth Buffer als Schichten im Bildschirm vor. Der Depth Buffer gibt an, wie weit das Objekt in den Bildschirm 'hinein' kann. Wir werden den Depth Buffer in diesem Programm nicht wirklich gebrauchen, aber jedes OpenGL Programm, dass etwas in 3D auf den Schirm bringt, benutzt den Depth Buffer. Er sortiert aus, welche Objekte als erstes gezeichnet werden, so dass ein Quadrat, dass Sie hinter einem Kreis zeichnen wollen, nicht vor dem Kreis erscheint. Der Depth Buffer ist ein sehr wichtiger Teil von OpenGL.
glClearDepth(1.0f); // Depth Buffer Setup
glEnable(GL_DEPTH_TEST); // aktiviert Depth Test
glDepthFunc(GL_LEQUAL); // Die Art des auszuführenden Depth Test
Als nächstes teilen wir OpenGL mit, dass wir die beste Perspektiven Korrektur wünschen, die möglich ist. Dadurch erleiden wir zwar einen winzigen Performanceverlust, erhalten aber eine besser aussehende Perspektive.
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // wirklich nette Perspektiven Berechnungen
Letztendlich geben wir TRUE zurück. Wenn wir überprüfen wollen, ob die Initialisierung fehlerfrei war, können wir überprüfen ob TRUE oder FALSE zurückgegeben wurde. Sie können Ihren eigenen Code einfügen und FALSE zurückgeben, wenn ein Fehler auftritt. Zur Zeit müssen wir uns darüber aber keine Gedanken machen.
return TRUE; // Initialisierung war OK
}
In diesem Abschnitt geschieht unser ganzer Zeichnen-Code. Alles was Sie planen, auf dem Bildschirm anzuzeigen, wird in diesem Abschnitt des Codes untergebracht. Jedes Tutorial nach diesem, wird etwas Code in diesem Abschnitt des Programms hinzufügen. Wenn Sie bereits eine Vorstellung von OpenGL haben, können Sie versuchen, einfache Figuren zu erzeugen, indem Sie zwischen glLoadIdentity() und vor return FALSE OpenGL-Code einfügen. Wenn Ihnen OpenGL neu ist, warten Sie bis zum nächsten Tutorial. Bisher haben wir die Bildschirm auf die Farbe gelöscht, die wir vorher ausgewählt haben, löschen den Depth Buffer und resetten die Szene. Wir werden noch nichts zeichnen.
Das return TRUE teilt unserem Programm mit, dass keine Probleme aufgetreten sind. Wenn Sie das Programm aus irgendwelchen Gründen stoppen wolle, fügen Sie eine return FALSE Zeile irgendwo vor return TRUE ein, um den Programm mitzuteilen, dass der Zeichnen-Code fehlgeschlagen ist. Das Programm wird dann beendet.
int DrawGLScene(GLvoid) // Hier kommt der ganze Zeichnen-Kram hin
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Lösche den Bildschirm und den Depth-Buffer
glLoadIdentity(); // Resettet die aktuelle Modelview Matrix
return TRUE; // Alles war OK
}
Der nächste Code-Abschnitt wird aufgerufen, kurz bevor das Programm beendet wird. Die Aufgabe von KillGLWindow() ist es, den Rendering Kontext, den Device Kontext und letzlich das Fenster-Handle freizugeben. Ich habe eine Menge Fehlerüberprüfungen eingefügt. Wenn das Programm irgendeinen Teil des Fensters nicht zerstören konnte, wird eine MessageBox mit einer Fehlernachricht angezeigt, welche Ihnen mitteilt, was fehlgeschlagen ist. Das macht es wesentlich einfacher Probleme in Ihrem Code zu finden.
GLvoid KillGLWindow(GLvoid) // Entferne das Fenster korrekt
{
Als erstes was wir in KillGLWindow() überprüfen sollten, ist, ob wir uns im Fullscreen-Modus befinden. Wenn wir es sind, wechseln wir zurück zum Desktop. Wir sollten das Fenster zerstören, bevor wir den Fullscreen-Modus deaktivieren, allerdings ist es auf einigen Grafikkarten der Fall, dass wenn wir das Fenster zerstören BEVOR wir den Fullscreen-Modus deaktivieren, der Desktop nicht richtig angezeigt wird. Deshalb deaktivieren wir erst den Fullscreen-Modus. Das verhindert, dass der Desktop korrupt dargestellt wird und arbeitet sowohl für Nvidia als auch 3dfx Grafikkarten!
if (fullscreen) // Sind wir im Fullscreen Modus?
{
Wir benutzen ChangeDisplaySettings(NULL,0) um uns den original Desktop zurückgeben zu lassen. Indem wir NULL als ersten Parameter und 0 als zweiten Parameter übergeben, zwingen wir Windows dazu, die aktuell in der Registry gespeicherten Werte (die Standard Auflösung, Bit-Tiefe, Frequenz, etc.) zu benutzen, um den original Desktop wieder herzustellen. Nachdem wir zurück zum Desktop gewechselt haben, machen wir den Cursor wieder sichtbar.
ChangeDisplaySettings(NULL,0); // Wenn ja, wechsle zurück zum Desktop
ShowCursor(TRUE); // Zeige den Maus-Zeiger
}
Der folgende Code überprüft, ob wir einen Rendering Context (hRC) haben. Falls nicht, springt das Programm in den Code-Abschnitt, wo überprüft wird, ob wir einen Device Context haben.
if (hRC) // Haben wir einen Rendering Context?
{
Wenn wir einen Rendering Context haben, überprüft der folgende Code, ob wir ihn freigeben können (den hRC vom hDC lösen können). Beachten Sie den Weg, wie ich auf Fehler überprüfe. Grundsätzlich teile ich unserem Programm mit, dass es versuchen soll ihn freizugeben (mit wglMakeCurrent(NULL,NULL)), dann überprüfe ich, ob das freigeben erfolgreich war oder nicht. Somit werden einige Zeilen zu einer Codezeile zusammengefasst.
if (!wglMakeCurrent(NULL,NULL)) // Können wir den DC und RC Kontext freigeben?
{
Wenn wir die DC und RC Kontexte nicht freigeben konnten, wird eine MessageBox angezeigt, die uns wissen lässt, dass DC und RC nicht freigegeben werden konnten. NULL bedeutet hier, dass die MessageBox kein Parent-Fenster besitzt. Der Text direkt nach NULL, ist der Text, der in der MessageBox erscheint. "SHUTDOWN ERROR" ist der Text, der über der MessageBox erscheint (im Titel). Als nächstes haben wir MB_OK, was bedeutet, dass die MessageBox einen Button namens "OK" haben soll. MB_ICONINFORMATION lässt ein kleines i in einem Kreis innerhalb der Messagebox erscheinen (um es auffälliger zu machen).
MessageBox(NULL,"Release Of DC And RC Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
}
Als nächstes versuchen wir den Rendering Kontext zu löschen. Wenn wir damit nicht erfolgreich sind, geben wir eine Fehlermeldung aus.
if (!wglDeleteContext(hRC)) // Können wir den RC löschen?
{
Wenn wir den Rendering Context nicht löschen konnten, wird der folgende Code eine Message-Box anzeigen, die uns wissen lässt, dass das Löschen des RC nicht erfolgreich war. hRC wird auf NULL gesetzt.
MessageBox(NULL,"Release Rendering Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
}
hRC=NULL; // Setze RC auf NULL
}
Nun überprüfen wir, ob unser Programm ein Device Context hat, und falls ja, versuchen wir diesen freizugeben. Wenn wir ihn nicht freigeben können, wird eine Fehlermeldung angezeigt und hDC auf NULL gesetzt.
if (hDC && !ReleaseDC(hWnd,hDC)) // Können wir DC freigeben?
{
MessageBox(NULL,"Release Device Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
hDC=NULL; // Setze DC auf NULL
}
Nun überprüfen wir, ob es ein Fenster-Handle gibt, und falls ja, versuchen wir das Fenster mittels DestroyWindow(hWnd) zu zerstören. Wenn wir das Fenster nicht zerstören konnten, wird eine Fehlermeldung angezeigt und hWnd wird auf NULL gesetzt.
if (hWnd && !DestroyWindow(hWnd)) // Können wir das Fenster zerstören?
{
MessageBox(NULL,"Could Not Release hWnd.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
hWnd=NULL; // Setze hWnd auf NULL
}
Als letztes de-registrieren wir unsere Fenster-Klasse. Das erlaubt es uns, dass Fenster ordentlich zu kellen und dann ein anderes Fenster zu öffnen, ohne die Fehlernachricht "Fenster-Klasse ist bereits registriert" zu erhalten.
if (!UnregisterClass("OpenGL",hInstance)) // Können wir die Klasse de-registrieren?
{
MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
hInstance=NULL; // Setze hInstance auf NULL
}
}
Der nächste Code-Abschnitt erzeugt unser OpenGL-Fenster. Ich habe ziemlich lange gebraucht, um zu entscheiden, ob ich ein fixes Fullscreen-Fenster erzeugen soll, welches nicht viel extra Code benötigt hätte oder ein einfaches anzuwendendes, benutzerfreundliches Fenster, dass mehr Code benötigt. Ich habe mich für das benutzerfreundliche Fenster mit viel mehr Code entschieden, weil ich es für die beste Wahl hielt. Ich wurde die folgende Fragen immer wieder per E-Mail gefragt: Wie kann ich ein Fenster erzeugen, anstatt Fullscreen zu benutzen? Wie ändere ich den Fenster-Titel? Wie ändere ich die Auflösung oder das Pixel Format des Fensters? Der folgende Code macht all das! Demnach ist er besser zum lernen geeignet und wird Ihnen das schreiben eigener OpenGL Programm um ein vielfaches erleichtern!
Wie Sie sehen können, gibt die Prozedur BOOL zurück (TRUE oder FALSE) und übernimmt 5 Parameter: Titel des Fensters, Breite des Fensters, Höhe des Fensters, Bits (16/24/32) und letzlich fullscreenflag gleich TRUE für Fullscreen oder FALSE für Fenster-Modus. Wir geben einen booleschen Wert zurück, der uns mitteilt, ob das Fenster erfolgreich erzeugt wurde.
BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag)
{
Wenn wir Windows auffordern uns ein passendes Pixel Format zu finden, wird die Nummer des Modus, mit dem Windows dann am Ende aufkommt, in der Variable PixelFormat gespeichert.
GLuint PixelFormat; // Enthält die Ergebnisse nachdem nach was passendem gesucht wurde
wc wird benutzt, um unsere Fenster-Klassen-Struktur aufzunehmen. Die Fenster-Klassen-Struktur enthält Informationen über unser Fenster. Indem wir verschiedene Felder in der Klasse ändern, können wir das Aussehen und Verhalten des Fensters ändern. Jedes Fenster gehört zu einer Fenster-Klasse. Bevor Sie ein Fenster erzeugen, MÜSSEN Sie eine Klasse für das Fenster registrieren.
WNDCLASS wc; // Fenster Klassen Struktur
dwExStyle und dwStyle nehmen die erweiterten und normalen Fenster-Stil-Informationen auf. Ich benutze Variablen um die Stile zu speichern, so dass ich die Stile abhängig von der Art des Fensters, dass ich erzeugen will, ändern kann (ein Popup Fenster für Fullscreen oder ein Fenster mit Rahmen für den Fenster-Modus).
DWORD dwExStyle; // erweiterter Fenster-Stil
DWORD dwStyle; // Fenster-Stil
Die folgenden 5 Zeilen Code ermitteln die obere linke und untere rechte Koordinaten eines Rechtecks. Wir werden diese Werte verwenden, um unser Fenster so zu justieren, dass die Fläche auf die wir zeichnen exakt der Auflösung ist, die wir wünschen. Normalerweise, wenn wir ein 640x480 Fenster erzeugen würden, würden die Kanten unseres Fensters etwas von dieser Fläche beanspruchen.
RECT WindowRect; // Enthält die obere linke / untere rechte Eckwerte des Rechtecks
WindowRect.left=(long)0; // Setze linken Wert auf 0
WindowRect.right=(long)width; // Setze rechten Wert auf die gewünschte Breite
WindowRect.top=(long)0; // Setze den oberen Wert auf 0
WindowRect.bottom=(long)height; // Setze unteren Wert auf die gewünschte Höhe
In der nächsten Codezeile setzen wir die globale Variable Fullscreen gleich fullscreenflag.
fullscreen=fullscreenflag; // Setze das globale Fullscreen Flag
Im nächsten Code-Abschnitt ermitteln wir eine Instanz für unser Fenster, dann defnieren wir die Fenster-Klasse.
Die Stile CS_HREDRAW und CS_VREDRAW zwingt das Fenster neu zu zeichnen, wenn dessen Größe geändert wird. CS_OWNDC erzeugt ein privaten DC für das Fenster. Das bedeutet, dass der DC nicht zwischen anderen Applikationen geteilt wird. WndProc ist die Prozedur, welche nach Nachrichten in unserem Programm schaut. Es werden keine extra Fenster-Daten benutzt, so dass wir die beiden Felder auf 0 setzen. Dann setzen wir die Instanz. Als nächstes setzen wir das hIcon auf NULL, was bedeutet, dass wir kein Icon im Fenster verwenden möchten und für den Mauszeiger verwenden wir den Standard-Pfeil. Die Hintergrundfarbe ist egal (wir setzen diese in GL). Wir wollen kein Menü in diesem Fenster, deswegen setzen wir es auf NULL und der Klassen-Name kann irgend ein von Ihnen gewählter Name sein. Ich benutze "OpenGL" wegen seiner Einfachheit.
hInstance = GetModuleHandle(NULL); // Ermittle die Instanz für unser Fenster
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; // Zeichne neu beim bewegen und eigener DC für's Fenster
wc.lpfnWndProc = (WNDPROC) WndProc; // WndProc behandelt die Nachrichten
wc.cbClsExtra = 0; // Keine extra Fenster-Daten
wc.cbWndExtra = 0; // Keine extra Fenster-Daten
wc.hInstance = hInstance; // Setze die Instanz
wc.hIcon = LoadIcon(NULL, IDI_WINLOGO); // Lade das Standard-Icon
wc.hCursor = LoadCursor(NULL, IDC_ARROW); // Lade den Pfeil-Zeiger
wc.hbrBackground = NULL; // es wird kein Hintergrund für GL benötigt
wc.lpszMenuName = NULL; // Wir wollen kein Menü
wc.lpszClassName = "OpenGL"; // Setze den Klassen-Namen
Nun registrieren wir die Klasse. Wenn etwas schief geht, wird eine Fehler-Nachricht angezeigt. Ein Klick auf OK der Fehler-Meldung beendet das Programm.
if (!RegisterClass(&wc)) // Versuche die Fenster-Klasse zu registrieren
{
MessageBox(NULL,"Failed To Register The Window Class.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // beende und gebe FALSE zurück
}
Nun überprüfen wir, ob das Programm im Fullscrenn-Modus oder im Fenster-Modus laufen soll. Wenn es im Fullscreen-Modus laufen soll, versuchen wir, den Fullscreen-Modus zu setzen.
if (fullscreen) // Fullscreen Modus?
{
Der nächste Codeabschnitt ist etwas, wo die Leute scheinbar Probleme mit haben... in den Fullscreen-Modus zu wechseln. Es gibt einige wichtige Dinge, die Sie im Hinterkopf behalten sollten, wenn Sie in den Fullscreen-Modus wechseln. Stellen Sie sicher, dass Breite und Höhe, welche Sie im Fullscreen-Modus verwenden, die selbe Breite und Höhe ist, die Sie für ihr Fenster verwenden wollen und am wichtigsten: setzen Sie den Fullscreen-Modus BEVOR Sie ihr Fenster erzeugen. In diesem Code müssen Sie sich nicht um Breite und Höhe kümmern, da Fullscreen und Fenster-Größe jeweils auf die erforderliche Größe gesetzt sind.
DEVMODE dmScreenSettings; // Device Modus
memset(&dmScreenSettings,0,sizeof(dmScreenSettings)); // Stelle sicher, dass der Speicher geleert ist
dmScreenSettings.dmSize=sizeof(dmScreenSettings); // Größe der Devmode Struktur
dmScreenSettings.dmPelsWidth = width; // ausgewählte Screen Breite
dmScreenSettings.dmPelsHeight = height; // ausgewählte Screen Höhe
dmScreenSettings.dmBitsPerPel = bits; // ausgewählte Bits Per Pixel
dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;
Im oberen Code machen wir Platz um unsere Video-Einstellungen zu speichern. Wir setzen die Breite, Höhe und Bits, zu der der Bildschirm wechseln soll. Im unteren Code versuchen wir den angeforderten Fullscreen-Modus zu setzen. Wir speichern alle Informationen über Breite, Höhe und Bits in dmScreenSettings. In der Zeile die auf ChangeDisplaySettings folgt, versuchen wir in einen Modus zu wechseln, der dem entspricht, welcher in dmScreenSettings gespeichert ist. Ich benutze den Parameter CDS_FULLSCREEN, wenn ich in einen anderen Modus wechsle, weil dann die Start-Leiste entfernt werden soll und die Fenster auf dem Desktop werden weder in ihrer Größe noch in ihrer Position verändert, wenn Sie in den Fullscreen-Modus und zurück wechseln.
// Versuche gewählten Modus zu setzen und Ergebnisse zurückliefern. ANMERKUNG: CDS_FULLSCREEN lässt die Start-Leiste nicht anzeigen.
if (ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL)
{
Wenn der Modus nicht gesetzt werden konnte, wird der untere Code ausgeführt. Wenn kein passender Fullscreen-Modus existiert, wird eine Message-Box angezeigt und bietet zwei Optionen an... Die Option, im Fenster-Modus zu laufen oder die Option zum Beenden.
// Wenn der Modus fehl schlägt, biete zwei Optionen an. Beenden oder im Fenster-Modus laufen lassen.
if (MessageBox(NULL,"The Requested Fullscreen Mode Is Not Supported By\nYour Video Card. Use Windowed Mode Instead?","NeHe GL",MB_YESNO|MB_ICONEXCLAMATION)==IDYES)
{
Wenn der Benutzer sich entscheided den Fenster-Modus benutzen will, wird die Variable fullscreen auf FALSE gesetzt und das Programm läuft weiter.
fullscreen=FALSE; // wähle Fenster-Modus aus (Fullscreen=FALSE)
}
else
{
Wenn sich der Benutzer entscheided zu beenden, wird eine MessageBox angezeigt, die mitteilt, dass das Programm dabei ist, geschlossen zu werden. Es wird FALSE zurückgegeben, um dem Programm mitzuteilen, dass das Fenster nicht erfolgreich erzeugt wurde. Das Programm wird beendet.
// Message Box anzeigen, die den Benutzer wissen lässt, dass das Programm geschlossen wird.
MessageBox(NULL,"Program Will Now Close.","ERROR",MB_OK|MB_ICONSTOP);
return FALSE; // beende und gebe FALSE zurück
}
}
}
Weil der obere Fullscreen Code vielleicht fehlgeschlagen ist und der Benutzer entschieden hat, dass Programm statt dessen im Fenster-Modus laufen zu lassen, überprüfen wir erneut, ob Fullscreen gleich TRUE oder FALSE ist, bevor wir den Screen / die Fenster-Art neu initialisieren.
if (fullscreen) // Sind wir immer noch im Fullscreen Modus?
{
Wenn wir immer noch im Fullscreen-Modus sind, setzen wir den erweiterten Stil auf WS_EX_APPWINDOW, was ein Top-Level-Fenster in die Taskbar verbannt, sobald unser Fenster sichtbar ist. Für den Fenster-Stil erzeugen wir ein WS_POPUP Fenster. Diese Art von Fenster hat keinen Rahmen, was es perfekt für den Fullscreen-Modus macht.
Zu letzt deaktivieren wir den Mauszeiger. Wenn ihr Programm nicht interaktiv ist, ist es in der Regel netter, den Mauszeiger zu verstecken, wenn man sich im Fullscreen-Modus befindet. Das liegt aber an Ihnen.
dwExStyle=WS_EX_APPWINDOW; // erweiterter Fenster-Stil
dwStyle=WS_POPUP; // Fenster-Stil
ShowCursor(FALSE); // verstecke Maus-Zeiger
}
else
{
Wenn wir statt eines Fullscreen-Modus ein Fenster verwenden, fügen wir WS_EX_WINDOWEDGE zum erweiterten Stil zu. Das gibt dem Fenster einen mehr 3 dimensionales Aussehen. Für den Stil benutzen wir WS_OVERLAPPEDWINDOW statt WS_POPUP. WS_OVERLAPPEDWINDOW erzeugt ein Fenster mit einer Titelleiste, Fenstermenü, Minimierungs- und Maximierungs-Buttons.
dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; // erweiterter Fenster-Stil
dwStyle=WS_OVERLAPPEDWINDOW; // Fenster Stil
}
Die folgende Zeile richtet unser Fenster abhängig vom Stil des Fensters aus. Durch die Ausrichtung wird unser Fenster exakt der geforderten Auflösung angepasst. Normalerweise überlappen die Ränder teilen von unserem Fenster. Indem wir aber AdjustWindowRectEx verwenden, wird nichts unserer OpenGL Szene von den Rändern verdeckt, sondern das Fenster wird größer gemacht, um entsprechend Platz für die Pixel zu machen, die am Fenster-Rand gezeichnet werden. Im Fullscreen-Modus hat dieser Befehl keinen Effekt.
AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle); // justiere das Fenster der angeforderten Größe entsprechend
Im nächsten Code-Abschnitt erzeugen wir unser Fenster und überprüfen, ob es auch wirklich korrekt erzeugt wurde. Wir übergeben CreateWindowEX() alle benötigten Parameter. Wir entscheiden uns, den erweiterten Stil zu verwenden. Der Klassenname (welcher identisch mit dem sein muss, den Sie bei der Registrierung der Fenster-Klasse verwendet haben). Der Fenster-Titel. Der Fenster-Stil. Die obere linke Ecke Ihres Fenster (0,0 ist immer gut). Die Breite und Höhe des Fensters. Wir wollen kein Parent-Fenster und wir brauchen kein Menü, weswegen wir diese beiden Parameter auf NULL setzen. Wir übergeben unsere Fenster-Instanz und zu letzt NULL als letzten Parameter.
Beachten Sie, dass wir die Stile WS_CLIPSIBLINGS und WS_CLIPCHILDREN den anderen Stilen hinzufügen, die wir benutzen wollen. WS_CLIPSIBLINGS und WS_CLIPCHILDREN werden bei BENÖTIGT damit OpenGL korrekt läuft. Diese Stile verhindern, dass andere Fenster über oder in unser OpenGL Fenster zeichnet.
if (!(hWnd=CreateWindowEx( dwExStyle, // erweiterter Stil für das Fenster
"OpenGL", // Klassen Name
title, // Fenster Titel
WS_CLIPSIBLINGS | // benötigter Fenster-Stil
WS_CLIPCHILDREN | // benötigter Fenster-Stil
dwStyle, // ausgewählter Fenster Stil
0, 0, // Fenster Position
WindowRect.right-WindowRect.left, // berechne die justierte Fenster-Breite
WindowRect.bottom-WindowRect.top, // berechne die justierte Fenster-Höhe
NULL, // Kein Parent-Fenster
NULL, // Kein Menu
hInstance, // Instanz
NULL))) // Leite nichts an WM_CREATE weiter
Als nächstes überprüfen wir, ob unser Fenster korrekt erzeugt wurde. Wenn unser Fenster erzeugt wurde, enthält hWnd unser Fenster-Handle. Wenn das Fenster nicht erzeugt wurde, wird eine Fehlernachricht angezeigt und das Programm beendet (return FALSE).
{
KillGLWindow(); // Resette die Ansicht
MessageBox(NULL,"Window Creation Error.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Gebe FALSE zurück
}
Der nächste Code-Abschnitt beschreibt ein Pixel-Format. Wir wählen ein Format aus, das OpenGL und Double-Buffering unterstützt, sowie RGBA (rot, grün, blau und alpha-Kanal). Wir versuchen ein Pixel-Format zu finden, dass den Bits entspricht, die wir verwenden wollen (16 Bit, 24 Bit, 32 Bit). Letztendlich benötigen wir noch ein 16 Bit Z-Buffer. Die restlichen Parameter werden entweder nicht benutzt oder sind nicht wichtig (vom Stencil Buffer und dem (langsamen) Accumulation Buffer abgesehen).
static PIXELFORMATDESCRIPTOR pfd= // pfd teilt Windows mit, wie wie die Dinge haben wollen
{
sizeof(PIXELFORMATDESCRIPTOR), // Größe des Pixel Format Descriptors
1, // Versions Nummer
PFD_DRAW_TO_WINDOW | // Format muss Fenster unterstützen
PFD_SUPPORT_OPENGL | // Format muss OpenGL unterstützen
PFD_DOUBLEBUFFER, // Muss Double Buffering unterstützen
PFD_TYPE_RGBA, // Fordere ein RGBA Format an
bits, // wähle unsere Farbtiefe aus
0, 0, 0, 0, 0, 0, // Color Bits werden ignoriert
0, // Kein Alpha Buffer
0, // Shift Bit wird ignoriert
0, // Kein Accumulation Buffer
0, 0, 0, 0, // Accumulation Bits werden ignoriert
16, // 16Bit Z-Buffer (Depth Buffer)
0, // Kein Stencil Buffer
0, // Kein Auxiliary Buffer
PFD_MAIN_PLANE, // Haupt-Zeichen-Schicht
0, // Reserviert
0, 0, 0 // Layer Masken werden ignoriert
};
Wenn keine Fehler während der Fenster-Erstellung auftreten, versuchen wir ein OpenGL Device Context zu bekommen. Wenn wir kein DC kriegen können, wird eine Fehler-Meldung angezeigt und das Programm beendet (return FALSE).
if (!(hDC=GetDC(hWnd))) // Haben wir einen Device Kontext erhalten?
{
KillGLWindow(); // Resette die Anzeige
MessageBox(NULL,"Can't Create A GL Device Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Gebe FALSE zurück
}
Wenn wir ein Device Context für unser OpenGL Fenster bekommen konnten, versuchen wir ein Pixel Format zu finden, welches mit dem oben beschriebenen übereinstimmt. Wenn Windows kein passendes Pixel Format finden konnte, wird eine Fehlernachricht angezeigt und das Programm beendet (return FALSE).
if (!(PixelFormat=ChoosePixelFormat(hDC,&pfd))) // Hat Windows ein passendes Pixel Format gefunden?
{
KillGLWindow(); // Resette die Anzeige
MessageBox(NULL,"Can't Find A Suitable PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Gebe FALSE zurück
}
Wenn Windows ein passendes Pixel Format gefunden hat, versuchen wir das Pixel Format zu setzen. Wenn das Pixel Format nicht gesetzt wurden konnte, wird eine Fehlernachricht angezeigt und das Programm beendet (return FALSE).
if(!SetPixelFormat(hDC,PixelFormat,&pfd)) // Können wir das Pixel Format setzen?
{
KillGLWindow(); // Resette die Anzeige
MessageBox(NULL,"Can't Set The PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Gebe FALSE zurück
}
Wenn das Pixel Format richtig gesetzt werden konnte, versuchen wir einen Rendering Context zu erhalten. Wenn wir kein Rendering Context erhalten konnten, wird eine Fehlernachricht angezeigt und das Programm beendet (return FALSE).
if (!(hRC=wglCreateContext(hDC))) // Können wir einenRendering Kontext kriegen?
{
KillGLWindow(); // Resette die Anzeige
MessageBox(NULL,"Can't Create A GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Gebe FALSE zurück
}
Wenn bis hier her keine Fehler aufgetreten sind und wir es geschafft haben sowohl Device Context als auch Rendering Context zu erzeugen, müssen wir den Rendering Context nur noch aktivieren. Wenn wir den Rendering Context nicht aktivieren konnten, wird eine Fehlernachricht angezeigt und das Programm beendet (return FALSE).
if(!wglMakeCurrent(hDC,hRC)) // Versuche den Rendering Kontext zu aktivieren
{
KillGLWindow(); // Resette die Anzeige
MessageBox(NULL,"Can't Activate The GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Gebe FALSE zurück
}
Wenn alles glatt gelaufen ist und unser OpenGL Fenster erzeugt wurde, zeigen wir das Fenster als Vordergrundsfenster (um ihm eine höhere Priorität zu geben) und setzen den Fokus auf das Fenster. Dann rufen wir ReSizeGLScene auf und übergeben die Bildschirm Breite und Höhe, um die Perspektive unseres OpenGL Screens zu setzen.
ShowWindow(hWnd,SW_SHOW); // Zeige das Fenster
SetForegroundWindow(hWnd); // Etwas höhere Priorität
SetFocus(hWnd); // Setze den Tastatur-Fokus auf das Fenster
ReSizeGLScene(width, height); // Initialisiere unseren perspektivischen GL-Screen
Letztendlich springen wir zu InitGL(), wo wir Beleuchtung, Texturen und alles andere vorbereiten, was wir benötigen. Sie können ihre eigene Fehlerbehandlung in InitGL() machen und TRUE (wenn alles OK war) oder FALSE (wenn was falsch gelaufen ist) zurückgeben. Wenn Sie zum Beispiel Texturen in InitGL() laden wollen und ein Fehler auftritt, wollen Sie das Programm vielleicht anhalten. Wenn Sie FALSE für InitGL() zurückgeben, sehen die folgende Zeilen das FALSE als eine Fehler-Nachricht an und beenden das Programm.
if (!InitGL()) // Initialisiere unser neu erzeugtes GL Fenster
{
KillGLWindow(); // Resette die Anzeige
MessageBox(NULL,"Initialization Failed.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Gebe FALSE zurück
}
Wenn wir es bishierher geschafft haben, können wir uns sicher sein, dass das Erzeugen des Fensters erfolgreich war. Wir geben TRUE an WinMain() zurück und teilen WinMain() damit mit, dass keine Fehler aufgetreten sind. Damit verhindern wir, dass das Programm beendet wird.
return TRUE; // Erfolg
}
Hier werden alle Fenster-Nachrichten bearbeitet. Wenn wir die Fenster-Klasse registrieren, teilen wir ihr mit, in diesen Code-Abschnitt zu springen, um die Fenster-Nachrichten zu bearbeiten.
LRESULT CALLBACK WndProc( HWND hWnd, // Handle für dieses Fenster
UINT uMsg, // Nachricht für dieses Fenster
WPARAM wParam, // Weitere Nachrichten Informationen
LPARAM lParam) // Weitere Nachrichten Informationen
{
Der folgende Code vergleich uMsg mit den Case-Statements. uMsg enthält den Namen der Nachricht, die wir bearbeiten werden.
switch (uMsg) // Überprüfe auf Fenster-Nachrichten
{
Wenn uMsg gleich WM_ACTIVATE ist, überprüfen wir, ob unser Fenster immer noch aktiv ist. Wenn unser Fenster minimiert wurde, wird die Variable active auf FALSE gesetzt. Wenn unser Fenster aktiv ist, wird die Variable active auf TRUE gesetzt.
case WM_ACTIVATE: // Wenn Aktivierungs-Nachricht reinkommt
{
if (!HIWORD(wParam)) // Überprüfe Minimierungs-Status
{
active=TRUE; // Programm ist aktiv
}
else
{
active=FALSE; // Programm ist nicht länger aktiv
}
return 0; // Kehre zurück zur Nachrichten-Schleife
}
Wenn die Nachricht gleich WM_SYSCOMMAND ist (System Befehl), vergleichen wir wParam mit den Case-Statements. Wenn wParam gleich SC_SCREENSAVE oder SC_MONITORPOWER ist, also ein Screensaver versucht sich zu starten oder der Monitor versucht in den Energiesparmodus überzutreten. In dem wir 0 zurückgeben, verhindern wir diese beiden Sachen.
case WM_SYSCOMMAND: // hereinkommende System Befehle
{
switch (wParam) // Überprüfe auf System Aufrufe
{
case SC_SCREENSAVE: // versucht der Screensaver sich zu starten?
case SC_MONITORPOWER: // versucht der Monitor in den Energiesparmodus zu gehen?
return 0; // verhindere das
}
break; // Ende
}
Wenn uMsg gleich WM_CLOSE ist, wird das Fenster geschlossen. Wir senden eine Beenden-Benachrichtung, dass die Haupt-Schleife beendet wird. Die Variable done wir auf TRUE gesetzt, die Haupt-Schleife in WinMain() wird beendet und das Programm wird geschlossen.
case WM_CLOSE: // Haben wir eine Nachricht zum Schließen erhalten?
{
PostQuitMessage(0); // Sende eine Beenden-Nachricht
return 0; // Springe zurück
}
Wenn eine Taste gedrückt wird, können wir das herausfinden, indem wir wParam auslesen. Ich setze dann das Feld der Taste in dem Array keys[] auf TRUE. Auf diesem Weg kann ich das Array später auslesen um herauszufinden, welche Taste gerdrückt wurde. Das erlaubt uns, dass mehr als eine Taste zur selben Zeit gedrückt wird.
case WM_KEYDOWN: // Wird eine Taste gedrückt?
{
keys[wParam] = TRUE; // Wenn ja, markiere sie mit TRUE
return 0; // Springe zurück
}
Wenn eine Taste losgelassen wurde, finden wir die Taste heraus, indem wir wParam auslesen. Wir markieren dann die Felder in dem Array keys[] mit FALSE. Auf diesem Weg weiß ich, wenn ich das Feld auslese, ob die Taste noch gedrückt ist oder ob sie losgelassen wurde. Jede Taste der Tastatur kann durch eine Zahl zwischen 0 und 255 repräsentiert werden. Wenn ich eine Taste drücke, die zum Beispiel durch die Nummer 40 repräsentiert wird, keys[40] wird auf TRUE gesetzt. Wenn ich wieder los lasse, wird es FALSE. So benutzen wir das Feld, um Tastendrücke zu speichern.
case WM_KEYUP: // Wurde eine Taste losgelassen?
{
keys[wParam] = FALSE; // Wenn ja, markiere sie mit FALSE
return 0; // Springe zurück
}
Jedes mal, wenn wir die Größe unseres Fensters verändern, wird uMsg die WM_SIZE Nachricht enthalten. Wir lesen die Werte LOWORD und HIWORD des lParam aus, um die neue Breite und Höhe des Fensters zu erhalten. Wir übergeben die neue Breite und Höhe an ReSizeGLScene(). Die OpenGL Szene wird dann der neue Breite und Höhe angepasst.
case WM_SIZE: // ändere die Größe des Fenster
{
ReSizeGLScene(LOWORD(lParam),HIWORD(lParam)); // LoWord=Breite, HiWord=Höhe
return 0; // Springe zurück
}
}
Jede Nachricht, die uns nicht interessiert, übergeben wir DefWindowProc, so dass Windows diese abarbeiten kann.
// Übergebe alle nicht bearbeiteten Nachrichten an DefWindowProc
return DefWindowProc(hWnd,uMsg,wParam,lParam);
}
Das ist der Einstiegspunkt unserer Windows-Applikation. Hier rufen wir unsere Fenster-Erzeugungs-Routine auf, behandeln Fenster-Nachrichten und überwachen die Interaktion mit dem Benutzer.
int WINAPI WinMain( HINSTANCE hInstance, // Instanz
HINSTANCE hPrevInstance, // vorherige Instanz
LPSTR lpCmdLine, // Kommandozeilen Parameter
int nCmdShow) // Fenster Anzeige Status
{
Wir führen zwei Variablen ein. msg wird benutzt um zu überprüfen, ob eine Nachricht gerade wartet, die behandelt werden sollte. Die Variable done ist anfangs auf FALSE gesetzt. Das bedeutet, dass unser Programm noch nicht zu Ende ist. So lange wie done auf FALSE bleibt, läuft das Programm weiter. Sobald done von FALSE auf TRUE wechselt, wird unser Programm beendet.
MSG msg; // Windows Nachrichten Struktur
BOOL done=FALSE; // Bool Variable um die Schleife zu beenden
Dieser Code-Abschnitt ist komplett optional. Es wird eine MessageBox angezeigt, die frage, ob im Fullscreen-Modus gestartet werden soll oder nicht. Wenn der Benutzer den NEIN-Button anklickt, wird die fullscreen-Variable von TRUE (dem Standardwert) auf FALSE gesetzt und das Programm im Fenster-Modus, anstatt des Fullscreen-Modus gestartet.
// Frage den Benutzer, in welchen Modus er starten will
if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
{
fullscreen=FALSE; // Fenster-Modus
}
So erzeugen wir unser OpenGL Fenster. Wir übergeben den Titel, die Breite, die Höhe, die Farbtiefe und TRUE (Fullscreen) oder FALSE (Fenster-Modus) der CreateGLWindow-Funktion. Das war's! Ich bin ziemlich glücklich mit der Einfachheit dieses Codes. Wurde das Fenster aus irgend einem Grund nicht erzeugt, wird FALSE zurückgegeben und unser Programm augenblicklich gestoppt (return 0).
// erzeuge unser OpenGL Fenster
if (!CreateGLWindow("NeHe's OpenGL Framework",640,480,16,fullscreen))
{
return 0; // Beende, wenn Fenster nicht erzeugt wurde
}
Das ist der Anfang unserer Schleife. Solange done gleich FALSE ist, wird die Schleife durchlaufen.
while(!done) // Schleife die so lange läuft, bis done=TRUE
{
Als erstes müssen wir überprüfen, ob irgendwelche Nachrichten warten. Indem wir PeekMessage() verwenden, können wir auf Nachrichten überprüfen ohne das Programm anzuhalten. Viele Programme verwenden GetMessage(). Das läuft zwar, aber wenn Sie GetMessage() verwenden, macht Ihr Programm so lange nichts, bis es eine Zeichnen-Nachricht oder eine andere Fenster-Nachricht erhält.
if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) // Wartet eine Nachricht?
{
Im nächsten Codeabschnitt überprüfen wir, ob eine Beendigungs-Nachricht vorliegt. Wenn die aktuelle Nachricht eine WM_QUIT Nachricht ist, die von PostQuitMessage(0) erzeugt wurde, dann wird die Variable done auf TRUE gesetzt, damit das Programm beendet wird.
if (msg.message==WM_QUIT) // Haben wir eine Nachricht zum beenden erhalten?
{
done=TRUE; // Wenn ja done=TRUE
}
else // Wenn nicht, bearbeite die Fenster-Nachrichten
{
Wenn die Nachricht keine Beendigungs-Nachricht ist, übersetzen wir die Nachricht und bearbeiten sie dann, so dass WndProc() oder Windows mit der Nachricht umgehen kann.
TranslateMessage(&msg); // Übersetze die Nachricht
DispatchMessage(&msg); // bearbeite die Nachricht
}
}
else // Wenn da keine Nachrichten sind
{
Wenn keine Nachrichten vorhanden waren, zeichnen wir unsere OpenGL Szene. Die erste Zeile Code überprüft, ob das Fenster aktiv ist. Wenn die ESC-Taste gedrückt wurde, ist die Variable done auf TRUE gesetzt und das Programm wird beendet.
// Zeichne die Szene. Schau nach der ESC-Taste und Beendigungs-Nachrichten von DrawGLScene()
if (active) // Programm aktiv?
{
if (keys[VK_ESCAPE]) // Wurde ESC gedrückt?
{
done=TRUE; // ESC Signalisiert, dass Beendet werden soll
}
else // Es ist noch nicht Zeit zum beenden, zeichne Screen neu
{
Wenn das Programm aktiv ist und ESC nicht gedrückt wurde, rendern wir die Szene und tauschen den Buffer (indem wir Double Buffering verwenden, erreichen wir weiche Flackerfreie Animationen). Indem wir Double Buffering verwenden, zeichnen wir alles in einen 'versteckten' Screen, den wir nicht sehen können. Wenn wir die Buffer tauschen, wird der angezeigte Screen, den wir sehen können, der versteckte Screen und der versteckte Screen wird sichtbar. Dadurch sehen wir nicht, wie unsere Szene nach und nach gezeichnet wird. Sie erscheint einfach komplett.
DrawGLScene(); // Zeichne die Szene
SwapBuffers(hDC); // Swap Buffers (Double Buffering)
}
}
Das nächste Stück Code ist neu und wurde gerade erst eingefügt (05-01-00). Es erlaubt uns, die F1-Taste zu drücken, um zwischen Fullscreen und Fenster-Modus, beziehungsweise andersherum, zu wechseln
if (keys[VK_F1]) // Wurde F1 gedrückt?
{
keys[VK_F1]=FALSE; // Wenn ja, setze Taste auf FALSE
KillGLWindow(); // Kill unser aktuelles Fenster
fullscreen=!fullscreen; // Wechsel zwischen Fullscreen und Fester-Modus
// Erzeuge unser OpenGL Fenster erneut
if (!CreateGLWindow("NeHe's OpenGL Framework",640,480,16,fullscreen))
{
return 0; // Beenden, wenn das Fenster nicht erzeugt wurde
}
}
}
}
Wenn die done Variable nicht länger auf FALSE gesetzt ist, wird das Programm beendet. Wir de-initialisieren das OpenGL-Fenster korrekt, so das alles freigegeben wird und wir beenden das Programm.
// Shutdown
KillGLWindow(); // Kill das Fenster
return (msg.wParam); // Beende das Programm
}
In diesem Tutorial habe ich so detailiert wie möglich versucht zu erklären, welche Schritte notwendig sind, um ein OpenGL Programm zu initialisieren und im Fullscreen-Modus laufen zu lassen, welches mit ESC beendet werden kann und das überwacht, ob das Fenster aktiv ist oder nicht. Ich habe ungefähr 2 Wochen gebraucht, um den Code zu schreiben, eine Woche, um die Fehler zu beheben & mit Programmier-Gurus mich zu unterhalten und 2 Tage (ungefähr 22 Stunden, um diese HTML-Datei zu schreiben). Wenn Sie Anmerkungen oder Fragen haben, schreiben Sie mir bitte eine E-Mail. Wenn Sie denken, ich habe etwas nicht korrekt kommentiert oder der Code könnte an einigen Stellen besser gemacht werden, lassen Sie es micht wissen. Ich möchte das beste OpenGL Tutorial machen. Ich bin an Ihrem Feedback interessiert
Jeff Molofee (NeHe)
* DOWNLOAD Visual C++ Code für diese Lektion.
* DOWNLOAD ASM Code für diese Lektion. ( Conversion by Foolman )
* DOWNLOAD Borland C++ Builder 6 Code für diese Lektion. ( Conversion by Christian Kindahl )
* DOWNLOAD BeOS Code für diese Lektion. ( Conversion by Rene Manqueros )
* DOWNLOAD C# Code für diese Lektion. ( Conversion by Joachim Rohde )
* DOWNLOAD VB.Net CsGL Code für diese Lektion. ( Conversion by X )
* DOWNLOAD Code Warrior 5.3 Code für diese Lektion. ( Conversion by Scott Lupton )
* DOWNLOAD Cygwin Code für diese Lektion. ( Conversion by Stephan Ferraro )
* DOWNLOAD D Language Code für diese Lektion. ( Conversion by Familia Pineda Garcia )
* DOWNLOAD Delphi Code für diese Lektion. ( Conversion by Michal Tucek )
* DOWNLOAD Dev C++ Code für diese Lektion. ( Conversion by Dan )
* DOWNLOAD Game GLUT Code für diese Lektion. ( Conversion by Milikas Anastasios )
* DOWNLOAD Irix Code für diese Lektion. ( Conversion by Lakmal Gunasekara )
* DOWNLOAD Java Code für diese Lektion. ( Conversion by Jeff Kirby )
* DOWNLOAD Java/SWT Code für diese Lektion. ( Conversion by Victor Gonzalez )
* DOWNLOAD JoGL Code für diese Lektion. ( Conversion by Kevin J. Duling )
* DOWNLOAD LCC Win32 Code für diese Lektion. ( Conversion by Robert Wishlaw )
* DOWNLOAD Linux Code für diese Lektion. ( Conversion by Richard Campbell )
* DOWNLOAD Linux/GLX Code für diese Lektion. ( Conversion by Mihael Vrbanec )
* DOWNLOAD Linux/SDL Code für diese Lektion. ( Conversion by Ti Leggett )
* DOWNLOAD LWJGL Code für diese Lektion. ( Conversion by Mark Bernard )
* DOWNLOAD Mac OS Code für diese Lektion. ( Conversion by Anthony Parker )
* DOWNLOAD Mac OS X/Cocoa Code für diese Lektion. ( Conversion by Bryan Blackburn )
* DOWNLOAD MASM Code für diese Lektion. ( Conversion by Nico (Scalp) )
* DOWNLOAD Power Basic Code für diese Lektion. ( Conversion by Angus Law )
* DOWNLOAD Pelles C Code für diese Lektion. ( Conversion by Pelle Orinius )
* DOWNLOAD Perl Code für diese Lektion. ( Conversion by Cora Hussey )
* DOWNLOAD Python Code für diese Lektion. ( Conversion by John Ferguson )
* DOWNLOAD Scheme Code für diese Lektion. ( Conversion by Jon DuBois )
* DOWNLOAD Solaris Code für diese Lektion. ( Conversion by Lakmal Gunasekara )
* DOWNLOAD Visual Basic Code für diese Lektion. ( Conversion by Ross Dawson )
* DOWNLOAD Visual Fortran Code für diese Lektion. ( Conversion by Jean-Philippe Perois )
* DOWNLOAD Visual Studio .NET Code für diese Lektion. ( Conversion by Grant James )
Deutsche Übersetzung: Joachim Rohde
Der original Text ist hier zu finden.
Die original OpenGL Tutorials stammen von NeHe's Seite.