www.codeworx.org/opengl-tutorials/Tutorial 28: Bezier-Kurven und Flächen

Tutorial 28: Bezier-Kurven und Flächen

Engl. Orginalfassung: David Nikdel ( ogapo@ithink.net )
Ins Deutsche übersetzt: Hans-Jakob Schwer ( webmaster@codeworx.org )

Dieses Tutorial gibt im ersten Teil eine kurze Einführung in Bezier-Kurven, danach wird es um die eigentlich interessanteren Bezier-Flächen ( 3D :) gehen. Dabei wird es nicht allzu kompliziert werden, es geht vorallem darum die Möglichkeiten von Bezier-Kurven an einem Beispieleffekt zu demonstrieren.

Ein wenig Mathe (...)

Bezier-Kurven (-Oberflächen) ohne ihre mathematische Grundlagen zu verstehen ist kaum möglich, daher dieser kurze Exkurs. Sicherlich wird jeder Grafik-Interessierte schon irgendwo Bezier-Kurven gesehen haben, sei es in Vektorprogrammen oder Flash-Spielereien. Irgendwie sowas also:

Dies ist solch eine Bezier-Kurve, nur definiert durch 4 Punkte in einem 2-dimensionalen Koordinatensystem. Zwei davon begrenzen die Kurve links und rechts, die anderen beiden dienen zur Manipulatiomn der Krümmung.

Die einfachste Bezier-Kurve, im Grunde genommen eine Gerade, wird durch die folgende Gleichung beschrieben:

t + (1 - t) = 1 

In der Algebra werden solche Gebilde als Polynomen ersten Grades bezeichnet, da die höchste Potenz über den Argumenten 1 ist ( t und t^1 sind identisch, also könnte man auch t^1 + (1 - t^1) = 1 schreiben). Jetzt wird die Gleichung um zwei Grade erhöht, auf beiden Seiten:

(t + (1-t))^3 = 1^3 

Umgeformt ergäbe das: ( 1^1 = 1^3 ...)

t^3 + 3*t^2*(1-t) + 3*t*(1-t)^2 + (1-t)^3 = 1 

Diese Gleichung wird sehr oft genutzt um Bezier-Kurven (3. Grades) zu berechnen, aus zwei Gründen:
1. Die Kurve muss nicht zwingend in einer Ebene liegen, hat aber trotzdem einen recht geringen Grad und ist damit einfach und "ressourcensparend" benutzbar.
2. Die beiden Tangenten können völlg unabhängig von einander angelegt werden, da sie jeweils einen eigenen Start- und Endpunkt haben.

Alles was auf der rechten Seite eingesetzt wird, muss 1 ergeben damit die Gleichung erfüllt wird. Aber wie werden jetzt die Punkte entlang der Kurve berechnet? Für t müssen Werte eingesetzt werden die größer gleich 0 sind und kleiner gleich 1 sind. Um jetzt die 4 Kontrollpunkte ins Spiel zu bringen müssen die Einzelterme innerhalb der Gleichung noch mit diesen multipliziert werden, nach diesem Schema: (Die Punkte werden durch P1 bis P4 repräsentiert)

P1*t^3 + P2*3*t^2*(1-t) + P3*3*t*(1-t)^2 + P4*(1-t)^3 = Pnew

Da Polynome stetig sind, also nicht unterbrochen werden, wird eine solche Kurve immer von P1 nach P4 laufen (P1 bei t = 0 und P4 bei t = 1).

Das zum mathematischen Hintergrund der Bezier-Kurven, Ziel soll es aber sein eine 3D-Fläche zu erzeugen.

Bezier-Flächen

Um eine Bezier-Fläche darzustellen bedarf es 16 Kontrollpunkten (4*4) und, zusätzlich zu t, einer weiteren Variable v. Auch v darf nur Werte wischen 0 und 1 enthalten. Dabei ist es sicherlich sinnvoll v zuerst 0 zu setzen, in bestimmten Abständen dann Punkte für t zwischen 0 und 1 zu erzeugen um danach t schrittweise zu erhöhen bis beide 1 werden.



