NeHe - Lektion 22 - Bump Mapping (Extensionen) (Teil 1)

Lektion 22



Diese Lektion wurde von Jens Schneider geschrieben. Sie basiert etwas auf Lektion 6, obwohl viele Änderungen gemacht wurden. In dieser Lektion werden Sie lernen:
  • wie Sie Ihre hardwarebeschleunigten Multitextur-Features nutzen können.
  • wie man ein "gefaketes" Emboss Bump Mapping umsetzt.
  • wie man professionell aussehende Logos mittels Blending über die gerenderte Szene "schweben" lässt.
  • Grundlagen über Multi-Pass Rendering Techniken.
  • wie man effizient Matrix-Transformationen umsetzt.
Da mindestens drei der vier genannten Punkte als "fortgeschrittene Rendering Techniken" angesehen werden können, sollten Sie zumindestens ein grundlegendes Verständnis der OpenGL Rendering Pipeline haben. Sie sollten die meisten Befehle, die in diesem Tutorial verwendet werden, bereits kennen und Sie sollten mit Vektor-Mathematik vertraut seien. Ab und zu werden Sie Absätze entdecken, die mit Anfang Theorie (...) als Überschrift haben und mit Ende Theorie (...) enden. Diese Abschnitte versuchen Ihnen die Theorie hinter dem Thema, welches in den Klammern angegeben ist, näher zu bringen. Damit können Sie diese Abschnitte leicht überspringen, wenn Sie mit dem Thema bereits vertraut sind. Wenn Probleme auftauchen, den Code zu verstehen, sollten Sie zurück zu den Theorie-Abschnitten gehen und dort nochmal nachlesen.

Zu guter Letzt: Diese Lektion besteht aus mehr als 1200 Codezeilen, wobei große Teile nicht nur langweilig sind, sondern auch aus vorherigen Tutorials bekannt sind. Deshalb werde ich nicht jede einzelne Zeile kommentieren, sondern nur die interessanten. Wenn Sie etwas wie >…<entdecken, bedeutet das, dass Codezeilen ausgelassen wurden.

Fangen wir an:
 
#include <windows.h>                                // Header Datei für Windows
#include <stdio.h>                                // Header Datei für Standard Input/Output
#include <gl\gl.h>                                // Header Datei für die OpenGL32 Library
#include <gl\glu.h>                                // Header Datei für die GLu32 Library
#include <gl\glaux.h>                                // Header Datei für die GLaux Library
#include "glext.h"                                // Header Datei für Multitexturing
#include <string.h>                                // Header Datei für die String Library
#include <math.h>                                // Header Datei für die Math Library

Das GLfloat MAX_EMBOSS spezifiziert die "Stärke" des Bump Mapping-Effektes. Größere Werte verstärken den Effekt, aber reduzieren gleichzeitig die optische Qualität, da an den Ecken der Oberfläche sogenannte "Artefakte" auftreten.
 
#define MAX_EMBOSS (GLfloat)0.01f                        // Maximum Emboss-Translate. Inkrementiere um höhere "Immersion" zu erhalten

Ok, nun bereiten wir uns darauf vor, die GL_ARB_multitexture Extension zu verwenden. Das ist ziemlich einfach:

Die meisten Beschleuniger haben mehr als nur eine Textur-Einheit heutzutage. Um dieses Feature ausnutzen zu können, müssen Sie auf GL_ARB_multitexture-Support überprüfen, was Ihnen erlaubt zwei oder mehr verschiedene Texturen auf ein OpenGL-Primitv zu mappen, in nur einem Durchgang. Klingt nicht allzu mächtig, ist es aber. Fast immer wenn Sie etwas programmieren, erhöht es die visuelle Qualität wenn Sie eine Textur auf das Objekt mappen. Da Sie normalerweise mehrere "Durchläufe" (passes) benötigen, bestehend aus überlagernden Textur-Auswahl und Zeichnen der Geometrie, kann das alles recht schnell rechenintensiv werden. Aber kein Grund zur Sorge, das klären wir später noch.

Nun zurück zum Code: __ARB_ENABLE wird dazu verwendet Multitexturing für einen speziellen Compiler-Durchlauf komplett zu überschreiben. Wenn Sie Ihre OpenGL Extensionen sehen möchten, kommentieren Sie das #define EXT_INFO einfach ein. Als nächstes überprüfen wir unsere Extensionen während der Laufzeit um sicher zu gehen, dass unser Code portabel bleibt. Deshalb benötigen wir etwas Platz für ein paar Strings. Das sind die folgenden zwei Zeilen. Nun wollen wir unterscheiden, ob wir fähig sind, Multitexturing zu verwenden und ob wir es auch wirklich verwenden, weshalb wir zwei weitere Flags benötigen. Zu letzt müssen wir wissen, wieviele Textur-Einheiten es gibt (auch wenn wir nur zwei davon verwenden werden). Mindestens eine Textur-Einheit ist auf jedem OpenGL-fähigen Beschleuniger vorhanden, weshalb wir maxTexelUnits mit 1 initialisieren.
 
#define __ARB_ENABLE true                            // wird verwendet um ARB Extensionen komplett zu deaktivieren
// #define EXT_INFO                                // kommentieren Sie diese Zeile ein, um alle Ihre Extensionen zu sehen
#define MAX_EXTENSION_SPACE 10240                        // Zeichen für Extension-Strings
#define MAX_EXTENSION_LENGTH 256                        // Maximale Zeichen in einem Extension-String
bool multitextureSupported=false;                        // Flag ob Multitexturing unterstützt wird oder nicht
bool useMultitexture=true;                            // wird es verwendet, wenn es unterstützt wird?
GLint maxTexelUnits=1;                                // Anzahl der Texel-Pipelines. Hier gibt es mindestens eine.

Die folgenden Zeilen werden benötigt um die Extensionen auf die C++ Funktions-Aufrufe zu "linken". Behandeln Sie die PFN-was-auch-immer als vordefinierte Datentypen, die fähig sind Funktions-Aufrufe zu beschreiben. Da wir noch unsicher sind, ob wir die Funktionen für diese Prototypen haben werden, setzen wir sie auf NULL. Der Befehl glMultTexCoordifARB mappt auf das allseits bekannte glTexCoordif, welches eine i-dimensionale Textur-Koordinate spezifiziert. Beachten Sie, dass dies die glTexCoordif-Befehle komplett ersetzen kann. Da wir nur die GLfloat-Version verwenden, benötigen wir lediglich Prototypen für die Befehle, die mit einem "f" enden. Andere sind ebenfalls verfügbar ("fv", "i", etc.). Die letzten beiden Prototypen sind auf die aktive Textur-Einheit zu setzen, die zur Zeit die Textur-Bindungen erhält ( glActiveTextureARB() ) und zur Bestimmung welche Textur-Einheit mit dem ArrayPointer-Befehl (a.k.a Client-Subset, deshalb glClientActiveTextureARB) verbunden ist. Übrigens: ARB ist eine Abkürzung für "Architectural Review Board". Extensionen mit einem ARB in ihrem Namen werden nicht unbedingt bei einer OpenGL-konformen Implementation benötigt, aber es wird angenommen, dass sie weitverbreitet unterstützt wird. Zur Zeit hat es nur die Multitexture-Extension es zum ARB-Status gebracht. Das mag man als Zeichen für den unglaublichen Einfluss bezüglich der Geschwindigkeit deuten, die Multitexturing bei verschiedenen fortgeschrittenen Rendering-Techniken hat.

Die ausgelassenen Zeilen sind GDI-Context, Handles, etc.
 
PFNGLMULTITEXCOORD1FARBPROC    glMultiTexCoord1fARB    = NULL;
PFNGLMULTITEXCOORD2FARBPROC    glMultiTexCoord2fARB    = NULL;
PFNGLMULTITEXCOORD3FARBPROC    glMultiTexCoord3fARB    = NULL;
PFNGLMULTITEXCOORD4FARBPROC    glMultiTexCoord4fARB    = NULL;
PFNGLACTIVETEXTUREARBPROC    glActiveTextureARB    = NULL;
PFNGLCLIENTACTIVETEXTUREARBPROC    glClientActiveTextureARB= NULL;

