Lektion 20: Masken
Willkommen zu Lektion 20. Das Bitmap-Format ist auf so ziemlich jedem Computer
und jedem Betriebssystem ein fester Standard. Das Format kann ohne Umwege geladen
und benutzt werden.
Bisher wurde immer das einfache blending genutzt um Text oder Bilder zu platzieren,
wenn sie teilweise durch andere Texturen überlagert wurden. Das ist effektiv,
sieht aber manchmal nicht sehr gut aus.
In einem Spiel zum Beispiel, das Sprites nutzt, sähe es nicht besonders
toll aus, wenn die Szene hinter der Spielfigur durch diese hindurchleuchtete.
Auch Text der vor anderen Bildern postiert wird, soll gut zu lesen sein, ohne
das dahinter ein schwarzer Streifen zu sehen ist.
Hier helfen Masken weiter, die als schwarz/weiss-Bilder vor die eigentlich
anzuzeigenden Texturen gelegt werden. Alle Bereiche des darunterliegenden Bildes,
die von schwarzen Bereichen der Maske bedeckt sind, werden transparent, alle
weissen Bereiche bleiben zu 100% sichtbar.
Wie immer wird der Code aus den ersten Lektionen modifiziert.
#include <windows.h> // Header File For Windows
#include <math.h> // Header File For Windows Math Library
#include <stdio.h> // Header File For Standard Input/Output
#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
HDChDC=NULL; // Private GDI Device Context
HGLRChRC=NULL; // Permanent Rendering Context
HWNDhWnd=NULL; // Holds Our Window Handle
HINSTANCEhInstance; // Holds The Instance Of The Application
Es werden 7 globale boolsche Variablen (TRUE oder FALSE) definiert. "masking"
hält fest, ob der Maskierungs-Modus an oder aus ist, mp speichert ob die
M-Taste gedrückt wurde, sp ist für die Leertase zuständig. Wie
man im Beispiel sehen kann soll es zwei Szenen geben, scene speichert welche
Szene gezeigt wird (Bei TRUE wird die zweite Szene ausgegeben, FALSE zeigt die
erste.). roll wird später noch für die Bewegung und Drehung der Szenen
genutzt.
bool keys[256]; // Array Used For The Keyboard Routine
bool active=TRUE; // Window Active Flag Set To TRUE By Default
bool fullscreen=TRUE;// Fullscreen Flag Set To Fullscreen Mode By Default
bool masking=TRUE; // Masking On/Off
bool mp; // M Pressed?
bool sp; // Space Pressed?
bool scene; // Which Scene To Draw
GLuint texture[5]; // Storage For Our Five Textures
GLuint loop; // Generic Loop Variable
GLfloat roll; // Rolling Texture
LRESULTCALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
// Declaration For WndProc
Der Code zum Laden der Bitmaps hat sich nicht geändert, einfach in Lektion
6 gucken oder gleich den Arbeitsbereich für Lektion 20 herrunterladen.
Es sollen 5 Texturen geladen werden, die Dateinamen geben ja Auskunft über
Sinn und Zweck der einzelnen Bilder.
int LoadGLTextures()// Load Bitmaps And Convert To Textures
{
int Status=FALSE;// Status Indicator
AUX_RGBImageRec *TextureImage[5];// Create Storage Space For The Texture Data
memset(TextureImage,0,sizeof(void *)*5);// Set The Pointer To NULL
if ((TextureImage[0]=LoadBMP("Data/logo.bmp")) && // Logo Texture
(TextureImage[1]=LoadBMP("Data/mask1.bmp")) && // First Mask
(TextureImage[2]=LoadBMP("Data/image1.bmp")) && // First Image
(TextureImage[3]=LoadBMP("Data/mask2.bmp")) && // Second Mask
(TextureImage[4]=LoadBMP("Data/image2.bmp"))) // Second Image
{
Status=TRUE;// Set The Status To TRUE
glGenTextures(5, &texture[0]);// Create Five Textures
for (loop=0; loop<5; loop++)// Loop Through All 5 Textures
{
glBindTexture(GL_TEXTURE_2D, texture[loop]);
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, TextureImage[loop]->sizeX, TextureImage[loop]->sizeY,
0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[loop]->data);
}
}
for (loop=0; loop<5; loop++)// Loop Through All 5 Textures
{
if (TextureImage[loop])// If Texture Exists
{
if (TextureImage[loop]->data)// If Texture Image Exists
{
free(TextureImage[loop]->data);// Free The Texture Image Memory
}
free(TextureImage[loop]);// Free The Image Structure
}
}
return Status;// Return The Status
}
ReSizeGLScene() ist so geblieben.
Die Init-Funktion ist recht einfach. Zuerst werden die Texturen geladen, dann
die Hintergrundfarbe eingestellt, das depth testing, das smooth shading und
das texture mapping aktiviert. Das Programm ist ja auch recht einfach, daher
die kurze Initialisierung ;).
int InitGL(GLvoid)// All Setup For OpenGL Goes Here
{
if (!LoadGLTextures())// Jump To Texture Loading Routine
{
return FALSE;// If Texture Didn't Load Return FALSE
}
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);// Clear The Background Color To Black
glClearDepth(1.0);// Enables Clearing Of The Depth Buffer
glEnable(GL_DEPTH_TEST);// Enable Depth Testing
glShadeModel(GL_SMOOTH);// Enables Smooth Color Shading
glEnable(GL_TEXTURE_2D);// Enable 2D Texture Mapping
return TRUE;// Initialization Went OK
}
Jetzt das Spannende, der Ausgabecode. Am Anfang das gewohnte Löschen des
Inhalts des letzten Frames. Die modelview matrix wird zurückgesetzt und
es geht zwei Einheiten in den Schirm, damit man was sieht.
int DrawGLScene(GLvoid)// Here's Where We Do All The Drawing
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// Clear The Screen And The Depth Buffer
glLoadIdentity();// Reset The Modelview Matrix
glTranslatef(0.0f,0.0f,-2.0f);// Move Into The Screen 2Units
Die erste Zeile unten wählt die Logo-Textur aus. Diese wird auf ein Rechteck
(GL_QUADS) gemappt. Man stelle sich die Fläche wie in den vorherigen Lektionen
als Quadrat vor, auf dem die Texturkoordinaten auf die definierten Ecken gelegt
werden. Die Ecke unten links hatte die Texturkoordinaten (0.0f, 0.0f), oben
links bekommt (0.0f, 1.0f), unten rechts (1.0, 0.0f) und schließlich oben
rechts (1.0f, 1.0f).
In dieser Lektion werden die Texturkoordinaten anders gelegt. Wenn die Werte
nämlich größer werden als 1.0f, wird die Textur mehrmals auf
die Fläche gemappt. Übergibt man die Texturkoordinaten (0.0f, 0.0f),
(0.0f, 3.0f), (3.0f, 0.0f) und (3.0f, 3.0f) dann wird die Textur 9 Mal auf die
Fläche projeziert.
Zusätzlich wird die Textur in Y-Richtung bewegt. Das passiert mit der
Variable "roll", die Werte zwischen 0.0f und 1.0f durchläuft.
glBindTexture(GL_TEXTURE_2D, texture[0]); // Select Our Logo Texture
glBegin(GL_QUADS); // Start Drawing A Textured Quad
glTexCoord2f(0.0f, -roll+0.0f); glVertex3f(-1.1f, -1.1f, 0.0f); // Bottom Left
glTexCoord2f(3.0f, -roll+0.0f); glVertex3f( 1.1f, -1.1f, 0.0f); // Bottom Right
glTexCoord2f(3.0f, -roll+3.0f); glVertex3f( 1.1f, 1.1f, 0.0f); // Top Right
glTexCoord2f(0.0f, -roll+3.0f); glVertex3f(-1.1f, 1.1f, 0.0f); // Top Left
glEnd(); // Done Drawing The Quad
Jetzt wird das blending aktiviert. Damit der Effekt funktioniert, muss das
depth testing unbedingt deaktiviert werden. Sonst gehts nicht ;)
glEnable(GL_BLEND);// Enable Blending
glDisable(GL_DEPTH_TEST);// Disable Depth Testing
Dann wird getestet ob der Maskierungseffekt überhaupt zu sehen sein soll:
if (masking)// Is Masking Enabled?
{
Die Maske ist im Prinzip ein schwarz/weisses-Abbild der Textur die angezeigt
werden soll (Einfach mal eins der beiden Bilder und die dazugehörige Maske
in einem Grafikprogramm anschauen!).
Das Blending-Kommando macht dann folgendes: Die Zielfarbe, also das was letztendlich
auf dem Bildschirm zu sehen ist, wird transparent wenn dieser Bereich der Maske
schwarz ist. Alle Bereiche die von weissen Gebieten der Maske überdseckt
werden, werden angezeigt.
glBlendFunc(GL_DST_COLOR,GL_ZERO);// Blend Screen Color With Zero (Black)
}
Welche Szene soll nun gezeichnet werden? Ist scene FALSE, die erste, andernfalls
die zweite.
if (scene)// Are We Drawing The Second Scene?
{
Damit nicht alles so groß aussieht, wird die Kamera noch ein Stück
in den Schirm geschoben. Aber auch eine Drehung wäre nicht schlecht, also
wird roll mit 360 multipliziert und schon gibt es eine volle Drehung.
glTranslatef(0.0f,0.0f,-1.0f);// Translate Into The Screen One Unit
glRotatef(roll*360,0.0f,0.0f,1.0f);// Rotate On The Z Axis 360 Degrees
Wenn der Maskierungs-Modus aktiviert ist, wird jetzt die zweite Maske geladen.
if (masking)// Is Masking On?
{
Jetzt wird die Maske 2 ausgegeben und später auf das Bild 2 angewandt:
glBindTexture(GL_TEXTURE_2D, texture[3]);// Select The Second Mask Texture
glBegin(GL_QUADS);// Start Drawing A Textured Quad
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.1f, -1.1f, 0.0f);// Bottom Left
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.1f, -1.1f, 0.0f);// Bottom Right
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.1f, 1.1f, 0.0f); // Top Right
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.1f, 1.1f, 0.0f); // Top Left
glEnd();// Done Drawing The Quad
}
Jetzt muss das Blending wieder verändert werden. OpenGL wird mitgeteilt,
dass alle Stücke der bunten Textur, die NICHT von schwarzen Teilen der
Maske überlagert werden ausgegeben werden sollen.
glBlendFunc(GL_ONE, GL_ONE);// Copy Image 2 Color To The Screen
glBindTexture(GL_TEXTURE_2D, texture[4]);// Select The Second Image Texture
glBegin(GL_QUADS);// Start Drawing A Textured Quad
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.1f, -1.1f, 0.0f);// Bottom Left
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.1f, -1.1f, 0.0f);// Bottom Right
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.1f, 1.1f, 0.0f);// Top Right
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.1f, 1.1f, 0.0f);// Top Left
glEnd();// Done Drawing The Quad
}
Die erste Szene soll gerendert werden:
else// Otherwise
{
Ist der Maskierungs-Modus aktiviert?
if (masking)// Is Masking On?
{
Diesmal wird die erste Maske genutzt:
glBindTexture(GL_TEXTURE_2D, texture[1]);// Select The First Mask Texture
glBegin(GL_QUADS);// Start Drawing A Textured Quad
glTexCoord2f(roll+0.0f, 0.0f); glVertex3f(-1.1f, -1.1f, 0.0f);// Bottom Left
glTexCoord2f(roll+4.0f, 0.0f); glVertex3f( 1.1f, -1.1f, 0.0f);// Bottom Right
glTexCoord2f(roll+4.0f, 4.0f); glVertex3f( 1.1f, 1.1f, 0.0f);// Top Right
glTexCoord2f(roll+0.0f, 4.0f); glVertex3f(-1.1f, 1.1f, 0.0f);// Top Left
glEnd();// Done Drawing The Quad
}
Diesmal wird die erste farbige Textur benutzt:
glBlendFunc(GL_ONE, GL_ONE);// Copy Image 1 Color To The Screen
glBindTexture(GL_TEXTURE_2D, texture[2]);// Select The First Image Texture
glBegin(GL_QUADS);// Start Drawing A Textured Quad
glTexCoord2f(roll+0.0f, 0.0f); glVertex3f(-1.1f, -1.1f, 0.0f);// Bottom Left
glTexCoord2f(roll+4.0f, 0.0f); glVertex3f( 1.1f, -1.1f, 0.0f);// Bottom Right
glTexCoord2f(roll+4.0f, 4.0f); glVertex3f( 1.1f, 1.1f, 0.0f); // Top Right
glTexCoord2f(roll+0.0f, 4.0f); glVertex3f(-1.1f, 1.1f, 0.0f); // Top Left
glEnd();// Done Drawing The Quad
}
Um wieder zur "normalen" Ausgabe zurückzukehren, wird das depth
testing wieder aktiviert und das blending deaktiviert.
glEnable(GL_DEPTH_TEST);// Enable Depth Testing
glDisable(GL_BLEND); // Disable Blending
Damit Bewegung in die ganze Sache kommt, wird roll bei jedem Frame ein wenig
erhöht und wenn es größer als 1.0f werden sollte, wieder auf
0.0f zurückgesetzt (Indem -1.0f abgezogen wird).
roll+=0.002f;// Increase Our Texture Roll Variable
if (roll>1.0f)// Is Roll Greater Than One
{
roll-=1.0f;// Subtract 1 From Roll
}
return TRUE;// Everything Went OK
}
KillGLWindow(), CreateGLWindow() und WndProc() bleiben so wie sie sind.
In der WinMain müssen noch ein paar Änderungen vorgenomen werden,
damit die Benutzereingaben entgegengenommen werden sollen:
int WINAPI WinMain(HINSTANCEh Instance, // Instance
HINSTANCEh PrevInstance,// Previous Instance
LPSTR lpCmdLine, // 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 Masking 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)
Hier werden die Eingaben behandelt (Das Prinzip sollte noch aus vorhergegangenen
Lektionen bekannt sein!). scene wird dabei umgekehrt (Aus TRUE wird FALSE, aus
FALSE wird TRUE)
if (keys[' '] && !sp)// Is Space Being Pressed?
{
sp=TRUE; // Tell Program Spacebar Is Being Held
scene=!scene; // Toggle From One Scene To The Other
}
Der gleiche Spaß wird auch mit der Leertaste gemacht:
if (!keys[' '])// Has Spacebar Been Released?
{
sp=FALSE;// Tell Program Spacebar Has Been Released
}
Der Benutzer soll auch den Maskierungs-Modus an und abstellen können (Mit
"M"):
if (keys['M'] && !mp)// Is M Being Pressed?
{
mp=TRUE;// Tell Program M Is Being Held
masking=!masking;// Toggle Masking Mode OFF/ON
}
Das gehört noch zur Tastenroutine zu "M":
if (!keys['M'])// Has M Been Released?
{
mp=FALSE;// Tell Program That M Has Been Released
}
Hier bleibt alles beim alten!
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 Masking Tutorial",640,480,16,fullscreen))
{
return 0;// Quit If Window Was Not Created
}
}
}
}
}
// Shutdown
KillGLWindow();// Kill The Window
return (msg.wParam);// Exit The Program
}
Ich hoffe Ihr hattet Spaß an diesem Tutorial, Maskierungen sind ja in
der Grafik ein wirklich wichtige Thema.
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 modifiziert von Hans-Jakob Schwer 05.12.2k2, www.codeworx.org