NeHe - Lektion 45 - Vertex Buffer Objekte

Lektion 45



Eins der größten Ziele in 3D Applikationen ist Geschwindigkeit. Sie sollten die Anzahl der Polygone, die Sie tatsächlich rendern immer limitieren, etweder durch Sortierung, Culling oder Level-of-Detail Algorithmen. Wie dem auch sei, wenn alles andere fehl schlägt und Sie rohe Polygon-Pushing-Power benötigen, können Sie sich die Optimierungen zu nutze machen, die Ihnen von OpenGL zur Verfügung gestellt werden. Vertex Arrays sind ein guter Weg um das zu machen plus ein paar neue Extensionen der Graifkkartern, namens Vertex Buffer Objekte lässt den Traum einer jeden FPS Steigerung wahr werden. Die Extension ARB_vertex_buffer_object funktioniert wie ein Vertex Array, mit dem Unterschied, dass es die Daten in den Grafikkarten High-Performance-Speicher lädt, was die Rendering-Zeit drastisch reduziert. Da die Extension relativ neu ist, unterstüzen logischerweise nicht alle Karte diese, weshalb wir das überprüfen müssen.

In diesem Tutorial werden wir
  • Daten aus einer HeightMap laden
  • Vertex Arrays verwenden um Mesh Daten effizienter an OpenGL zu senden
  • Daten in den High-Performance Speicher laden mittels der VBO Extension
Fangen wir also an! Als erstes definieren wir ein paar Applikations Parameter.

#define MESH_RESOLUTION 4.0f                            // Pixel Pro Vertex
#define MESH_HEIGHTSCALE 1.0f                            // Mesh Höhen Skalierung
//#define NO_VBOS                                // wenn definiert, werden VBOs ausgeschaltet

Die ersten beiden Konstanten sind Standard Heightmap Werte - der erste setzt die Auflösung mit der die Heightmap pro Pixel generiert wird und der letztere setzt die vertikale Skalierung der Daten, die von der Heightmap ermittelt werden. Die dritte Konstante, wenn sie definiert wird, schaltet VBOs aus - nur eine Massnahme die ich für die Leute eingefügt habe, die eine High-End-GRafikkarte haben, damit diese sich den Unterschied ansehen können.

Als nächstes haben wir die VBO Extensions Konstanten, Datentypen und Funktionszeiger Definitionen.

// VBO Extension Definitionen, aus der glext.h
#define GL_ARRAY_BUFFER_ARB 0x8892
#define GL_STATIC_DRAW_ARB 0x88E4
typedef void (APIENTRY * PFNGLBINDBUFFERARBPROC) (GLenum target, GLuint buffer);
typedef void (APIENTRY * PFNGLDELETEBUFFERSARBPROC) (GLsizei n, const GLuint *buffers);
typedef void (APIENTRY * PFNGLGENBUFFERSARBPROC) (GLsizei n, GLuint *buffers);
typedef void (APIENTRY * PFNGLBUFFERDATAARBPROC) (GLenum target, int size, const GLvoid *data, GLenum usage);

// VBO Extension Funktionszeiger
PFNGLGENBUFFERSARBPROC glGenBuffersARB = NULL;                    // VBO Namens Generations-Prozedur
PFNGLBINDBUFFERARBPROC glBindBufferARB = NULL;                    // VBO Bind-Prozedur
PFNGLBUFFERDATAARBPROC glBufferDataARB = NULL;                    // VBO Daten-Lade-Prozedur
PFNGLDELETEBUFFERSARBPROC glDeleteBuffersARB = NULL;                // VBO Lösch-Prozedur

Ich habe nur das inkludiert, was für das Demo notwendig ist. Wenn Sie mehr Funktionalitäten benötigen, empfehle ich Ihnen, die neueste glext.h von http://www.opengl.org herunterzuladen und die Definitionen von dort zu verwenden (das macht Ihren Code ohnehin lesbarer). Wir gehen näher auf die Eigenarten der Funktionen ein, sobald wir diese verwenden.