Wir benötigen ein paar globale Variablen:
  • filter spezifziert welcher Filter verwendet wird. Schauen Sie diese bei Lektion 06 nach. Wir verwenden eigentlich nur GL_LINEAR, weshalb wir mit 1 initialisieren.
  • texture enthält unsere Basis-Texture, drei Mal, eine pro Filter.
  • bump enthält unsere Bump Maps
  • invbump enthält unsere inverse Bump Maps. Das wird später im Theorie-Abschnitt erklät.
  • Die Logo-Dinger enthalten Texturen für verschiedene Billboards die später im letzten Durchlauf der Rendering-Ausgabe hinzugefügt werden.
  • Der Light...-Kram enthält Daten über unsere OpenGL Lichtquelle.
 
GLuint  filter=1;                                // welcher Filter verwendet wird
GLuint  texture[3];                                // Speicherplatz für 3 Texturen
GLuint  bump[3];                                // Unsere Bumpmappings
GLuint  invbump[3];                                // Inverse Bumpmaps
GLuint  glLogo;                                    // Handle für das OpenGL-Logo
GLuint  multiLogo;                                // Handle für das Multitexture-Enabled-Logo
GLfloat LightAmbient[]    = { 0.2f, 0.2f, 0.2f};                    // Ambientes Licht ist 20% Weiss
GLfloat LightDiffuse[]    = { 1.0f, 1.0f, 1.0f};                    // Diffuses Licht ist Weiss
GLfloat LightPosition[]    = { 0.0f, 0.0f, 2.0f};                    // Position ist irgendwo vor dem Screen
GLfloat Gray[]        = { 0.5f, 0.5f, 0.5f, 1.0f};

Der nächste Codeblock enthält die nummerische Repräsentation eines texturierten Würfels, der aus GL_QUADS erzeugt wird. Jede fünf Nummern spezifizieren einen Satz 2D-Textur-Koordinaten und einen Satz 3D-Vertex-Koordinaten. Damit können wir den Würfel mittels for-Schleifen erzeugen, da wir den Würfel mehrmals benötigen. Dem data-Block folgt der bereits bekannte WndProc()-Prototyp aus vorangegangenen Lektionen.
 
// Data enthält die Seiten des Würfels im Format 2xTexCoord, 3xVertex.
// Beachten Sie, dass die Tesselation des Würfels nur das absolute Minimum ist.

GLfloat data[]= {
    // VORDERE SEITE
    0.0f, 0.0f,        -1.0f, -1.0f, +1.0f,
    1.0f, 0.0f,        +1.0f, -1.0f, +1.0f,
    1.0f, 1.0f,        +1.0f, +1.0f, +1.0f,
    0.0f, 1.0f,        -1.0f, +1.0f, +1.0f,
    // HINTERE SEITE
    1.0f, 0.0f,        -1.0f, -1.0f, -1.0f,
    1.0f, 1.0f,        -1.0f, +1.0f, -1.0f,
    0.0f, 1.0f,        +1.0f, +1.0f, -1.0f,
    0.0f, 0.0f,        +1.0f, -1.0f, -1.0f,
    // obere Seite
    0.0f, 1.0f,        -1.0f, +1.0f, -1.0f,
    0.0f, 0.0f,        -1.0f, +1.0f, +1.0f,
    1.0f, 0.0f,        +1.0f, +1.0f, +1.0f,
    1.0f, 1.0f,        +1.0f, +1.0f, -1.0f,
    // untere Seite
    1.0f, 1.0f,        -1.0f, -1.0f, -1.0f,
    0.0f, 1.0f,        +1.0f, -1.0f, -1.0f,
    0.0f, 0.0f,        +1.0f, -1.0f, +1.0f,
    1.0f, 0.0f,        -1.0f, -1.0f, +1.0f,
    // rechte Seite
    1.0f, 0.0f,        +1.0f, -1.0f, -1.0f,
    1.0f, 1.0f,        +1.0f, +1.0f, -1.0f,
    0.0f, 1.0f,        +1.0f, +1.0f, +1.0f,
    0.0f, 0.0f,        +1.0f, -1.0f, +1.0f,
    // linke Seite
    0.0f, 0.0f,        -1.0f, -1.0f, -1.0f,
    1.0f, 0.0f,        -1.0f, -1.0f, +1.0f,
    1.0f, 1.0f,        -1.0f, +1.0f, +1.0f,
    0.0f, 1.0f,        -1.0f, +1.0f, -1.0f
};

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

Der nächste Codeblock bestimmt die unterstützten Extensionen zur Laufzeit.

Zuerst können wir annehmen, dass wir einen langen String haben werden, der alle unterstützten Extensionen als '\n' getrennte Substrings enthalten wird. Alles was wir also machen müssen, ist nach einem '\n' suchen und anfangen den String mit search zu vergleichen, bis wir auf ein weiteres '\n' treffen oder bis der String nicht mehr mit search übereinstimmt. Im ersten Fall gebe true für "gefunden" zurück, im anderen Fall, nehme den nächsten Substring, bis Sie das Ende des Strings erreicht haben. Sie müssen am Anfang des Strings etwas aufpassen, da er nicht mit einem Newline-Zeichen beginnt.

Übrigens: eine gängige Regel ist es, IMMER während der Laufzeit auf die Verfügbarkeit von Extensionen zu überprüfen!
 
bool isInString(char *string, const char *search) {
    int pos=0;
    int maxpos=strlen(search)-1;
    int len=strlen(string);
    char *other;
    for (int i=0; i<len; i++) {
        if ((i==0) || ((i>1) && string[i-1]=='\n')) {            // Neue Extension beginnt hier!
            other=&string[i];
            pos=0;                            // Beginne neue Suche
            while (string[i]!='\n') {                // durchsuche gesamten Extension-String
                if (string[i]==search[pos]) pos++;        // nächste Position
                if ((pos>maxpos) && string[i+1]=='\n') return true;    // wir haben eine Gewinner!
                i++;
            }
        }
    }
    return false;                                // Sorry, nichts gefunden!
}

Nun müssen wir den Extension-String hohlen und ihn in eine durch '\n' getrennte Reihenfolge bringen, damit wir nach unserer gewünschten Extension suchen können. Wenn wir den Substring "GL_ARB_MULTITEXTURE" darin finden, wird dieses Feature unterstützt. Aber wir können es nur verwenden, wenn __ARB_ENABLE ebenfalls true ist. Als letztes muss noch GL_EXT_texture_env_combine unterstützt werden. Diese Extension führt neue Arten ein, wie die Textur-Einheiten zusammenarbeiten. Wir benötigen dies, da GL_ARB_multitexture die Ausgabe nur aus einer Textur-Einheit zur nächsten mit der nächst höheren Zahl füttert. Deshalb überprüfen wir auf diese Extension, anstatt eine weitere komplexe Blending-Gleichung zu verwenden (die nicht ganz den selben Effekt erzielen würde!) Wenn alle Extensionen unterstützt werden und wir nicht überschrieben wurden, bestimmen wir als erstes wieviele Textureinheiten verfügbar sind und speichern die Anzahl in maxTexelUnits. Dann müssen wir die Funktionen mit unseren Namen linken. Das wird mit den wglGetProcAdress()-Aufrufen gemacht, wobei als Parameter ein String mit dem Namen des Funktionsaufrufes und einem Prototyp-Cast übergeben wird, um sicherzustellen, dass wir den richtigen Funktionstypen bekommen.
 
bool initMultitexture(void) {
    char *extensions;
    extensions=strdup((char *) glGetString(GL_EXTENSIONS));            // hole Extension String
    int len=strlen(extensions);
    for (int i=0; i<len; i++)                        // trenne ihn mit Newline anstatt von Leerzeichen
        if (extensions[i]==' ') extensions[i]='\n';

#ifdef EXT_INFO
    MessageBox(hWnd,extensions,"supported GL extensions",MB_OK | MB_ICONINFORMATION);
#endif

    if (isInString(extensions,"GL_ARB_multitexture")            // wird Multitexturing unterstützt?
        && __ARB_ENABLE                            // überschreibe Flag
        && isInString(extensions,"GL_EXT_texture_env_combine"))        // w5rd texture-environment-combining unterstützt?
    {      
        glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB,&maxTexelUnits);
        glMultiTexCoord1fARB = (PFNGLMULTITEXCOORD1FARBPROC) wglGetProcAddress("glMultiTexCoord1fARB");
        glMultiTexCoord2fARB = (PFNGLMULTITEXCOORD2FARBPROC) wglGetProcAddress("glMultiTexCoord2fARB");
        glMultiTexCoord3fARB = (PFNGLMULTITEXCOORD3FARBPROC) wglGetProcAddress("glMultiTexCoord3fARB");
        glMultiTexCoord4fARB = (PFNGLMULTITEXCOORD4FARBPROC) wglGetProcAddress("glMultiTexCoord4fARB");
        glActiveTextureARB   = (PFNGLACTIVETEXTUREARBPROC) wglGetProcAddress("glActiveTextureARB");
        glClientActiveTextureARB= (PFNGLCLIENTACTIVETEXTUREARBPROC) wglGetProcAddress("glClientActiveTextureARB");
              
#ifdef EXT_INFO
        MessageBox(hWnd,"The GL_ARB_multitexture extension will be used.","feature supported!",MB_OK | MB_ICONINFORMATION);
#endif

        return true;
    }
    useMultitexture=false;                            // wir können es nicht verwenden, wenn es nicht unterstützt wird!
    return false;
}