Aber nun zum Code:

   
#include <windows.h> // Header File For Windows #include <math.h> // Header File For Math Library Routines #include <stdio.h> // Header File For Standard I/O Routines #include <stdlib.h> // Header File For Standard Library #include <gl\gl.h> // Header File For The OpenGL32 Library #include <gl\glu.h> // Header File For The GLu32 Library #include <gl\glaux.h> // Header File For The Glaux Library
typedef struct point_3d {   // Structure For A 3-Dimensional Point ( NEW )
   double x, y, z;
   } POINT_3D;
typedef struct bpatch {     // Structure For A 3rd Degree Bezier Patch ( NEW )
   POINT_3Danchors[4][4];   // 4x4 Grid Of Anchor Points
   GLuintdlBPatch;          // Display List For Bezier Patch
   GLuinttexture;           // Texture For The Patch
   } BEZIER_PATCH;
HDChDC=NULL;                // Private GDI Device Context
   HGLRChRC=NULL;           // Permanent Rendering Context
   HWNDhWnd=NULL;           // Holds Our Window Handle
   HINSTANCEhInstance;      // Holds The Instance Of The Application
DEVMODEDMsaved;             // Saves The Previous Screen Settings ( NEW )
boolkeys[256];              // Array Used For The Keyboard Routine
   boolactive=TRUE;         // Window Active Flag Set To TRUE By Default
   boolfullscreen=TRUE;     // Fullscreen Flag Set To Fullscreen Mode By Default
GLfloatrotz = 0.0f;         // Rotation About The Z Axis
   BEZIER_PATCHmybezier;    // The Bezier Patch We're Going To Use ( NEW )
   BOOLshowCPoints=TRUE;    // Toggles Displaying The Control Point Grid ( NEW )
   intdivs = 7;             // Number Of Intrapolations (Controls Poly Resolution) ( NEW )
LRESULTCALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);// Declaration For WndProc

Hier einige grundlegenden Funktionen um mit Vektoren umzugehen. Es gibt natürlich auch maßgeschneiderte C++-Klassen die ähnliches bieten:



   // Adds 2 Points. Don't Just Use '+' ;)
   POINT_3D pointAdd(POINT_3D p, POINT_3D q) 
   {
      p.x += q.x;p.y += q.y;p.z += q.z;
      return p;
   }
   // Multiplies A Point And A Constant. Don't Just Use '*'
   POINT_3D pointTimes(double c, POINT_3D p) 
   {
      p.x *= c;p.y *= c;p.z *= c;
      return p;
   }
   // Function For Quick Point Creation
   POINT_3D makePoint(double a, double b, double c) 
   {
      POINT_3D p;
      p.x = a;p.y = b;p.z = c;
      return p;
   }

Hier jetzt der Kern des Ganzen, die Gleichung um die Punkte auf der Fläche zu berechnen. Übergeben wird u und ein Array aus 4 Punkten:

// Calculates 3rd Degree Polynomial Based On Array Of 4 Points
// And A Single Variable (u) Which Is Generally Between 0 And 1

POINT_3D Bernstein(float u, POINT_3D *p) 
{
   POINT_3Da, b, c, d, r;
   a = pointTimes(pow(u,3), p[0]);
   b = pointTimes(3*pow(u,2)*(1-u), p[1]);
   c = pointTimes(3*u*pow((1-u),2), p[2]);
   d = pointTimes(pow((1-u),3), p[3]);
   r = pointAdd(pointAdd(a, b), pointAdd(c, d));
   return r;
}

Der Löwenanteil wird von dieser Gleichung berechnet. Erzeugt wird ein triangle strip, also eine Ansammlung von aneinandergepappten Dreiecken, der aus Performance-Gründen in einer display list gespeichert werden. Die Fläche muss daher auch nicht in jedem Frame neu berechnet werden, nur wenn es Veränderungen gibt. Man könnte jetzt zum Beispiel die Kontrollpunkte bewegen um einen sehr netten organischen Effekt zu erreichen.

Das Array "last" speichert die Punkte des letzten Durchgangs, da triangle strips auch diese Werte brauchen. Die Texturkoordinaten u und v werden ebenfalls jedesmal neu berechnet.

Die Normalen werden nicht generiert, da diese vorallem für die Beleuchtung notwendig sind, es in diesem Tut aber eher nicht um Beleuchtungseffekte gehen soll.

   
// Generates A Display List Based On The Data In The Patch
// And The Number Of Divisions

