NeHe - Lektion 29 - Blitter Funktion, .RAW-Dateien laden

Lektion 29



Diese Tutorial wurde ursprünglich von Andreas Löffler geschrieben. Es hat zudem auch den ganzen HTML Kram für dieses Tutorial geschrieben. Ein paar Tage später hat mir Rob Fletcher eine Irix Version von Lektion 29 gemailt. In seiner Version hat er den größten Teil des Codes umgeschrieben. Deshalb habe ich Rob's Irix / GLUT Code nach Visual C++ / Win 32 portiert. Ich habe dann den Code der Nachrichtenschleife und des Fullscreen Codes modifziert. Wenn das Programm minimiert ist, sollte es 0% der CPU (oder fast so wenig) nutzen. Beim hin und her wechseln zum Fullscreen Modus sollten die meisten Probleme beseitigt worden sein (Screen wird nicht wieder richtig hergestellt, vermurkste Anzeige, etc).

Andreas Tutorial ist nun besser denn je. Unglücklicherweise hat sich der Code ein wenig geändert, so dass der HTML Kram dazu von mir komplett neugeschrieben wurde. Vielen Dank an Andreas, der den Ball ins rollen gebracht hat und sich einen Wolf gearbeitet hat, um dieses Killer-Tutorial zu machen. Dank an Rob für die Modifizierungen!

Lassen Sie uns beginnen... Wir erzeugen eine Device Modus Struktur namens DMsaved. Wir werden diese Struktur zum speichern der Informationen über die Benutzer Desktop Standard-Auflösung verwenden, wie Farbtiefe, etc., bevor wir zum Fullscreen Modus wechseln. Mehr darüber später! Beachten Sie, dass wir nur Speicherplatz für eine Textur alloziieren (texture[1]).

#include    <windows.h>                                // Header Datei für Windows
#include    <gl\gl.h>                                // Header Datei für die OpenGL32 Library
#include    <gl\glu.h>                                // Header Datei für die GLu32 Library
#include    <stdio.h>                                // Header Datei für Datei Operationen

HDC        hDC=NULL;                                // Privater GDI Device Context
HGLRC        hRC=NULL;                                // Permanenter Rendering Context
HWND        hWnd=NULL;                                // Enthält unser Fenster-Handle
HINSTANCE    hInstance = NULL;                            // Enthält die Instanz der Applikation

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

DEVMODE        DMsaved;                                // speichert die vorherigen Bildschirm Einstellungen (NEU)

GLfloat        xrot;                                    // X Rotation
GLfloat        yrot;                                    // Y Rotation
GLfloat        zrot;                                    // Z Rotation

GLuint        texture[1];                                // Speicherplatz für 1 Textur

Nun zum spaßigen Teil. Wir erzeugen eine Struktur namens TEXTURE_IMAGE. Die Struktur enthält Informationen über unsere Bild-Breite, Höhe und Format (Bytes pro Pixel). data ist ein Zeiger auf ein unsigned char. Später wird data auf unsere Image Daten zeigen.

typedef struct Texture_Image
{
    int width;                                    // Breite des Bildes in Pixeln
    int height;                                    // Hähe des Boldes in Pixeln
    int format;                                    // Anzahl der Bytes pro Pixel
    unsigned char *data;                                // Textur Daten
} TEXTURE_IMAGE;

Wir erzeugen dann einen Zeiger namens P_TEXTURE_IMAGE auf den TEXTURE_IMAGE Datentyp. Die Variablen t1 und t2 sind vom Typen P_TEXTURE_IMAGE, wobei P_TEXTURE_IMAGE eine redefinierte Art Zeiger auf TEXTURE_IMAGE ist.

typedef TEXTURE_IMAGE *P_TEXTURE_IMAGE;                            // Ein Zeiger auf den Textur Image-Datentyp

P_TEXTURE_IMAGE t1;                                    // Zeiger auf den Textur Image Datentyp
P_TEXTURE_IMAGE t2;                                    // Zeiger auf den Textur Image Datentyp

LRESULT    CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);                    // Deklaration für WndProc

Es folgt der Code um den Speicher für eine Textur zu alloziieren. Wenn wir diesen Code aufrufen, übergeben wir Breite, Höhe und Bytes pro Pixel des Bildes, das wir laden wollen. ti ist ein Zeiger auf unseren TEXTURE_IMAGE Datentyp. Ihm wird ein NULL Wert übergeben. c ist ein Zeiger auf unsigned char und wird ebenfalls auf NULL gesetzt.

