Lektion 9: Bitmaps in einer 3D Umgebung
Willkommen zu Tutorial Nummer 9. Mittlerweile solltet ihr schon ein Grundverständnis
für OpenGL entwickelt haben. Ihr habt alls vom erstellen eines OpenGL Fensters
über Texture Mapping an einem drehenden Objekt während es beleuchtet wird bis
hin zu Blending. Dies wird das erste semiproffesionelle Tutorial sein. Ihr werdet
folgendes lernen: Bitmaps in 3D auf dem Bildschirm umherbewegen, die schwarzen
Pixel um das Bitmap herum entfernen (mit Hilfe von Blending), Farbe zu einer
schwarz/ weißen Textur hinzufügen und schließlich noch wie an einige Farben
erzeugt und eine Animation aus unterschiedlich eingefärbten Texturen erstellt.
Wir werden den Code des ersten Tutorials benutzen. Beginnen wir damit einige
neue Variablen am Anfang des Programms hinzuzufügen. Ich werde die ganze Sektion
des Codes neu angeben, dadurch ist es einfacher nachzuvollziehen, was sie verändert
hat.
#include <windows.h> // Header File For Windows
#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
static HGLRC hRC; // Permanent Rendering Context
static HDC hDC; // Private GDI Device Context
BOOL keys[256]; // Array Used For The Keyboard Routine
Die nun folgenden Zeilen sind neu: twinkle und tp sind Boolean Variablen, was
bedeutet das sie wahr oder falsch sein können. twinkle wird überwachen, ob der
Twinkle-Effekt aktiviert ist. tp wird genutzt um zu prüfen ob die "T"-Taste gedrückt
wird oder nicht (gedrückt: tp = TRUE, nicht gedrückt tp = FALSE).
BOOL twinkle; // Twinkling Stars
BOOL tp; // 'T' Key Pressed?
num wird enthalten, wie viele Sterne wir auf dem Bildschirm darstellen. Es ist
als Const (Konstante) definiert. Das bedeutet es ändert sich nie etwas am Inhalt.
Der Grund wieso wir das als Konstante definieren ist das man einen Array nicht
redefinieren kann. Also wenn wir einen Array mit 50 Sternen definieren und im
Code irgendwo num auf 51 setzen, kann der Array nicht auf 51 Inhalte anwachsen
und ein Fehler würde auftauchen. Ihr könnt den Wert nur in dieser Zeile ändern.
Versucht nicht den Wert von num später zu verändern es sei denn ihr wollt das
eine Katastrophe passiert.
const num=50; // Number Of Stars To Draw
Nun erstellen wir eine Struktur. Das Wort Struktur klingt kompliziert, ist
es aber nicht. Eine Struktur ist eine Gruppe von simplen Daten (Variablen usw.),
die zusammen eine große Gruppe bilden. Zu Deutsch: Wir wissen damit das wir
Kontrolle über die Sterne haben. Ihr seht das die 7. Zeile des unternstehenden
Codes 'stars' ist. Wir wissen das jeder Stern 3 Werte für die Farben haben wird
und das diese Werte vom Typ Integer sein werden. Die dritte Zeile "int r, g,
b" enthält 3 Integerwerte. Einen für Rot (r), einen für Grün (g) und einen für
Blau (b). Wir wissen auch das jeder Stern eine eigene Entfernung vom Mittelpunkt
des Bildschirmes hat und kann 360° um den Mittelpunkt herum platziert werden.
Wenn ihr auf die 4. Zeile achtet, dort definieren wir eine floating point Variable
mit Namen dist. In eben jener wird die Entfernung festgehalten. Die 5. Zeile
erstellt eine Variable namens angle. Diese wird die Gradzahl des Winkels der
Sterne enthalten.
Also jetzt haben wir eine Gruppe von Daten, welche die Farbe, Entfernung und
Neigung eines Sterns enthält. Unglücklicherweise haben wir mehr als nur einen
Stern zu überwachen. Anstatt 50 mal Rot, 50 mal Grün, 50 mal Blau und 50 mal
die Neigung zu definieren erstellen wir einfach einen Array mit Namen "star".
Jede Zelle im Array "star" wird die Informationen aus der Struktur "stars" enthalten.
Wir erstellen den star Array in der 8. zeile. Die Zeile lautet 'stars star[num]'.
Das bedeutet das der Array vom Typ stars sein wird. Da stars eine Struktur ist
wird der Array alle Inhalte der Struktur enthalten. Der Name des Arrays ist
star. Die Größe des Arrays ist num. Da num = 50 haben wir jetzt einen Array
dieser Größe und jedes Element enthält den Inhalt der Struktur. Dies ist viel
einfacher zu handhaben als für jeden Stern eigene, seperate Variablen zu deklarieren.
Dies wäre abgesehen davon eine sinnlose Idee, da wir nicht so einfach die Anzahl
der Sterne ändern könnten nur indem wir num verändern.
typedef struct // Create A Structure For Star
{
int r, g, b; // Stars Color
GLfloat dist; // Stars Distance From Center
GLfloat angle; // Stars Current Angle
}
stars; // Structures Name Is stars
stars star[num]; // Make 'star' Array Of 'num'
// Using Info From The Structure 'stars'
Als nächstes erstellen wir Variablen um zu überprüfen wie weit die Sterne von
dem Betrachter entfernt sind (zoom) und von welchem Winkel wir die Sterne aus
sehen (tilt). Wir erstellen zudem noch eine Variable namens "spin" welche die
Sterne um die Z-Achse rotiert, was dann so aussieht als würden sie sich drehen.
"loop" ist eine Variable, welche wir nutzen um alle 150 Sterne zu zeichnen
und texture[1] wird eine schwarz/ weiß Textur enthalten. Wenn ihr mehr Texturen
wollt solltet ihr den Wert eins auf die Anzahl eurer gewünschten Texturen setzen.
GLfloat zoom=-15.0f; // Viewing Distance Away From Stars
GLfloat tilt=90.0f; // Tilt The View
GLfloat spin; // Spin Twinkling Stars
GLuint loop; // General loop Variable
GLuint texture[1]; // Storage For One Texture
Direkt nach den oben aufgeführten Zeilen laden wir unsere Textur. Ich sollte auf
diesen Code eigentlich nicht mehr eingehen müssen - es ist der selbe welchen wir
schon in Kurs 6, 7 und 8 genutzt haben. Das Bitmap welches wir diesmal laden heißt
'star.bmp'. Wir generieren nur eine Textur mit 'glGenTextures(1,&Texture[0])'.
Die Textur wird lineare Filterung nutzen.
// Load Bitmap And Convert To A Texture
GLvoid LoadGLTextures()
{
// Load Texture
AUX_RGBImageRec *texture1;
// Record To Store RBG Data From The Bitmap
texture1 = auxDIBImageLoad("Data/star.bmp");
if (!texture1)
// Check If The Texture Loaded
{
exit(1);
// If Not, Exit With An Error Message
}
glGenTextures(1, &texture[0]);
// Generate 1 Texture
// Create Linear Filtered Texture
glBindTexture(GL_TEXTURE_2D, texture[0]);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, 3, texture1->sizeX, texture1->sizeY,
0, GL_RGB, GL_UNSIGNED_BYTE, texture1->data);
};
Jetzt richten wir OpenGL ein das auch alles richtig dargestellt wird. Wir werden
in diesem Projekt kein Depth Testing nutzen, also stellt sicher wenn ihr den Code
von den vorherigen Teilen nutzt das ihr die Zeilen 'glDepthFunc(GL_LESS);' und
'glEnable(GL_DEPTH_TEST);' entfernt oder ihr werdet einige unschöne Nebeneffekte
haben. Wir nutzen aber Texture Mapping, geht also sicher das ihr wirklich 'LoadGLTextures();'
und 'glEnable(GL_TEXTURE_2D);' einbindet.
GLvoid InitGL(GLsizei Width, GLsizei Height)
// This Will Be Called Right After
// The GL Window Is Created
{
LoadGLTextures(); // Load The Texture glEnable
(GL_TEXTURE_2D); // Enable Texture Mapping
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
// This Will Clear The Background Color To Black
glClearDepth(1.0);
// Enables Clearing Of The Depth Buffer
glShadeModel(GL_SMOOTH);
// Enables Smooth Color Shading
glMatrixMode(GL_PROJECTION);
// Select The Projection Matrix
glLoadIdentity();
// Reset The Projection Matrix
gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,100.0f);
// Calculate The Aspect Ratio Of The Window
glMatrixMode(GL_MODELVIEW);
// Select The Modelview Matrix
glBlendFunc(GL_SRC_ALPHA,GL_ONE);
// Set The Blending Function For Translucency
glEnable(GL_BLEND);
// Enable Blending
Der folgende Code ist neu. Er setzt die Anfangswerte von Winkel, Entfernung und
Farbe von jedem Stern. Beachtet wie einfach es ist eine Information innerhalb
einer Struktur zu verändern. Die Schleife wird durch alle 50 Sterne laufen. Um
den Winkel von star[1] zu ändern müssen wir nur 'star[1].angle = Wert' angeben.
So einfach ist das!
for (loop=0; loop<num; loop++)
// Create A Loop That Goes Through All The Stars
{
star[loop].angle=0.0f;
// Start All The Stars At Angle Zero
Ich berechne die Entfernung indem ich den aktuellen Stern (mit der Nummer loop)
nehme und ihn durch die maximale Anzahl von Sternen dividiere. Dann multipliziere
ich das Ergebnis mit 5.0f. Im Grunde genommen bewegt das jeden Stern ein bischen
weiter vom Zentrum weg als den letzten. Wenn loop 50 entspricht (der letzte
Stern) wird loop durch 50 genau 1 entsprechen. Der Grund weshalb ich mit 5.0f
multipliziere ist weil 1.0f * 5.0f = 5.0f entspricht. 5.0f ist seinerseits der
Rand des Bildschirmes. Ich will keine Sterne außerhalb des Bildschirmes, also
ist 5.0f perfekt. Wenn ihr mit zoom euch weiter in den Bildschirm bewegt könnt
ihr auch eigene Werte für 5.0f einsetzen, aber die Sterne werden dann aufgrund
der Perspektive kleiner.
Ihr werdet feststellen das die Farbe für jeden Stern ein zufälliger Wert zwischen
0 und 255 ist. Ihr wundert euch wahrscheinlich wieso wir so hohe Werte nehmen
können, obwohl der normale Wertebereich doch nur von 0.0f bis 1.0f geht. Wenn
wir die Farben setzen nutzen wir glColor4ub anstatt glColor4f. "ub" steht für
unsigned Byte. Ein Byte kann von 0 bis 255 gehen. In diesem Programm ist es
einfacher bytes zu benutzen anstatt zufällige floating Point Werte zu errechnen.
star[loop].dist=(float(loop)/num)*5.0f;
// Calculate Distance From The Center
star[loop].r=rand()%256;
// Give star[loop] A Random Red Intensity
star[loop].g=rand()%256;
// Give star[loop] A Random Green Intensity
star[loop].b=rand()%256;
// Give star[loop] A Random Blue Intensity
}
}
Der Resize Code ist der gleiche, also springen wir gleich zur Darstellung. Wenn
ihr den Code vom ersten Teil nutzt, löscht den DrawGLScene-Abschnitt und ersetzt
ihn durch den unten stehenden. Der vom ersten Mal bestand so wie so nur aus zwei
Zeilen.
GLvoid DrawGLScene(GLvoid)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Clear The Screen And The Depth Buffer
glBindTexture(GL_TEXTURE_2D, texture[0]);
// Select Our Texture
for (loop=0; loop<num; loop++)
// Loop Through All The Stars
{
glLoadIdentity();
// Reset The View Before We Draw Each Star
glTranslatef(0.0f,0.0f,zoom);
// Zoom Into The Screen (Using The Value In 'zoom')
glRotatef(tilt,1.0f,0.0f,0.0f);
// Tilt The View (Using The Value In 'tilt')
Jetzt bewegen wir den Stern. Ein Stern beginnt in der Mitte des Bildschirms.
Das erste was wir tun ist die Szene um die Y-Achse zu drehen. Wenn wir um 90°
drehen wird die X-Achse nicht mehr von links nach rechts, sondern in den Bildschirm
hinein und hinaus laufen. Ein Beispiel um da zu verdeutlichen: stellt euch vor
ihr steht in der Mitte eines Raumes. Auf der Wand zu eurer linken steht ein
-X, auf der Wand vor euch steht eun -Z, auf der rechten Wand ein +X und hinter
euch +Z. Wenn sich nun der Raum um 90° nach rechts dreht, ihr euch aber nicht
bewegt, dann würde auf der Wand vor euch anstatt -Z nun -X stehen. Alle Wände
haben sich bewegt. -Z ist nun links, -X ist vor euch und +X hinter euch. Macht
das Sinn? Indem wir die Szene rotieren ändern wir die Richtung der X und Z Achsen.
Die zweite Zeile des Codes bewegt sich auf einen positiven Wert auf der X-Achse.
Normalerweise würde uns ein positiver Wert auf der X-Achse nach rechts bewegen
(wo +X normalerweise ist), aber da wir um die Y-Achse rotiert haben könnte +X
sonst wohin zeigen. Wenn wir um 180° rotiert hätten würde +X sich nach links
bewegen anstatt nach rechts. Also wenn wir uns entlang der positiven X-Achse
bewegen können wir uns nach rechts, links, oben oder unten bewegen.
glRotatef(star[loop].angle,0.0f,1.0f,0.0f);
// Rotate To The Current Stars Angle
glTranslatef(star[loop].dist,0.0f,0.0f);
// Move Forward On The X Plane
Jetzt kommen wir zu etwas komplizierterem Code. Der Stern ist in Wirklichkeit
eine flache Textur. Wenn ihr eine texturierte quadratische Fläche auf dem Bildschirm
darstellt sieht das gut aus, da es gerade vor dem Betrachter steht. Wenn ihr
aber um 90° um die Y-Achse rotiert würde die Textur in Richtung der linken und
rechten Seite des Bildschirms zeigen. Alles was ihr sehen würdet wäre ein flacher
Streifen. Allerdings wollen wir das nicht, wir wollen das der Stern jederzeit
zu uns gewandt ist, egal wie wir rotieren und bewegen.
Wir erreichen das indem wir alle Rotationen wieder rückgängig machen bevor
wir die Szene darstellen. Ihr macht die Rotationen in genau entgegengesetzter
Reihenfolge rückgängig. Erst Unrotieren (neue Wortschöpfung) wir die Neigung
des aktuellen Sterns. Um dies zu tun nehmen wir den negativen Wert der vorherigen
Neigungund rotieren um diesen. Also wenn wir einen Stern um 10° rotieren wird
eine Rotation um -10° ihn wieder zum Bildschirm hin gewandt erscheinen lassen.
Also die erste Zeile wirkt der Rotation um die Y-Achse entgegen. Dann müssen
wir die Neigung auf der X-Achse wieder aufheben. Das machen wir indem wir den
Bildschirm, um -tilt neigen. Demnach haben wir alle X und Y Rotationen rückgängig
gemacht und der Stern zeigt wieder zum Bildschirm hin.
glRotatef(-star[loop].angle,0.0f,1.0f,0.0f);
// Cancel The Current Stars Angle
glRotatef(-tilt,1.0f,0.0f,0.0f);
// Cancel The Screen Tilt
Wenn "twinkle" true ist zeichnen wir einen nicht-drehenden Stern. Um eine andere
Farbe zu erhalten nehmen wir die maximale Anzahl von Sternen (num) und ziehen
die Nummer des aktuellen Sterns ab (loop). Dann ziehen wir noch eins ab, da
unsere loop nur von 0 bis num-1 reicht. Wenn das Ergebnis 10 ist nutzen wir
eben die Farbe 10. Auf diese Weise ist die Farbe zweier Sterne normalerweise
immer unterschiedlich. Keine elegante Art dieses Problem zu lösen, aber effektiv.
Der letzte Wert ist der Alpha Wert. Je niedriger der Wert, desto dunkler ist
der Stern.
Wenn "twinkle" aktiviert ist wird jeder Stern zweimal gezeichnet. Dies wird
das Programm je nach eurer Rechenleistung verlangsamen. Wenn es aktiviert ist
werden die Farben von 2 Sternen zu einer neuen Farbe gemischt. Da die Sterne
sich nicht drehen sieht es so aus als wären die Sterne animiert (schaut es euch
selbst an, wenn ihr nicht wisst was ich meine).
Beachtet wie leicht es ist einer Textur Farbe hinzuzufügen. Obwohl die Textur
schwarz/weiß ist, wird sie die von uns ausgewählte Farbe erhalten. Beachtet
auch das wir Bytes für die Farbwerte benutzen anstatt float Werte. Selbst der
Alpha Wert ist ein Byte.
if (twinkle) // Twinkling Stars Enabled
{
// Assign A Color Using Bytes
glColor4ub(star[(num-loop)-1].r,
star[(num-loop)-1].g,
star[(num-loop)-1].b,
255);
glBegin(GL_QUADS); // Begin Drawing The Textured Quad
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,-1.0f, 0.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,-1.0f, 0.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 0.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 0.0f);
glEnd(); // Done Drawing The Textured Quad
}
Jetzt zeichnen wir den Hauptstern. Der einzige Unterschied zu dem oben stehenden
Code ist das der Stern immer gezeichnet wird und auf der Z-Achse rotiert.
glRotatef(spin,0.0f,0.0f,1.0f);
// Rotate The Star On The Z Axis
// Assign A Color Using Bytes
glColor4ub(star[loop].r,star[loop].g,star[loop].b,255);
glBegin(GL_QUADS); // Begin Drawing The Textured Quad
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,-1.0f, 0.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,-1.0f, 0.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 0.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 0.0f);
glEnd(); // Done Drawing The Textured Quad
Und hier bewegen wir alles. Wir drehen die normalen Sterne indem wir den Wert
in "spin" erhöhen. Dann verändern wir die Neigung jedes Sterns. Die Neigung der
Sterne wird erhöht durch loop/ num. Das bewirkt das die weiter entfernten Sterne
sich schneller bewegen. Sterne nahe dem Zentrum bewegen sich langsamer. Und schließlich
verringen wir den Wert wie weit die Sterne vom Mittelpunkt entfernt sind. Das
bewirkt das es so aussieht als würden die Sterne in die Bildmitte gezogen.
spin+=0.01f;
// Used To Spin The Stars
star[loop].angle+=float(loop)/num;
// Changes The Angle Of A Star
star[loop].dist-=0.01f;
// Changes The Distance Of A Star
Die folgenden Zeilen überprüfen ob ein Stern im Zentrum ist oder nicht. Wenn ja
bekommt der Stern eine neue Farbe und wird 5 Einheiten vom Zentrum entfernt versetzt
so das er wieder als neuer Stern seine Reise antritt.
if (star[loop].dist<0.0f) // Is The Star In The Middle Yet
{
star[loop].dist+=5.0f; // Move The Star 5 Units From The Center
star[loop].r=rand()%256; // Give It A New Red Value
star[loop].g=rand()%256; // Give It A New Green Value
star[loop].b=rand()%256; // Give It A New Blue Value
}
}
}
Sucht jetzt die Zeile '(keys[VK_ESCAPE]) SendMessage(hWnd,WM_CLOSE,0,0)' und
ersetzt sie durch den unten stehenden Code.
Die neuen Zeilen dienen dazu zu überprüfen ob die T-Taste gedrückt wurde. Wenn
dies der Fall ist und sie nicht gedrückt gehalten wird passiert folgendes: wenn
twinkle FALSE ist wird es TRUE. Wenn es TRUE ist wird es FALSE. Sobald T gedrückt
wird ändert sich der Inhalt von tp auf TRUE. Dies stellt sicher das der Code
nciht dauernd ausgeführt wird solange der User die Taste gedrückt hält.
if (keys['T'] && !tp)
// Is T Being Pressed And Is tp FALSE
{
tp=TRUE;
// If So, Make tp TRUE
twinkle=!twinkle;
// Make twinkle Equal The Opposite Of What It Is
}
Der unten stehende Code überprüft, ob die T-Taste wieder los gelassen wurde. Wenn
ja wird tp = FALSE. T zu drücken macht also nichts bis die Taste wieder los gelassen
wurde.
if (!keys['T']) // Has The T Key Been Released
{
tp=FALSE; // If So, make tp FALSE
}
Der Rest des Codes überprüft, ob die Richtungstasten gedrückt werden.
if (keys[VK_UP]) // Is Up Arrow Being Pressed
{
tilt-=0.5f; // Tilt The Screen Up
}
if (keys[VK_DOWN]) // Is Down Arrow Being Pressed
{
tilt+=0.5f; // Tilt The Screen Down
}
if (keys[VK_PRIOR]) // Is Page Up Being Pressed
{
zoom-=0.2f; // Zoom Out
}
if (keys[VK_NEXT]) // Is Page Down Being Pressed
{
zoom+=0.2f; // Zoom In
}
}
}
In diesem Teil habe ich euch versucht zu erklären wie man ein Graustufen-Bitmap
einlädt, den schwarzen Rand mit Blending entfernt, Farbe hinzufügt und ein Bild
in einem 3D Raum bewegt. Ich habe euch auch gezeigt, wie man schöne Farben und
Animationen erzeugen kann indem man eine Kopie des Bildes über das Original
legt. Wenn ihr alles bis jetzt verstanden habt solltet ihr keine Probleme mehr
haben selbst 3D Demos zu schreiben. Alle Grundlagen sind durchgenommen!
Jeff Molofee (NeHe)
Die Source Codes und Ausführbaren Dateien zu den Kursen liegen auf der Neon
Helium Website
Übersetzung von Delax & ChaosAngel/ Sundancer
Inc.
(Präsentiert von www.codeworx.org)