www.codeworx.org/opengl-tutorials/Tutorial 9: Bitmaps in einer 3D Umgebung

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)