// alloziiere eine Image-Struktur und alloziiere darin den benötigten Speicher
P_TEXTURE_IMAGE AllocateTextureBuffer( GLint w, GLint h, GLint f)
{
    P_TEXTURE_IMAGE ti=NULL;                            // Zeiger auf Image Struct
    unsigned char *c=NULL;                                // Zeiger auf Speicherblock für's Image

Hier alloziieren wir den Speicher für unsere Image-Struktur. Wenn alles glatt verläuft, zeigt ti auf den alloziierten Speicher.

Nachdem der Speicher alloziiert wurde und sichergestellt wurde, dass ti nicht gleich NULL ist, können wir die Struktur mit den Bild-Attributen füllen. Als erstes setzen wir die Breite (w), dann die Höhe (h) und zu letzt das Format (f). Behalten Sie im Hinterkopf, dass format die Bytes pro Pixel ist.

    ti = (P_TEXTURE_IMAGE)malloc(sizeof(TEXTURE_IMAGE));                // eine Image Struktur bitte

    if( ti != NULL ) {
        ti->width  = w;                                // Setze Breite
        ti->height = h;                                // Setze Höhe
        ti->format = f;                                // Setze  Format

Nun müssen wir Speicher für die eigentlichen Bild-Daten alloziieren. Die Berechnung ist einfach! Wir multiplizieren die Breite des Bilds (w) mit der Höhe (h) und multiplizieren dann mit format (f - Bytes pro Pixel).

        c = (unsigned char *)malloc( w * h * f);

Wir überprüfen, ob alles glatt verlief. Wenn der Wert in c ungleich NULL ist, setzen wir die data Variable in unserer Struktur, so dass diese auf den alloziierten Speicher zeigt.

Falls es ein Problem gab, zeigen wir eine Fehlermeldung an, die den Benutzer wissen lässte, dass das Programm keinen Speicher für den Textur-Buffer alloziieren konnte. Zurückgegeben wird NULL.

        if ( c != NULL ) {
            ti->data = c;
        }
        else {
            MessageBox(NULL,"Could Not Allocate Memory For A Texture Buffer","BUFFER ERROR",MB_OK | MB_ICONINFORMATION);
            return NULL;
        }
    }

Wenn irgend etwas schief lief, als wir versucht haben, Speicher für unsere Image Struktur zu alloziieren, wird der folgende Code eine Fehlermeldung anzeigen und NULL zurückgeben.

Wenn es keine Probleme gab, geben wir ti zurück, was der Zeiger auf unsere gerade alloziierte Image-Struktur ist. Hui... Ich hoffe das alles macht Sinn.

    else
    {
        MessageBox(NULL,"Could Not Allocate An Image Structure","IMAGE STRUCTURE ERROR",MB_OK | MB_ICONINFORMATION);
        return NULL;
    }
    return ti;                                    // gebe Zeiger auf Image Struct zurück
}

Wenn es Zeit ist, den Speicher wieder freizugeben, wird der folgende Code den Textur-Buffer de-alloziieren und dann die Image-Struktur freigeben. t ist ein Zeiger auf die TEXTURE_IMAGE Daten-Struktur, die wir de-alloziieren wollen.

// Gebe Image Daten frei
void DeallocateTexture( P_TEXTURE_IMAGE t )
{
    if(t)
    {
        if(t->data)
        {
            free(t->data);                            // gebe seinen Image Buffer frei
        }

        free(t);                                // gebe den Rest frei
    }
}

Nun lesen wir unser .RAW Image ein. Wir üebergeben den Dateinamen und einen Zeiger auf die Image-Struktur, in die wir das Bild laden wollen. Wir setzen diverse Variablen und berechnen dann die Größe einer Zeile. Wir finden die Größe einer Zeile heraus, indem wir die Breite unseres Bildes mit format (Bytes pro Pixel) multiplizieren. Wenn das Bild also 256 Pixel breit wäre und es 4 Bytes pro Pixel geben würde, wäre die Größe einer Zeile 1024 Bytes. Wir speichern die Breite einer Zeile in stride.

Wir initialsieren einen Zeiger (p) und versuchen dann die Datei zu öffnen.

