Lektion 28
Bezier Flächen (Bezier Patches)
Geschrieben von: David Nikdel ( ogapo@ithink.net )
Dieses Tutorial soll Sie in Bezier Flächen einführen, in der Hoffnung, dass jemand der künstlerisch mehr begabt als ich ist, etwas ziemlich cooles damit macht und es uns zeigt. Es ist nicht als eine komplette Bezier Patch Bibliothek beabsichtig, sondern viel mehr als Einstiegspunkt, um Sie damit vertraut zu machen, wie gekrümmte Oberflächen arbeiten. Außerdem, da dies hier nur zu Information dient, habe ich nicht die ganz korrekte Terminologie zugunsten des Verständnisses benutzt; Ich hoffe, dass jeder damit einverstanden ist. Für jeden, der bereits mit Beziers vertraut ist und das hier nur liest, um zu sehen, ob ich es verhunzt habe: schämen Sie sich ;-), aber wenn Sie etwas völlig falsches finden, lassen sie es mich oder NeHe wissen, da ja keiner perfekt ist, oder nicht? Oh, noch eine weitere Sache: der komplette Code wurde in keinster Weise über meinen normalen Programmierstil hinaus optimiert. Ich möchte, dass jeder genau weiß, was vorgeht. Nun denn, ich denke das ist genug zur Einführung. Auf geht's!
Die Mathematik - ::dämonische Musik:: (achtung, etwas längerer Abschnitt)
Ok, es dürfte recht schwierig sein Beziers zu verstehen, ohne die grundlegendsten Verständnisse der Mathematik, die dahinter steht, wie dem auch sei, wenn Sie diesen Teil gerade nicht lesen wollen oder Sie bereits die Mathematik kennen, können Sie diesen Teil überspringen. Ich werde mit der Beschreibung beginnen, was eine Bezier Kurve ist und dann dahin übergehen, wie man ein Bezier Patch erzeugt.
Komisch ist, dass, wenn Sie jemals ein Graphic-Programm verwendet haben, Sie bereits mit Bezier Kurven vertraut sind, wenn auch wahrscheinlich nicht unter diesem Namen. Sie sind die verbreitetste Methode um krumme Linien zu zeichnen und werden in der Regel durch eine Reihe von Punkten repräsentiert, wobei jeder 2 Punkte hat, die die Tangente repräsentieren, am Punkt links und rechts davon. So sieht das aus:
Das ist die einfachst mögliche Bezier Kurve (längere werden durch viele kleine gebildet (häufig ohne dass der Benutzer es merkt)). Diese Kurve ist tatsächlich nur durch 4 Punkte definiert, das wären die 2 End-Kontroll-Punkte und die 2 mittleren Kontroll-Punkte. Für den Computer sind alle Punkte gleich, aber um uns das Designen zu erleichtern verbinden wir jeweils die ersten und letzten zwei, da diese immer Tangenten zum Endpunkt sind. Die Kurve ist eine parametrische Kurve und wird gezeichnet, indem Punkte, die direkt auf der Kuve liegen, mit geraden Linien verbunden werden. Auf diesem Weg können Sie die Auflösung des Patch kontrollieren (und den rechen Aufwand). Der gebräuchlichste Weg ist es bei höherer Entfernung weniger Details zu verwenden und je näher der Betrachter kommt, entsprechend mehr, so dass es immer nach einer perfekt gekrümmten Oberfläche aussieht, mit dem geringsten Rechenaufwand.
Bezier Kurven basieren auf einer Basis-Funktion, von der kompliziertere Versionen abgeleitet werden. Hier die Funktion:
t + (1 - t) = 1
Klingt einfach, was? Nunja, ist es auch, das ist die einfachste Bezier Kurve, eine Kurve ersten Grades. Wie Sie vielleicht aus der Terminologie erkennen können, handelt es sich bei Bezier Kurven um Polynome und wie wir uns aus der Algebra erinnen, ein Polynom ersten Grades ist einfach nur eine gerade Linie; nicht gerade interessant. Nun, da die Basis-Funktion für alle Nummer t wahr ist, können wir quadrieren, sie in die dritte Potenz erheben, was auch immer, es würde immer noch wahr sein, richtig? Versuchen wir's mit der dritten Potenz.
(t + (1-t))^3 = 1^3
t^3 + 3*t^2*(1-t) + 3*t*(1-t)^2 + (1-t)^3 = 1
Das ist die Gleichung die wir benutzen werden, um das gängiste Bezier zu berechnen, die Bezier Kurve dritten Grades. Sie ist am gängisten für zwei Gründe, a) es ist das Polynomal des kleinsten Grades, das nicht notwendigerweise in einer Ebene lieben muss (es gibt 4 Kontrollpunkte) und b) die Tangenten Linien an den Seiten sind nicht voneinander abhängig (bei 2ten Grades gäbe es nur 3 Kontrollpunkte). Sehen Sie bereits die Bezier Kurve? Hehe, ich auch nicht, deshalb muss ich eine Sache noch hinzufügen.
Ok, da die gesamte linke Seite gleich 1 ist, ist es sicher anzunehmen, dass wenn Sie alle Komponenten addieren, Sie immer noch gleich 1 sind. Hört sich das danach, ob wir damit entscheiden können, wieviel jeder Kontrollpunkt benutzt wird, um einen Punkt in der Kurve zu berechnen? (hinweise: sagen Sie einfach ja ;-) ) Sie haben recht! Wenn wir den Wert eines Punktes einige Prozent entlängs der Kurve berechnen wollen, multiplizieren wir einfach jeden Teil mit einem Kontrollpunkt (als Vektor) und finden die Summe. Generell kann man sagen, wir arbeiten mit 0
P1*t^3 + P2*3*t^2*(1-t) + P3*3*t*(1-t)^2 + P4*(1-t)^3 = Pneu
Da Polynome immer stetig sind, ist das ein guter Weg, um zwischen den 4 Punkten zu morphen. Die einzigen Punkte die tatsächlich erreicht werden, sind P1 und P4 wenn t = 1, bzw. gleich 0 ist.
Nun, das ist alles schön und gut, aber wie kann ich das in 3D verwenden werden Sie sich fragen. Nun, eigentlich ist es recht einfach, um eine Bezier Patch zu formen, brauchen Sie 16 Kontrollpunkte (4*4) und 2 Variablen t und v. Daraus berechnen Sie ein Punkte bei v entlängs der 4 parallelen Kurven, dann benutzen Sie diese 4 Punkte um eine neue Kurve zu machen und berechnen t entlängs dieser Kurve. Indem wir genügend dieser Punkte berechnen, können wir Triangle Strips zeichnen und diese miteinander verbinden, woraus unser Bezier Patch ensteht.
Ich denke das war erst mal genug Mathe für jetzt, auf zum Code!#include <windows.h> // Header Datei für Windows #include <math.h> // Header Datei für Math Library Routines #include <stdio.h> // Header Datei für Standard I/O Routinen #include <stdlib.h> // Header Datei für Standard Library #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 dieGlaux Library typedef struct point_3d { // Struktur für einen 3-Dimensionalen Punkt ( NEU ) double x, y, z; } POINT_3D; typedef struct bpatch { // Struktur für ein Bezier Patch 3ten Grades ( NEU ) POINT_3D anchors[4][4]; // 4x4 Gitter an Anker-Punkten GLuint dlBPatch; // Display Liste für Bezier Patch GLuint texture; // Textur für das Patch } BEZIER_PATCH; HDC hDC=NULL; // Privater GDI Device Context HGLRC hRC=NULL; // Permanenter Rendering Context HWND hWnd=NULL; // Enthält unser Fenster-Handle HINSTANCE hInstance; // Enthält die Instanz der Applikation DEVMODE DMsaved; // speichert die vorherigen Bildschirmeinstellungen ( NEU ) 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 GLfloat rotz = 0.0f; // Rotation um die Z-Achse BEZIER_PATCH mybezier; // Das Bezier Patch welches wir verwenden werden ( NEU ) BOOL showCPoints=TRUE; // schaltet das Anzeigen des Kontroll-Punkt-Gitters ( NEU ) int divs = 7; // Anzahl der Intrapolationen(steuert Polygon Auflösung) ( NEU ) LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // Deklaration für WndProc
Die folgenden Funktionen sind nur ein paar kleine Funktionen für einfache Vektor-Mathematik. Wenn Sie ein Fan von C++ sind, könnten Sie auch eine Punkt-Klasse in betracht ziehen (stellen Sie sicher, dass diese 3D ist).// Addiert 2 Punkte. Benutzen Sie nicht einfach '+' ;) POINT_3D pointAdd(POINT_3D p, POINT_3D q) { p.x += q.x; p.y += q.y; p.z += q.z; return p; } // Multiplziert einen Punkt un eine Konstante. Benutzen Sie nicht einfach '*' POINT_3D pointTimes(double c, POINT_3D p) { p.x *= c; p.y *= c; p.z *= c; return p; } // Funktion zur schnellen Punkt-Erzeugung POINT_3D makePoint(double a, double b, double c) { POINT_3D p; p.x = a; p.y = b; p.z = c; return p; }
Das ist grundlegend nur die Basis-Funktion 3ten Grades in C. Ihr wird eine Variable u und ein Array von 4 Punkten übergeben und berechnet dann einen Punkt auf der Kurve. Indem u in gleichen Schritten zwischn 0 und 1 erhöht wird, erhalten wir eine nette Annäherung der Kurve.// berechnet Polygon dritten Grades basierend auf einem Array aus 4 Punkten // und einer einzelnen Variable (u) welche in der Regel zwischen 0 und 1 ist POINT_3D Bernstein(float u, POINT_3D *p) { POINT_3D a, 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; }
Diese Funktion macht den Löwenanteil der Arbeit, indem Sie alle Triangle Strips (Dreiecks Streifen) erzeugt und in einer Display Liste speichert. Wir machen das, damit wir den Patch nicht jeden Frame erneut berechnen müssen, sondern nur dann, wenn er sich ändert. Nebenbei, ein cooler Effekt wäre es, wenn Sie versuchen würden das Morphing Tutorial zu verwenden und die Kontrollpunkte des Patch's morphen würden. Das würde einen coolen weichen, organischen Morphing-Effekt ergeben, für realtiv wenig Overhead (da Sie nur 16 Punkte morphen müssen, aber neu berechnen müssten). Das 'last' Array wird benutzt, um die vorherogen Linien an Punkten zu behalten (da ein Triangle Strip beide Reihen benötigt). Außerdem werden die Texturkoordinaten berechnet, indem die Werte u und v als Prozente verwendet werden (planares Mapping).
Was wir nicht machen ist die Berechnung des Normalenvektors für die Beleuchtung. Wenn es dazu kommt haben Sie grundsätzlich zwei Möglichkeiten. Die erste ist, die Mitte jeden Dreiecks zu finden, dann benutzten Sie etwas Matematik und berechnen die Tangente auf der X und Y-Achse, dann bilden Sie das Kreuzprodukt, damit die Vektoren senkrecht zueinander stehen, DANN normalisieren Sie den Vektor und benutzen dies als den Normalenvektor. ODER (ja, es gibt einen schnelleren Weg) Sie schummeln etwas und benutzen einfach den Normalenvektor des Dreiecks (berechnen Sie es, wie Sie es bevorzugen), um eine ziemlich gute Annäherug zu erhalten. Ich bevorzuge letzteres; der Geschwindigkeitsvorteil ist meiner Meinung nach vorzuziehen, da es das klein wenige extra Realismus nicht Wert wäre.// erzeugt eine Display Liste basierend auf den Daten aus dem Patch // und der Anzahl der Divisionen GLuint genBezier(BEZIER_PATCH patch, int divs) { int u = 0, v; float py, px, pyold; GLuint drawlist = glGenLists(1); // erzeuge die Display Liste POINT_3D temp[4]; POINT_3D *last = (POINT_3D*)malloc(sizeof(POINT_3D)*(divs+1)); // Array an Punkten, um die erste Linie Polygone zu kennzeichnen if (patch.dlBPatch != NULL) // werde alle alten Display Listen los glDeleteLists(patch.dlBPatch, 1); temp[0] = patch.anchors[0][3]; // die erste hergeleitete Kurve (entlängs der X-Achse) temp[1] = patch.anchors[1][3]; temp[2] = patch.anchors[2][3]; temp[3] = patch.anchors[3][3]; for (v=0;v// erzeuge die erste Linien an Punkten px = ((float)v)/((float)divs); // Prozent entlängs der Y-Achse // benutze die 4 Punkte aus der hergeleiteten Kurve, um die Punkte entlängs der Kurve zu berechnen last[v] = Bernstein(px, temp); } glNewList(drawlist, GL_COMPILE); // Starte eine neue Display Liste glBindTexture(GL_TEXTURE_2D, patch.texture); // Binde die Texturen for (u=1;u// Prozent entlängs der Y-Achse pyold = ((float)u-1.0f)/((float)divs); // Prozent entlängs der alten Y-Achse temp[0] = Bernstein(py, patch.anchors[0]); // berechne neue Bezier Punkte 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); // Beginne einen neuen Triangle Strip for (v=0;v// Prozent entlängs der X-Achse glTexCoord2f(pyold, px); // wende die alten Texturkoordinaten an glVertex3d(last[v].x, last[v].y, last[v].z); // alter Punkt last[v] = Bernstein(px, temp); // erzeuge neuen Punkt glTexCoord2f(py, px); // wende die neuen Texturkoordinaten an glVertex3d(last[v].x, last[v].y, last[v].z); // Neuer Punkt } glEnd(); // BEENDE Triangle Strip } glEndList(); // BEENDE die Liste free(last); // gebe das alte Vertices-Array frei return drawlist; // gebe die Display Liste zurück }
Hier laden wir lediglich die Matrix mit ein paar Werten, die ich gewählt habe, weil ich denke, dass sie cool aussehen. Spielen Sie einfach damit herum und schauen Sie, was dabei herauskommt. :-)void initBezier(void) { mybezier.anchors[0][0] = makePoint(-0.75, -0.75, -0.50); // Setze die Bezier Vertices 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; // mache weiter und initialisiere dieses mit NULL }
Dies ist lediglich eine optimierte Routine um ein einzelnen Bitmap zu laden. Sie kann einfach verwendet werden, um ein Array davon zu laden, indem Sie einfach eine einfache Schleife verwenden.// Lade Bitmaps und konvertiere in Texturen BOOL LoadGLTexture(GLuint *texPntr, char* name) { BOOL success = FALSE; AUX_RGBImageRec *TextureImage = NULL; glGenTextures(1, texPntr); // erzeuge 1 Textur FILE* test=NULL; TextureImage = NULL; test = fopen(name, "r"); // Teste, ob die Datei existiert if (test != NULL) { // wenn ja fclose(test); // schließe die Datei TextureImage = auxDIBImageLoad(name); // und lade die Textur } if (TextureImage != NULL) { // wenn sie geladen wurde success = TRUE; // normalerweise benutzt die Textur-Erzeugung Daten aus dem 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; }
Fügen Sie die Patch Initialisierung einfach hier ein. Sie würden das immer dann machen, wenn Sie ein Patch erzeugen. Erneut wäre das ein cooler Platz um C++ einzusetzen (Bezier Klasse?).int InitGL(GLvoid) // Der ganze Setup Kram für OpenGL kommt hier rein { glEnable(GL_TEXTURE_2D); // aktiviert Textur Mapping glShadeModel(GL_SMOOTH); // aktiviert Smooth Shading glClearColor(0.05f, 0.05f, 0.05f, 0.5f); // Schwarzer Hintergrund glClearDepth(1.0f); // Depth Buffer Setup glEnable(GL_DEPTH_TEST); // aktiviert Depth Testing glDepthFunc(GL_LEQUAL); // Die Art des auszuführenden Depth Test glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // wirklich nette Perspektiven Berechnungen initBezier(); // Initialisiert das Bezier Kontroll-Gitter ( NEU ) LoadGLTexture(&(mybezier.texture), "./Data/NeHe.bmp"); // Lade die Textur ( NEU ) mybezier.dlBPatch = genBezier(mybezier, divs); // erzeuge Patch ( NEU ) return TRUE; // Initialisierung war in Ordnung }
Als erstes rufen Sie die Bezier Display Liste auf. Dann (wenn die Umriße angeschaltet sind), zeichne die Linien, die die Kontroll-Punkte verbinden. Sie können diese an und ausschalten, indem Sie die LEERTASTE drücken.int DrawGLScene(GLvoid) { // Hier kommt der ganze Zeichnen-Kram hin int i, j; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Lösche den Bildschirm und den Depth-Buffer glLoadIdentity(); // Resette die aktuelle Modelview Matrix glTranslatef(0.0f,0.0f,-4.0f); // 1.5 Einheiten nach Links dann 6.0 Einheiten in den Bildschirm hinein glRotatef(-75.0f,1.0f,0.0f,0.0f); glRotatef(rotz,0.0f,0.0f,1.0f); // Rotiere das Dreieck auf der Z-Achse glCallList(mybezier.dlBPatch); // rufe die Bezier Display Liste auf // Dies muss nur aktualisiert werden, wenn das Patch geändert wird if (showCPoints) { // wenn das Gitter gezeichnet werden soll glDisable(GL_TEXTURE_2D); glColor3f(1.0f,0.0f,0.0f); for(i=0;i// zeichne die horizontalen Linien glBegin(GL_LINE_STRIP); for(j=0;j// zeichne die vertikalen Linien glBegin(GL_LINE_STRIP); for(j=0;j// weiter geht's }
Diese Funktion enthält etwas modifizierten Code, um Ihre Projekte kompatibler zu halten. Es hat nichts mit Bezier Kurven zu tun, aber behebt ein kleines Problem beim Zurückschalten der Auflösung, nach dem Fullscreen-Modus, der bei einigen Grafikkarten auftritt (inklusiver meiner, einer alten ATI Rage PRO und ein paar anderen). Ich hoffe Sie verwenden das von nun an, so dass ich und andere mit ähnlichen Karten euren coolen GL Beispiel Codes korrekt ansehen kann. Um diese Modiifkation vorzunehmen, ändern Sie die KillGLWindow(), stellen Sie sicher, dass Sie DMsaved defniert haben und ändern Sie die eine Zeile in CreateGlWindow() (ist markiert).GLvoid KillGLWindow(GLvoid) // Entferne das Fenster korrekt { if (fullscreen) // Sind wir im Fullscreen Modus? { if (!ChangeDisplaySettings(NULL,CDS_TEST)) { // wenn der Shortcut nicht funktionier ( NEU ) ChangeDisplaySettings(NULL,CDS_RESET); // mache es trotzdem (um die Werte aus der Registry zu holen) ( NEU ) ChangeDisplaySettings(&DMsaved,CDS_RESET); // ändere sie auf die gespeicherten Einstellungen ( NEU ) } else { ChangeDisplaySettings(NULL,CDS_RESET); // Wenn es funktioniert, mache weiter ( NEU ) } ShowCursor(TRUE); // Zeige den Maus-Zeiger } if (hRC) // Haben wir einen Rendering Context? { if (!wglMakeCurrent(NULL,NULL)) // Können wir den DC und RC Kontext freigeben? { MessageBox(NULL,"Release Of DC And RC Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION); } if (!wglDeleteContext(hRC)) // Können wir den RC löschen? { MessageBox(NULL,"Release Rendering Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION); } hRC=NULL; // Setze RC auf NULL } 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 } 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 } 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 EnumDisplaySettings() Befehl wurde hinzugefügt um die alten Display-Einstellungen zu speichern. (Teile der alten Grafikkartenprobleme wurden behoben).// Dieser Code erzeugt unser OpenGL Fenster. Parameter sind: // title - Titel der im Fenster erscheinen soll // width - Breite des GL Fensters oder Fullscreen Modus // height - Höhe des GL Fensters oder Fullscreen Modus // bits - Anzahl der zu benutzenden Farben (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) { GLuint PixelFormat; // Enthält die Ergebnisse nachdem nach was passendem gesucht wurde WNDCLASS wc; // Fenster Klassen Struktur DWORD dwExStyle; // erweiterter Fenster-Stil DWORD dwStyle; // Fenster-Stil 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 fullscreen=fullscreenflag; // Setze das globale Fullscreen Flag 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 EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &DMsaved); // speichere den aktuellen Display Status ( NEU ) if (fullscreen) // Fullscreen Modus? { 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; ... Code Cut To Save Space (No Further Changes To This Function) ... return TRUE; // Erfolg }
Alles was ich hier hinzugefügt habe, sind Befehl, um die Fläche zu rotieren, die Auflösung zu erhöhen / runter zu setzen und die Kontrolllinien an und auszuschalten.
int WINAPI WinMain( HINSTANCE hInstance, // Instanz HINSTANCE hPrevInstance, // vorherige Instanz LPSTR lpCmdLine, // Kommandozeilen Parameter int nCmdShow) // Fenster Anzeige Status { MSG msg; // Windows Nachrichten Struktur BOOL done=FALSE; // Bool Variable um die Schleife zu beenden // 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 } // erzeuge unser OpenGL Fenster if (!CreateGLWindow("NeHe's Solid Object Tutorial",640,480,16,fullscreen)) { return 0; // Beende, wenn Fenster nicht erzeugt wurde } while(!done) // Schleife die so lange läuft, bis done=TRUE { if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) // Wartet eine Nachricht? { 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 { TranslateMessage(&msg); // Übersetze die Nachricht DispatchMessage(&msg); // bearbeite die Nachricht } } else // Wenn da keine Nachrichten sind { // Zeichne die Szene. Schaue, ob ESC oder DrawGLScene() zum beenden signalisieren if ((active && !DrawGLScene()) || keys[VK_ESCAPE]) // Activ? Soll beendet werden? { done=TRUE; // ESC oder DrawGLScene Signalisiert, dass Beendet werden soll } else // Es ist noch nicht Zeit zum beenden, zeichne Screen neu { SwapBuffers(hDC); // Swap Buffers (Double Buffering) } if (keys[VK_LEFT]) rotz -= 0.8f; // rotiere links ( NEU ) if (keys[VK_RIGHT]) rotz += 0.8f; // rotiere rechts ( NEU ) if (keys[VK_UP]) { // mehr Details ( NEU ) divs++; mybezier.dlBPatch = genBezier(mybezier, divs); // akutalisiere die Fläche keys[VK_UP] = FALSE; } if (keys[VK_DOWN] && divs > 1) { // weniger Details ( NEU ) divs--; mybezier.dlBPatch = genBezier(mybezier, divs); // aktualisiere die Fläche keys[VK_DOWN] = FALSE; } if (keys[VK_SPACE]) { // LEERTASTE invertiert showCPoints ( NEU ) showCPoints = !showCPoints; keys[VK_SPACE] = FALSE; } 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 Solid Object Tutorial",640,480,16,fullscreen)) { return 0; // Beenden, wenn das Fenster nicht erzeugt wurde } } } } // Shutdown KillGLWindow(); // Kill das Fenster return (msg.wParam); // Beende das Programm }
Nun, ich hoffe dieses Tutorial hat etwas Licht in die Sache gebracht und Sie lieben nun Bezier Kurven genauso sehr, wie ich ;-). Wenn Sie dieses Tutorial mögen, werde ich vielleicht ein weiteres Tutorial über NURBS Kurven schreiben, wenn Interesse besteht. Mailen Sie mir und lassen Sie mich wissen, was Sie über dieses Tutorial denken.
Über den Autor: David Nikdel ist zur Zeit 18 und Senior an der Bartow Senior High School. Seine aktuellen Projekte beinhalten ein Research Paper über gekrümmte Flächen in 3D Grpahiken, ein OpenGL basiertes Spiel namens Blazing Sands und faulenzen. Seine Hobbies sind programmieren, Fussball und Paintball. Er wird (hoffentlich) nächstes Jahr an der Georgia Tech anfangen.
David Nikdel
Jeff Molofee (NeHe)
* DOWNLOAD Visual C++ Code für diese Lektion.
* DOWNLOAD Borland C++ Builder 6 Code für diese Lektion. ( Conversion by Christian Kindahl )
* DOWNLOAD Code Warrior 5.3 Code für diese Lektion. ( Conversion by Scott Lupton )
* DOWNLOAD Delphi Code für diese Lektion. ( Conversion by Joachim Rohde )
* DOWNLOAD Dev C++ Code für diese Lektion. ( Conversion by Dan )
* DOWNLOAD Euphoria Code für diese Lektion. ( Conversion by Evan Marshall )
* DOWNLOAD LCC Win32 Code für diese Lektion. ( Conversion by Robert Wishlaw )
* DOWNLOAD Linux/GLX Code für diese Lektion. ( Conversion by Rodolphe Suescun )
* DOWNLOAD LWJGL Code für diese Lektion. ( Conversion by Mark Bernard )
* DOWNLOAD Mac OS X/Cocoa Code für diese Lektion. ( Conversion by Bryan Blackburn )
* 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.