GLuint genBezier(BEZIER_PATCH patch, int divs) 
{
   intu = 0, v;
   floatpy, px, pyold;
   GLuintdrawlist = glGenLists(1);// Make The Display List
   POINT_3Dtemp[4];
   POINT_3D*last = (POINT_3D*)malloc(sizeof(POINT_3D)*(divs+1));
   // Array Of Points To Mark The First Line Of Polys
   if (patch.dlBPatch != NULL)// Get Rid Of Any Old Display Lists
   glDeleteLists(patch.dlBPatch, 1);
   temp[0] = patch.anchors[0][3];// The First Derived Curve (Along X-Axis)
   temp[1] = patch.anchors[1][3];
   temp[2] = patch.anchors[2][3];
   temp[3] = patch.anchors[3][3];
   for (v=0;v<=divs;v++) 
   {  // Create The First Line Of Points
      px = ((float)v)/((float)divs);// Percent Along Y-Axis
      // Use The 4 Points From The Derived Curve To Calculate The Points Along That    Curve
      last[v] = Bernstein(px, temp);
   }
   glNewList(drawlist, GL_COMPILE);// Start A New Display List
      glBindTexture(GL_TEXTURE_2D, patch.texture);// Bind The Texture
      for (u=1;u<=divs;u++) 
      {
         py = ((float)u)/((float)divs);// Percent Along Y-Axis
         pyold = ((float)u-1.0f)/((float)divs);// Percent Along Old Y Axis
         temp[0] = Bernstein(py, patch.anchors[0]);// Calculate New Bezier Points
         temp[1] = Bernstein(py, patch.anchors[1]);
         temp[2] = Bernstein(py, patch.anchors[2]);
         temp[3] = Bernstein(py, patch.anchors[3]);
         glBegin(GL_TRIANGLE_STRIP);// Begin A New Triangle Strip
         for (v=0;v<=divs;v++) 
         {
            px = ((float)v)/((float)divs);// Percent Along The X-Axis
            glTexCoord2f(pyold, px);// Apply The Old Texture Coords
            glVertex3d(last[v].x, last[v].y, last[v].z);// Old Point
            last[v] = Bernstein(px, temp);// Generate New Point
            glTexCoord2f(py, px);// Apply The New Texture Coords
            glVertex3d(last[v].x, last[v].y, last[v].z);// New Point
         }

         glEnd();// END The Triangle Strip
      }
   glEndList();// END The List
   free(last);// Free The Old Vertices Array

   return drawlist;// Return The Display List
}


Hier werden die 4*4 Kontrollpunkte festgelegt, natürlich können diese beliebig verändert werden:

void initBezier(void) 
{
                 
   mybezier.anchors[0][0] = makePoint(-0.75,-0.75,-0.50);
   mybezier.anchors[0][1] = makePoint(-0.25,-0.75, 0.00);
   mybezier.anchors[0][2] = makePoint( 0.25,-0.75, 0.00);
   mybezier.anchors[0][3] = makePoint( 0.75,-0.75,-0.50);
   mybezier.anchors[1][0] = makePoint(-0.75,-0.25,-0.75);
   mybezier.anchors[1][1] = makePoint(-0.25,-0.25, 0.50);
   mybezier.anchors[1][2] = makePoint( 0.25,-0.25, 0.50);
   mybezier.anchors[1][3] = makePoint( 0.75,-0.25,-0.75);
   mybezier.anchors[2][0] = makePoint(-0.75, 0.25, 0.00);
   mybezier.anchors[2][1] = makePoint(-0.25, 0.25,-0.50);
   mybezier.anchors[2][2] = makePoint( 0.25, 0.25,-0.50);
   mybezier.anchors[2][3] = makePoint( 0.75, 0.25, 0.00);
   mybezier.anchors[3][0] = makePoint(-0.75, 0.75,-0.50);
   mybezier.anchors[3][1] = makePoint(-0.25, 0.75,-1.00);
   mybezier.anchors[3][2] = makePoint( 0.25, 0.75,-1.00);
   mybezier.anchors[3][3] = makePoint( 0.75, 0.75,-0.50);
   mybezier.dlBPatch = NULL;   // Go Ahead And Initialize This To NULL
}

Die optimierte Version der Funktion zum Laden von Bitmaps:

