NeHe - Lektion 36 - Radial Blur

Lektion 36



Hi! Ich bin Dario Corno, auch als rIo von SpinningKids bekannt.. Als erstes möchte ich erklären, warum ich mich entschieden habe, dieses Tutorial zu schreiben. Ich bin ein Scener seit 1989. Ich möchte, dass Sie alle einige Demos herunterladen, so dass Sie verstehen, was ein Demo ist und was Demo-Effekte sind.

Demos werden gemacht, um Hardcore und manchmal 'brutales' Programmieren sowie künstlerische Fähigkeiten zu zeigen. Sie können in der Regel in heutigen Demos einige echte Killer-Effekte finden! Dies hier wird kein Killer-Effekt Tutorial, aber das Endresultat ist sehr cool! Sie finden eine große Ansammlung an Demos unter http://www.pouet.net und http://ftp.scene.org.

Nun, da wir die Einleitung hinter uns haben, können wir mit dem Tutorial weitermachen...

Ich werde erklären, wie man einen Augenschmauseffekt (der in Demos benutzt wird) macht, der aussieht wie strahlenförmige Verschwommenheit (Radial Bluer). Manchmal wird auch von volumetrischen Licht gesprochen, glaube Sie das nicht, es ist nur ein gefakter Radial Blur! ;D

Radial Blur wurde normalerweise realsiert (als es nur Software Renderer gab) indem jeder Pixel des original Bildes in eine entgegengesetzte Richtung vom Zentrum des Blurs aus verwischt wurde.

Mit heutiger Hardware ist es ziemlich schwierig von Hand aus zu verwischen und den selben Farb-Buffer zu verwenden (zumindest auf eine Art, die von allen Grafikkarten unterstütz wird), weshalb wir einen kleinen Trick anwenden müssen, um den selben Effekt zu erzielen.

Als kleiner Bonus lernen Sie neben dem Radial Blur Effekt auch, wie Sie auf einfache Art auf eine Textur rendern!

Ich habe mich entschlossen eine Feder als Form in diesem Tutorial zu verwenden, da es eine coole Form ist und ich die Würfel satt habe :)

Es ist wichtig anzumerken, dass dieses Tutorial mehr ein Leitfaden ist, wie man den Effekt erzeugt. Ich werde nicht großartig ins Detail gehen, wenn ich den Code erkläre. Das meiste sollten Sie bereits im Schlaf kennen :)

Folgend sind die Variablen Definitionen und die verwendeten Includes:

#include <math.h>                            // wir benötigen etwas Mathe

float        angle;                            // wird benutzt um die Schneckenlinien zu rotieren
float        vertexes[4][3];                        // enthält Fließkommawerte Infos für 4 Sätze an Vertices
float        normal[3];                        // Ein Array um die Normalenvektor Daten zu speichern
GLuint        BlurTexture;                        // Ein Unsigned Int um die Texturnummer zu speichern

Die Funktion EmptyTexture() erzeugt eine leere Textur und gibt die Texturnummer zurück. Wir alloziieren einfach etwas freien Speicher (genau 128 * 128 * 4 unsigned integers).

128 * 128 ist die Größe der Textur (128 Pixel breit und hoch), die 4 bedeutet, dass wir für jeden Pixel 4 Bytes zum speichern der ROT, GRÜN, BLAU und ALPHA Komponenten haben wollen.

GLuint EmptyTexture()                            // erzeuge eine leere Textur
{
    GLuint txtnumber;                        // Textur ID
    unsigned int* data;                        // gespeicherte Daten

    // erzeuge Speicherplatz für Texturdaten (128x128x4)
    data = (unsigned int*)new GLuint[((128 * 128)* 4 * sizeof(unsigned int))];

Nachdem der Speicherplatz alloziiert wurde, setzen wir diesen auf Null, indem wir die ZeroMemory Funktion verwenden, übergeben den Zeiger (data) und die Größe des Speichers, der "genullt" werden soll.

Eine halbwegs wichtige Sache, die angemerkt werden muss, dass wir die Magnification und Minification Methoden auf GL_LINEAR setzen. Der Grund dafür ist, dass wir unsere Textur strecken werden und GL_NEAREST sieht gestreckt ziemlich mies aus.

    ZeroMemory(data,((128 * 128)* 4 * sizeof(unsigned int)));    // lösche Speicherplatz

    glGenTextures(1, &txtnumber);                    // erzeuge 1 Textur
    glBindTexture(GL_TEXTURE_2D, txtnumber);            // Binde die Textur
    glTexImage2D(GL_TEXTURE_2D, 0, 4, 128, 128, 0,
        GL_RGBA, GL_UNSIGNED_BYTE, data);            // Builde Textur und benutze dieInformation in data
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

    delete [] data;                            // gebe data frei

    return txtnumber;                        // gebe die Textur ID zurück
}

