NeHe - Lektion 47 - CG Vertex Shader

Lektion 47



Wenn man Vertex und Fragment (oder Pixel) Shader verwendet, um etwas trickreiches Rendern zu realisieren, kann das eine Menge Vorteile haben. Der wohl offensichtlichste ist der, dass der CPU Grafik-relevante Dinge abgenommen werden und von der CPU zur GPU verlagert werden. Cg bietet eine (ziemlich) einfache Sprache um sehr mächtige Shader zu schreiben.

Dieses Tutorial hat mehrere Ziele. Das erste ist, einen einfachen Vertex Shader zu präsentieren, der auch tatsächlich was macht, ohne unnötige Beleuchtung etc. zu verwenden. Das zweite ist, die Basis Mechanismen, um einen Vertex Shader mit sichtbaren Ergebniss mittels OpenGL zum laufen zu kriegen, zu vermitteln. Somit ist es an Anfänger gerichtet, die Interesse in Cg haben und etwas Erfahrung in OpenGL bereits besitzen.

Dieses Tutorial basiert auf dem aktuellsten NeHeGL Basecode. Für mehr Informationen über Cg, schauen Sie sich nVidia’s Website (developer.nvidia.com) an und www.cgshaders.org für ein paar coole Shader.

ANMERKUNG: Dieses Tutorial hat nicht die Absicht, Ihnen alles beizubringen, was Sie zum schreiben von Vertex Shadern mit Cg benötigen. Es beabsichtigt Ihnen beizubringen, wie Sie erfolgreich Vertex Shader in OpenGL laden und laufen lassen können.

Setup:

Der erste Schritt (so weit es bisher noch nicht getan wurde) ist das downloaden des Cg Compiler von nVidia. Es ist wichtig, dass Sie Version 1.1 herunterladen, da nVidia scheinbar einige Änderungen zwischen Version 1.0 und 1.1 gemacht hat (verschieden Variablen-Namensgebung, Funktionen ersetzt, etc.) und somit compilierter Code für die andere Version also nicht unbedingt funktionieren muss.

Der nächste Schritt ist das Bereitstellen der Header und Library Dateien von Cg, an einem Ort, wo Visual Studio diese finden kann. Da ich sehr skeptisch bin, dass Installer ihre Arbeit so machen, wie sie es sollen, kopiere ich die Library Dateien...

From: C:\Program Files\NVIDIA Corporation\Cg\lib
To:   C:\Program Files\Microsoft Visual Studio\VC98\Lib

und die Header Dateien (Cg Unterverzeichnis und GLext.h in das GL Unterverzeichnis)...

From: C:\Program Files\NVIDIA Corporation\Cg\include
To:   C:\Program Files\Microsoft Visual Studio\VC98\Include

Nun sind wir bereit, mit dem Tutorial weiter zu machen.

Cg Tutorial:

Die Informationen über Cg, die in diesem Tutorial enthalten sind, stammen zum größten Teil aus dem Cg Toolkit Benutzerhandbuch.

Es gibt einige wichtige Punkt, die Sie im Hinterkopf behalten sollten, wenn Sie mit Vertex (und später Fragment) Programme arbeiten. Die erste Sache ist, dass ein Vertex Programm in seiner Gesamtheit auf JEDEN Vertex angewendet wird. Die einzige Möglichkeit, das Vertex Programm nur auf ausgewählte Vertices anzuwenden, ist, entweder das Vertex Programm für jeden einzelnen Vertex zu laden/beenden oder Vertices, die davon beeinflusst werden sollen, in einen Stream zu packen und die, die nicht bearbeitet werden sollen, in einen anderen Stream.

Die Ausgabe eines Vertex Programm wird an einer Fragment Shader übergeben, ganz egal, ob Sie einen Fragment Shader implementiert oder aktiviert haben.

Und zu letzt, denken Sie daran, dass ein Vertex Programm vor der Zusammensetzung der Primitiven ausgeführt wird, während ein Fragment Shade nach der Rasterisierung ausgeführt wird. Weiter mit dem Tutorial.

