www.codeworx.org/opengl-tutorials/Tutorial 2: "Nur ein schwarzes Fenster"


Lektion 2-" Nur ein schwarzes Fenster"

Diese Lektion ist mit Abstand die komplizierteste, alle weiteren, wesentlich spannenderen Tutorials bauen auf dem Folgenden auf. Es ist schwer die ganze Sache von Anfang an, 100%ig zu verstehen, ein ungefähres Grundverständnis ist wie immer völlig ausreichend ;)

Gliederung

- Diverse Definitionen
- ReSizeGLScene
- InitGL
- DrawGLScene
- KillGLWindow
- CreateGLWindow
- WindProc
- WinMain
- Download des Arbeitsbereiches

Nachdem wir in der vorherigen Lektion die Vorbereitungen getroffen haben, geht es jetzt in den Code:

#include <windows.h>  // diverse Windowsfunktionen
#include <gl\glu.h>   // Damit kann Glu32 genutzt werden.
#include <gl\gl.h>    // Damit kann OpenGL32 genutzt werden.
#include <gl\glaux.h> // Und das Gleiche nochmal für Glaux  

Diese 4 Headerdateien ermöglichen die Benutzung einiger Windowsfunktionen und natürlich der OpenGL-Bibliotheken.

Hier werden die verwendeten Variablen/Instanzen deklariert. Da wir "nur" ein schwarzes Fenster erzeugen wollen, reichen diese wenigen schon aus. Bevor mit OpenGL unter Windows irgendetwas angefangen werden kann, müssen eine Reihe von Initialisierungen durchgeführt werden. Jede Windows-OpenGL-Anwendung muß ihren Rendering Context ( OpenGL-Seite ) mit seinem Device Context ( Windows-Seite ) verbinden. hWnd verwaltet die Anwendung.

HGLRC hRC=NULL;         // Der OpenGL Darstellungs Kontext (RC)
HDC hDC=NULL;           // GDI Geräte Kontext (DC)
HWND hWnd=NULL;         // Windows Handle
HINSTANCE hInstance;	// Die Instanz der Anwendung
bool keys[256]; // Vektor (Array) der den Status 
                // einzelner Tasten enthält    
                // (gedrückt/nicht gedrückt)

bool active=TRUE;  // Wenn active FALSE ist, wurde das 
                   // Fenster vom Benutzer minimiert.

bool fullscreen=TRUE; // Läuft das Programm im Vollbildmodus 
                      // oder nicht?

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);    
// WndProc wird deklariert

Da WndProc später von CreateGLWindow() benutzt wird, muss hier diese Prozedur schon vorab deklariert worden sein. (Wird später noch ausführlicher erklärt.)

GLvoid ReSizeGLScene(GLsizei width, GLsizei height) 
// Initialisierung des OpenGL-Fensters
{
  if (height==0)
  {      
    height=1;    
  }
// height darf nicht 0 sein, damit es im späteren
// Programmablauf nicht zu einer Division durch 0 kommt. glViewport(0, 0, width, height);
// Hier wird der Mittelpunkt auf den die Perspektive zuläuft // zurückgesetzt.
  glMatrixMode(GL_PROJECTION); 
  // Hier wird die Projektionsmatrix festgelegt
   
  glLoadIdentity(); 
  // und angepasst
  gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f); 
  // Hier wird die das Verhältnis der Höhe zur Breite übergeben
  // und der Verzerrungswinkel von 45 Grad übergeben
  glMatrixMode(GL_MODELVIEW); 
  // Hier wird die sogenannte modelview-Matrix festgelegt
  
  glLoadIdentity(); 
  // und angepasst.
}

Wozu das Ganze? Da der Benutzer während das Programm läuft, auch die Größe des Fensters verändern darf, muss die dargestellte Szene an diese Veränderung angepasst werden. (Als ob man das gleiche Fernsehbild auf zwei verschieden großen Schirmen darstellen und dabei die jeweiligen Proportionen erhalten wollte.)
Die Routine wird mindestens einmal aufgerufen und jedesmal wenn der Benutzer meint die Fenstergröße ändern zu müssen (Was natürlich sein gutes Recht sein sollte!).