Diese Funktion normalisiert einfach die Länge des Normalenvektors. Vektoren werden durch Arrays mit 3 Elementen vom Typen float dargestellt, wobei das erste Element X repräsentiert, das zweite Y und das dritte Z. Ein normalisierter Vektor (Nv) wird durch Vn = (Vox / |Vo| , Voy / |Vo|, Voz / |Vo|) ausgedrückt, wobei Vo der original Vektor ist, |vo| ist der Modulus (oder Länge) des Vektors und x,y,z sind die Komponenten dieses Vektors. Um das "digital" zu machen, rechnen wir: sqrt(x^2 + y^2 + z^2) , wobei x,y,z die 3 Komponenten des Vektors sind. Und dann dividiert man jede Normalenvektor-Komponente durch die Länge des Vektors.

void ReduceToUnit(float vector[3])                    // Reduziert einen Normalenvektor (3 Koordinaten)
{                                    // in einen Einheiten Normalenvektor mit einer Länge von eins.
    float length;                            // enthält die Längeneinheit
    // berechnet die Länge des Vektors
    length = (float)sqrt((vector[0]*vector[0]) + (vector[1]*vector[1]) + (vector[2]*vector[2]));

    if(length == 0.0f)                        // verhindert einen Division durch 0 Fehler indem ein akzeptabler
        length = 1.0f;                        // Wert für Vektoren die fast 0 sind zur Verfügung gestellt wird.

    vector[0] /= length;                        // dividiere jedes Element durch
    vector[1] /= length;                        // die Länge, daraus resultiert ein
    vector[2] /= length;                        // Einheiten Normalenvektor.
}

Die folgende Routine berechnet den Normalenvektor aus 3 Vertices (immer in dem 3 Float Array). Wir haben zwei Parameter: v[3][3] und out[3], selbstverständlich ist der erste Parameter eine Matrix aus floats mit m=3 und n=3 wobei jede Zeile ein Vertex des Dreiecks ist. In out speichern wir den resultierenden Normalenvektor.

Ein bißchen (einfache) Mathematik. Wir verwenden das bekannte Kreuzprodukt, per Definition ist das Kreuzprodukt eine Operation zwischen zwei Vektoren, die einen weiteren Vektor der orthogonal zu den beiden ursprünglichen Vektoren zurückliefert. Der Normalenvektor ist ein zur Oberfläche orthogonaler Vektor, der von der Oberfläche weg zeigt (und normalerweise einer normalisierten Länge). Stellen Sie sich nun vor, dass diese zwei Vektoren die Seiten von einem Dreieck sind, dann ist der orthogonale Vektor (durch das Kreuzprodukt berechnet) der zwei Seiten des Dreiecks, genau der Normalenvektor zu diesem Dreieck.

Schwieriger zur erklären als zu machen.

Wir fangen damit an, den Vektor zu finden, indem wir von Vertex 0 zu Vertex 1 gehen und den Vektor von Vertex 1 zu Vertex 2, dass ist eigentlich durch (brutales) Subtrahieren jeder Komponente von jedem Vertex zum nächsten getan. Nun haben wir die Vektoren unserer Dreiecksseiten. Indem wir das Kreuzprodukt bilden (vXw) erhalten wir den Normalenvektor des Dreiecks.

Schauen wir uns den Code an.

v[0][] ist der erste Vertex, v[1][] ist der zweite Vertex, v[2][] ist der dritte Vertex. Jeder Vertex hat: v[][0] die x Koordinate des Vertex, v[][1] die y Koordinate des Vertex, v[][2] die z Koordinate des Vertex.