Nun kommen die Standard-Mathematischen-Funktionen sowie unsere Mesh-Klasse. Alle sind recht rudimentär und speziell nur für dieses Demo designed. Wie immer, empfehle ich Ihnen, ihre eigene Mathe-Bibliothek zu entwickeln.

class CVert                                    // Vertex Klasse
{
public:
    float x;                                // X Komponente
    float y;                                // Y Komponente
    float z;                                // Z Komponente
};
typedef CVert CVec;                                // Die Definitionen sind synonym

class CTexCoord                                    // Texturkoordinaten Klasse
{
public:
    float u;                                // U Komponente
    float v;                                // V Komponente
};

class CMesh
{
public:
    // Mesh Daten
    int        m_nVertexCount;                        // Vertex Anzahl
    CVert*        m_pVertices;                        // Vertex Daten
    CTexCoord*    m_pTexCoords;                        // Texturkoordinaten
    unsigned int    m_nTextureId;                        // Textur ID

    // Vertex Buffer Objekt Namen
    unsigned int    m_nVBOVertices;                        // Vertex VBO Name
    unsigned int    m_nVBOTexCoords;                    // Texturkoordinaten VBO Name

    // Temporäre Daten
    AUX_RGBImageRec* m_pTextureImage;                    // Heightmap Daten

public:
    CMesh();                                // Mesh Konstruktor
    ~CMesh();                                // Mesh Destruktor

    // Heightmap Loader
    bool LoadHeightmap( char* szPath, float flHeightScale, float flResolution );
    // einzelne Punkt Höhe
    float PtHeight( int nX, int nY );
    // VBO Build Funktion
    void BuildVBOs();
};

Der meiste Code ist ziemlich selbsterklärend. Beachten Sie, dass ich die Vertex und Texturkoordinaten Daten seperat halte, obwohl das nicht unbedingt zwingend ist, wie ich später noch zeigen werde.

Hier haben wir unsere globale Variablen. Als erstes haben wir ein VBO Extension Validierungs Flag, welches im Initialisierungs Code gesetzt wird. Dann haben wir unser Mesh, gefolgt von unserem Y Rotations Zähler. Es folgen die FPS Überwachungs Variablen. Ich habe mich entschieden einen FPS Counter einzufügen, als Hilfe um die Optimierungen, die durch diesen Code erfolgen, anzuzeigen.

bool        g_fVBOSupported = false;                    // wird ARB_vertex_buffer_object unterstützt?
CMesh*        g_pMesh = NULL;                            // Mesh Daten
float        g_flYRot = 0.0f;                        // Rotation
int        g_nFPS = 0, g_nFrames = 0;                    // FPS und FPS Zähler
DWORD        g_dwLastFPS = 0;                        // Letzte gemessene FPS Zeit

Lassen Sie zu den CMesh Funktions Definitionen springen, beginnend mit LoadHeightmap. Für die, die es noch nicht wissen, eine Heightmap ist ein zwei-dimensionales Datenfeld, in der Regel ein Image, welche die vertikalen Terrain Mesh-Daten spezifiziert. Es gibt viele Wege, eine Heightmap zu implementieren und sicherlich nicht nur einen richtige Art. Meine Implementation liest ein drei Channel-Bitmap ein und verwendet den Luminosity Algorithmus, um die Höhe der Daten zu bestimmen. Die resultierenden Daten würde exakt die selben sein, wenn das Bild farbig oder grauskaliert wäre, was es erlaubt, dass die Heightmap farbig ist. Ich persönlich empfehle ein 4 Channel-Image, wie zum Beispiel Targa (Anm. des Übersetzers: das sind .TGA Dateien) und würde den Alpha-Kanal für die Höhen verwenden. Wie dem auch sei, für dieses Tutorial habe ich mich für ein einfaches Bitmap entschieden.