// lese eine .RAW Datei in den alloziierten Image Buffer ein, unter der Benutzung der Daten im Image Struktur Header.
// Flippe das Image von unten nach oben. Gebe 0 für einen Fehler beim Lesen zurück oder die Anzahl gelesener Bytes.
int ReadTextureData ( char *filename, P_TEXTURE_IMAGE buffer)
{
    FILE *f;
    int i,j,k,done=0;
    int stride = buffer->width * buffer->format;                    // Größe einer Zeile (Breite * Bytes pro Pixel)
    unsigned char *p = NULL;

    f = fopen(filename, "rb");                            // öffne "filename" um Bytes zu lesen
    if( f != NULL )                                    // wenn Datei existiert
    {

Wenn die Datei existiert, fangen wir mit den Schleifen an, um unsere Textur einzulesen. i beginnt am unteren Rand des Bildes und bewegt sich jedes Mal um eine Zeile höher. Wir starten von unten, so dass das Bild gleich richtig gedreht wird. .RAW Bilder werden auf dem Kopf gespeichert. Wir müssen nun unseren Zeiger setzen, so dass die Daten an die richtige Stelle in den Buffer geladen werden. Jedes Mal, wenn wir eine Zeile weiter gehen (i dekrementiert wird), setzen wir den Zeiger auf den Anfang einer neuen Zeile. Bei data beginnt unser Image-Buffer und um sich eine gesamte Zeile auf einmal im Buffer zu bewegen, multiplizieren Sie i mit stride. Wenn Sie sich dran erinnern ist stride die Länge einer Zeile in Bytes und i ist die aktuelle Zeile. Indem wir also die beiden miteinander multiplizieren, bewegen wir uns um eine gesamte Zeile.

Die j Schleife bewegt sich von links (0) nach rechts (Breite der Zeile in Pixel, nicht Bytes).

        for( i = buffer->height-1; i >= 0 ; i-- )                // durchlaufe die Höhe (von unten nach oben - Flippe Image)
        {
            p = buffer->data + (i * stride );
            for ( j = 0; j <buffer->width ; j++ )                // durchlaufe die Breite
            {

Die k Schleife liest unsere Bytes pro Pixel ein. Wenn format (Bytes pro Pixel) gleich 4 ist, wird k von 0 bis 2 durchlaufen, was die Bytes pro Pixel minus 1 (format-1) ist. Der Grund, warum wir 1 subtrahieren, ist der, dass die meisten RAW Images keinen Alpha-Wert haben. Wir wollen, dass das 4te Byte unser Alpha Wert ist und wir wollen den Alpha Wert manuell setzen.

Beachten Sie, dass wir innerhalb der Schleife auch den Zeiger (p) inkrementieren und eine Variable namens done. Mehr über done später.

Die Zeile innerhalb der Schleife liest ein Zeichen aus unserer Datei und speichert es in dem Textur Buffer an unseren aktuellen Zeiger-Position. Wenn unser Image 4 Bytes pro Pixel hat, werden die ersten 3 Bytes aus der .RAW Datei eingelesen (format-1) und das 4te Byte wird manuell auf 255 gesetzt. Nachdem wir das 4te Byte auf 255 gesetzt haben, inkrementieren wir den Zeiger um eins, so dass unser 4tes Byte nicht mit dem nächsten Byte aus der Datei überschrieben wird.

Nachdem alle Bytes pro Pixel eingelesen wurden und alle Pixel pro Zeile eingelesen wurden und alle Zeile eingelesen wurden, sind wir fertig! Wir können die Datei schließen.

                for ( k = 0 ; k <buffer->format-1 ; k++, p++, done++ )
                {
                    *p = fgetc(f);                    // lese Wert aus der Datei und speicher diesen im Speicher
                }
                *p = 255; p++;                        // Speichere 255 im Alpha Channel und inkrementiere Zeiger
            }
        }
        fclose(f);                                // schließe die Datei
    }

Wenn es ein Problem beim Öffnen der Datei gab (existierte nicht, etc.), wird durch den folgenden Code eine Message Box angezeigt, die den Benutzer wissen lässt, dass die Datei nicht geöffnet werden konnte.

Als letztes geben wir done zurück. Wenn die Datei nicht geöffnet werden konnte, wird done gleich 0 sein. Wenn alles Ok war, sollte done gleich der Zahl der aus der Datei ausgelesenen Bytes sein. Erinnern Sie sich daran, dass wir done bei jedem ausgelesenen Byte in der obigen Schleife (k Schleife) inkrementiert haben.

    else                                        // ansonsten
    {
        MessageBox(NULL,"Unable To Open Image File","IMAGE ERROR",MB_OK | MB_ICONINFORMATION);
    }
    return done;                                    // gebe die Anzahl der gelesenen Bytes zurück
}

Dies müsste nicht mehr erklärt werden. Sie wissen bereits, wie man Texturen erzeugt. tex ist der Zeiger auf die TEXTURE_IMAGE Struktur, die wir benutzen wollen. Wir erzeugen eine linear gefilterte Textur. In diesem Beispiel erzeugen wir MipMaps (sieht weicher aus). Wir übergeben Breite (width), Höhe (height) und data, genauso, als wenn wir glaux verwenden würden, aber diesmal erhalten wir die Informationen aus der ausgewählten TEXTURE_IMAGE Struktur.