Durch einfaches subtrahieren jeder Koordinate eines Vertex vom nächsten, erhalten wir den VECTOR von diesem Vertex zum nächsten. v1[0] = v[0][0] - v[1][0], dadurch berechnen wir die X Komponente des VECTOR, der von VERTEX 0 zu Vertex 1 geht. v1[1] = v[0][1] - v[1][1], berechnet die Y Komponente v1[2] = v[0][2] - v[1][2], berechnet die Z Komponente und so weiter...

Nun haben wir zwei VECTORS, deshalb lassen Sie uns nun das Kreuzprodukt dieser berechnen, um den Normalenvektor des Dreiecks zu erhalten.

Die Formel für das Kreuzprodukt ist:

out[x] = v1[y] * v2[z] - v1[z] * v2[y]

out[y] = v1[z] * v2[x] - v1[x] * v2[z]

out[z] = v1[x] * v2[y] - v1[y] * v2[x]

Am Ende haben wir den Normalenvektor des Dreiecks in out[].

void calcNormal(float v[3][3], float out[3])                // berechnet Normalenvektor für ein Quad indem 3 Punkte verwendet werden
{
    float v1[3],v2[3];                        // Vektor 1 (x,y,z) & Vektor 2 (x,y,z)
    static const int x = 0;                        // Definiere X Koordinate
    static const int y = 1;                        // Definiere Y Koordinate
    static const int z = 2;                        // Definiere Z Koordinate

    // Findet den Vektor zwischen 2 Punkten durch Subtraktion
    // der x,y,z Koordinaten von einem Punkt zum anderen.

    // berechen den Vektor von Punkt 1 zu Punkt 0
    v1[x] = v[0][x] - v[1][x];                    // Vector 1.x=Vertex[0].x-Vertex[1].x
    v1[y] = v[0][y] - v[1][y];                    // Vector 1.y=Vertex[0].y-Vertex[1].y
    v1[z] = v[0][z] - v[1][z];                    // Vector 1.z=Vertex[0].y-Vertex[1].z
    // berechen den Vektor von Punkt 2 zu Punkt 1
    v2[x] = v[1][x] - v[2][x];                    // Vector 2.x=Vertex[0].x-Vertex[1].x
    v2[y] = v[1][y] - v[2][y];                    // Vector 2.y=Vertex[0].y-Vertex[1].y
    v2[z] = v[1][z] - v[2][z];                    // Vector 2.z=Vertex[0].z-Vertex[1].z
    // berechne das Kreuzprodukt um einen Oberflächen Normalenvektor zu erhalten
    out[x] = v1[y]*v2[z] - v1[z]*v2[y];                // Kreuzprodukt für Y - Z
    out[y] = v1[z]*v2[x] - v1[x]*v2[z];                // Kreuzprodukt für X - Z
    out[z] = v1[x]*v2[y] - v1[y]*v2[x];                // Kreuzprodukt für X - Y

    ReduceToUnit(out);                        // Normalisiere den Vektor
}

Die folgende Routine setzt einfach einen Point-of-View, mittels gluLookAt. Wir setzen den Point-of-View bei 0, 5, 50 und schauen auf 0, 0, 0 und der UP Vektor (der nach oben zeigt) bei (0, 1, 0)! :D

void ProcessHelix()                            // zeichnet eine Schneckenform (Helix)
{
    GLfloat x;                            // Helix x Koordinate
    GLfloat y;                            // Helix y Koordinate
    GLfloat z;                            // Helix z Koordinate
    GLfloat phi;                            // Winkel
    GLfloat theta;                            // Winkel
    GLfloat v,u;                            // Winkel
    GLfloat r;                            // Radius der Windung
    int twists = 5;                            // 5 Windungen

    GLfloat glfMaterialColor[]={0.4f,0.2f,0.8f,1.0f};        // Setzt die Material Farbe
    GLfloat specular[]={1.0f,1.0f,1.0f,1.0f};            // Setzt spekuläre Beleuchtung

    glLoadIdentity();                        // Resette die Modelview Matrix
    gluLookAt(0, 5, 50, 0, 0, 0, 0, 1, 0);                // Augen Position (0,5,50) Zentrum der Szene (0,0,0)
                                    // Up auf der Y Achse.
    glPushMatrix();                            // Pushe die Modelview Matrix

    glTranslatef(0,0,-50);                        // Translatiere 50 Einheiten in den Screen hinein
    glRotatef(angle/2.0f,1,0,0);                    // Rotiere um angle/2 auf der X-Achse
    glRotatef(angle/3.0f,0,1,0);                    // Rotatiere um angle/3 auf der Y-Achse

    glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE,glfMaterialColor);
    glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,specular);