Als erstes müssen wir eine leere Datei erzeugen (speichern Sie diese unter "wave.cg"). Wir erzeugen dann eine Struktur, die die Variablen und Informationen, die wir unserem Shader zur Verfügung stellen wollen, enthalten wird. Dieser Code wird der wave.cg Datei hinzugefügt.

struct appdata
{
    float4 position : POSITION;
    float4 color    : COLOR0;
    float3 wave    : COLOR1;
};

Jede der 3 Variablen (position, color und wave) wird jeweils einem vordefiniertem Namen zugewiesen (POISITION, COLOR0 und COLOR1). Diese vordefinierten Namen werden als Binding Semantics bezeichnet. In OpenGL spezifizieren diese vordefinierten Namen implizit wie Eingaben auf die einzelnen Hardware Register umgelegt werden. Das Hauptprogramm muss die Daten für jede dieser Variablen bereitstellen. Die position Variable wird BENÖTIGT da sie für die Rasterisierung verwendet wird. Es ist die einzige Variable die als Eingabe für das Vertex Programm benötigt wird.

Der nächste Schritt ist das Erzeugen einer Struktur, die die Ausgabe enthalten wird, welche dem Fragment Prozessor nach der Rasterisierung übergeben wird.

struct vfconn
{
    float4 HPos    : POSITION;
    float4 Col0    : COLOR0;
};

Wie bei der Eingabe ist jede Ausgabe-Variable an einen vordefinierten Namen gebunden. Hpos repräsentiert die Position transformiert in den Homogenen Clip-Space. Col0 repräsentiert die Farbe des Vertex nachdem Änderungen an ihm durch das Vertex Programm vorgenommen wurden.

Was übrig bleibt, ist das Schreiben des eigentlichen Vertex Programmes, welches unsere beiden neu definierten Strukturen verwendet.

vfconn main(appdata IN,    uniform float4x4 ModelViewProj)
{
    vfconn OUT;                                    // Variable um unsere Ausgabe vom Vertex Shader zu händeln
                                            // (geht an einen Fragment Shader soweit verfügbar)

Wie in C definieren unsere Funktion mit einem Rückgabetypen (struct vfconn), einem Funktionsnamen (main, aber kann auch alles andere sein, was wir haben wollen) und den Parametern. In unserem Beispiel nehme wir unsere Struktur appdata als Eingabe (enthält die aktuelle Position des Vertex, die Farbe des Vertex und einen Wave Wert, um die Sinus-Welle über den Mesh zu bewegen).

Außerdem übergeben wir einen konstanten Parameter, welcher unsere aktuelle Modelview Matrix aus OpenGL (in unserem Hauptprogramm) ist. Dieser Wert ändert sich in der Regel nicht, da wir unsere Vertices verändern und er somit konstant bleibt. Diese Matrix wird benötigt die Vertex Position in den homogenen Clip-Space zu transformieren.

Wir deklarieren eine Variable, die unsere modifzierten Werte vom Vertex Shader enthalten wird. Diese Werte werden am Ende der Funktion zurück geliefert und an den Fragment-Shader weitergegeben (wenn er existiert).

Nun müssen wir unsere Modifikationen an den Vertex Daten vornehmen.

    // ändere die Y-Position des Vertex basierend auf Sinus Wellen
    IN.position.y = ( sin(IN.wave.x + (IN.position.z / 4.0) ) + sin(IN.wave.x + (IN.position.x / 5.0) ) ) * 2.5f;

Wir ändern die Y Position des Vertex abhängig von der aktuellen X / Z Position des Vertex. Die X und Z Positionen des Vertex werden durch 4.0 bzw. 5.0 dividiert, um sie weicher zu machen (um zu sehen, was ich meine, ändern Sie beide Werte einmal auf 1.0).

Unsere IN.wave Variable enthält einen ständig inkrementierenden Wert, was unsere Sinus Wellen weich über unseren Mesh bewegen lässt. Diese Variable ist in unserem Hauptprogramm spezifiziert. Deshalb berechnen wir die Y Position der X / Y Position des Mesh als Sinus des Wave Wertes + die aktuelle X oder Z Position. Letztlich multiplizieren wir den Wert mit 2.5, um die Wellen sichtbarer (höher) zu machen.

Wir führen nun die benötigten Operationen aus, um die Output Werte für das Fragment Programm zu bestimmen.

    // Transformiere die Vertex Position in den homogenen Clip-Space (benötigt)
    OUT.HPos = mul(ModelViewProj, IN.position);

    // Setze die Farbe auf den Wert, der in IN.color spezifiziert ist
    OUT.Col0.xyz = IN.color.xyz;

    return OUT;
}