void BuildTexture (P_TEXTURE_IMAGE tex)
{
    glGenTextures(1, &texture[0]);
    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);
    gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, tex->width, tex->height, GL_RGBA, GL_UNSIGNED_BYTE, tex->data);
}

Nun zum Blitter Code :) Der Blitter Code ist sehr mächtig. Er lässt Sie jeglichen Abschnitt einer Textur (src) kopieren und in eine Ziel Textur (dst) einfügen. Sie können so viele Texturen miteinander kombinieren wie Sie wollen, Sie können den Alpha Wert für Blending setzen und Sie können auswählen, ob die Bilder ineinander übergehen oder sich gegenseitig ausblenden.

src ist die TEXTURE_IMAGE Struktur, die als Quell Bild verwendet wird. dst ist die TEXTURE_IMAGE Struktur die als Ziel-Bild verwendet wird. src_xstart ist die Stelle auf der X-Achse im Quellbild, ab wo Sie mit dem Kopieren anfangen wollen. src_ystart ist die Stelle auf der Y-Achse im Quellbild, ab wo Sie mit dem Kopieren anfangen wollen. src_height ist die Höhe der zu kopierenden Fläche in Pixel. dst_xstart und dst_ystart ist die Stelle, wo Sie die Pixel vom Quell-Bild in das Ziel-Bild hinkopieren wollen. Wenn blend gleich 1 ist, werden die zwei Bilder ineinander geblendet. alpha gibt an, wie transparent das kopierte Bild sein wird, wenn es auf das Ziel-Bild gemapped wird. 0 ist komplett durchsichtig und 255 massiv.

Wir setzen unsere diversen Schleifen-Variablen, zusammen mit den Zeigern für das (die) Quell-Bild(er) und Ziel-Bild(er). Wir überprüfen, ob der Alpha-Wert innerhalb des Gültigkeitsbereiches liegt. Wenn nicht, passen wir ihn an. Das selbe machen wir mit dem blend Wert. Wenn er nicht 0-aus oder 1-an ist, passen wir ihn an.