InitLights() initialsiert einfach die OpenGL-Beleuchtung und wird später von InitGL() aufgerufen.
 
void initLights(void) {
        glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient);                // Lade Light-Parameter nach GL_LIGHT1
        glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse);
        glLightfv(GL_LIGHT1, GL_POSITION, LightPosition);
        glEnable(GL_LIGHT1);
}

Hier laden wie VIELE Texturen. Da auxDIBImageLoad() einen eigene Fehlerbehandlung hat und LoadBMP() ohne try-catch-Block nicht gerade verlässlich war, habe ich es rausgeworfen. Aber nun zu unseren eigenen Lade-Routine. Als erstes laden wir die Basis-Textur und erzeugen daraus drei gefilterte Texturen ( GL_NEAREST, GL_LINEAR und GL_LINEAR_MIPMAP_NEAREST). Beachten Sie, dass ich nur eine Daten-Struktur habe, die die Bitmaps aufnimmt, da wir nur eine zur Zeit geöffnet haben müssen. Darüber hinaus habe ich eine weitere Daten-Struktur namens alpha hier eingefügt. Sie enthält die Alpha-Schichten der Texturen, so dass ich RGBA-Bilder als zwei Bitmaps speichern kann: ein 24bpp RGB und ein 8bpp grauskaliertes Alpha. Damit der Status-Indikator korrekt funktioniert, müssen wir den Bild-Block nach jedem Laden auf NULL resetten.

Beachten Sie auch, dass ich GL_RGB8 anstatt von nur "3" verwende, wenn ich die Textur-Art spezifiziere. Das ist konformanter zu anstehenden OpenGL-ICD Releases und sollte immer statt nur einer Zahl verwendet werden. Ich habe es in FETT für Sie markiert.
 
int LoadGLTextures() {                                // Lade Bitmaps und konvertiere in Texturen
    bool status=true;                            // Status Indikator
    AUX_RGBImageRec *Image=NULL;                        // erzeuge Speicherplatz für die Textur
    char *alpha=NULL;

    // Lade das Tile-Bitmap für die Basis-Textur
    if (Image=auxDIBImageLoad("Data/Base.bmp")) {
        glGenTextures(3, texture);                    // erzeuge drei Texturen

        // erzeuge Nearest-gefilterte Textur
        glBindTexture(GL_TEXTURE_2D, texture[0]);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, Image->sizeX, Image->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, Image->data);

        // erzeuge Linear-gefilterte Textur
        glBindTexture(GL_TEXTURE_2D, texture[1]);
        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, GL_RGB8, Image->sizeX, Image->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, Image->data);

        // erzeuge MipMapped Textur
        glBindTexture(GL_TEXTURE_2D, texture[2]);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);
        gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB8, Image->sizeX, Image->sizeY, GL_RGB, GL_UNSIGNED_BYTE, Image->data);
    }
    else status=false;

    if (Image) {                                // wenn Textur Existiert
        if (Image->data) delete Image->data;                // wenn Textur Bild existiert
        delete Image;
        Image=NULL;
    }

Nun laden wir die Bump Map. Für Gründe, die später erläutert werden, hat Sie nur eine Durchsichtigkeit von 50%, weshalb wir Sie auf die eine oder andere Weise skalieren müssen. Ich habe mich dazu entschlossen Sie mittels glPixelTrsferf()-Befehlen zu skalieren, welche spezifizieren, wie Daten aus Bitmaps in Texturen auf Pixel-Basis konvertiert werden. Ich verwende es um die RGB-Komponenten des Bitmaps auf 50% zu skalieren. Sie sollten wirklich sich mal die glPixelTransfer()-Befehlsfamilie ansehen, wenn Sie sie noch nicht in Ihren Programmen verwendet haben. Die sind wirklich nützlich.

Ein weiteres Thema ist, dass wir unser Bitmap nicht immer wieder und wieder in der Textur haben wollen. Wir wollen es nur einmal haben, gemapped auf die Textur-Koordinaten (s,t)=(0.0f, 0.0f) bis zu (s,t)=(1.0f, 1.0f). Alle anderen Textur-Koordinaten sollten auf einfaches schwarz gemapped werden. Das wird mit den zwei glTexParameteri()-Aufrufen erreicht, die ziemlich selbsterklärend sind und das Bitmap in s und t-Richtung "abschneiden".
 
    // Lade die Bumpmaps
    if (Image=auxDIBImageLoad("Data/Bump.bmp")) {
        glPixelTransferf(GL_RED_SCALE,0.5f);                // Skaliere RGB um 50%, so dass wir nur die 
        glPixelTransferf(GL_GREEN_SCALE,0.5f);                // halbe Intensität haben
        glPixelTransferf(GL_BLUE_SCALE,0.5f);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);    // Kein Wrapping, bitte!
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);
        glGenTextures(3, bump);                        // erzeuge drei Texturen

        // erzeuge Nearest-gefilterte Textur
        >…// erzeuge Linear-gefilterte Textur
        >…// erzeuge MipMapped Textur
        >…


Sie kennen den Satz bereits: Aus Gründen, die später erläutert werden, müssen wir eine inverse Bump Map erzeugen, mit einer Durchsichtigkeit von fast 50 % wieder. Wie subtrahieren also die Bumpmap von purem Weiß, was als Integer-Zahlen gleich {255, 255, 255} ist. Da wir die RGB-Skalierung NICHT zurück auf 100% gesetzt haben (hat mich ungefähr drei Stunden gekostet, um herauszufinden, dass das einer der großen Fehler in meiner ersten Version war!), wird die inverse Bumpmap erneut auf 50% Durchsichtigkeit skaliert.



for (int i=0; isizeX*Image->sizeY; i++) // Invertier die Bumpmap Image->data[i]=255-Image->data[i]; glGenTextures(3, invbump); // erzeuge drei Texturen // erzeuge Nearest-gefilterte Textur >…// erzeuge Linear-gefilterte Textur >…// erzeuge MipMapped Textur >…// wenn Textur existiert if (Image->data) delete Image->data; // wenn Textur-Image existiert delete Image; Image=NULL; } 

Das Laden der Logo-Bitmaps ist ziemlich einfach, außer die RGB-A re-Kombinierung, welche so klar sein sollte, dass Sie sie selbst verstehen. Beachten Sie, dass die Textur aus dem alpha-Speicherblock erzeugt wird, nicht aus dem Image-Speicherblock! Nur ein Filter wird hier verwendet.
 
    // Lade die Logo-Bitmaps
    if (Image=auxDIBImageLoad("Data/OpenGL_ALPHA.bmp")) {
        alpha=new char[4*Image->sizeX*Image->sizeY];
        // erzeuge Speicher für RGBA8-Textur
        for (int a=0; a<Image->sizeX*Image->sizeY; a++)
            alpha[4*a+3]=Image->data[a*3];                // nehme nur Rot-Werte als Alpha!
        if (!(Image=auxDIBImageLoad("Data/OpenGL.bmp"))) status=false;
        for (a=0; a<Image->sizeX*Image->sizeY; a++) {
            alpha[4*a]=Image->data[a*3];                // R
            alpha[4*a+1]=Image->data[a*3+1];            // G
            alpha[4*a+2]=Image->data[a*3+2];            // B
        }

        glGenTextures(1, &glLogo);                    // erzeuge eine Texture

        // erzeuge Linear-gefilterte RGBA8-Textur
        glBindTexture(GL_TEXTURE_2D, glLogo);
        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, GL_RGBA8, Image->sizeX, Image->sizeY, 0, GL_RGBA, GL_UNSIGNED_BYTE, alpha);
        delete alpha;
    }
    else status=false;

    if (Image) {                                // wenn Textur existiert
        if (Image->data) delete Image->data;                // wenn Textur-Image existiert
        delete Image;
        Image=NULL;
    }

    // Lade das "Extension Enabled"-Logo
    if (Image=auxDIBImageLoad("Data/multi_on_alpha.bmp")) {
        alpha=new char[4*Image->sizeX*Image->sizeY];            // erzeuge Speicher für RGBA8-Textur
        >…// erzeuge eine Textur
        // erzeuge Linear-gefilterte RGBA8-Textur
        >…// wenn Textur existiert
        if (Image->data) delete Image->data;                // wenn Textur-Image existiert
        delete Image;
        Image=NULL;
    }
    return status;                                // gebe den Status zurück
}