Als erstes transformieren wir die neue Vertex Position in den homogenen Clip-Space. Dann setzen wir unsere Output Farbe auf die Input Farbe, welche in unserem Hauptprogramm spezifiziert ist. Letztlich geben wir unsere Werte zurück, damit sie von einem Fragment Shader verwendet werden können (wenn wir einen haben).

Wir machen weiter mit dem Hauptprogramm, welches ein Dreiecks Mesh erzeugt und unseren Shader für jeden Vertex ausführt, um einen netten Wellen-Effekt zu erzeugen.

OpenGL Tutorial:

Die Reihenfolge der Schritte, um unseren Cg Shader zu verwenden sind: das Generieren unseres Mesh, das Laden und kompilieren unseres Cg Programms und dann das ausführen dieses Programms auf jeden Vertex, sobald dieser gezeichnet ist.

Als erstes müssen wir einige notwendige Initialisierungen bewältigen. Wir müssen die notwendigen Header-Dateien einbinden, um unsere Cg Shader mit OpenGL laufen lassen zu können. Nach unseren anderen #include Statements, müssen wir die Cg und CgGL Header einbinden.

#include <cg\cg.h>                                    // NEU: Cg Header
#include <cg\cggl.h>                                    // NEU: Cg OpenGL spezifischer Header

Nun sollten wir bereit sein, um unser Projekt zu intialisieren und zum laufen zu kriegen. Bevor wir anfangen, stellen wir sicher, dass Visual Studio die korrekten Libraries findet. Der folgende Code macht das!

#pragma comment( lib, "cg.lib" )                            // suche nach Cg.lib während des Linkens
#pragma comment( lib, "cggl.lib" )                            // suche nachCgGL.lib während des Linkens

Als nächstes erzeugen wir einige globale Variablen für unseren Mesh und um das CG Programm ein und auszuschalten.

#define        SIZE    64                                // Definiere die Größe der X/Z Achsen des Mesh
bool        cg_enable = TRUE, sp;                            // schalte Cg Programm an / aus, Leertaste gedrückt?
GLfloat        mesh[SIZE][SIZE][3];                            // unser statischer Mesh
GLfloat        wave_movement = 0.0f;                            // unsere Variable, um die Wellen über den Mesh zu bewegen

Wir definieren die Größe mit 64 Punkten an jeder Ecke unseres Mesh (X und Z Achse). Dann erzeugen wir ein Array für jeden Vertex unseres Mesh. Die letzte Variable wird benötigt, um die Sinus-Welle über unseren Mesh zu 'bewegen'.

Nun müssen wir einige Cg spezifische globale Variablen defnieren.

CGcontext    cgContext;                                // Ein Kontext um unsere Cg Programm(e) zu halten

Die erste Variable die wir benötigen ist vom Typen CGcontext. Eine CGcontext Variable ist ein Kontainer für mehrere Cg Programme. Im Allgemeinen benötigen Sie nur eine CGcontext Variable, unabhängig von der Anzahl der Vertex und Fragment Programme, die Sie haben. Sie können verschieden Programme aus dem selben CGcontext auswählen, indem Sie die Funktionen cgGetFirstProgram und cgGetNextProgram verwenden.