void Blit( P_TEXTURE_IMAGE src, P_TEXTURE_IMAGE dst, int src_xstart, int src_ystart, int src_width, int src_height,
       int dst_xstart, int dst_ystart, int blend, int alpha)
{
    int i,j,k;
    unsigned char *s, *d;                                // Quelle & Ziel

    // korrigiere Alpha wenn Wert außerhalb der Grenze liegt
    if( alpha > 255 ) alpha = 255;
    if( alpha <0 ) alpha = 0;

    // überprüfe auf inkorrekte Blend Flag Werte
    if( blend <0 ) blend = 0;
    if( blend > 1 ) blend = 1;

Nun müssen wir unsere Zeiger setzen. Der Ziel-Zeiger ist der Ort der Ziel Daten plus des Startpunkts auf der Y-Achse des Ziel-Bildes (dst_ystart) * die Ziel-Bild-Breite in Pixel * die Bytes pro Pixel des Ziel-Bildes (format). Das sollte uns die Startzeile unseres Ziel-Bildes liefern.

Wir machen so ziemlich das Selbe für den Quell-Zeiger. Der Quell-Zeiger ist der Ort der Quell-Daten plus des Startpunktes auf der Y-Achse des Quell-Bildes (src_ystart) * die Quell-Bild-Breite in Pixeln * die Bytes pro Pixel des Quell-Bildes (format). Das sollte uns die Startzeile unseres Quell-Bildes liefern.

i wird von 0 bis src_height durchlaufen, welches die Anzahl der zu kopierenden Pixel des Quell-Bildes ist.

    d = dst->data + (dst_ystart * dst->width * dst->format);              // Start Zeile - dst (Zeile * Breite in Pixel * Bytes Pro Pixel)
    s = src->data + (src_ystart * src->width * src->format);            // Start Zeile - src (Zeile * Breite In Pixels * Bytes Pro Pixel)

    for (i = 0 ; i <src_height ; i++ )                        // Höhen-Schleife
    {

Wir haben die Quell- und Ziel-Zeiger auf die korrekten Zeilen in den jeweiligen Bildern gesetzt. Nun müssen wir uns zum korrekten Ort von links nach rechts in jedem Bild bewegen, bevor wir mit dem blitten der Daten anfangen können. Wir inkrementieren den Ort des Quell-Zeiger (s) um src_xstart welches der Startpunkt auf der X-Achse des Quell-Bildes mal die Bytes pro Pixel des Quell-Bildes ist. Das bewegt den Quell-Zeiger (s) zum Start-Pixel auf der X-Achse (von links nach rechts) im Quell-Bild.

Wir machen genau das Selbe für den Ziel-Zeiger. Wir inkrementieren den Ort des Ziel-Zeiger (d) um dst_xstart welches der Startpunkt auf der X-Achse des Ziel-Bildes mal die Bytes pro Pixel des Ziel-Bildes ist. Das bewegt den Ziel-Zeiger (d) zum Start-Pixel auf der X-Achse (von links nach rechts) im Ziel-Bild.

Nachdem wir berechnet haben, von wo wir im Speicher unsere Pixel herholen wollen (s) und wohin wir sie bewegen wollen (d), fangen wir mit der j-Schleife an. Wir werden die j-Schleife verwenden, um uns von links nach rechts durch das Quell-Bild zu bewegen.

        s = s + (src_xstart * src->format);                    // bewege durch Src Daten um Bytes Pro Pixel
        d = d + (dst_xstart * dst->format);                    // bewege durchDst Daten um Bytes Pro Pixel
        for (j = 0 ; j <src_width ; j++ )                    // Breiten-Schleife
        {

Die k-Schleife wird dazu verwendet, um durch alle Bytes pro Pixel zu iterieren. Beachten Sie, dadurch dass k inkrementiert, sich auch unsere Zeiger für das Quell- und Ziel-Bild inkrementieren.

Innerhalb der Schleife überprüfen wir, ob Blending ein- oder ausgeschaltet ist. Wenn blend gleich 1 ist, bedeutet das, dass wir blenden sollen, wir verwenden dazu etwas trickreiche Mathematik, um die Farbe unseres geblendeten Pixels zu berechnen. Der Ziel-Wert (d) wird gleich unseres Quell-Wertes (s) multipliziert mit unserem Alpha Wert + unserem aktuellen Ziel-Wert (d) mal 255 minus des Alpha-Wertes. Der Shift-Operator (>>8) lässt den Wert innerhalb der Grenze von 0-255 sein.

Wenn Blending deaktiviert ist (0), kopieren wir die Daten aus dem Quell-Bild direkt in unser Ziel-Bild. Es wird kein Blending vorgenommen und der Alpha-Wert wird ignoriert.

            for( k = 0 ; k <src->format ; k++, d++, s++)            // "n" Bytes auf einmal
            {
                if (blend)                        // wenn Blending eingeschaltet ist
                *d = ( (*s * alpha) + (*d * (255-alpha)) ) >> 8;    // Multipliziere Src Daten*Alpha addiere Dst Daten*(255-alpha)
                else                            // in der 0-255 Grenze bleiben mittels >> 8
                *d = *s;                        // kein Blending einfach nur eine einfache Kopie
            }
        }
        d = d + (dst->width - (src_width + dst_xstart))*dst->format;        // füge Ende der Zeile an
        s = s + (src->width - (src_width + src_xstart))*src->format;        // füge Ende der Zeile an
    }
}

Der InitGL() Code hat sich etwas geändert. Der gesamte folgende Code ist neu. Wir fangen damit an, genügend Speicher zu reservieren, um ein 256x256x4 Bytes Pro Pixel Image aufzunehmen. t1 wird auf den reservierten Speicher zeigen, wenn alles glatt läuft.

Nachdem der Speicher für unser Bild reserviert wurde, versuchen wir das Bild zu laden. Wir übergeben ReadTextureData() den Namen der Datei, die wir öffnen wollen, zusammen mit dem Zeiger auf unsere Image Struktur (t1).

Wenn wir das .RAW Image nicht laden konnten, wird eine Message Box angezeigt, die den Benutzer wissen lässt, dass es ein Problem beim Laden der Textur gab.

Wir machen das Selbe für t2. Wir reservieren Speicher und versuchen unser zweites .RAW Bild einzulesen. Wenn etwas schief läuft, zeigen wir eine Message Box an.