Damit die dargestellten Objekte auch räumlich wirken, muss eine Projektiuonsmatrix erstellt werden. (Objekte die dem Betrachter näher sind erscheinen größer als andere die "tiefer" im Bildschirm zu sein scheinen. 0.1f und 100.0f sind die Grenzen dieses Raumes in der tiefe, Objekte die weiter weg oder näher dran sind vom Betrachter werden nicht mehr dargellt. 45.0f gibt den Verzerrungswinkel an mit die Objekte im Raum platziert werden. (Einfach mal ein wenig mit herrumexperimentieren, läßt sich schlecht erklären.) In der modelview-Matrix sind einige Objektinformationen gespeichert, das dürfte später alles wesentlich verständlicher werden, keine Angst ;)

In den nächsten Zeilen werden einige Initialisierungen für OpenGL vorgenommen. Der zurückgegebene Wert (true ode false) gibt Auskunft über den Erfolg der Initialisierung oder das Auftreten eines Fehlers.

int InitGL(GLvoid)
{
glShadeModel(GL_SMOOTH); // Das Smooth Shading wird aktiviert, das // sehr schöne Farbübergänge auf Körpern ermöglicht.

Hier werden die Farben für den Hintergrund festgelegt, die ersten drei Ziffern geben Auskunft über die RGB-Werte (Rot, Grün und Blau), der letzte Wert bestimmt den Alphafaktor, also die Transparenz des Hintergrundes. Wer nicht mit dem Farbsytem im allgemeinen und in OpenGL vertraut ist, kann diese Lücke HIER schließen.

  glClearColor(0.0f, 0.0f, 0.0f, 0.0f); 
  // In diesem Falle ein schwarzer Hintergrund
In den nächsten drei Zeilen wird der sogenannte depht buffer initialisiert, der dafür sorgt das Objekte weiter hinten Raum von anderen weiter vorn liegenden Objekten überdeckt werden können. (Ansonsten würden alle Objekte merkwürdig übereinanderkleben, was natürlich weder schön noch besonders realistisch wirkt.)
  glClearDepth(1.0f); 
  // depht buffer zurückgesetzt

  glEnable(GL_DEPTH_TEST); 
  // Aktivierung des depht Tests (dazu später mehr.)

  glDepthFunc(GL_LEQUAL); 
  // Der Typ des Depht Tests wird festgelegt
Als nächstes wird die Art der anzuzeigenden Perspektive festgelegt, (In diesem Falle GL_NICEST, allerdings sehr rechenintensiv...)
  glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); 
  // Perspektive wird festgelegt

True wird zurückgegen um der aufrufenden Prozedur zu signalisieren, das alles geklappt hat.

  return TRUE; // Initialisierung scheint geklappt zu haben!
}

Und hier wirds dann endlich spannend, alles was auf den Schirm soll, wird von hier aus gezeichnet. Auch DrawGLScene(GLvoid) wird wieder ein TRUE zurückgeben wenn alles geklappt hat.

int DrawGLScene(GLvoid) 
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 
  // Die vorherige Szene wird vom Bildschirm gelöscht, 
  // damit die neuen nicht einfach über die alten    
  // Objekte gezeichnet werden

  glLoadIdentity(); 
  // modelview-Matrix wird wieder einmal zurückgesetzt

  return TRUE; // Alles hat geklappt
}

KillGLWindow() wird beim Beenden des Programmes aufgerufen um das Fenster ordentlich schließen zu können und allen besetzten Speicherplatz wieder frei zu geben. Es wurden viele Fehlerkontrollen eingebaut, die wenigstens anzeigen warum irgendetwas nicht so geklappt hat wie beabsichtigt.