Wir definieren als nächstes eine CGprogram Variable für unser Vertex Programm.

CGprogram    cgProgram;                                // unser Cg Vertex Programm

Unsere CGprogram Variable wird zum speichern unseres Vertex Programms verwendet. Ein CGprogram ist notwendig, um unsere Vertex (oder Fragment) Programme zu händeln. Dieses wird zu unserem CGcontext hinzugefügt.

Als nächstes benötigen wir eine Variable um unser Vertex Profil zu speichern.

CGprofile    cgVertexProfile;                            // Das Profil welches für unseren Vertex Shader benutzt werden soll

Unser CGprofile definiert das passendste Profil. Als nächstes benötigen wir Variablen, die eine Verbindung zwischen den Variablen in unserem Hauptprogramm und Variablen in unserem Shader darstellen.

CGparameter    position, color, modelViewMatrix, wave;                    // Die Parameter die für unseren Shader benötigt werden

Jeder CGparameter ist notwendigerweise ein Handle zum entsprechenden Parameter in unserem Shader.

Nun, da wir uns um unsere globalen Variablen gekümmert haben, wird es Zeit, um unseren Mesh und unser Vertex Programm zu schreiben.

In unserer Initialisierungs-Funktion, müssen wir, bevor wir "return TRUE;" aufrufen, unseren eigenen Code einfügen.

    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);                    // Zeichne unser Mesh im Drahtgitter-Modus

    // Create Our Mesh
    for (int x = 0; x <SIZE; x++)
    {
        for (int z = 0; z <SIZE; z++)
        {
            mesh[x][z][0] = (float) (SIZE / 2) - x;                // Wir wollen unseren Mesh um den Ursprung plazieren
            mesh[x][z][1] = 0.0f;                        // Setze die Y Werte für alle Punkte auf 0
            mesh[x][z][2] = (float) (SIZE / 2) - z;                // Wir wollen unseren Mesh um den Ursprung plazieren
        }
    }

Als erstes rufen wir glPolygonMode auf, um die Anzeige auf Drahtgitter umzustellen (flatshaded sieht grausam ohne die korrekte Beleuchtung aus). Wir spannen dann unseren Mesh auf, indem wir die X und Z Werte um den Ursprung herum setzen. Der Y Wert wird für jeden Punkt auf 0.0f gesetzt. Es ist interessant anzumerken, dass die in diesem Schritt erzeugten Werte sich zu keiner Zeit der Ausführung ändern.

Wenn wir die Initialisierung unseres Mesh auch hinter uns haben, sind wir nun bereit, unseren Cg Kram zu initialisieren.

    // Setup Cg
    cgContext = cgCreateContext();                            // erzeuge einen neuen Kontext für unser(e) Cg Programm(e)

    // überprüfe ob unsere Kontext-Erzeugung erfolgreich war
    if (cgContext == NULL)
    {
        MessageBox(NULL, "Failed To Create Cg Context", "Error", MB_OK);
        return FALSE;                                // Wir können nicht weitermachen
    }

Als erstes versuchen wir einen neun CGcontext zu erzeugen, um unsere Cg Programme darin zu speichern. Wenn unser Rückgabewert gleich NULL ist, dann schlug unsere Kontext-Erzeugung fehl. Diese schlägt in der Regel nur bei Speicher-Alloziierungs-Fehlern fehl.

    cgVertexProfile = cgGLGetLatestProfile(CG_GL_VERTEX);                // ermittle das letzte GL Vertex Profil

    // überprüfe, ob unsere Profil-Bestimmung erfolgreich war
    if (cgVertexProfile == CG_PROFILE_UNKNOWN)
    {
        MessageBox(NULL, "Invalid profile type", "Error", MB_OK);
        return FALSE;                                // Wir können nicht weitermachen
    }

    cgGLSetOptimalOptions(cgVertexProfile);                        // Setze das aktuelle Profil