Als erstes stellen wir sicher, dass die Heightmap existiert und wenn dem so ist, laden wir sie mit dem GLaux Bitmap Loade. Ja, ja, es wäre wahrscheinlich besser eine eigene Image-Lade-Routine zu schreiben, aber das ist nicht Ziel dieses Tutorials.

bool CMesh :: LoadHeightmap( char* szPath, float flHeightScale, float flResolution )
{
    // Fehler-Überprüfung
    FILE* fTest = fopen( szPath, "r" );                    // Öffne das Image
    if( !fTest )                                // stelle sicher, das es gefunden wurde
        return false;                            // wenn nicht, fehlt die Datei
    fclose( fTest );                            // Handle wird nicht mehr benötigt

    // Lade Textur Daten
    m_pTextureImage = auxDIBImageLoad( szPath );                // benutze GLaux's Bitmap Lade Routine

Nun werden die Dinge etwas interessanter. Als erstes möchte ich darauf hinweisen, dass meine Heightmap drei Vertices für jedes Dreick generiert wird - es gibt keine gemeinsamen Vertices. Ich werde später erklären, warum ich das gemacht habe, aber ich denke, dass Sie das wissen sollten, bevor Sie sich den Code anschauen.

Ich fange mit der Berechnung der Vertices-Menge in dem Mesh an. Der Algorithmus funktioniert grundsätzlich so: ( ( Terrain Breite / Auflösung ) * ( Terrain Länge / Auflösung ) * 3 Vertices in einem Dreieck * 2 Dreiecke in einem Quadrat ). Dann alloziiere ich meine Daten und fange an, das Vertex Feld durchzuarbeiten und die Daten zu setzen.

    // Generiere Vertex Feld
    m_nVertexCount = (int) ( m_pTextureImage->sizeX * m_pTextureImage->sizeY * 6 / ( flResolution * flResolution ) );
    m_pVertices = new CVec[m_nVertexCount];                    // Alloziiere Vertex Daten
    m_pTexCoords = new CTexCoord[m_nVertexCount];                // Alloziiere Texturkoordinaten-Daten
    int nX, nZ, nTri, nIndex=0;                        // erzeuge Variablen
    float flX, flZ;
    for( nZ = 0; nZ < m_pTextureImage->sizeY; nZ += (int) flResolution )
    {
        for( nX = 0; nX < m_pTextureImage->sizeX; nX += (int) flResolution )
        {
            for( nTri = 0; nTri < 6; nTri++ )
            {
                // mit diesem schnellen Hack finden wir die X,Z Position des Punktes heraus
                flX = (float) nX + ( ( nTri == 1 || nTri == 2 || nTri == 5 ) ? flResolution : 0.0f );
                flZ = (float) nZ + ( ( nTri == 2 || nTri == 4 || nTri == 5 ) ? flResolution : 0.0f );

                // Setze die Daten, benutze PtHeight um den Y-Wert zu erhalten
                m_pVertices[nIndex].x = flX - ( m_pTextureImage->sizeX / 2 );
                m_pVertices[nIndex].y = PtHeight( (int) flX, (int) flZ ) *  flHeightScale;
                m_pVertices[nIndex].z = flZ - ( m_pTextureImage->sizeY / 2 );

                // strecke die Textur über das gesamte Mesh
                m_pTexCoords[nIndex].u = flX / m_pTextureImage->sizeX;
                m_pTexCoords[nIndex].v = flZ / m_pTextureImage->sizeY;

                // Inkrementiere unseren Index
                nIndex++;
            }
        }
    }

Ich beende die Funktion mit dem Laden der Heightmap Textur und dem freigeben unsere Daten-Kopie. Das sollte Ihnen aus vorherigen Tutorials bekannt sein.

    // Lade die Textur
    glGenTextures( 1, &m_nTextureId );                    // ermittle eine freie ID
    glBindTexture( GL_TEXTURE_2D, m_nTextureId );                // Binde die Textur
    glTexImage2D( GL_TEXTURE_2D, 0, 3, m_pTextureImage->sizeX, m_pTextureImage->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, m_pTextureImage->data );
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

    // gebe die Textur-Daten wieder frei
    if( m_pTextureImage )
    {
        if( m_pTextureImage->data )
            free( m_pTextureImage->data );
        free( m_pTextureImage );
    }
    return true;
}