Als nächstes kommt die einzige nicht geänderte Funktion ReSizeGLScene(). Ich habe sie hier weggelassen. Darauf folgt eine Funktion namens doCube(), die einen Würfel zeichnet, komplett mit normalisierten Normalenvektoren. Beachten Sie, dass diese Version nur Textur-Einheit #0 füttert, da glTexCoord2f(s,t) das Selbe wie glMultiTexCoord2f(GL_TEXTURE0_ARB,s,t) ist. Beachten Sie auch, dass man den Würfel mit verschachtelten Arrays machen könnte, aber das ist definitiv ein anderes Thema. Beachten Sie darüber hinaus, dass dieser Würfel NICHT mit einer Display-Liste erzeugt werden kann, da Display-Listen scheinbar interne Fließkommazahlen verwenden, deren Genauigkeit von GLfloat abweicht. Da dies zu verschiedenen unschönen Effekten führt, auch als "decaling"-Probleme bekannt, habe ich die Display-Listen weggelassen. Ich denke, dass eine allgemeine Regel für Multipass-Algorithmen es ist, die gesamte Geometrie entweder mit oder ohne Display Listen zu machen. Denken Sie also nicht mal dran, beides zu vermischen, selbst wenn es so scheint, dass es auf Ihrer Hardware läuft, kann es auf anderer Hardware ggf. gar nicht laufen!
 