Wir bestimmen nun, das zu letzt benutzte Vertex Profil. Um das letzte Fragment Profil zu bestimmen, rufen wir cgGLGetLatestProfile zusammen mit der CG_GL_FRAGMENT Profil Art auf. Wenn unser Rückgabewert gleich CG_PROFILE_UNKNOWN ist, gibt es kein passendes verfügbares Profil. Mit einem gültigen Profil, können cgGLSetOptimalOptions aufrufen. Diese Funktion setzt die Compiler Parameter basierend auf verfügbaren Compiler Parametern, GPU und Treibern. Diese Funktionen werden jedes Mal verwendet, wenn ein neues Cg Programm kompiliert wird. (Im wesentlichen wir die Kompilation des Shaders abhängig von der aktuellen Grafikhardware und den Treibern optimiert).

    // Lade und kompiliere den Vertex Shader aus der Datei
    cgProgram = cgCreateProgramFromFile(cgContext, CG_SOURCE, "CG/Wave.cg", cgVertexProfile, "main", 0);

    // überprüfe, ob erfolgreich
    if (cgProgram == NULL)
    {
        // wir müssen ermitteln, was schief lief
        CGerror Error = cgGetError();

        // zeige eine Message Box, die erklärt, was schief lief
        MessageBox(NULL, cgGetErrorString(Error), "Error", MB_OK);
        return FALSE;                                // wir können nicht weitermachen
    }

Wir versuchen nun, unser Programm aus unserer Source Datei zu erzeugen. Wir rufen cgCreateProgramFromFile auf, was unser Cg Programm aus der angegebenen Datei laden und kompilieren wird. Der erste Parameter definiert welcher CGcontext Variable unser Programm zugeordnet wird. Der zweite Parameter definiert, ob sich unser Cg Code in einer Datei, welche den Source Code enthält (CG_SOURCE), befindet oder in einer Datei, welche den Objekt-Code aus einem vor-kompilierten Cg Programm (CG_OBJECT) enthält. Der dritte Parameter ist der Namen der Datei, die unser Cg Programm enthält. Der vierte Parameter ist das letzte Profil für eine bestimmte Art von Programm (benutzen Sie ein Vertex Profil für Vertex Programme, Fragment Profile für Fragment Programme). Der fünfte Parameter bestimmt die Einstiegsfunktion unseres Cg Programms. Diese Funktion kann willkürlich spezifiziert werden und sollte häufig etwas wie "main" sein. Der letzte Parameter ist für weitere Parameter, die an den Cg Compiler übergeben werden können. Dieser wir häufig auf NULL gesetzt.

Wenn cgCreateProgramFromFile aus irgend welchen Gründen fehl schlägt, ermitteln wir den letzten Fehler, indem wir cgGetError aufrufen. Wir können dann einen lesbaren String des Fehlers erhalten, welcher in unserer CGerror Variable enthalten sein wird, wenn wir cgGetErrorString aufrufen.

Wir sind fast fertig mit unserer Initialisierung.

    // Lade das Programm
    cgGLLoadProgram(cgProgram);

Der nächste zu machende Schritt ist das eigentliche Laden unseres Programms und die Vorbereitung es zu binden. Alle Programme müssen geladen werden, bevor sie an den aktuellen Status gebunden werden können.

    // ermittle Handles für jeden unserer Parameter, so dass wir
    // diese innerhalb unseres Codes bei Bedarf ändern können
    position    = cgGetNamedParameter(cgProgram, "IN.position");
    color        = cgGetNamedParameter(cgProgram, "IN.color");
    wave        = cgGetNamedParameter(cgProgram, "IN.wave");
    modelViewMatrix    = cgGetNamedParameter(cgProgram, "ModelViewProj");

    return TRUE;                                    // gebe TRUE zurück (Initialisierung erfolgreich)

Beim letzten Schritt der Initialisierung muss unser Programm die Handle zu den Variablen ermitteln, die wir in unserem Cg Programm manipulieren wollen. Für jeden CGparameter versuchen wir ein Handle zum entsprechenden Cg Programm Parameter zu ermitteln. Wenn ein Parameter nicht existiert, liefert cgGetNamedParameter NULL zurück.