PtHeight ist relativ simple. Es berechnet den Index der angeforderten Daten, wrappt jegliche Überläufe, um Fehler zu vermeinden und berechnet die Höhe. Die Luminanz Formel ist sehr einfach, wie Sie sehen können, als keine Angst.

float CMesh :: PtHeight( int nX, int nY )
{
    // Berechne die Position in der Textur, darauf achten, dass keine Überlauf stattfindet
    int nPos = ( ( nX % m_pTextureImage->sizeX )  + ( ( nY % m_pTextureImage->sizeY ) * m_pTextureImage->sizeX ) ) * 3;
    float flR = (float) m_pTextureImage->data[ nPos ];            // ermittle die rote Komponente
    float flG = (float) m_pTextureImage->data[ nPos + 1 ];            // ermittle die grüne Komponente
    float flB = (float) m_pTextureImage->data[ nPos + 2 ];            // ermittle die blaue Komponente
    return ( 0.299f * flR + 0.587f * flG + 0.114f * flB );            // berechne die Höhe mit dem Luminance Algorithmus
}

Hurra, Zeit um mit den Vertex Arrays und VBOs sich zu befassen. Was sind eigentlich Vertex Arrays? Grundsätzlich ist es ein System mit dem Sie OpenGL auf Ihre geometrischen Daten zeigen lassen können und dann danach Daten mit relativ wenigen Aufrufen rendern können. Die resultierenden Einsparungen bei den Funktionsaufrufen (glVertex, etc.) ermöglichen eine signifikante Geschwindigkeitssteigerung. Was sind VBOs? Nun, Vertex Buffer Objekte verwenden high-performance Grafikkartenspeicher anstatt Ihres Standard Ram-alloziierten Speichers. Das vermindert nicht nur die Speicheroperationen bei jedem Frame, sondern reduziert auch die Bus-Distanz, die Ihre Daten wandern muss. Bei meinen Spezifikationen verdreifachen VBOs sogar die Framerate, was sonst nicht so leicht zu erreichen ist.

So, nun erzeugen wir die Vertex Buffer Objekte. Es gibt tatsächlich ein paar Wege, um das zu machen, einer davon wird 'in den Speicher "mappen"' genannt. Ich denke, dass der einfachste Weg hier der beste ist. Der Prozess ist wie folgt: als erstes benutzen Sie glGenBufferARB um einen gültigen VBO "Namen" zu erhalten. Eigentlich ist ein Name eine ID Nummer, welche OpenGL mit Ihren Daten assoziiert. Wir wollen einen Namen generieren, da der selbe nicht immer verfügbar sein wird. Als nächstes machen wir das VBO zum gerade aktiven, indem wird es mit glBindBufferARB binden. Zu letzt laden wir die Daten in unsere Grafikkarten Daten, mit einem glBufferDataARB Aufruf, welchem wir die Größe und den Zeiger auf die Daten übergeben. glBufferDataARB kopiert die Daten in unseren Grafikkartenspeicher, was für uns bedeutet, dass wir diese nicht länger behalten müssen und somit löschen können.