Wir berechnen dann die Helix Formel und rendern die Feder. Das ist ziemlich einfach, ich werde es nicht erklären, da es nicht das Hauptziel dieses Tutorials ist. Der Helix Code ist ausgehliehen (und etwas optimiert) und zwar von Listen Software Freunden. Das ist auf die einfache Art geschrieben und nicht die schnellste Methode. Wenn man Vertex Arrays verwenden würde, wäre es schneller!

    r=1.5f;                                // Radius

    glBegin(GL_QUADS);                        // fange an Quads zu zeichnen
    for(phi=0; phi <= 360; phi+=20.0)                // 360 Grad in 20er Schritten
    {
        for(theta=0; theta<=360*twists; theta+=20.0)        // 360 Grad * Anzahl der Windungen in 20er Schritten
        {
            v=(phi/180.0f*3.142f);                // berechne Winkel des ersten Punktes (  0 )
            u=(theta/180.0f*3.142f);            // berechne Winkel des ersten Punktes (  0 )

            x=float(cos(u)*(2.0f+cos(v) ))*r;        // berechne x Position (erster Punkt)
            y=float(sin(u)*(2.0f+cos(v) ))*r;        // berechne y Position (erster Punkt)
            z=float((( u-(2.0f*3.142f)) + sin(v) ) * r);    // berechne z Position (erster Punkt)

            vertexes[0][0]=x;                // Setze x Wert des erstenVertex
            vertexes[0][1]=y;                // Setze y Wert des erstenVertex
            vertexes[0][2]=z;                // Setze z Wert des erstenVertex

            v=(phi/180.0f*3.142f);                // berechne Winkel des zweiten Punktes (  0 )
            u=((theta+20)/180.0f*3.142f);            // berechne Winkel des zweiten Punktes (  20 )

            x=float(cos(u)*(2.0f+cos(v) ))*r;        // berechne x Position (zweiter Punkt)
            y=float(sin(u)*(2.0f+cos(v) ))*r;        // berechne y Position (zweiter Punkt)
            z=float((( u-(2.0f*3.142f)) + sin(v) ) * r);    // berechne z Position (zweiter Punkt)

            vertexes[1][0]=x;                // Setze x Wert des zweitenVertex
            vertexes[1][1]=y;                // Setze y Wert des zweitenVertex
            vertexes[1][2]=z;                // Setze z Wert des zweitenVertex

            v=((phi+20)/180.0f*3.142f);            // berechne Winkel des dritten Punktes (  20 )
            u=((theta+20)/180.0f*3.142f);            // berechne Winkel des dritten Punktes (  20 )

            x=float(cos(u)*(2.0f+cos(v) ))*r;        // berechne x Position (dritter Punkt)
            y=float(sin(u)*(2.0f+cos(v) ))*r;        // berechne y Position (dritter Punkt)
            z=float((( u-(2.0f*3.142f)) + sin(v) ) * r);    // berechne z Position (dritter Punkt)

            vertexes[2][0]=x;                // Setze x Wert des drittenVertex
            vertexes[2][1]=y;                // Setze y Wert des drittenVertex
            vertexes[2][2]=z;                // Setze z Wert des drittenVertex

            v=((phi+20)/180.0f*3.142f);            // berechne Winkel des vierten Punktes (  20 )
            u=((theta)/180.0f*3.142f);            // berechne Winkel des vierten Punktes (  0 )

            x=float(cos(u)*(2.0f+cos(v) ))*r;        // berechne x Position (vierter Punkt)
            y=float(sin(u)*(2.0f+cos(v) ))*r;        // berechne y Position (vierter Punkt)
            z=float((( u-(2.0f*3.142f)) + sin(v) ) * r);    // berechne z Position (vierter Punkt)

            vertexes[3][0]=x;                // Setze x Wert des viertenVertex
            vertexes[3][1]=y;                // Setze y Wert des viertenVertex
            vertexes[3][2]=z;                // Setze z Wert des viertenVertex

            calcNormal(vertexes,normal);            // berechne den Quad Normelnvektor

            glNormal3f(normal[0],normal[1],normal[2]);    // Setze den Normalenvektor

            // Render den Quad
            glVertex3f(vertexes[0][0],vertexes[0][1],vertexes[0][2]);
            glVertex3f(vertexes[1][0],vertexes[1][1],vertexes[1][2]);
            glVertex3f(vertexes[2][0],vertexes[2][1],vertexes[2][2]);
            glVertex3f(vertexes[3][0],vertexes[3][1],vertexes[3][2]);
        }
    }
    glEnd();                            // fertig mit dem rendern von Quads

    glPopMatrix();                            // Poppe die Matrix
}