GLvoid KillGLWindow(GLvoid)
{

Falls das Programm im Vollbildmodus läuft, muss bei einigen Grafikkarten zuerst wieder zum Desktop zurückgeschaltet werden um Abstürzen vorzubeugen.

  if (fullscreen) // Kontrolle auf Vollbildmodus
  {
ChangeDisplaySettings(NULL,0); // Zurück zum Desktop ShowCursor(TRUE); // Der abgeschaltete Mauszeiger // wird wieder angezeigt (Nicht // vergessen ;) }

ChangeDisplaySettings(NULL,0) schaltet wieder in den Ausgangsmodus zurück. Die beiden Null-Werte veranlassen Windows, auf die in der Regestry gespeicherten Standardwerte.

Falls ein Rendering Context vorhanden ist, wird dieser gelöscht.

  if (hRC) // Rendering Context (RC) vorhanden?
  {
    if (!wglMakeCurrent(NULL,NULL)) // Kann der DC und RC überhaupt 
                                    // gelöscht werden?
    {
      MessageBox(NULL,"Entfernen des DC und RC 
                       fehlgeschlagen.","Fehler",
                       MB_OK | MB_ICONINFORMATION);
    }
   
    if (!wglDeleteContext(hRC)) // Kann der RC gelöscht werden?
    {
      MessageBox(NULL,"Entfernen des RC 
                       fehlgeschlagen.","Fehler...",
                       MB_OK | MB_ICONINFORMATION);
    }
      hRC=NULL; // Der RC wird NULL gesetzt, also entfernt
    }
   if (hDC && !ReleaseDC(hWnd,hDC)) 
   // Kann der Device Context (DC) freigegeben werden?
   {
     MessageBox(NULL,"Freigabe des 
                Device Context fehlgeschlagen.","
                Fehler",MB_OK | MB_ICONINFORMATION);

     hDC=NULL; // Der DC wird entfernt
   }
   if (hWnd && !DestroyWindow(hWnd)) 
   // Kann das Programmfenster geschlossen werden?
   {
     MessageBox(NULL,"Konnte hWnd nicht löschen.","
                SHUTDOWN ERROR",
                MB_OK | MB_ICONINFORMATION);

     hWnd=NULL; // Setze den hWnd auf NULL
   }
Jetzt muss die Registrierung der Fensterklasse noch rückgängihg gemacht werden, damit das Programm ohne Probleme aufs neue gestartet werden kann
   if (!UnregisterClass("OpenGL",hInstance)) 
   // Kann die Registrierung rückgängig gemacht werden?

   {
     MessageBox(NULL,"Konnte Klasse nicht 
                      entfernen.","SHUTDOWN ERROR",
                      MB_OK | MB_ICONINFORMATION);

     hInstance=NULL; // Setze hInstance auf NULL
   }
 }
Als nächstes soll das Opengl-Fenster geöffnet werden, dies soll wahlweise im Fenster- oder Vollbildmodus passieren können.

Die CreateGLWindow() Prozedur gibt einen BOOL-Wet zurück, falls ein Problem auftritt wird FALSE zurückgegeben. Zum ausführen wird der Titel, die Höhe, die Breite, die bits die pro Pixel verwendet werden können (Farbtiefe) und ob das Fenster im Vollbild oder Fenster ausgeführt werden soll, übergeben.

BOOL CreateGLWindow(char* title, int width, int height, 
                    int bits, bool fullscreenflag)
{
Hier muss herrausgefunden werden welches Pixelformat Windows unterstützt, also wieviele bitx pro Pixel möglich sind. (Bei ca 65000 Farben z.B. 16 Bit pro Pixel)
  GLuint PixelFormat; // Speichert das Pixelformat

Die Instanz der Fensterklasse (wc) enthält Informationen über die Eigenschaften und Verhaltensweisen eines geöffneten Fensters. Jedes geöffnete Fenster gehört zu einer Fensterklasse, welches vorher registriert werden muss.

  WNDCLASS wc; // wc wird eine Instanz der Fensterklasse

Die nächsten beiden Variablen enthalten Informationen über das Programmfenster im Vollbild- und Fenstermodus.

  DWORD dwExStyle; // weitere Informationen
  DWORD dwStyle; // Fensterinformationen

Hier werden die Abmessungen des Programfensters ermittelt und in dem Rechteck Windowrect gespeichert.

  RECT WindowRect; 
  // Speicher für aktuelle Auflösung
  
  WindowRect.left=(long)0; 
  // Die linke Seite des Rechtecks wirtd auf 0 gesetzt

  WindowRect.right=(long)width; 
  // Hier wird die gewünschte Breite des Fensters gespeichert

  WindowRect.top=(long)0; 
  // Die obere Seite wird auch auf 0 gesetzt

  WindowRect.bottom=(long)height; 
  // Und hier wird die Höhe abgelegt

fullscreen=fullscreenflag; // Hier wird fullscreen // auf den Wert von fullscreenflag // gesetzt, welches ja übergeben wurde

hInstance = GetModuleHandle(NULL); // Die Instanz des Programmes bekommt ein // Handle zugeordnet

Der Instanz der Fensterklasse müssen auch noch einige Werte zugeordnet werden

  wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; 
  // Bei Veränderungen in der Höhe und/oder Breite, 
  // soll ne gezeichnet werden

  wc.lpfnWndProc = (WNDPROC) WndProc; 
  // WndProc behandelt die auftretenden Nachrichten

  wc.cbClsExtra = 0; // Wird nicht benötigt
  wc.cbWndExtra = 0; // und das auch nicht
  wc.hInstance = hInstance; // Die Instanz wird festgelegt
  wc.hIcon = LoadIcon(NULL, IDI_WINLOGO); 
  // Lädt das Standardsymbol
  wc.hCursor = LoadCursor(NULL, IDC_ARROW); 
  // Lädt einen Cursor
  wc.hbrBackground = NULL; 
  // Es soll kein bestimmter Hintergrund angezeigt werden
  wc.lpszMenuName = NULL; // Auch ein Menü wird nicht benötigt.
  wc.lpszClassName = "OpenGL"; // OpenGL wird der Name der Klasse

Jetzt wird die Klasse registriert, falls dabei irgendein Fehler auftreten solltem wird die Prozedur mit einer Fehlermeldung abgebrochen

  if (!RegisterClass(&wc)) // Versuch die Klasse zu registrieren
  {
    MessageBox(NULL,"Konnte die Fensterklasse nicht registrieren.",
               "ERROR",MB_OK|MB_ICONEXCLAMATION);
    return FALSE; // FALSE zurückgeben und beenden
  }

Falls das Fenster im Vollildmodus laufen soll, werden die folgenden Zeilen ausgeführt.

  if (fullscreen) // Soll im Vollbildmodus gestartet werden
  {

Jetzt wird in den Vollbildmodus geschaltet, dabei ist es wichtig, dieser die gleichen Abmessungen wie das Programmfenster hat, auf in das dann gezeochnet werden soll.

    DEVMODE dmScreenSettings; 
    // Instanz von DEVMODE wird erzeugt

    memset(&dmScreenSettings,0,sizeof(dmScreenSettings)); 
    // Diese wird geleert

    dmScreenSettings.dmSize=sizeof(dmScreenSettings); 
    // dmsize soll genauso groß wie die dmScreenSettings sein

    dmScreenSettings.dmPelsWidth = width; 
    // Die drei Werte (height, width und bits) 
    // wurden der Prozedur übergeben und werden 
    // nun in dmScreenSettings gespeichert

    dmScreenSettings.dmPelsHeight = height;
    dmScreenSettings.dmBitsPerPel = bits; 
    dmScreenSettings.dmFields=DM_BITSPERPEL|
    DM_PELSWIDTH|DM_PELSHEIGHT; 
    // Hier werden die drei Werte in einem Ausdruck gespeichert
Und nun wird versucht den Vollbildmodus mit den neuen Werten auszugeben.
    if (ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)
        !=DISP_CHANGE_SUCCESSFUL)    
    {

    // CDS_FULLSCREEN blendet zusätzlich die Startleiste aus

Falls das nicht klappen sollte, wird nachgefragt, ob stattdessen der Fenstermodus gestartet oder beendet werden soll

      if (MessageBox(NULL,"Der gewünschte Vollbildmodus 
                           wird nicht unterstützt, soll
                           stattdessen im Fenstermodus
                           ausgegeben werden?","OpenGL"
                           ,MB_YESNO|MB_ICONEXCLAMATION)==IDYES)
      {
        fullscreen=FALSE; 
        // Der Benutzer möchte im Fenster weitermachen, 
        // dazu wird fullscreen auf FALSE gesetzt
      }

      else
      {
        return FALSE;
        // Falls der Benutzer das Programm aus gegebenen 
        // Anlass beenden will, wird FALSE zurückgegeben.
      }
    }
  }
  if (fullscreen) 
  // Konnte in den Vollbildmodus geschaltet werden? 
  // (Wenn nicht, wird ja im Fenster weitergemacht!)
  {


Für den Vollbildmodus sind die folgenden Fenstereigenschaften die besten.

    dwExStyle=WS_EX_APPWINDOW; // Fenstereigenschaften
    dwStyle=WS_POPUP; // -"-
    ShowCursor(FALSE); // Der Mauszeiger wird nicht angezeigt
  }

  else
  {

Falls der Fenstermodus aktiv ist, werden die folgenden Fenstereigenschaften festgelegt werden:

    dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; 
    // Das Fenster soll zusätzlich einen 3D Rahmen bekommen

    dwStyle=WS_OVERLAPPEDWINDOW; 
    // Ein typisches Windowsfenster mit 
    // Minimieren, Maximieren, etc
  }


Der nächste Befehl stattet das jeweilig Fenster mit den oben gewählten Eigenschaften aus, damit wird das Verdecken der eigentlichen Grafikausgabe durch Schaltflächen verhindert.

  AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle); 
  // Fenster wird angepasst

CreateWindowEx() ist dafür zuständig das das Fenster angezeigt wird, wir müssen jetzt einige Werte übergeben. Der Klassenname "OpenGL" muss der gleiche wie in wc.lpszClassName sein.

  if (!(hWnd=CreateWindowEx( dwExStyle, 
  // Die erweiterten Eigenschaften des    Fensters
  "OpenGL", // Der Name der Klasse
  title, // Der Titel des Fensters
  WS_CLIPSIBLINGS | // Wird von OpenGL benötigt
  WS_CLIPCHILDREN | // Wird auch von OpenGL benötigt
  dwStyle, // auch Eigenschaften des Fensters
  0, 0, // Die Position des zu erstellenden Fensters
  WindowRect.right-WindowRect.left, 
  // Hier werden die ermittelten Werte für die Breite eingesetzt
  WindowRect.bottom-WindowRect.top, // und hier für die Länge
  NULL, // Es soll kein übergordnetes Fendster erstellt werden
  NULL, // kein Menü
  hInstance, // Die Instanz wird übergeben
  NULL))) // Wird nicht benötigt

Falls das nicht geklappt haben sollte wird das gerade erstellte Fenster wieder entfernt, es kommt eine Fehlermeldung und die Prozedur wird beendet.

  {
    KillGLWindow(); // Grafikeinstellungen zurücksetzen
    MessageBox(NULL,"Fenster konnte nicht erstellt werden.",
               "ERROR",MB_OK|MB_ICONEXCLAMATION);
    return FALSE; 
  }


Die nächsten Zeilen beschreiben das Pixelformat, die meisten Features werden aber jetzt noch nicht benutzt

  static PIXELFORMATDESCRIPTOR pfd= 
  // pdf ist jetzt ein PIXELFORMATDESCRIPTOR
  {
    sizeof(PIXELFORMATDESCRIPTOR), 
    // Die größe muss natürlich stimmen
    1, // Versionsnummer
    PFD_DRAW_TO_WINDOW | 
    // Das Format muss in Fenster sichtbar sein können
    PFD_SUPPORT_OPENGL | 
    // OpenGL muss unterstützt werden
    PFD_DOUBLEBUFFER, 
    // Double Buffering muss unterstützt werden
    PFD_TYPE_RGBA, 
    // Das RGBA (Rot,Grün,Blau,Alpha(Transparenz)) 
    // muss unterstützt werden
    bits, 
    // Die Farbtiefe, die schon 
    // übergeben wurde, wird hier benötigt
    0, 0, 0, 0, 0, 0, // wird nicht benötigt
    0, // kein Alpha Buffer
    0, // Shift Bit ignoriert
    0, // kein Accumulation Buffer
    0, 0, 0, 0, // nicht benötigt
    16, // 16Bit Z-Buffer (Depth Buffer)
    0, // kein Stencil Buffer
    0, // kein Auxiliary Buffer
    PFD_MAIN_PLANE, 
    // Die Hauptebene auf die später gezeichnet wird
    0, // unwichtig
    0, 0, 0 // keine Ebenenmasken benötigt
  };

Jetzt wird versucht den Device Context zu bekommen

 if (!(hDC=GetDC(hWnd))) // Versuch, den DC zu bekommen
   {
     KillGLWindow(); 
     // Alles rückgängig machen
     MessageBox(NULL,"Konnte keinen DC erstellen.",
     "ERROR",MB_OK|MB_ICONEXCLAMATION);
     return FALSE; // FALSE zurückgeben, beenden
   }

Jetzt muss noch ein Pixelformat gefunden werden, das zu dem oben deklarierten passt. Falls sich keins findet, Fehlerpopup, Rückgängig machen und raus...

  if (!(PixelFormat=ChoosePixelFormat(hDC,&pfd))) 
  // Kann Windows ein passendes finden? 
  { 
	// Falls keins gefunden werden kann:
    KillGLWindow(); // Alles zurücksetzen
    MessageBox(NULL,"Konnte kein passendes Pixelformat finden."
                     ,"ERROR",MB_OK|MB_ICONEXCLAMATION);
    return FALSE; // FALSE zurück und Ende.
  }

Falls ein passendes gefunden wurde, wird es jetzt getestet, treten dabei Fehler auf...das gleiche wie immer.

  if(!SetPixelFormat(hDC,PixelFormat,&pfd))
  // Kann das Pixelformat gesetzt werden?
    {
      KillGLWindow(); // Leider nicht, Fehlerpopup und raus
      MessageBox(NULL,"Konnte Pixelformat nicht setzen.",
      "ERROR",MB_OK|MB_ICONEXCLAMATION);
      return FALSE; // FALSE zurück und raus
   }

Jetzt fehlt noch der Rendering Context.

  if (!(hRC=wglCreateContext(hDC))) // Versuch den RC zu bekommen
    {
      KillGLWindow(); // Alles rückgängig machen
      MessageBox(NULL,"Konnte keinen Rendering Context bekommen.",
      "Fehler",MB_OK|MB_ICONEXCLAMATION);
      return FALSE;
    }


Und dieser muss jetzt aktiviert werden

  if(!wglMakeCurrent(hDC,hRC)) // Versuch den RC zu aktivieren
  {
    KillGLWindow(); // hat nicht geklappt, also alles zurück
    MessageBox(NULL,"Konnte den Rendering Context nmicht aktivieren.",
                    "Fehler",MB_OK|MB_ICONEXCLAMATION);
    return FALSE;
  }

Es hat alles geklappt, also wird das Fenster jetzt angezeigt.

  ShowWindow(hWnd,SW_SHOW); // Fenster anzeigen
  SetForegroundWindow(hWnd); // Priorität des Programms wird erhöht
  SetFocus(hWnd); // Tastatureingaben werden 
                  // jetzt an das Programm geleitet

  ReSizeGLScene(width, height); // Die Perspektive wird aktiviert

InitGL muss aufgerufen werden, damit jetzt die Objekte, Lichter, Texturen etc. geladen werden können. Auch hier wird wieder auf Fehler überprüft.

  if (!InitGL()) // Initialisiere das OpenGL Fenster
  {
    KillGLWindow(); // Falls das nicht geklappt 
                    // haben sollte alles rückgängig machen

    MessageBox(NULL,"Initialisierung fehlgeschlagen.",
               "Fehler",MB_OK|MB_ICONEXCLAMATION);
    return FALSE; // FALSE wird zurückgegeben
  }
  return TRUE; // Alles hat geklappt!!!
   
}