void CMesh :: BuildVBOs()
{
    // Generiere und Binde den Vertex Buffer
    glGenBuffersARB( 1, &m_nVBOVertices );                    // ermittle einen gültigen Namen
    glBindBufferARB( GL_ARRAY_BUFFER_ARB, m_nVBOVertices );            // Binde den Buffer
    // Lade die Daten
    glBufferDataARB( GL_ARRAY_BUFFER_ARB, m_nVertexCount*3*sizeof(float), m_pVertices, GL_STATIC_DRAW_ARB );

    // Generiere und Binde den Textur Koordinaten Buffer
    glGenBuffersARB( 1, &m_nVBOTexCoords );                    // ermittle einen gültigen Namen
    glBindBufferARB( GL_ARRAY_BUFFER_ARB, m_nVBOTexCoords );        // Binde den Buffer
    // Lade die Daten
    glBufferDataARB( GL_ARRAY_BUFFER_ARB, m_nVertexCount*2*sizeof(float), m_pTexCoords, GL_STATIC_DRAW_ARB );

    // unsere Kopie der Daten wird nicht länger benötigt, diese sind sicher in der Grafikkarte
    delete [] m_pVertices; m_pVertices = NULL;
    delete [] m_pTexCoords; m_pTexCoords = NULL;
}

Ok, Zeit für die Initialisierung. Als erstes alloziieren und laden wir unsere Mesh Daten. Dann überprüfen wir, ob GL_ARB_vertex_buffer_object unterstütze wird. Wenn ja, ermittlen wir die Funktionszeiger mit wglGetProcAdress und erzeugen unsere VBOs. Beachten Sie, dass wenn VBOs nicht unterstützt wird, die Daten einfach beibehalten werden. Beachten Sie auch die Vorkehrung zur Erzwingung der Nicht-Verwendung von VBOs.

    // Lade die Mesh Daten
    g_pMesh = new CMesh();                            // Instanziiere unser Mesh
    if( !g_pMesh->LoadHeightmap( "terrain.bmp",                // Lade unsere Heightmap
                MESH_HEIGHTSCALE, MESH_RESOLUTION ) )
    {
        MessageBox( NULL, "Error Loading Heightmap", "Error", MB_OK );
        return false;
    }

    // überprüfe ob VBOs unterstützt werden
#ifndef NO_VBOS
    g_fVBOSupported = IsExtensionSupported( "GL_ARB_vertex_buffer_object" );
    if( g_fVBOSupported )
    {
        // ermittle Zeiger auf die GL Funktionen
        glGenBuffersARB = (PFNGLGENBUFFERSARBPROC) wglGetProcAddress("glGenBuffersARB");
        glBindBufferARB = (PFNGLBINDBUFFERARBPROC) wglGetProcAddress("glBindBufferARB");
        glBufferDataARB = (PFNGLBUFFERDATAARBPROC) wglGetProcAddress("glBufferDataARB");
        glDeleteBuffersARB = (PFNGLDELETEBUFFERSARBPROC) wglGetProcAddress("glDeleteBuffersARB");
        // Lade Vertex Daten in den Grafikkartenspeicher
        g_pMesh->BuildVBOs();                        // erzeuge die VBOs
    }
#else /* NO_VBOS */
    g_fVBOSupported = false;
#endif

IsExtensionSupported ist eine Funktion die Sie von OpenGL.org bekommen können. Meine Variante, ist meines Erachten snach, etwas sauberer.

bool IsExtensionSupported( char* szTargetExtension )
{
    const unsigned char *pszExtensions = NULL;
    const unsigned char *pszStart;
    unsigned char *pszWhere, *pszTerminator;

    // Extension Namen sollten keine Leerzeichen enthalten
    pszWhere = (unsigned char *) strchr( szTargetExtension, ' ' );
    if( pszWhere || *szTargetExtension == '\0' )
        return false;

    // ermittle Extensions String
    pszExtensions = glGetString( GL_EXTENSIONS );

    // such nach einer exakten Kopie des Extensions Strings
    pszStart = pszExtensions;
    for(;;)
    {
        pszWhere = (unsigned char *) strstr( (const char *) pszStart, szTargetExtension );
        if( !pszWhere )
            break;
        pszTerminator = pszWhere + strlen( szTargetExtension );
        if( pszWhere == pszStart || *( pszWhere - 1 ) == ' ' )
            if( *pszTerminator == ' ' || *pszTerminator == '\0' )
                return true;
        pszStart = pszTerminator;
    }
    return false;
}