int InitGL(GLvoid)                                    // Dies wird direkt nachdem das GL Fenster erzeugt wurde, aufgerufen
{
    t1 = AllocateTextureBuffer( 256, 256, 4 );                    // hole eine Image Struktur
    if (ReadTextureData("Data/Monitor.raw",t1)==0)                    // Fülle die Image Struktur mit Daten
    {                                        // nichts gelesen?
        MessageBox(NULL,"Could Not Read 'Monitor.raw' Image Data","TEXTURE ERROR",MB_OK | MB_ICONINFORMATION);
        return FALSE;
    }

    t2 = AllocateTextureBuffer( 256, 256, 4 );                    // zweite Image Struktur
    if (ReadTextureData("Data/GL.raw",t2)==0)                    // fülle die Image Struktur mit Daten
    {                                        // nichts gelesen?
        MessageBox(NULL,"Could Not Read 'GL.raw' Image Data","TEXTURE ERROR",MB_OK | MB_ICONINFORMATION);
        return FALSE;
    }

Wenn wir soweit gekommen sind, kann man davon ausgehen, dass der Speicher alloziiert wurde und die Bilder geladen wurden. Nun verwenden wir unseren Blit() Befehl um die zwei Bilder in eins zu verschmelzen.

Wir fangen damit an, Blit() t2 und t1 zu übergeben, beide zeigen auf unsere TEXTURE_IMAGE Strukturen (t2 ist das zweite Bild, t1 ist das erste Bild).

Dann müssen wir Blit mitteilen, wo angefangen werden soll, die Daten aus dem Quell-Bild zu ermitteln. Wenn Sie das Quell-Bild in Adobe Photshop oder einem anderen Programm laden, welches .RAW Bilder anzeigen kann, werden Sie sehen, dass das gesamte Bild leer ist, außer der oberen rechten Ecke. Die obere rechte Ecke enthält das Bild eines Balls, in dem GL geschrieben steht. Die untere linke Seite des Bildes ist 0,0. Die obere rechte des Bildes ist die Breite des Bildes-1 (255), die Höhe des Bildes-1 (255). Mit diesem Wissen, wollen wir nur 1/4 des Quell-Bildes kopieren (oben rechts), wir teilen Blit() mit, bei 127,127 anzufangen (Zentrum unseres Quell-Bildes).

Als nächstes teilen wir Blit mit, wie viele Pixel wir von unserem Quell-Punkt bis nach rechts kopiert haben wollen und von unserem Quell-Punkt nach oben. Wir wollen 1/4 des Bildes haben. Unser Bild ist 256x256 Pixel groß, 1/4 davon sind 128x128 Pixel. Alle Quell-Informationen sind zusammengetragen. Blit() weiß nun, dass es von 127 auf der X-Achse bis 12+128 (255) azf der X-Achse kopieren soll und von 127 auf der Y-Achse bis 127+128(255) auf der Y-Achse.

Da Blit() weiß, was kopiert werden soll und woher die Daten kommen sollen, fehlt nur noch, wohin die Daten hinkopiert werden sollen, wenn diese erst einmal ermittelt wurden. Wir wollen den Ball, in dem GL drin steht, in die Mitte des Monitor-Bildes kopieren. Sie finden den Mittelpunkt des Ziel-Bildes (256x256) welches 128x128 minus die Hälfte der Breite und Höhe des Quell-Bildes, was 64x64 ist. Daraus ergibt sich (128-64) x (128-64) gibt uns den Startpunkt 64,64.

Als letztes müssen wir unser Blitter-Routine mitteilen, dass wir die beiden Bilder blenden wollen (eine eins bedeutet blenden, eine null bedeutet nicht blenden), und um wieviel die Bilder geblendet werden sollen. Wenn der letzte Wert gleich 0 ist, blenden wir die Bilder zu 0%, was bedeutet, dass alles was wir kopieren, das ersetzen wird, was schon da war. Wenn wir einen Wert von 127 verwenden, werden die beiden Bilder zu 50% zusammen geblendet und wenn Sie 255 verwenden, wird das Bild, das Sie kopieren komplett transparent und überhaupt nicht sichtbar sein.

Die Pixel werden von image2 (t2) nach image1 (t1) kopiert. Das gemischte Bild wird in t1 gespeichert.

    // Image in das geblendet werden soll, Original Image, Src Start X & Y, Src Breite & Höhe, Dst Location X & Y, Blend Flag, Alpha Wert
    Blit(t2,t1,127,127,128,128,64,64,1,127);                    // rufe dieBlitter Routine auf

Nachdem wir die beiden Bilder (t1 und t2) zusammengemischt haben, erzeugen wir eine Textur aus den kombinierten Bildern (t1).

Nachdem die Textur erzeugt wurde, können wir den Speicher de-alloziieren, der unsere zwei TEXTURE_IMAGE Strukturen enthält.

Der Rest des Codes ist so ziemlich Standard. Wir aktivieren Textur-Mapping, Depth-Testing, etc.

    BuildTexture (t1);                                // Lade die Textur Map in den Textur Speicher

    DeallocateTexture( t1 );                            // lösche den Image Speicher, da die Textur nun
    DeallocateTexture( t2 );                            // im GL Textur Speicher ist

    glEnable(GL_TEXTURE_2D);                            // aktiviert Textur Mapping

    glShadeModel(GL_SMOOTH);                            // aktiviert Smooth Color Shading
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);                        // dies löscht Schwarzer den Hintergrund auf schwarz
    glClearDepth(1.0);                                // aktiviert die Löschung des Depth Buffers
    glEnable(GL_DEPTH_TEST);                            // aktiviert Depth Testing
    glDepthFunc(GL_LESS);                                // Die Art des auszuführenden Depth Test

    return TRUE;
}