LRESULT CALLBACK WndProc() definert wie das Programm mit Nachrichten (Messages) vom Betriebssytem (z.B. F1 Taste wird gedrückt) umzugehen hat.

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, 
                         WPARAM wParam, LPARAM lParam)
{

In uMsg werden die ankommenden Nachrichten gespeichert

  switch (uMsg) // Sind Nachrichten in der Nachrichtenschleife?
  {

Hat uMsg den Wert WM_ACTIVE und ist dieser TRUE, dann ist das Programm im Moment nicht minimiert, läuft also.

    case WM_ACTIVATE: // Ist das Programm aktiv?
    {
      if (!HIWORD(wParam)) // Ist das Programm nicht minimiert?
      {
        active=TRUE; // active wird TRUE
      }

     else
     {
       active=FALSE; // Programm ist minimiert
     }   
  
     return 0; // Rückgabe: 0
   }


Hier soll verhindert werden, das der Monitor in den Stromsparmodus geht, oder ein Bildschirmschoner startet

    case WM_SYSCOMMAND: // Ist ein Systemkommando 
                        // (wie z.B. "Bildschirmschoner 
                        // soll gestartet werden") vorhanden?
    {
      switch (wParam) // wParam würde diesen Befehl enthalten
      {
        case SC_SCREENSAVE: 
        // Versucht Windows den Bildschirmschoner zu starten

        case SC_MONITORPOWER: 
        // Soll der Monitor in den Stromsparmodus gehen?
        
        return 0; 
        // Beide Fälle werden durch die Rückgabe von 0 verhindert
      }
      break; // Das wars.
    }

Wenn uMsg die Nachricht WM_CLOSE enthält, soll das Fenster geschlossen werden.

    case WM_CLOSE: // Ist eine WM_CLOSE Nachricht vorhanden?
    {
      PostQuitMessage(0); 
      // Die Nachricht zum Beenden wird "gepostet"

      return 0; // und zurück.
    }

Wird eine Taste gedrückt, wird WM_KEYDOWN true, wParam enthält dann den konkreten Wert dieser Taste

    case WM_KEYDOWN: // Drückt der Benutzer eine Taste???
    {
      keys[wParam] = TRUE; 
      // Der Wert im Array keys[] der dem Code 
      // der Taste entspricht, wird true gesetzt

      return 0; // und zurück...
    }

Wenn diese Taste jezt wieder losgelassen wird, macht sich das an WM_KEYUP bemerkbar.

    case WM_KEYUP: // Wurde eine Taste losgelassen?
    {
      keys[wParam] = FALSE; 
      // Wen ja, dann soll dieser Wert im Array keys[] 
      // auf FALSE gesetzt werden
      return 0; // und zurück.
    }

Sollte der Benutzer die Größe des Fensters ändern, wird WM_SIZE wahr, LOWORD und HIWORD speichern neuen Abmessungen des Fensters.

    case WM_SIZE: // Die Fenstergröße wurde geändert
    {
      ReSizeGLScene(LOWORD(lParam),HIWORD(lParam)); 
      // LoWord ist die Breite, HiWord die Höhe, 
      // ReSizeGLScene() verändert dann die 
      // Größe des OpenGL-Fensters
      return 0; // und zurück
    }
  }

Alle nicht verarbeiteten Nachrichten werden an DefWindowProc zurückgegeben.

  return DefWindowProc(hWnd,uMsg,wParam,lParam);
}