Diese beiden Routinen (ViewOrtho und ViewPerspective) wurden programmiert, um es einfacher zu machen, orthogonal zu zeichnen und ganz leich zurück zum perspektivischen rendern zu kommen.

ViewOrtho setzt einfach die Projections Matrix und pusht dann eine Kopie der aktuellen Projections Matrix auf den OpenGL stack. Die Einheits Matrix wird dann geladen und eine orthographische Sicht mit der aktuellen Bildschirmauflösung ist initialisiert.

Auf diese Art ist es möglich unter Benutzung von 2D Koordinaten zu zeichnen, mit 0,0 in der oberen linke Ecke des Bildschirms und mit 640, 480 in der oberen Ecke des Bildschirms.

Zu letzt wird die modelview Matrix aktiviert, um Dinge zu rendern.

ViewPerspective setzt den Projections Matrix Modus und poppt die nicht-orthogonal Matrix, die ViewOrtho auf den Stack gepusht hat. Die modelview Matrix wird dann ausgewählt, so dass wir Dinge rendern können.

Ich schlage vor, Sie behalten diese beiden Prozeduren, es ist nett, in der Lage zu sein, in 2D zu rendern ohne sich um die Projections Matrix kümmern zu müssen!

void ViewOrtho()                            // Setzt eine Ortho View
{
    glMatrixMode(GL_PROJECTION);                    // wähle Projection
    glPushMatrix();                            // Pushe die Matrix
    glLoadIdentity();                        // Resette die Matrix
    glOrtho( 0, 640 , 480 , 0, -1, 1 );                // wähle Ortho Modus (640x480)
    glMatrixMode(GL_MODELVIEW);                    // wähle Modelview Matrix
    glPushMatrix();                            // Pushe die Matrix
    glLoadIdentity();                        // Resette die Matrix
}

void ViewPerspective()                            // Setze eine Perspective View
{
    glMatrixMode( GL_PROJECTION );                    // wähle Projection
    glPopMatrix();                            // Poppe die Matrix
    glMatrixMode( GL_MODELVIEW );                    // wähle Modelview
    glPopMatrix();                            // Poppe die Matrix
}

Nun ist es an der Zeit zur erklären, wie der gefakte Radial Blur Effekt gemacht wurde:

Wir müssen die Szene so zeichnen, dass es so aussieht, dass diese in alle Richtungen vom Zentrum aus verwischt ist. Der Trick dabei ist es, dies ohne größere Performance Einbüßungen zu machen. Wir können keine Pixel lesen und schreiben und wir wollen Kompatibilität mit nicht High-End Grafikkarten wahren, wir können keine Extensionen oder Treiber-spezifischen Befehle verwenden.

Zeit zum aufgeben... ?