// Load Bitmaps And Convert To Textures
BOOL LoadGLTexture(GLuint *texPntr, char* name)
{
   BOOL success = FALSE;
   AUX_RGBImageRec *TextureImage = NULL;
   glGenTextures(1, texPntr);           // Generate 1 Texture
   FILE* test=NULL;
   TextureImage = NULL;
   test = fopen(name, "r");             // Test To See If The File Exists
   if (test != NULL) 
   {                                    // If It Does
      fclose(test);// Close The File
      TextureImage = auxDIBImageLoad(name);
      // And Load The Texture
   }
   if (TextureImage != NULL) 
   {          // If It Loaded
      success = TRUE;
      // Typical Texture Generation Using Data From The Bitmap
      glBindTexture(GL_TEXTURE_2D, *texPntr);
      glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage->sizeX, TextureImage->sizeY,    
                0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage->data);
      glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
      glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
   }
   if (TextureImage->data)
   free(TextureImage->data);
   return success;
}

Hier wird OpenGL und die Bezier-Fläche initialisiert:
int InitGL(GLvoid)// All Setup For OpenGL Goes Here
{
   glEnable(GL_TEXTURE_2D);// Enable Texture Mapping
   glShadeModel(GL_SMOOTH);// Enable Smooth Shading
   glClearColor(0.05f, 0.05f, 0.05f, 0.5f);// Black Background
   glClearDepth(1.0f);// Depth Buffer Setup
   glEnable(GL_DEPTH_TEST);// Enables Depth Testing
   glDepthFunc(GL_LEQUAL);// The Type Of Depth Testing To Do
   glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);// Really Nice Perspective    Calculations
   initBezier();// Initialize the Bezier's Control Grid ( NEW )
   LoadGLTexture(&(mybezier.texture), "./Data/NeHe.bmp");// Load    The Texture ( NEW )
   mybezier.dlBPatch = genBezier(mybezier, divs);// Generate The Patch ( NEW )
   return TRUE;// Initialization Went OK
}

Die Kontrollgeraden werden durch rote Striche in die fertige Szene eingezeichnet. Mit der Leertaste läßt sich das an- und ausschalten:

int DrawGLScene(GLvoid)  // Here's Where We Do All The Drawing
{
   int i, j;
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   // Clear Screen And Depth Buffer
   glLoadIdentity();// Reset The Current Modelview Matrix
   glTranslatef(0.0f,0.0f,-4.0f);// Move Left 1.5 Units And Into The Screen 6.0
   glRotatef(-75.0f,1.0f,0.0f,0.0f);
   glRotatef(rotz,0.0f,0.0f,1.0f);// Rotate The Triangle On The Z-Axis
   glCallList(mybezier.dlBPatch);// Call The Bezier's Display List
   // This Need Only Be Updated When The Patch Changes
   if (showCPoints) 
   {                // If Drawing The Grid Is Toggled On
      glDisable(GL_TEXTURE_2D);
      glColor3f(1.0f,0.0f,0.0f);
      for(i=0;i<4;i++) 
      {// Draw The Horizontal Lines
         glBegin(GL_LINE_STRIP);

            for(j=0;j<4;j++)
               glVertex3d(mybezier.anchors[i][j].x, 
                          mybezier.anchors[i][j].y, 
                          mybezier.anchors[i][j].z);

         glEnd();
      }

      for(i=0;i<4;i++) 
      {// Draw The Vertical Lines
         glBegin(GL_LINE_STRIP);
         for(j=0;j<4;j++)
            glVertex3d(mybezier.anchors[j][i].x, 
                       mybezier.anchors[j][i].y, 
                       mybezier.anchors[j][i].z);
         glEnd();
      }

      glColor3f(1.0f,1.0f,1.0f);
      glEnable(GL_TEXTURE_2D);
   }
   return TRUE;// Keep Going
}