GLvoid ReSizeGLScene(GLsizei width, GLsizei height)
// Resize und initalisiere das GL Fenster
>…// vordere Seite
        glNormal3f( 0.0f, 0.0f, +1.0f);
        for (i=0; i// hintere Seite
        glNormal3f( 0.0f, 0.0f,-1.0f);
        for (i=4; i// obere Seite
        glNormal3f( 0.0f, 1.0f, 0.0f);
        for (i=8; i// untere Seite
        glNormal3f( 0.0f,-1.0f, 0.0f);
        for (i=12; i// rechte Seite
        glNormal3f( 1.0f, 0.0f, 0.0f);
        for (i=16; i// linke Seite
        glNormal3f(-1.0f, 0.0f, 0.0f);
        for (i=20; i


Zeit um OpenGL zu initialisieren. Alles das Selbe wie in Lektion 06, außer dass ich initLights() hier aufrufe, anstatt sie zu setzen. Oh, und ich rufe natürlich das Multitexture-setup hier auf!



int InitGL(GLvoid) // das gesamte Setup für OpenGL kommt hier hin { multitextureSupported=initMultitexture(); if (!LoadGLTextures()) return false; // springe zur Textur Lade Routine glEnable(GL_TEXTURE_2D); // aktiviere Textur Mapping glShadeModel(GL_SMOOTH); // aktiviere Smooth Shading glClearColor(0.0f, 0.0f, 0.0f, 0.5f); // schwarzer Hintergrund glClearDepth(1.0f); // Depth Buffer Setup glEnable(GL_DEPTH_TEST); // aktiviere Depth Testing glDepthFunc(GL_LEQUAL); // Die Art des Depth Testing glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // wirklich nette Perspektiven Berechnung initLights(); // initialsiere OpenGL Licht return true // Initialisierung verlief OK } 

Nun kommt ungefähr 95% der Arbeit. Alle Verweise wie "Gründe dafür werden später genannt" werden im folgenden Theorie-Block gelöst.

Anfang Theorie ( Emboss Bump Mapping )

Wenn Sie einen Powerpoint-Viewer installiert haben, sollten Sie sich unbedingt die folgende Präsentation herunterladen:

Emboss Bump Mapping von Michael I. Gold, nVidia Corp. [.ppt, 309K]

Für die unter Ihnen, die keinen Powerpoint-Viewer haben, habe ich versucht die in dem Dokument enthaltenden Informationen ins html-Format zu konvertieren. Hier:

Emboss Bump Mapping

Michael I. Gold

NVidia Corporation

Bump Mapping

Richtiges Bump Mapping verwendet Per-Pixel Lighting (pro-Pixel-Beleuchtung).
  • Beleuchtungs-Berechnung für jeden Pixel basiert auf verschobenen Normalenvektoren.
  • Rechen-intensiv
  • For mehr Informationen schauen sich an: Blinn, J. : Simulation of Wrinkled Surfaces, Computer Graphics. 12,3 (August 1978) 286-292.
  • Für Informationen aus dem Web gehen Sie auf: http://www.r3.nu/~cass/thesis/thesis.html um Cass Everitt’s Orthogonal Illumination These zu sehen. (Anm.: Jens)
Emboss Bump Mapping

Emboss Bump Mapping ist ein Hack
  • Nur Diffuse Beleuchtung, keine speukuäre Komponente
  • "Under-sampling" von Artefakte (kann verwischte Bewegungen erzeugen, Anm.: Jens)
  • Möglich auf heutiger Konsumenten Hardware (wie gezeigt, Anm.: Jens)
  • Wenn es gut aussieht, machen Sie es!
Diffuse Beleuchtung Berechnung

C=(L*N) x Dl x Dm
  • L ist der Lichtvektor
  • N ist der Normalenvektor
  • Dl ist die Licht diffuse Farbe
  • Dm ist die Material diffuse Farbe
  • Bump Mapping ändert N pro Pixel
  • Emboss Bump Mapping schätzt (L*N)
Schätzung des diffusen Faktors L*N

Textur-Map repräsentiert ein Heightfield
  • [0,1] repräsentiert die Spanne der Bump Funktion
  • Erste Ermittlung repräsentiert Schlinge m (Beachten Sie, dass m nur 1D ist. Stellen Sie sich m als "inf.-norm des grad(s,t)" zu einem gegebenen Satz Koordinaten (s,t) vor!, Anm.: Jens)
  • m inkrementiert / deckrementiert Basis diffuse Faktor Fd
  • (Fd+m) schätzt (L*N) pro Pixel
Schätzung Derivative

Embossing Approximates Derivative
  • Schaue Höhe H0 bei Punkt (s,t) nach
  • Schaue Höhe H1 bei dem Punkt der leicht zu Lichtquelle versetzt ist nach (s+ds,t+dt)
  • Subtrahiere original Höhe H0 von der verschobenen Höhe H1
  • Differenz repräsentiert die Schlinge m=H1-H0
Berechne den Bump



1) Original Bump (H0).



2) Original Bump (H0) überlagert mit einem zweiten Bump (H1) etwas zur Lichtquelle hin verschoben.



3) Subtrahiere original Bump vom zweiten (H0-H1). Dadurch entstehen hellere (B) und dunklere (D) Flächen.

Berechne die Beleuchtung

Evaluiere Fragment Farbe Cf
  • Cf = (L*N) x Dl x Dm
  • (L*N) ~ (Fd + (H1-H0))
  • Dm x Dl ist in der Oberflächen-Textur Ct kodiert. Könnten Dl seperat kontrollieren, wenn man klever ist. (Wir kontrollieren es, indem wir die OpenGL-Beleuchtung verwenden!, Anm.: Jens)
  • Cf = (Fd + (H0-H1) x Ct
Ist das alles? Es ist so einfach!

Wir sind noch nicht ganz fertig. Wir müssen immer noch:
  • Eine Textur erzeugen (mittels eines Grafikprogramms, Anm.: Jens)
  • Die Textur Koordinaten offsets berechnen (ds,dt)
  • Den diffusen Faktor Fd berechnen (wird kontrolliert durch die Benutzung der OpenGL-Beleuchtung!, Anm.: Jens)
  • Beide werden aus dem Normalenvektor N und dem Lichtvektor L ermittelt (in unserem Fall werden nur (ds,dt) explizit berechnet!, Anm.: Jens)
  • Nun müssen wir uns etwas mit Mathe beschäftigen
Erzeugung einer Textur

Konserviere Texturen!
  • Aktuelle Multitexture-Hardware unterstützt nur zwei Texturen! (Mittlerweile stimmt das nicht mehr, aber Sie sollten es trotzdem lesen!, Anm.: Jens)
  • Bump Map im ALPHA Kanal (ist nicht die Weise, wie wir es machen, Sie könnten das zur Übung selbst implementieren, wenn Sie einen TNT-Chipsatz haben. Anm.: Jens)
  • Maximum bump = 1.0
  • Level Grund = 0.5
  • Maximum Depression = 0.0
  • Oberflächenfarbe in RGB Kanälen
  • Setze internes Format auf GL_RGBA8 !!
Textur Offset Berechnung

Rotiere Lichtvektor in den normalen Raum
  • Benötigt Normal Koordinaten System
  • Ermittle Koordinate System aus Normalenvektor und “up” Vektor (wir übergeben explizit die texCoord Richtung unserer Offset-Erzeugung, Anm.: Jens)
  • Normalenvekor ist die z-Achse
  • Kreuzprodukt ist x-Achse
  • Schmeisse "up" Vektor weg, ermittle Y-Achse als Kreuzprodukt aus X- und Z-Achse
  • Erzeuge 3x3 matrix Mn aus den Achsen
  • Transformiere Lichtvektor in den normalen Raum. (Mn wird auch orthonormale Basis genannt. Stellen Sie sich Mn*v als "Ausdruck" von v vor, als Basis die einen tangenten Raum beschreibt, anstatt der Bedeutung der Standard-Basis. Beachten Sie auch, dass orthonormale Basen invariant gegen Skalierungen sind, weshalb Sie ihre Normalisierung nicht verlieren, wenn Sie mit Vektoren multipliziert werden! Anm.: Jens)
Textur Offset Berechnung (Fortsetzung)

Verwende Normalen-Raum Licht Vektor für Offsets
  • L’ = Mn x L
  • Verwende L’x, L’y für (ds,dt)
  • Verwende L’z für diffusen Faktor! (Eher nicht! Wenn Sie kein TNT-Besitzer sind, verwenden Sie statt dessen die OpenGL-Beleuchtung, da Sie eh einen zusätzlichen Durchgang machen müssen!, Anm.: Jens)
  • Wenn Lichtvektor nahe dem Normalenvektor ist, sind L’x, L’y klein.
  • Wenn Livhtvektor nahe der tangenten Ebene ist, sind L’x, L’y groß.
  • Was ist, wenn L’z kleiner als null ist?
  • Licht ist auf der entgegengesetzten Seite des Normalenvektors
  • Fade auf null.
Implementation auf TNT

Berechne Vektoren, Texcoords auf dem Host
  • Übergebe diffusen Faktor als Vertex Alpha
  • Könnten Vertex Farbe für die diffuse Lichtfarbe verwenden
  • H0 und Oberflächenfarbe von Textur-Einheit 0
  • H1 von Textureinheit 1 (selbe Textur, andere Koordinaten)
  • ARB_multitexture Extension
  • Kombiniere Extension (genauer: die NVIDIA_multitexture_combiners Extension, wird von allen Karten der TNT-Familie unterstützt, Anm.: Jens)
Implementation auf TNT (Fortsetzung)

Combiner 0 Alpha-Setup:
  • (1-T0a) + T1a - 0.5 (T0a steht für "texture-unit 0, alpha channel", Anm.: Jens)
  • (T1a-T0a) mappt auf (-1,1), aber die Hardware schneidet bei (0,1) ab
  • 0.5 "bias" gleicht den Verlust des Abschneidens aus (ziehen Sie in Erwägung 0.5 Skalierung zu verwenden, da Sie eine größere Auswahl an Bump Maps dann haben, Anm.: Jens)
  • Könnte diffuse Lichtfarbe mit T0c modulieren
  • Combiner 0 rgb-setup:
  • (T0c * C0a + T0c * Fda - 0.5 )*2
  • 0.5 "bias" gleicht den Verlust des Abschneidens aus
  • Skalierung um 2 hellt das Bild auf
Ende Theorie ( Emboss Bump Mapping )

Obwohl wir es etwas anders machen, als die TNT-Implementation, damit unser Programm auf ALLEN Beschleunigern läuft, können wir hier zwei oder drei Dinge lernen. Eine Sache ist, dass Bump Mapping ein Multi-pass Algorithmus auf den meisten Karten ist (nicht auf der TNT-Familie, wo es als 2-Texturen-Durchlauf implementiert werden kann). Sie sollten nun eine Vorstellung davon haben, wie nett Multitexturing wirklich ist. Wir implementieren nun einen 3-Pass nicht-Multitexturing Algorithmus, welcher in einen 2-Pass Multitexuring Algorithmus entwickelt werden kann (und wird).

Nun sollten Sie etwas aufpassen, da wir ein paar Matrix-Matrix-Multiplikationen machen müssen (und Matrix-Vektor-Multikplikationen ebenfalls). Aber nichts wovor man Angst haben müsste: OpenGL wird die Matrix-Matrix-Multiplikation für uns übernehmen (wenn man es richtig anstellt) und die Matrix-Vektor-Multiplikatian ist wirklich einfach: VMatMult(M,v) multipliziert die Matrix M mit dem Vektor v und speichert das Ergebniss in v: v:=M*v. Alle Matrizen und Vektoren, die übergeben werden, müssen in homogenen-Koordinaten sein, woraus sich dann 4x4 Matrizen und 4-dim Vektoren ergeben. Damit wird die Konformität zu OpenGL sichergestellt, für den Fall, dass unsere eigenen Vektoren mit OpenGL-Matrizen multipliziert werden.
 
// berechne v=vM, M ist 4x4 in Spalten-Form, v ist 4dim. Zeile (z.B. "Transponiert")
void VMatMult(GLfloat *M, GLfloat *v) {
    GLfloat res[3];
    res[0]=M[ 0]*v[0]+M[ 1]*v[1]+M[ 2]*v[2]+M[ 3]*v[3];
    res[1]=M[ 4]*v[0]+M[ 5]*v[1]+M[ 6]*v[2]+M[ 7]*v[3];
    res[2]=M[ 8]*v[0]+M[ 9]*v[1]+M[10]*v[2]+M[11]*v[3];
    v[0]=res[0];
    v[1]=res[1];
    v[2]=res[2];
    v[3]=M[15];                                // Homogene Koordinate
}

Anfang Theorie ( Emboss Bump Mapping Algorithmen )

Hier werden wir zwei verschiedene Algorithmen diskutieren. Ich habe den ersten erst ein paar Tage zuvor gefunden und zwar unter:
http://www.nvidia.com/marketing/Developer/DevRel.nsf/TechnicalDemosFrame?OpenPage

Das Programm heißt GL_BUMP und wurde von Diego Tártara im Jahre 1999 geschrieben.
Es implementiert wirklich gut aussehendes Bump Mapping auch wenn es einige Nachteile hat.
Aber zuerst werfen wir einen Blick auf Tártara’s Algorithmus:
  1. Alle Vektoren müssen ENTWEDER im Objekt ODER Welt Raum sein
  2. Berechne Vector v aus dem aktuellen Vertex zur Lichtposition
  3. Normalisiere v
  4. Projeziere v in den tangenten Raum. (Das ist die Ebene welche die Oberfläche im aktuellen Vertex berührt. Normalerweise ist das, wenn man mit flachen Oberflächen arbeitet, die Oberfläche selber).
  5. Verschiebe (s,t)-Koordinaten um die projizierte v’s x und y Komponente
Das sieht gar nicht schlecht aus! Es ist grundsätzlich der Algorithmus, der von oben von Michael I. Gold vorgestellt wurde. Aber er hat einen gravierenden Nachteil: Tártara macht die Projektion nur für eine xy-Ebene! Das ist für unser Vorhaben gravierend, da es den Projektions-Schritt vereinfacht, indem nur die xy-Komponenten von v genommen werden und die Z-Komponente verworfen wird.

Aber seine Implementation realsiert die diffuse Belechtung genauso, wie wir es machen: mittels der von OpenGL vordefinierten Beleuchtung. Da wir die Kombinier-Methode, die Gold vorschlägt, nicht verwenden können (wir wollen, dass unsere Programme überall laufen können, nicht nur auf TNT-Karten!), können wir den diffusen Faktor nicht im Alpha-Kanal speichern. Da wir bereits ein 3-Pass Non-Multitexturing / 2-Pass Multitextur Problem haben, warum sollten wir im letzten Durchgang nicht einfach die OpenGL-Beleuchtung anwenden und das ambiente Licht und den Farben-Kram für uns machen lassen? Das ist möglich (und sieht recht gut aus), da wir keine komplexe Geometrie verwenden, behalten Sie das im Hinterkopf. Wenn Sie mehrere tausend Bump Mapped Dreiecke rendern wollen, versuchen Sie etwas Neues zu erfinden!

Des weiteren verwendet er Multitexturing, was, wie wir sehen solten, nicht so einfach ist, wie Sie vielleicht gedacht haben, in Bezug auf diesen speziellen Fall.

Aber nun zu unserer Implementation. Sie sieht ziemlich genauso aus, wie der obige Algorithmus, außer des Projektions-Schrittes, wo wir etwas eigenes verwenden:
 
  • Wir verwenden OBJEKT KOORDINATEN, was bedeutet, dass wir die Modelview Matrix nicht in unseren Berechnungen verwenden. Das hat einen unschönen Neben-Effekt: da wir den Würfel rotieren lassen wollen, die Objekt-Koordinaten des Würfels sich nicht ändern, aber die Welt-Koordinaten (auch als Betrachter-/Augen-Koordinaten bekannt) dafür. Unser Lichtposition sollte nicht mit dem Würfel rotiert werwden, sie sollte einfach nur statisch sein, was bedeutet, dass sich ihre Welt-Koordinaten nicht ändern. Um das zu kompensieren, wenden wir einen gängigen Trick an: anstatt jeden Vertex im vorraus in den Welten-Raum zu transformieren, um die Bumps zu berechnen, transformieren wir einfach das Licht in den Objekt-Raum, indem wir die inverse Modelview-Matrix anwenden. Das ist recht einfach, da wir genau wissen, wie die Modelview-Matrix Schritt-für-Schritt erzeugt wurde, weshalb eine Invertierung auch Schritt-für-Schritt durchgeführt werden kann. Wir kommen zu diesem Thema nochmal später zurück.
  • Wir berechnen den aktuellen Vertex c auf unserer Oberfläche (indem wir einfach in den Daten nachschauen).
  • Dann berechnen wir einen Normalenvektor n mit der Länge 1 (wir kennen n in der Regel für jede Seite des Würfels!). Das ist wichtig, da wir Rechenzeit sparen können, indem wir die normalisierten Vektoren anfordern. Berechnung des Licht-Vektors v von c zur Lichposition l
  • Wenn es Arbeit gibt, erzeuge eine Matrix Mn, die die orthonormale Projektion repräsentiert. Das wird als f gemacht
  • Berechne den Textur-Koordinaten-Offset, indem die gelieferte Textur-Koordinaten Richtung s und t jeweils mit v und MAX_EMBOSS multipliziert wird: ds = s*v*MAX_EMBOSS, dt=t*v*MAX_EMBOSS. Beachten Sie, dass s, t und v Vektoren sind und MAX_EMBOSS nicht.
  • Addiere den Offset zu den Textur-Koordinaten im 2. Durchlauf.
Warum das Gut ist:
  • Schnell (benötigt nur eine Wurzel und ein paar Multiplikationen pro Vertex)!
  • Sieht sehr nett aus!
  • Funktioniert mit allen Oberflächen, nicht nur Ebenen.
  • Läuft auf allen Beschleunigern.
  • Ist glBegin/glEnd freundlich: benötigt keine "verbotene" GL-Befehle.
Nachteile:
  • Physikalisch nicht ganz korrekt.
  • Hinterlässt kleinere Artefakte.


Diese Abbildung zeigt, wo sich unsere Vektoren befinden. Sie erhalten t und s indem Sie einfach die adjazenten Vertices subtrahieren, aber stellen Sie vorher sicher, dass sie in die richtige Richtung zeigen und normalisieren Sie sie. Der blaue Punkt markiert den Vertex wo texCoord2f(0.0f,0.0f) hingemapped wird.

Ende Theorie ( Emboss Bump Mapping Algorithmen )

Lassen Sie uns zuerst einen Blick auf die Textur-Koordinaten Offset Erzeugung werfen. Die Funktion wird SetUpBumps() genannt, da sie genau das macht:
 
// initialisiere die Textur-Offsets
// n : Normalenvektor auf der Oberfläche. Muss die Länge 1 haben
// c : aktueller Vertex auf der Oberfläche
// l : Lichtposition
// s : Richtung der s-Textur-Koordinate im Objekt-Raum (muss normalisiert sein!)
// t : Richtung der t-Textur-Koordinate im Objekt-Raum (muss normalisiert sein!)
void SetUpBumps(GLfloat *n, GLfloat *c, GLfloat *l, GLfloat *s, GLfloat *t) {
    GLfloat v[3];                                // Vector von der aktuellen Position zum Licht
    GLfloat lenQ;                                // wird für die Normalisierung verwendet
    // berechne v aus dem aktuellen Vertex c zur Lichtposition und Normalisiere v
    v[0]=l[0]-c[0];
    v[1]=l[1]-c[1];
    v[2]=l[2]-c[2];
    lenQ=(GLfloat) sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]);
    v[0]/=lenQ;
    v[1]/=lenQ;
    v[2]/=lenQ;
    // Projeziere v so, dass wir zwei Werte entlängs jeder Textur-Koordinaten-Achse erhalten
    c[0]=(s[0]*v[0]+s[1]*v[1]+s[2]*v[2])*MAX_EMBOSS;
    c[1]=(t[0]*v[0]+t[1]*v[1]+t[2]*v[2])*MAX_EMBOSS;
}

Sieht nicht allzu kompliziert mehr aus, oder? Aber die Thorie ist nötig, um den Effekt zu verstehen und kontrollieren zu können. (Ich habe DAS selbst gelernt, als ich dieses Tutorial geschrieben habe).

Ich mag es immer, wenn Logos angezeigt werden, während ein Präsentationsprogramme laufen. Wir haben zur Zeit zwei davon. Da ein Aufruf von doLogo() die GL_MODELVIEW-Matrix resettet, muss dies als letzter Rendering-Durchlauf aufgerufen werden.

Diese Funktion zeigt zwei Logos an: ein OpenGL-Logo und ein Multitexture-Logo, wenn dieses Feature aktiviert ist. Die Logos sind Alpha-blended und etwas Halbtransparent. Da sie einen Alpha-Kanal haben, blende ich sie mittels GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, wie von allen OpenGL-Dokumentationen vorgeschlagen wird. Da sie alle Co-planar sind, müssen wir nicht erst nach z sortieren. Die Zahlen die für die Vertices verwendet werden sind "empirisch" (auch bekannt als try-and-error), um sie nett in den Screen-Ecken zu platzieren. Wir müssen Blending aktivieren und Beleuchtung deaktivieren, um hässliche Effekte zu vermeiden. Um sicher zu gehen, dass sie vor allem anderen sind, resetten Sie einfach die GL_MODELVIEW-Matrix und setzen die Depth-Funktion auf GL_ALWAYS.
 
void doLogo(void) {
    // MUSS ALS LETZTES AUFGERUFEN WERDEN!!!, Billboarded die zwei Logos
    glDepthFunc(GL_ALWAYS);
    glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_BLEND);
    glDisable(GL_LIGHTING);
    glLoadIdentity();
    glBindTexture(GL_TEXTURE_2D,glLogo);
    glBegin(GL_QUADS);
        glTexCoord2f(0.0f,0.0f);    glVertex3f(0.23f, -0.4f,-1.0f);
        glTexCoord2f(1.0f,0.0f);    glVertex3f(0.53f, -0.4f,-1.0f);
        glTexCoord2f(1.0f,1.0f);    glVertex3f(0.53f, -0.25f,-1.0f);
        glTexCoord2f(0.0f,1.0f);    glVertex3f(0.23f, -0.25f,-1.0f);
    glEnd();
    if (useMultitexture) {
        glBindTexture(GL_TEXTURE_2D,multiLogo);
        glBegin(GL_QUADS);
            glTexCoord2f(0.0f,0.0f);    glVertex3f(-0.53f, -0.25f,-1.0f);
            glTexCoord2f(1.0f,0.0f);    glVertex3f(-0.33f, -0.25f,-1.0f);
            glTexCoord2f(1.0f,1.0f);    glVertex3f(-0.33f, -0.15f,-1.0f);
            glTexCoord2f(0.0f,1.0f);    glVertex3f(-0.53f, -0.15f,-1.0f);
        glEnd();
    }
}

Hier kommt die Funktion um Bump Mapping ohne Multitexturing zu realisieren. Es ist eine Drei-Pass-Implementation. Als erster Schritt wird die GL_MODELVIEW Matrix invertiert, indem die Identity-Matrix angewandt wird, und alle folgenden Schritte wird die GL_MODELVIEW angewandt, in umgekehrter Reihenfolge und invertiert. Das Ergebniss ist eine Matrix, die die GL_MODELVIEW "ungeschehen" macht, wenn sie auf ein Objekt angewandt wird. Wir holen sie einfach von OpenGL, indem wir glGetFloatv() verwenden. Denken Sie daran, dass die Matrix ein Matrix mit 16 Elementen sein muss und dass die Matrix "transponiert" ist!

Übrigens: Wenn Sie nicht genau wissen, wie die Modelview erzeugt wurde, sollten Sie in Betracht ziehen, den Welten-Raum zu verwenden, da Matrix-Invertierungen kompliziert und kostspielig sind. Wenn Sie aber größere Mengen an Vertices haben, könnte das invertieren der Modelview mit einem allgemeinerem Vorhaben schneller sein.
 
bool doMesh1TexelUnits(void) {
    GLfloat c[4]={0.0f,0.0f,0.0f,1.0f};                    // enthält den aktuellenVertex
    GLfloat n[4]={0.0f,0.0f,0.0f,1.0f};                    // Normalisiere Normalenvektor der aktuellen Oberfläche
    GLfloat s[4]={0.0f,0.0f,0.0f,1.0f};                    // s-Textur Koordinate Richtung, normalisiert
    GLfloat t[4]={0.0f,0.0f,0.0f,1.0f};                    // t-Textur Koordinate Richtung, normalisiert
    GLfloat l[4];                                // enthält unsere Lichtposition die in den Objekt-Raum transformiert werden soll
    GLfloat Minv[16];                            // enthält die invertierte Modelview Matrix, um das zu machen
    int i;

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);            // lösche den Screen und den Depth Buffer

    // erzeuge als erstes die inverse Modelview Matrix. Dies substituiert ein Push/Po mit einemglLoadIdentity();
    // erzeuge sie einfach indem alle Transformationen negiert werden und zwar in umgekehrter Reihenfolge
    glLoadIdentity();
    glRotatef(-yrot,0.0f,1.0f,0.0f);
    glRotatef(-xrot,1.0f,0.0f,0.0f);
    glTranslatef(0.0f,0.0f,-z);
    glGetFloatv(GL_MODELVIEW_MATRIX,Minv);
    glLoadIdentity();
    glTranslatef(0.0f,0.0f,z);
    glRotatef(xrot,1.0f,0.0f,0.0f);
    glRotatef(yrot,0.0f,1.0f,0.0f);

    // Transformiere die Lichtposition in Objekt-Koordinaten:
    l[0]=LightPosition[0];
    l[1]=LightPosition[1];
    l[2]=LightPosition[2];
    l[3]=1.0f;                                // Homogene Koordinate
    VMatMult(Minv,l);

Erster Durchlauf:
  • Verwende Bump-Textur
  • Deaktiviere Blending
  • Deaktiviere Beleuchtung
  • Verwende Non-Offset Textur-Koordinaten
  • Mache die Geometrie
Dies wird einen Würfel zeichnen, der nur aus Bump Map besteht.
 
    glBindTexture(GL_TEXTURE_2D, bump[filter]);
    glDisable(GL_BLEND);
    glDisable(GL_LIGHTING);
    doCube();

Zweiter Durchlauf:
  • Verwende invertierte Bump-Textur
  • Aktiviere Blending GL_ONE, GL_ONE
  • Lasse Beleuchtung deaktiviert
  • Verwende Offset Textur-Koordinaten (Das bedeutet, dass Sie SetUpBumps() vor jeder Seite des Würfels aufrufen)
  • Mache die Geometrie
Dies wird den Würfel mit korrektem Emboss-Bump-Mapping rendern, aber ohne Farben.

Sie könnten Rechenzeit sparen, indem Sie den Lichtvektor einfach in die invertierte Richtung rotieren. Allerdings hat das nicht richtig funktioniert, weshalb wir den einfachen Weg gehen: jeden Normalenvektor und Mittelpunkt rotieren lassen, genauso wie wir unsere Geometrie rotieren lassen!
 
    glBindTexture(GL_TEXTURE_2D,invbump[filter]);
    glBlendFunc(GL_ONE,GL_ONE);
    glDepthFunc(GL_LEQUAL);
    glEnable(GL_BLEND);

    glBegin(GL_QUADS);
        // vordere Seite
        n[0]=0.0f;
        n[1]=0.0f;
        n[2]=1.0f;
        s[0]=1.0f;
        s[1]=0.0f;
        s[2]=0.0f;
        t[0]=0.0f;
        t[1]=1.0f;
        t[2]=0.0f;
        for (i=0; i// hintere Seite
        n[0]=0.0f;
        n[1]=0.0f;
        n[2]=-1.0f;
        s[0]=-1.0f;
        s[1]=0.0f;
        s[2]=0.0f;
        t[0]=0.0f;
        t[1]=1.0f;
        t[2]=0.0f;
        for (i=4; i// obere Seite
        n[0]=0.0f;
        n[1]=1.0f;
        n[2]=0.0f;
        s[0]=1.0f;
        s[1]=0.0f;
        s[2]=0.0f;
        t[0]=0.0f;
        t[1]=0.0f;
        t[2]=-1.0f;
        for (i=8; i// untere Seite
        n[0]=0.0f;
        n[1]=-1.0f;
        n[2]=0.0f;
        s[0]=-1.0f;
        s[1]=0.0f;
        s[2]=0.0f;
        t[0]=0.0f;
        t[1]=0.0f;
        t[2]=-1.0f;
        for (i=12; i// rechte Seite
        n[0]=1.0f;
        n[1]=0.0f;
        n[2]=0.0f;
        s[0]=0.0f;
        s[1]=0.0f;
        s[2]=-1.0f;
        t[0]=0.0f;
        t[1]=1.0f;
        t[2]=0.0f;
        for (i=16; i// linke Seite
        n[0]=-1.0f;
        n[1]=0.0f;
        n[2]=0.0f;
        s[0]=0.0f;
        s[1]=0.0f;
        s[2]=1.0f;
        t[0]=0.0f;
        t[1]=1.0f;
        t[2]=0.0f;
        for (i=20; i
Dritter Durchlauf:
  • Verwende (farbige) Basis-Textur
  • aktiviere Blending GL_DST_COLOR, GL_SRC_COLOR
  • Diese Blending Gleichung multipliziert mit 2: (Cdst*Csrc)+(Csrc*Cdst)=2(Csrc*Cdst)!
  • aktiviere Beleichtung um den ambienten und diffusen Kram zu erledigen
  • Resette die GL_TEXTURE-Matrix um zurück zu "normalen" Textur Koordinaten zu kommen
  • Mache die Geometrie
Damit wird das Würfel-rendern beendet, komplett mit Beleuchtung. Da wir zwischen Multitexturing und Non-Multitexturing hin- und herspringen können, müssen wir die Textur-Umgebung als erstes auf "normales" GL_MODULATE resetten. Wir machen den dritten Durchlauf nur dann, wenn der Benutzer nicht nur den Emboss sehen will.
 
    if (!emboss) {
        glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
        glBindTexture(GL_TEXTURE_2D,texture[filter]);
        glBlendFunc(GL_DST_COLOR,GL_SRC_COLOR);
        glEnable(GL_LIGHTING);
        doCube();
    }

Letzter Durchlauf:
  • aktualisiere die Geometrie (insbesondere Rotationen)
  • bearbeite die Logos
 
    xrot+=xspeed;
    yrot+=yspeed;
    if (xrot>360.0f) xrot-=360.0f;
    if (xrot360.0f) yrot-=360.0f;
    if (yrot// weiter gehts
}

Diese Funktion wird den ganzen Kram in 2 Durchläufen inklusive Multitexturing Unterstützung machen. Wir unterstützen zwei Texel-Einheiten. Mehr währen extrem kompliziert, auf Grund der Blending-Gleichung. Dann sollte man besser zu TNT wechseln. Beachten Sie, dass fast der einzige Unterschied zu doMesh1TexelUnits() lediglich der ist, dass wir zwei Sätze an Textur-Koordinaten für jeden Vertex senden!
 
bool doMesh2TexelUnits(void) {
    GLfloat c[4]={0.0f,0.0f,0.0f,1.0f};                    // enthält aktuellen Vertex
    GLfloat n[4]={0.0f,0.0f,0.0f,1.0f};                    // Normalisierter Normalenvektor der aktuellen Oberfläche
    GLfloat s[4]={0.0f,0.0f,0.0f,1.0f};                    // s-Textur Koordinate Richtung, normalisiert
    GLfloat t[4]={0.0f,0.0f,0.0f,1.0f};                    // t-Textur Koordinate Richtung, normalisiert
    GLfloat l[4];                                // enthält unsere Lichtposition die in den Objekt-Raum transformiert werden soll
    GLfloat Minv[16];                            // enthält die invertierte Modelview Matrix, um das zu machen
    int i;

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);            // lösche den Screen und den Depth Buffer

    // erzeuge als erstes die inverse Modelview Matrix. Dies substituiert ein Push/Po mit einemglLoadIdentity();
    // erzeuge sie einfach indem alle Transformationen negiert werden und zwar in umgekehrter Reihenfolge
    glLoadIdentity();
    glRotatef(-yrot,0.0f,1.0f,0.0f);
    glRotatef(-xrot,1.0f,0.0f,0.0f);
    glTranslatef(0.0f,0.0f,-z);
    glGetFloatv(GL_MODELVIEW_MATRIX,Minv);
    glLoadIdentity();
    glTranslatef(0.0f,0.0f,z);

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

    // Transformiere die Lichtposition in Objekt-Koordinaten:
    l[0]=LightPosition[0];
    l[1]=LightPosition[1];
    l[2]=LightPosition[2];
    l[3]=1.0f;                                // Homogene Koordinate
    VMatMult(Minv,l);

Erster Durchlauf:
  • Kein Blending
  • Keine Beleuchtung
Initialisiere die Textur-Einheit 0 mit
  • Verwende Bump-Textur
  • Verwende Non-Offset Textur-Koordinaten
  • Textur-Operation GL_REPLACE, woraus resultiert, dass die Textur nur gezeichnet wird
Initialisiere die Textur-Einheit 1 mit
  • Offset Textur-Koordinaten
  • Textur-Operation GL_ADD, welches das Multitexture-Equivalent zu ONE, ONE-Blending, ist.
Dies rendert den Würfel, bestehend aus eine grauskalierten Map.
 
    // TEXTUR-EINHEIT #0
    glActiveTextureARB(GL_TEXTURE0_ARB);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, bump[filter]);
    glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
    glTexEnvf (GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_REPLACE);

    // TEXTUR-EINHEIT #1
    glActiveTextureARB(GL_TEXTURE1_ARB);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, invbump[filter]);
    glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
    glTexEnvf (GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_ADD);

    // generelle Schalter
    glDisable(GL_BLEND);
    glDisable(GL_LIGHTING);

Nun rendere eine Seite nach der anderen, genauso wie es bereits in doMesh1TexelUnits() gemacht wurde. Einzige Neuigkeit: es wird glMultiTexCoor2fARB() statt nur glTexCoord2f() verwendet. Beachten Sie, dass Sie angeben müssen, welche Textur-Einheit Sie mit dem ersten Parameter meinen, welcher gleich GL_TEXTUREi_ARB sein muss, wobei i eine Zahl aus [0..31] sein kann. (Welche Hardware hat 32 Textur-Einheiten? Und wofür?)
 
    glBegin(GL_QUADS);
        // vordere Seite
        n[0]=0.0f;
        n[1]=0.0f;
        n[2]=1.0f;
        s[0]=1.0f;
        s[1]=0.0f;
        s[2]=0.0f;
        t[0]=0.0f;
        t[1]=1.0f;
        t[2]=0.0f;
        for (i=0; i// hintere Seite
        n[0]=0.0f;
        n[1]=0.0f;
        n[2]=-1.0f;
        s[0]=-1.0f;
        s[1]=0.0f;
        s[2]=0.0f;
        t[0]=0.0f;
        t[1]=1.0f;
        t[2]=0.0f;
        for (i=4; i// obere Seite
        n[0]=0.0f;
        n[1]=1.0f;
        n[2]=0.0f;
        s[0]=1.0f;
        s[1]=0.0f;
        s[2]=0.0f;
        t[0]=0.0f;
        t[1]=0.0f;
        t[2]=-1.0f;
        for (i=8; i// untere Seite
        n[0]=0.0f;
        n[1]=-1.0f;
        n[2]=0.0f;
        s[0]=-1.0f;
        s[1]=0.0f;
        s[2]=0.0f;
        t[0]=0.0f;
        t[1]=0.0f;
        t[2]=-1.0f;
        for (i=12; i// rechte Seite
        n[0]=1.0f;
        n[1]=0.0f;
        n[2]=0.0f;
        s[0]=0.0f;
        s[1]=0.0f;
        s[2]=-1.0f;
        t[0]=0.0f;
        t[1]=1.0f;
        t[2]=0.0f;
        for (i=16; i// Linke Seite
        n[0]=-1.0f;
        n[1]=0.0f;
        n[2]=0.0f;
        s[0]=0.0f;
        s[1]=0.0f;
        s[2]=1.0f;
        t[0]=0.0f;
        t[1]=1.0f;
        t[2]=0.0f;
        for (i=20; i
Zweiter Durchlauf
  • Verwende die Basis-Textur
  • aktiviere Beleuchtung
  • Keine Offset Textur-Koordinaten => resette GL_TEXTURE-matrix
  • Resette Textur-Umgebung auf GL_MODULATE um danach OpenGLLighting auszuführen (funktioniert sonst nicht!)
Dies wir unseren kompletten bump-mapped Würfel rendern.
 
    glActiveTextureARB(GL_TEXTURE1_ARB);
    glDisable(GL_TEXTURE_2D);
    glActiveTextureARB(GL_TEXTURE0_ARB);
    if (!emboss) {
        glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
        glBindTexture(GL_TEXTURE_2D,texture[filter]);
        glBlendFunc(GL_DST_COLOR,GL_SRC_COLOR);
        glEnable(GL_BLEND);
        glEnable(GL_LIGHTING);
        doCube();
    }

Letzter Durchlauf
  • aktualisiere Geometrie (insbesondere Rotationen)
  • bearbeite die Logos
 
    xrot+=xspeed;
    yrot+=yspeed;
    if (xrot>360.0f) xrot-=360.0f;
    if (xrot360.0f) yrot-=360.0f;
    if (yrot// weiter gehts
}

Zu guter Letzt eine Funktion, um einen Würfel ohne Bumpmapping zu rendern, damit Sie den Unterschied sehen können!
 
bool doMeshNoBumps(void) {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);            // lösche den Screen und den Depth Buffer
    glLoadIdentity();                            // Resette die View
    glTranslatef(0.0f,0.0f,z);

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

    if (useMultitexture) {
        glActiveTextureARB(GL_TEXTURE1_ARB);
        glDisable(GL_TEXTURE_2D);
        glActiveTextureARB(GL_TEXTURE0_ARB);
    }

    glDisable(GL_BLEND);
    glBindTexture(GL_TEXTURE_2D,texture[filter]);
    glBlendFunc(GL_DST_COLOR,GL_SRC_COLOR);
    glEnable(GL_LIGHTING);
    doCube();

    xrot+=xspeed;
    yrot+=yspeed;
    if (xrot>360.0f) xrot-=360.0f;
    if (xrot360.0f) yrot-=360.0f;
    if (yrot// weiter gehts
}

Weiter zu Teil 2