Nein, die Lösung ist recht einfach, OpenGL gibt uns die Möglichkeit Texturen zu "verwischen". Ok... nicht wirkliches verwischen, aber wenn wir eine Textur mit linearer Filterung skalieren, sieht das Ergebnis (mit etwas Vorstellungsvermögen) wie "gaussian blur" (gaus'sche Verwischung) aus.

Was würde also passieren, wenn wir viele gestreckte Texturen über die 3D Szene legen und diese skalieren?

Die Antwort ist einfach... Ein Radial Blur Effekt!

Es gibt dabei zwei Probleme: Wie erzeugen wir die Textur in Echtzeit und wie plazieren wir die Textur genau vor das 3D Objekt?

Die Lösungen sind einfacher als Sie denken!

Problem EINS: Auf eine Textur rendern

Das PRoblem ist einfach mit Pixel Formaten zu lösen, die einen Back Buffer haben. Eine Textur ohne Back Buffer zu rendern kann recht schmerzhaft für die Augen werden!

Auf die Textur zu rendern wird mit nur einer Funktion erreicht! Wir müssen unser Objekt zeichnen und dann das Ergebnis auf eine Textur kopieren (BEVOR DER BACK BUFFER MIT DEM FRONT BUFFER GETAUSCHT WIRD), indem wir die glCopytexSubImage Funktion verwenden.

Problem ZWEI: Die Textur genau vor das 3D Objekt bringen

Wir wissen, dass wenn wir den Viewport ändern ohne die richtige Perspektive zu setzen, erhalten wir gestrecktes gerednertes Objekt. Wenn wir zum Beispiel einen Viewport setzen, der wirklich sehr weit ist, erhalten wir ein vertikal gestrecktes Rednering.

Die Lösung ist unseren Viewport so zu setzen, dass er genauso quadratisch wie unsere Textur ist (128,128). Nach dem rendern des Objektes auf die Textur, rendern wir die Textur auf den Screen, in dem wir die aktuelle Bildschirmauflösung verwenden. Auf diesem Weg reduziert OpenGL das Objekt so, dass es in die Textur passt und wenn wir die Textur auf die volle Größe des Screens strecken, passt OpenGL die Textur perfekt an, so dass diese über unserem 3D Objekt liegt. Hoffentlich habe ich jetzt niemanden verwirrt. Ein anderes kurzes Beispiel... Wenn Sie einen 640x480 Screenshot machen und dann den Screenshot zu einem 256x256 Bitmap verkleinern, könnten Sie dieses Bitmap als Textur laden und es so strecken, dass es auf einen 640x480 Screen passt. Die Qualität wäre nicht so gut, aber die Textur sollte ziemlich ähnlich dem original 640x480 Bild sein.

Weiter zum spaßigen Teil. Diese Funktion ist wirklich einfach und ist eine meiner bevorzugten "Design Tricks". Sie setzt einen Viewport mit ein Größe, die mit den Dimensionen unserer BlurTexture (128x128) übereinstimmt. Dann ruft sie die Routine zum rendern der Feder auf. Die Feder wird wegen des Viewports (128x128 Viewport) so gestreckt, dass sie auf die 128*128 Textur passt.

Nachdem die Feder so gerendert wurde, dass sie in den 128x128 Viewport passt, binden wir die BlurTexture und kopieren den Farb Buffer vom Viewport zu BlurTexture indem wir glCopyTexSubImage2D verwenden.

Die Parameter sind wie folgt:

GL_TEXTURE_2D indiziert dass wir eine 2 Dimensionale Textur verwenden, 0 ist das mip map Level in das wir den Buffer kopieren wollen, das Standard Level ist 0, GL_LUMINANCE indiziert, das Format der zu kopierende Daten. Ich habe GL_LUMINANCE verwendet, da das finale Ergebnis besser aussieht, auf diesem Weg wird der Luminance Teil des Buffers auf die Textur kopiert. Weitere Parameter könnten GL_ALPHA, GL_RGB, GL_INTENSITY und mehr sein.

Die nächsten 2 Parameter teilen OpenGL mit, wo mit dem kopieren begonnen werden soll (0,0). Die Breite und Höhe (128,128) ist die Anzahl der zu kopierenden Pixel von links nach rechts und wieviele von oben nach unten kopiert werden sollen. Der letzte Parameter wird nur verwendet, wenn wir einen Rahmen haben wollen, was wir aber nicht haben wollen.

Nun, da wir eine Kopie unseres Farb Buffers (mit der gestreckten Feder) in unserer BlurTexture haben, können wir den Buffer löschen und den Viewport zurück auf die passenden Dimensionen setzen (640x480 - Fullscreen).

WICHTIG:

Dieser Trick kann nur mit doppelt gepufferten Pixel Formaten benutzt werden. Der Grund dafür ist, dass all diese Operation versteckt vom Betrachter ausgeführt werden (im Back Buffer gemacht werden).

void RenderToTexture()                            // Rendert auf eine Textur
{
    glViewport(0,0,128,128);                    // Setze unseren Viewport (passend zur Texturgröße)

    ProcessHelix();                            // Render Helix

    glBindTexture(GL_TEXTURE_2D,BlurTexture);            // Binde die Blur Textur

    // kopiere unseren ViewPort auf die Blur Textur (von 0,0 bis 128,128... kein Rahmen)
    glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, 0, 0, 128, 128, 0);

    glClearColor(0.0f, 0.0f, 0.5f, 0.5);                // Setze die Löschfarbe auf ein mittleres Blau
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);        // lösche den Screen und Depth Buffer

    glViewport(0 , 0,640 ,480);                    // Setze Viewport (0,0 bis 640x480)
}