Sie ist relativ simpel. Einige Leute benutzen einfach eine Sub-String Suche mit strstr, aber so wie es aussieht vertraut OpenGL.org nicht genug auf die Konsistenz von Extension Strings, um das als Beweis zu akzeptieren. Und hey, ich will mich nicht mit den Jungs streiten.

Fast fertig! Wie müssen nur noch die Daten rendern.

void Draw (void)
{
    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);            // lösche Screen und Depth Buffer
    glLoadIdentity ();                            // Resette die Modelview Matrix

    // ermittle FPS
    if( GetTickCount() - g_dwLastFPS >= 1000 )                // wenn eine Sekunden vergangen ist...
    {
        g_dwLastFPS = GetTickCount();                    // aktualisiere unsere Zeit-Variable
        g_nFPS = g_nFrames;                        // speichere die FPS
        g_nFrames = 0;                            // Resette den FPS Zähler

        char szTitle[256]={0};                        // erzeuge den Titel-String
        sprintf( szTitle, "Lesson 45: NeHe & Paul Frazee's VBO Tut - %d Triangles, %d FPS", g_pMesh->m_nVertexCount / 3, g_nFPS );
        if( g_fVBOSupported )                        // füge eine Notiz über VBOs ein
            strcat( szTitle, ", Using VBOs" );
        else
            strcat( szTitle, ", Not Using VBOs" );
        SetWindowText( g_window->hWnd, szTitle );            // Setze den Titel
    }
    g_nFrames++;                                // Inkrementiere unseren FPS Zähler

    // bewege die Kamera
    glTranslatef( 0.0f, -220.0f, 0.0f );                    // bewege über das Terrain
    glRotatef( 10.0f, 1.0f, 0.0f, 0.0f );                    // schaue etwas runter
    glRotatef( g_flYRot, 0.0f, 1.0f, 0.0f );                // Rotiere die Kamera

Ziemlich einfach - jede Sekunde wird der Frame-Zähler als FPS gespeichert und dann wird der Frame-Zähler resettet. Ich habe mich entschlossen die Polygonenanzahl mit einwirken zu lassen. Dann bewegen wir die Kamera über das Terrain (Sie müssen das vielleicht anpassen, wenn Sie die Heighmap ändern) und machen ein paar Rotationen. g_flYRot wird in der Update Funktion inkrementiert.

Um Vertex Arrays (und VBOs) zu benutzen, müssen Sie OpenGL mitteilen, was für Daten Sie im Speicher liegen haben. Der erste Schritt ist es also den Client Status GL_VERTEX_ARRAY und GL_TEXTURE_COORD_ARRAY zu aktivieren. Dann setzen wir unsere Zeiger. Ich bezweifel, dass Sie das bei jedem Frame machen müssen, es sei denn, Sie haben mehrere Meshes, aber da uns das Geschwindigkeitsmäßig nicht gerade weh tut, sehe ich hier kein Problem, das so zu machen.

Um einen Zeiger auf einen bestimmten Datentypen zu setzen, müssen Sie die richtige Funktion verwenden - glVertexPointer und glTexCoordPointer, in unserem Fall. Die Benutzung ist ziemlich einfach - übergeben Sie die Anzahl der Variablen in einem Punkt (drei für ein Vertex, zwei für ein texcoord), den Daten Cast (float), den Abstand zwischen den gewünschten Daten (für den Fall, dass die Vertices nicht alleine in der Struktur gespeichert werden) und den Zeiger auf die Daten. Sie können eigentlich glInterleavedArrays verwenden und all Ihre Daten in einem großen Buffer speichern, aber ich habe sie getrennt gehalten, um Ihnen zu zeigen, wie man meherere VBOs verwendet.