Es wurden noch einige Modifikationen an KillGLWindow() und CreateGLWindow() vorgenommen um ein Problem zu beheben das bei bestimmten älteren Grafikkarten auftritt. (Die Änderungen müssen allerdings nicht vorgenommen, da sie für dieses Tutorial eigentlich nicht relevant sind.

GLvoid KillGLWindow(GLvoid)// Properly Kill The Window
{
   if (fullscreen)// Are We In Fullscreen Mode?
   {
      if (!ChangeDisplaySettings(NULL,CDS_TEST)) 
      { // If The Shortcut Doesn't Work    ( NEW )
         ChangeDisplaySettings(NULL,CDS_RESET);// Do It Anyway (To Get The Values Out    Of The Registry) ( NEW )
         ChangeDisplaySettings(&DMsaved,CDS_RESET);// Change It To The Saved Settings    ( NEW )
      } 

      else 
      {
         ChangeDisplaySettings(NULL,CDS_RESET);// If It Works, Go Right Ahead ( NEW )
      }
      ShowCursor(TRUE);// Show Mouse Pointer
   }
   if (hRC)// Do We Have A Rendering Context?
   {
      if (!wglMakeCurrent(NULL,NULL))// Are We Able To Release The DC And RC Contexts?
      {
         MessageBox(NULL,"Release Of DC And RC Failed.","SHUTDOWN ERROR",MB_OK    | MB_ICONINFORMATION);
      }
   if (!wglDeleteContext(hRC))// Are We Able To Delete The RC?
   {
      MessageBox(NULL,"Release Rendering Context Failed.","SHUTDOWN    ERROR",MB_OK | MB_ICONINFORMATION);
   }

   hRC=NULL;// Set RC To NULL

   }
   if (hDC && !ReleaseDC(hWnd,hDC))// Are We Able To Release The DC
   {
      MessageBox(NULL,"Release Device Context Failed.","SHUTDOWN ERROR",MB_OK    | MB_ICONINFORMATION);
      hDC=NULL;// Set DC To NULL
   }
   if (hWnd && !DestroyWindow(hWnd))// Are We Able To Destroy The Window?
   {
      MessageBox(NULL,"Could Not Release hWnd.","SHUTDOWN ERROR",MB_OK    | MB_ICONINFORMATION);
      hWnd=NULL;// Set hWnd To NULL
   }
   if (!UnregisterClass("OpenGL",hInstance))// Are We Able To Unregister    Class
   {
      MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK    | MB_ICONINFORMATION);
      hInstance=NULL;// Set hInstance To NULL
   }
}


EnumDisplaySettings() wurde hinzugefügt um das Grafikkartenproblem zu lösen:

// This Code Creates Our OpenGL Window. Parameters Are:*
// title- Title To Appear At The Top Of The Window*
// width- Width Of The GL Window Or Fullscreen Mode*
// height- Height Of The GL Window Or Fullscreen Mode*
// bits- Number Of Bits To Use For Color (8/16/24/32)*
// fullscreenflag- Use Fullscreen Mode (TRUE) Or Windowed Mode (FALSE)*/
BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag)
{
   GLuintPixelFormat;// Holds The Results After Searching For A Match
   WNDCLASSwc;// Windows Class Structure
   DWORDdwExStyle;// Window Extended Style
   DWORDdwStyle;// Window Style
   RECTWindowRect;// Grabs Rectangle Upper Left / Lower Right Values
   WindowRect.left=(long)0;// Set Left Value To 0
   WindowRect.right=(long)width;// Set Right Value To Requested Width
   WindowRect.top=(long)0;// Set Top Value To 0
   WindowRect.bottom=(long)height;// Set Bottom Value To Requested Height
   fullscreen=fullscreenflag;// Set The Global Fullscreen Flag
   hInstance= GetModuleHandle(NULL);// Grab An Instance For Our Window
   wc.style= CS_HREDRAW | CS_VREDRAW | CS_OWNDC;// Redraw On Size, And Own DC For    Window
   wc.lpfnWndProc= (WNDPROC) WndProc;// WndProc Handles Messages
   wc.cbClsExtra= 0;// No Extra Window Data
   wc.cbWndExtra= 0;// No Extra Window Data
   wc.hInstance= hInstance;// Set The Instance
   wc.hIcon= LoadIcon(NULL, IDI_WINLOGO);// Load The Default Icon
   wc.hCursor= LoadCursor(NULL, IDC_ARROW);// Load The Arrow Pointer
   wc.hbrBackground= NULL;// No Background Required For GL
   wc.lpszMenuName= NULL;// We Don't Want A Menu
   wc.lpszClassName= "OpenGL";// Set The Class Name
   EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &DMsaved);// Save The    Current Display State ( NEW )
   if (fullscreen)// Attempt Fullscreen Mode?
   {
      DEVMODE dmScreenSettings;// Device Mode
      memset(&dmScreenSettings,0,sizeof(dmScreenSettings));// Makes Sure Memory's    Cleared
      dmScreenSettings.dmSize=sizeof(dmScreenSettings);// Size Of The Devmode Structure
      dmScreenSettings.dmPelsWidth= width;// Selected Screen Width
      dmScreenSettings.dmPelsHeight= height;// Selected Screen Height
      dmScreenSettings.dmBitsPerPel= bits;// Selected Bits Per Pixel
      dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;
      // Der Rest der Funktion bleibt unverändert

      (........)
Es wurden noch einige Zeilen eingefügt um die Figur zu drehen, die Details zu erhöhen/verringern und die roten Kontrolllinien mit der Leertaste an- und abzuschalten:
int WINAPI WinMain(HINSTANCEhInstance,     // Instance
                   HINSTANCEhPrevInstance, // Previous Instance
                   LPSTRlpCmdLine,         // Command Line Parameters
                   int nCmdShow)           // Window Show State
{

   MSGmsg;// Windows Message Structure
   BOOLdone=FALSE;// Bool Variable To Exit Loop
   // Ask The User Which Screen Mode They Prefer
   if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start    FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
   {
      fullscreen=FALSE;// Windowed Mode
   }
   // Create Our OpenGL Window
   if (!CreateGLWindow("NeHe's Solid Object Tutorial",640,480,16,fullscreen))
   {
      return 0;// Quit If Window Was Not Created
   }
   while(!done)// Loop That Runs While done=FALSE
   {
      if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))// Is There A Message Waiting?
      {
         if (msg.message==WM_QUIT)// Have We Received A Quit Message?
         {
            done=TRUE;// If So done=TRUE
         }

         else// If Not, Deal With Window Messages
         {
            TranslateMessage(&msg);// Translate The Message
            DispatchMessage(&msg);// Dispatch The Message
         }
      }

   else// If There Are No Messages
   {
      // Draw The Scene. Watch For ESC Key And Quit Messages From DrawGLScene()
      if ((active && !DrawGLScene()) || keys[VK_ESCAPE])
      // Active? Was There A Quit Received?
      {
         done=TRUE;// ESC or DrawGLScene Signalled A Quit
      }

      else// Not Time To Quit, Update Screen
      {
         SwapBuffers(hDC);// Swap Buffers (Double Buffering)
      }
      if (keys[VK_LEFT])rotz -= 0.8f;// Rotate Left ( NEW )
      if (keys[VK_RIGHT])rotz += 0.8f;// Rotate Right ( NEW )
      if (keys[VK_UP]) {// Resolution Up ( NEW )

      divs++;
      mybezier.dlBPatch = genBezier(mybezier, divs);// Update The Patch

      keys[VK_UP] = FALSE;
   }

   if (keys[VK_DOWN] && divs > 1) 
   {// Resolution Down ( NEW )
      divs--;
      mybezier.dlBPatch = genBezier(mybezier, divs);// Update The Patch
      keys[VK_DOWN] = FALSE;
   }

   if (keys[VK_SPACE]) 
   {// SPACE Toggles showCPoints ( NEW )
      showCPoints = !showCPoints;
      keys[VK_SPACE] = FALSE;
   }
   if (keys[VK_F1])// Is F1 Being Pressed?
   {
      keys[VK_F1]=FALSE;// If So Make Key FALSE
      KillGLWindow();// Kill Our Current Window
      fullscreen=!fullscreen;// Toggle Fullscreen / Windowed Mode
      // Recreate Our OpenGL Window
      if (!CreateGLWindow("NeHe's Solid Object Tutorial",640,480,16,fullscreen))
      {
         return 0;// Quit If Window Was Not Created
      }
   }
   }
   }
   // Shutdown
   KillGLWindow();// Kill The Window
   return (msg.wParam);// Exit The Program
}


Vielen Dank fürs Durcharbeiten dieses Tutorials, das Thema ist (wie immer ;) stark mathematisch gefärbt, aber eigentlich grundsätzlich "verstehbar".

David Nikdel

Jeff Molofee (NeHe) http://nehe.gamedev.net

Die Source Codes und Ausführbaren Dateien zu den Kursen liegen auf der Neon Helium Website

Übersetzt und leicht modifiziert von Hans-Jakob Schwer 19.04.2k3, www.codeworx.org