Ich müsste den folgende Code eigentlich nicht erklären. Wir bewegen uns 5 Einheiten in den Screen hinein, wähle unsere einzelne Textur aus und zeichnen einen texturierten Würfel. Sie sollten bemerken, dass nun beide Texturen in einer vereint sind. Wir müssen nicht alles zweimal rendern, um beide Texturen auf den Würfel zu mappen. Der Blitter-Code hat die Bild für uns vereint.

GLvoid DrawGLScene(GLvoid)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);                // Lösche den Bildschirm und den Depth-Buffer
    glLoadIdentity();                                // Resette die View
    glTranslatef(0.0f,0.0f,-5.0f);

    glRotatef(xrot,1.0f,0.0f,0.0f);
    glRotatef(yrot,0.0f,1.0f,0.0f);
    glRotatef(zrot,0.0f,0.0f,1.0f);

    glBindTexture(GL_TEXTURE_2D, texture[0]);

    glBegin(GL_QUADS);
        // vordere Seite
        glNormal3f( 0.0f, 0.0f, 1.0f);
        glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);
        glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);
        glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);
        glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);
        // hintere Seite
        glNormal3f( 0.0f, 0.0f,-1.0f);
        glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);
        glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
        glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
        glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
        // obere Seite
        glNormal3f( 0.0f, 1.0f, 0.0f);
        glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
        glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);
        glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,  1.0f,  1.0f);
        glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,  1.0f,  1.0f);
        // untere Seite
        glNormal3f( 0.0f,-1.0f, 0.0f);
        glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);
        glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);
        glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
        glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
        // rechte Seite
        glNormal3f( 1.0f, 0.0f, 0.0f);
        glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
        glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
        glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);
        glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);
        // linke Seite
        glNormal3f(-1.0f, 0.0f, 0.0f);
        glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
        glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);
        glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);
        glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);
    glEnd();

    xrot+=0.3f;
    yrot+=0.2f;
    zrot+=0.4f;
}

Der KillGLWindow() Code hat einige Änderungen erfahren. Sie werden bemerken, dass der Code zum Wechseln vom Fullscreen Modus zu Ihrem Desktop jetzt am Anfang von KillGLWindow() ist. Wenn der Benutzer das Programm im Fullscreen-Modus laufen lies, ist das Erste was wir machen, wenn wir das Fenster zerstören, wir versuchen zurück zur Desktop-Auflösung zu wechseln. Wenn die schnelle Methode nicht funktioniert, resetten wir den Screen und benutzen die Informationen die in DMsaved gespeichert sind. Das sollte unsere original Desktop-Einstellungen wieder herstellen.

GLvoid KillGLWindow(GLvoid)                                // Entferne das Fenster korrekt
{
    if (fullscreen)                                    // Sind wir im Fullscreen Modus?
    {
        if (!ChangeDisplaySettings(NULL,CDS_TEST)) {                // wenn der Shortcut nicht funktioniert
            ChangeDisplaySettings(NULL,CDS_RESET);                // mache es trotzdem (um die Werte aus der Registry zu holen)
            ChangeDisplaySettings(&DMsaved,CDS_RESET);            // ändere sie auf die gespeicherten Einstellungen
        }
        else                                    // kein Fullscreen
        {
            ChangeDisplaySettings(NULL,CDS_RESET);                // mache nichts
        }

        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
    }
}

Ich habe einige Änderungen in CreateGLWindow gemacht. Die Änderungen werden hoffentlich viele Probleme eliminieren, die Leute hatten, wenn sie in den Fullscreen-Modus und zurück gewechselt haben. Ich habe den ersten Teil von CreateGLWindow() mit aufgeführt, so dass Sie leichter den Änderungen folgen können.

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

    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