Wenn man von VBOs spricht so ist die Implementierung nicht viel anders. Die einzige wirkliche Änderung ist, anstatt einen Zeiger auf die Daten zu haben, binden wir das VBO, dass wir haben wollen und setzen den Zeiger auf Null. Schauen Sie selbst.

    // Setze Pointer auf unsere Daten
    if( g_fVBOSupported )
    {
        glBindBufferARB( GL_ARRAY_BUFFER_ARB, g_pMesh->m_nVBOVertices );
        glVertexPointer( 3, GL_FLOAT, 0, (char *) NULL );        // Setze den Vertex Pointer auf den Vertex Buffer
        glBindBufferARB( GL_ARRAY_BUFFER_ARB, g_pMesh->m_nVBOTexCoords );
        glTexCoordPointer( 2, GL_FLOAT, 0, (char *) NULL );        // Setze den TexCoord Pointer auf den TexCoord Buffer
    } else
    {
        glVertexPointer( 3, GL_FLOAT, 0, g_pMesh->m_pVertices );    // Setze den Vertex Pointer auf unsere Vertex Daten
        glTexCoordPointer( 2, GL_FLOAT, 0, g_pMesh->m_pTexCoords );    // Setze den Vertex Pointer auf unsere TexCoord Daten
    }

Wissen Sie was? Rendern ist sogar noch einfacher.

    // Render
    glDrawArrays( GL_TRIANGLES, 0, g_pMesh->m_nVertexCount );        // zeichne alle Dreiecke auf einmal

Hier benutzen wir glDrawArrays, um unsere Daten an OpenGL zu senden. glDrawArrays überprüft welche Client Status aktiviert sind und benutzt dann deren Pointer zum rendern. Wir teilen die Geometrie Art, den Index mit dem wir starten wollen und wieviele Vertices gerendert werden sollen, mit. Es gibt noch viele andere Möglichkeiten wie wir unsere Daten zum rendern schicken können, wie zum Beispiel als glArrayElement, aber dies ist die schnellste Möglichkeit. Sie werden bemerken, dass glDrawArrays nicht innerhalb von glBegin / glEnd Statements ist. Das ist hier auch nicht notwendig.

glDrawArrays ist der Grund, warum ich meine Vertex-Daten nicht zwischen den Dreiecken teile - es ist nicht möglich. So weit ich weiß, ist die beste Möglichkeit den Speicherzugriff zu optimieren, die, dass man Triangle Strips verwendet, was wiederum nicht die Absicht des Tutorials war. Außerdem sollten Sie darauf achten, dass Normalenvektor-Operationen 'eine nach dem anderen' auf die Vertices angewandt werden, das heisst, wenn Sie Normalenvektoren verwenden, sollte jeder Vertex einen dazugehörigen Normalenvektor besitzen. Betrachten Sie das als Möglichkeit Ihre Normalenvektoren pro Vertex zu berechnen, was die visuelle Genauigkeit um einiges verbessern wird.

Alles was wir noch machen müssen, ist das Deaktiveren der Vertex Arrays und wir sind durch.

    // deaktivere Pointer
    glDisableClientState( GL_VERTEX_ARRAY );                // deaktviere Vertex Arrays
    glDisableClientState( GL_TEXTURE_COORD_ARRAY );                // deaktiviere Texturkoordinaten Arrays
}

Wenn Sie mehr Informationen über Vertex Buffer Objekte haben wollen, empfehle ich Ihnen die Dokumentation im SGI Extension Register zu lesen - http://oss.sgi.com/projects/ogl-sample/registry. Es ist etwas trockner zu lesen als ein Tutorial, aber bringt Ihnen wesentlich detailiertere Informationen.

Nun, das war's. Wenn Sie irgendwelche Fehler oder Fehlinformationen finden oder einfach Fragen haben, können Sie mich unter paulfrazee@cox.net kontaktieren.

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

* DOWNLOAD Borland C++ Builder 6 Code für diese Lektion. ( Conversion by Le Thanh Cong )
* 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 Gerald Buchgraber )
* DOWNLOAD Linux/SDL Code für diese Lektion. ( Conversion by Ilias Maratos )
* 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.