www.codeworx.org/opengl-tutorials/Tutorial 20: Masken

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