Die DrawBlur Funktion zeichnet einfache ein paar geblendete quads vor unsere 3D Szene und benutzt die BlurTexture die wir zuvor erhalten haben. Auf diesem Weg, mit etwas herumspielen mit Alpha und Skalierung der Textur, erhalten wir etwas, dass wirklich wie Radial Blur aussieht.

Ich deaktiviere als erstes GEN_S und GEN_T (Ich bin süchtig nach Sphere Mapping, weshalb meine Routinen in der Regel diese Instruktionen aktivieren :P ).

Wir aktivieren 2D Texturierung, deaktivieren Depth Testing, setzen die passende Blend Funktion, aktivieren Blending und binden dann BlurTexture.

Als nächstes wechseln wir zur Ortho View, auf diesem Weg ist es einfacher, ein Quad zu zeichnen der perfekt mit dem Bildschirmgröße übereinstimmt. So wird die Textur über das 3D Objekt gelegt (indem die Textur auf die Bildschirmverhältnisse gestreckt wird). So wird Problem zwei gelöst!

void DrawBlur(int times, float inc)                    // zeichne das verwischte Bild
{
    float spost = 0.0f;                        // Startende Texturkoordinaten Offset
    float alphainc = 0.9f / times;                    // Fade Geschwindigkeit für Alpha Blending
    float alpha = 0.2f;                        // Start Alpha Wert

    // deaktiviere AutoTexture Koordinaten
    glDisable(GL_TEXTURE_GEN_S);
    glDisable(GL_TEXTURE_GEN_T);

    glEnable(GL_TEXTURE_2D);                    // aktiviere 2D Texture Mapping
    glDisable(GL_DEPTH_TEST);                    // deaktiviere Depth Testing
    glBlendFunc(GL_SRC_ALPHA,GL_ONE);                // Setze Blending Mode
    glEnable(GL_BLEND);                        // aktiviere Blending
    glBindTexture(GL_TEXTURE_2D,BlurTexture);            // Binde die Blur Textur
    ViewOrtho();                            // wechsel zur Ortho View

    alphainc = alpha / times;                    // alphainc=0.2f / Wie of Blur gerendert werden soll

Wir zeichnen die Textur viele Male um den Radial Effekt zu erzielen, skalieren die Texturkoordinaten und erhöhen den Blend Faktor bei jedem erneuten Durchlauf. Wir zeichen 25 Quads und strecken die Textur bei jedem Mal um 0.015f.

    glBegin(GL_QUADS);                        // fange an Quads zu zeichnen
        for (int num = 0;num <times;num++)            // Wie oft Blur gerendert werden soll
        {
            glColor4f(1.0f, 1.0f, 1.0f, alpha);        // Setze den Alpha Wert (beginnend bei 0.2)
            glTexCoord2f(0+spost,1-spost);            // Texturkoordinate     (   0,   1 )
            glVertex2f(0,0);                // erster Vertex    (   0,   0 )

            glTexCoord2f(0+spost,0+spost);            // Texturkoordinate     (   0,   0 )
            glVertex2f(0,480);                // zweiter Vertex    (   0, 480 )

            glTexCoord2f(1-spost,0+spost);            // Texturkoordinate     (   1,   0 )
            glVertex2f(640,480);                // dritter Vertex    ( 640, 480 )

            glTexCoord2f(1-spost,1-spost);            // Texturkoordinate     (   1,   1 )
            glVertex2f(640,0);                // vierter Vertex    ( 640,   0 )

            spost += inc;                    // inkrementiere stufenweise spost (zoomt näher ans Textur Zentrum)
            alpha = alpha - alphainc;            // dekrementiere stufenweise alpha (schrittweises ausblenden des Bildes)
        }
    glEnd();                            // fertig mit dem Zeichnen der Quads

    ViewPerspective();                        // wechsel zur perspektivischen  View

    glEnable(GL_DEPTH_TEST);                    // aktiviere Depth Testing
    glDisable(GL_TEXTURE_2D);                    // deaktiviere 2D Texture Mapping
    glDisable(GL_BLEND);                        // deaktiviere Blending
    glBindTexture(GL_TEXTURE_2D,0);                    // Unbind die Blur Textur
}