WinMain ist das Hauptprogramm einer jeden Win32-Anwendung, von hier aus werden die meisten Proceduren aufgerufen und die Benutzereingaben entgegengenommen.

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
                   LPSTR lpCmdLine, int nCmdShow)
{

msg überprüft, ob Nachrichten warten, wenn ja, ist msg TRUE
Sobald done TRUE wird, soll das Programm beendet werden.

  MSG msg;
  BOOL done=FALSE;

Beim Start wird der Benutzer gefragt, ob er das Programm im Vollbild oder Fenstermodus ausführen will.

  if (MessageBox(NULL,"Soll im Vollbildmodus gestartet werden?",
      "Vollbilmodus gewünscht?",MB_YESNO|MB_ICONQUESTION)==IDNO)
  {
    fullscreen=FALSE; // Falls nein gedrückt wurde, 
                      // wird fullscreen false gesetzt
  }

Hier wird die Prozedur zum Erstellen des OpenGL Fensters aufgerufen

  if (!CreateGLWindow("Opengl",640,480,16,fullscreen))
  {
    return 0; // Falls ein Fehler auftrat, beenden
  }

Alle Initialisierungen wurden vorgenommen, jetzt starter die Programmschleife und läuft solange "done" FALSE ist

  while(!done) // Solange done nicht TRUE ist:
  {

Zuerst müssen wir mit PeekMessage() überprüfen, ob Nachrichten vorliegen. (Das häufig verwendete GetMessage() hat viele Nachteile...)

    if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) 
    // Sind Nachrichten vorhanden
    {
    if (msg.message==WM_QUIT) 
    // Liegt eine Nachricht zum beenden vpr?
    {
      done=TRUE; // Wenn dem so ist, wird done 
                 // true und das Programm beendet
    }

    else 
    // Wenn nicht werden die anderen Nachrichten ausgewertet

    {
      TranslateMessage(&msg); // Umformen der Nachricht
      DispatchMessage(&msg); 
    }
  }

  else // Falls keine Nachrichten bereit liegen
  {

Wenn keine Nachrichten vorliegen wird die aktuelle Szene ausgegeben. Außerdem wird überprüft ob das Programm noch aktiv ist und ob der Benutzer die ESCAPE Taste gedrückt hat.

    if (active) // Programm aktiv?
    {
      if (keys[VK_ESCAPE]) // Wurde ESC gedrückt?
      {
        done=TRUE; // Wenn ESC gedrückt wurde, beenden
      }

      else // ESC wurde nicht gedrückt
      {

Jetzt wird die Szene gerendert (Die Bildinformationen werden in konkrete Pixel auf dem Bildschirm umgerechnet). Außerdem werden die Bildpuffer ausgetauscht (double buffering), das Bild also ausgeben.

        DrawGLScene(); // Die Szene ausgeben
        SwapBuffers(hDC); // Die Puffer werden getauscht
      }
    }

Durch das Drücken von F1 soll vom Vollbild in die Fensterdarstellung umgeschaltet werden, oder vom Fenster ins Vollbild.

    if (keys[VK_F1]) // Wird F1 gedrückt?
    {
      keys[VK_F1]=FALSE; 
      // Es darf nicht gewartet werden bis F1 losgelassen wird,
      // ansonsten könnte das Bild mehrmals hin und herschalten

      KillGLWindow(); // Das aktuelle Fenster wird gelöscht
      fullscreen=!fullscreen; 
      // fullscreen erhält seinen entgegengesetzten Wert
      // (bei FALSE TRUE, und bei TRUE FALSE.)
      if (!CreateGLWindow("Lektion 1",640,480,16,fullscreen))    
      {
        return 0; // Zurück falls ein Fehler auftrat
      }
    }
  }
}

Falls done TRUE ist, wird der folgende Code ausgeführt:

  KillGLWindow(); // Das Fenster löschen.
  return (msg.wParam); // Das Programm verlassen.
}
 

Damit wäre der unspannende Teil abgeschlossen! Und hier ist der versprochene Quelltext in gepackter Form: opengl_tut1.zip
In der nächsten Lektion kommt "sichtbare Grafik" ins Spiel