Die große Änderung hier ist, dass wir nun die aktuelle Bildschirm Auflösung, Bit-Tiefe, etc. sichern, bevor wir in den Fullscreen-Modus wechseln. So können wir beim Beenden des Programms alles genau so zurücksetzen wie es vorher war. Die erste folgende Zeile kopiert die Anzeige-Einstellungen in die DMsaved Device Modus Struktur. Sonst hat sich nichts geändert, nur eine neue Codezeile.

    EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &DMsaved);            // sichere den aktuellen Anzeige Status (NEU)

    if (fullscreen)                                    // Fullscreen Modus?
    {
        DEVMODE dmScreenSettings;                        // Device Modus
        memset(&dmScreenSettings,0,sizeof(dmScreenSettings));            // stelle sicher, dass der Speicher gelöscht ist
        dmScreenSettings.dmSize=sizeof(dmScreenSettings);            // Größe derDevmode Structure
        dmScreenSettings.dmPelsWidth    = width;                // ausgewählte Bildschirm Breite
        dmScreenSettings.dmPelsHeight    = height;                // ausgewählte Bildschirm Höhe
        dmScreenSettings.dmBitsPerPel    = bits;                    // ausgewählte Bits Pro Pixel
        dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;

        // Versuche den gewählten Modus zu setzen und hole Ergebnisse.  ANMERKUNG: CDS_FULLSCREEN lässt die Start Bar nicht anzeigen.
        if (ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL)
        {
            // wenn der Modus fehl schlug, schlage zwei Alternativen vor.  Beenden oder Fenster-Modus.
            if (MessageBox(NULL,"The Requested Fullscreen Mode Is Not Supported By\nYour Video Card. Use Windowed Mode Instead?","NeHe GL",MB_YESNO|MB_ICONEXCLAMATION)==IDYES)
            {
                fullscreen=FALSE;                    // Fenster-Modus gewählt.  Fullscreen = FALSE
            }
            else
            {
                // zeige Message Box an, die den Benutzer wissen lässt, dass das Programm geschlossen wird.
                MessageBox(NULL,"Program Will Now Close.","ERROR",MB_OK|MB_ICONSTOP);
                return FALSE;                        // gebeFALSE zurück
            }
        }
    }

WinMain() fängt so wie immer an. Frage den Benutzer, ob im Fullscreen gestartet werden soll oder nicht, dann starte die Schleife.

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("Andreas Löffler, Rob Fletcher & NeHe's Blitter & Raw Image Loading Tutorial", 640, 480, 32, fullscreen))
    {
        return 0;                                // Beende, wenn Fenster nicht erzeugt wurde
    }

    while(!done)                                    // Schleife die so lange läuft, solange done=FALSE
    {
        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
            }
        }

Ich habe am folgenden Code einige Änderungen vorgenommen. Wenn das Programm nicht aktiv ist (minimiert ist), warten wir mittels des Befehls WaitMessage () auf eine Nachricht. Alles stoppt, bis das Programm eine Nachricht erhält (normalerweise zum Maximieren des Fensters). Das bedeutet, dass das Programm nicht länger den Prozessor belastet, wenn es minimiert ist. Dank an Jim Strong, für diesen Vorschlag.

        if (!active)                                // Programm inaktiv?
        {
            WaitMessage();                            // Warte auf Message / Mache nichts ( NEU ... Danke Jim Strong )
        }

        if (keys[VK_ESCAPE])                            // Wurde ESC gedrückt?
        {
            done=TRUE;                            // ESC Signalisiert, dass Beendet werden soll
        }

        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("Andreas Löffler, Rob Fletcher & NeHe's Blitter & Raw Image Loading Tutorial",640,480,16,fullscreen))
            {
                return 0;                        // Beenden, wenn das Fenster nicht erzeugt wurde
            }
        }

        DrawGLScene();                                // Zeichne die Szene
        SwapBuffers(hDC);                            // Swap Buffers (Double Buffering)
    }

    // Shutdown
    KillGLWindow();                                    // Kill das Fenster
    return (msg.wParam);                                // Beende das Programm
}

Nun denn, das war's! Nun steht nichts mehr im Wege, in Ihren Spielen, Engines oder Applikationen, einige coole Blending Effekte zu erzeugen. Mit Textur-Buffern, wie wir Sie in diesem Tutorial verwendet haben, können Sie noch einige andere coole Effekte machen, wie Echtzeit-Plasma oder Wasser. Wenn Sie diese Effekte alls zusammen verwenden, sollten Sie in der Lage sein, was fotorealisitsches Terrain zu erzeugen. Wenn etwas in diesem Tutorial nicht funktioniert oder Sie Vorschläge haben, wie man es besser machen könnte, dann zägern Sie mir bitte nicht, mir eine E-Mail zu schreiben. Vielen Dank für's Lesen und viel Glück beim erzeugen Ihrer eigenen Spezial Effekte!

Einige Informationen über Andreas: Ich bin ein 18 jähriger Schüler, der gerade Software Engineer lernt. Ich programmiere bereits seit fast 10 Jahren nun. Ich programmiere OpenGL für ungefähr 1.5 Jahre.

Andreas Löffler & Rob Fletcher

Jeff Molofee (NeHe)

* DOWNLOAD Visual C++ Code für diese Lektion.

* DOWNLOAD Borland C++ Builder 6 Code For This Lesson. ( 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.