Wenn die Parameter in dem Cg Programm unbekannt sind, können cgGetFirstParameter und cgGetNextParameter dazu verwendet werden, um die Parameter eines gegebenen CGprogram nach und nach zu ermitteln.

Wir sind nun fertig mit der Initialisierung unseres Cg Programms, weshalb wir uns noch schnell um das Aufräumen kümmern werden und dann zum spaßigen Teil, dem Zeichnen kommen.

In der Funktion Deinitialize müssen wir unser(e) Cg Programm(e) wieder aufräumen.

    cgDestroyContext(cgContext);                            // zerstöre unseren Cg Kontext und all Programme, die darin enthalten sind

Wir rufen einfach cgDestroyContext für jede unsere CGcontext Variable auf (wir können mehrere haben, aber in der Regel gibt's nur eine). Sie können individuell alle Ihrer CGPrograms löschen, indem Sie cgDestoryProgram aufrufen, wie dem aber auch sei, indem Sie cgDestoryContext aufrufen, werden alle CGprograms die in dem Kontext enthalten sind und der CGcontext selbst, gelöscht.

Nun werden wir etwas Code unserer Update Funktion hinzufügen. Der folgende Code überprüft, ob die Leertaste gedrückt wurde und nicht gedrückt bleibt. Wenn die Leertaste gedrückt ist und nicht gehalten wird, wechseln wir cg_enable von true auf false oder von false auf true.

    if (g_keys->keyDown [' '] && !sp)
    {
        sp=TRUE;
        cg_enable=!cg_enable;
    }

Das letzte Stück Code überprüft, ob die Leertaste losgelassen wurde und wenn dem so ist, wird sp (space pressed (Leertaste gedrückt)?) auf false gesetzt.

    if (!g_keys->keyDown [' '])
        sp=FALSE;

Nun, da wir das alles gemanaged haben, wird es Zeit zum eigentlichen Spaß, dem eigentlichen Zeichnen unseres Mesh und das ausführen unseres Vertex Programmes kommen.

Die letzte Funktion die wir modifizieren müssen, ist die Draw Funktion. Wir fügen unseren Code hinter glLoadIdentity und vor glFlush ein.

    // Position der Kamera, um auf unseren Mesh aus der Ferne zu schauen
    gluLookAt(0.0f, 25.0f, -45.0f, 0.0f, 0.0f, 0.0f, 0, 1, 0);

Als erstes wollen wir unseren Viewpoint weit genug vom Ursprung weg bewegen, damit wir unseren Mesh sehen können. Wir bewegen die Kamera 25 Einheiten vertikal, 45 Einheiten weg vom Screen und zentrieren unseren Brennpunkt auf den Ursprung.

    // Setze die Modelview Matrix unseres Shaders auf unsere OpenGL Modelview Matrix
    cgGLSetStateMatrixParameter(modelViewMatrix, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY);

Das nächste was wir machen wollen, ist das Setzen der ModelView Matrix unseres Vertex Shaders auf die aktuelle OpenGL Modelview Matrix. Das muss getan werden, da die Position, die durch unsere Vertex Shader verändert wird, zur neuen Position in den homogenen Clip-Space transformiert werden muss, was durch die Multiplizierung von der neuen Position mit unserer Modelview Matrix getan wird.

    if (cg_enable)
    {
        cgGLEnableProfile(cgVertexProfile);                    // aktiviere unser Vertex Shader Profil

        // Binde unser Vertex Programm an den aktuellen Status
        cgGLBindProgram(cgProgram);

Dann müssen wir unser Vertex Profil aktivieren. cgGLEnableProfile aktiviert ein gegebenes Profil, indem die notwendigen OpenGL Aufrufe getätigt werden. cgGLBindProgram bindet unser Programm an den aktuellen Status. Das aktiviert im Grunde unser Programm und ruft somit unser Programm für jeden Vertex auf, der an die GPU weitergegeben wird. Das selbe Programm wird für jeden Vertex durchlaufen, bis wir unser Profil deaktivieren.

        // Setze die Farbe zum Zeichnen auf Hellgrün (kann vom Shader, etc. geändert werden)
        cgGLSetParameter4f(color, 0.5f, 1.0f, 0.5f, 1.0f);
    }

Als nächstes setzen wir die Zeichnefarbe für unseren Mesh. Dieser Wert kann dynamisch geändert werden, während wir den Mesh zeichnen, um einen coolen Farbverlauf-Effekt zu erzielen.

Beachten Sie die Überprüfung, ob cg_enable gleich true ist. Wenn dem nicht so ist, führen wir keine der obigen Cg Befehle aus. Das verhindert, dass der CG Code läuft.

Nun sind wir bereit unseren Mesh zu rendern!

    // fange an unseren Mesh zu zeichnen
    for (int x = 0; x <SIZE - 1; x++)
    {
        // zeichne ein Triangle Strip für jede Spalte unseres Mesh
        glBegin(GL_TRIANGLE_STRIP);
        for (int z = 0; z <SIZE - 1; z++)
        {
            // Setze die Wave Parameter unseres Shaders auf den inkrementierten Wave Wert aus unserem Hauptprogramm
            cgGLSetParameter3f(wave, wave_movement, 1.0f, 1.0f);
            glVertex3f(mesh[x][z][0], mesh[x][z][1], mesh[x][z][2]);    // zeichne Vertex
            glVertex3f(mesh[x+1][z][0], mesh[x+1][z][1], mesh[x+1][z][2]);    // zeichne Vertex
            wave_movement += 0.00001f;                    // Inkrementiere unsere Wellenbewegung
        }
        glEnd();
    }

Um unseren Mesh zu rendern, durchlaufen wir einfach die Z-Achse für jede X-Achse (im wesentlichen arbeiten wir in Spalten von einer Seite unseres Mesh zu anderen). Für jede Spalte beginnen wir einen neuen Triangle Strip.

Für jeden Vertex rendern wir, indem wir dynamisch den Wert unseres Wave Parameter von unserem Vertex Programm übergeben. Da dieser Wert durch die wave_movement Variable in unserem Hauptprogramm bestimmt wird, welche ständig inkrementiert wird, scheinen sich unsere Sinuskurven über unseren Mesh zu bewegen.

Wir übergeben dann die Vertices, die wir gerade zeichnen, an unsere GPU, während die GPU automatisch unser Vertex Programm für jeden Vertex ausführen wird. Wir inkrementieren langsam unsere wave_movement Variable, um eine langsame und weiche Bewegung zu erhalten.

    if (cg_enable)
        cgGLDisableProfile(cgVertexProfile);                    // deaktiviere unser Vertex Profil

Wenn wir mit unserem Rendern erst einmal fertig sind, überprüfen wir, ob cg_enable gleich true ist und wenn dem so ist, deaktivieren wir unser Vertex Profil und fahren mit dem Rendern von Dingen fort, die wir noch rendern wollen.

Owen Bourne

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


* DOWNLOAD Borland C++ Builder 6 Code für diese Lektion. ( Conversion by Ingo Boller )
* DOWNLOAD Delphi Code für diese Lektion. ( Conversion by Michal Tucek )
* DOWNLOAD Linux/GLut Code für diese Lektion. ( Conversion by Gray Fox )
* DOWNLOAD Linux/GLX Code für diese Lektion. ( Conversion by Jason Schultz )
* DOWNLOAD Python Code für diese Lektion. ( Conversion by Brian Leair )
* DOWNLOAD Visual Studio .NET Code für diese Lektion. ( Conversion by Joachim Rohde )



Deutsche Übersetzung: Joachim Rohde
Der original Text ist hier zu finden.
Die original OpenGL Tutorials stammen von NeHe's Seite.