Und voilà, das ist die kürzeste Draw Routine die Sie je gesehen haben, mit einem großartig aussehendem Effekt!

Wir rufen die RenderToTexture Funktion auf. Diese rendert die gestrechte Feder, nochmals Dank an unseren Viewport Wechsel. Die gestreckte Feder wird auf unsere Textur gerendert und der Buffer gelöscht.

Dann zeichnen wir die "WIRKLICHE" Feder (das 3D Objekt, dass Sie auf dem Screen sehe), indem ProcessHelix() aufgerufen wird.

Zu letzt zeichnen wir einige geblendete Quads vor der Feder. Die texturierten Quads werden gestreckt, damit sie über die WIRKLICHE 3D Feder passt.

void Draw (void)                            // Zeichne die Szene
{
    glClearColor(0.0f, 0.0f, 0.0f, 0.5);                // Setze die Farbe auf schwarz
    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);        // Löscht den Bildschirm und den Depth-Buffer
    glLoadIdentity();                        // Resette die View
    RenderToTexture();                        // Render auf eine Textur
    ProcessHelix();                            // zeichne unsere Schneckenform
    DrawBlur(25,0.02f);                        // zeichne den Blur Effect
    glFlush ();                            // Flushe die GL Rendering Pipeline
}

Ich hoffe, Sie haben dieses Tutorial genossen, es lehrt nicht sonderlich viel außer das Rendern auf eine Textur, aber es ist defintiv ein interessanter Effekt, den Sie in Ihre 3D Programme einfügen können.

Wenn Sie irgendwelche Kommentare, Vorschläge haben oder Sie einen besseren Weg kennen, um diesen Effekt zu implementieren, kontaktieren Sie mich rio@spinningkids.org.

Sie können diesen Code frei in Ihren eigenen Produkten verwenden, aber bevor Sie ihn RIPPEN, werfen Sie einen Blick drauf und versuchen Sie zu verstehen, was er macht, das ist der einzige Weg, auf dem rippen erlaubt ist! Außerdem, wenn Sie diesen Code verwenden, geben Sie mir bitte Credits!

Ich möchte Ihnen noch eine Liste geben, die gemacht werden können (Hausaufgaben) :D

1) Modifizieren Sie die DrawBlur Routine um einen horizontalen Blur zu erhalten, vertikalen Blur und einige gute Effekte mehr (Wirbel Blur!).
2) Spielen Sie mit den DrawBlur Parameter (hinzufügen, entfernen) um eine gute Routine zur Synchronisation mit Ihrer Musik zu erhalten.
3) Spielen Sie mit den DrawBlur Parameter herum und verwenden Sie eine KLEINE Textur, die GL_LUMINANCE verwendet (abgefahrenes Leuchten!).
4) Versuchen Sie gefakte Schatten zu erzeugen, indem Sie dunkle Texturen anstatt scheinenden verwenden!

Ok, das wär alles für jetzt.

Besucht meine Seite (und SK's) für anstehende Tutorials unter http://www.spinningkids.org/rio.

Dario Corno (rIo)

Jeff Molofee (NeHe)

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

* DOWNLOAD Borland C++ Builder 6 Code für diese Lektion. ( 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 Michal Tucek )
* DOWNLOAD Dev C++ Code für diese Lektion. ( Conversion by Warren Moore )
* DOWNLOAD Euphoria Code für diese Lektion. ( Conversion by Evan Marshall )
* DOWNLOAD LCC Win32 Code für diese Lektion. ( Conversion by Yann Parmentier )
* DOWNLOAD Linux/GLX Code für diese Lektion. ( Conversion by Patrick Schubert )
* DOWNLOAD Linux/SDL Code für diese Lektion. ( Conversion by Anthony Whitehead )
* DOWNLOAD Mac OS X/Cocoa Code für diese Lektion. ( Conversion by Bryan Blackburn )
* DOWNLOAD Visual Basic Code für diese Lektion. ( Conversion by Dario Corno )
* 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.