Vertexarray

Aus DGL Wiki
Version vom 10. März 2009, 19:48 Uhr von DGLBot (Diskussion | Beiträge) (Der Ausdruck ''<cpp>(.*?)</cpp>'' wurde ersetzt mit ''<source lang="cpp">$1</source>''.)

(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)
Wechseln zu: Navigation, Suche

Bei einem Vertexarray werden die Vertexdaten in einem Array zur Grafikkarte geschickt.

Was sind Vertexarrays

Ein Vertexarray ist ein großes Array, in dem die Vertexdaten stehen. Idealerweise existiert dabei jeder Vertex nur einmal in diesem Array. Daneben wird noch ein Indexarray erstellt, dass auf die Vertices in dem Vertexarray verweist. Anhand dieses Indexarrays wird gezeichnet. Natürlich können auf einzelne Vertices aus dem Indexarray heraus mehrfach verwiesen werden.

Wie helfen Vertexarrays bei der Performance

Dadurch, dass alle Daten an einem Block liegen und die GPU stumpf die Daten abarbeiten kann, sind die Vertexarrays weitaus schneller als die glBegin/glEnd-Methode. Werden die Daten dann auch noch als Displayliste an die Grafikkarte geschickt, kommt Freude auf. :) Außerdem wird Speicher gespart, indem durch die Indirektstufe, die man durch das Indexarray hat, einen Vertex mehrfach zeichnen kann.

Verwendung

Prinzip

Wie wird nun so ein Vertexarray aufgebaut und gezeichnet?
Beispiel, wie es intern abläuft für zwei nebeneinanderliegende 2D-Quads:
Vertexarray vertices: {(0.0f, 0.0f), (0.0f, 1.0f), (1.0f, 0.0f), (1.0f, 1.0f), (2.0f, 0.0f), (2.0f, 1.0f)}
Indexarray indices: {0, 2, 3, 1, 2, 4, 5, 3}
Beim Zeichnen wird nun folgendermaßen vorgegangen:

int vertexCount = length(indices);
glBegin(GL_QUADS);
for (int i = 0; i < vertexCount; ++i) {
	int vIndex = indices[i];
	Vertex2F v = vertices[vIndex];
	glVertex2f(v[0], v[1]);
}
glEnd();

Dieses Minimalbeispiel soll nur verdeutlichen, wie Vertexarrays prinzipiell funktionieren. Pro Vertex gibt es natürlich noch andere interessante Informationen wie dessen Normale, dessen Farbe und Texturkoordinate. Dazu später mehr.

Umsetzung in C

Das Beispiel von eben soll nun in wirklichen Code umgesetzt werden. Dazu sind zwei Schritte nötig:
1. Die Initialisierung. 2. Das eigentliche Zeichnen

Die Initialisierung

Als erstes definieren wir einen Vertex:

typedef struct {
	GLfloat x, y, z;
	GLfloat r, g, b;
} Vertex;

Dieser Typ besteht aus zwei Teilen: Einmal 3 Floats für die Koordinate und einmal 3 Floats für die Farbe. Das Vertexarray besteht eben aus einem Array mit diesem Typen. Bei einer Zeile sind die ersten drei Zahlen die Koordinate und die nächsten drei die Farbe

static const Vertex vertices[18] = {
	0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
	0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f,
	1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
	1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
	2.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,
	2.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f
	};

Nun müssen noch die Indices aufgebaut werden: GLuint *indices;

static const GLuint indices[8] = {
	0, 2, 3, 1, 2, 4, 5, 3
	};

static const int indexCount = 8;

Damit hätten wir alle Daten beisammen. Nun muss nur noch OpenGL davon wissen.

void initVertexArray(void) {
	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_COLOR_ARRAY);

	glVertexPointer(3, /* Komponenten pro Vertex (x,y,z) */
			GL_FLOAT, /* Typ der Komponenten */
			sizeof(Vertex), /* Offset zwischen 2 Vertizes im Array */
			&(vertices[0].x)); /* Zeiger auf die 1. Komponente */

	glColorPointer (3, GL_FLOAT, sizeof(Vertex), &(vertices[0].r));
}

Was geschieht hier?
Als erstes wird das Vertex-Array eingeschaltet sowie das zugehörige Farbarray. Dann wird als erstes der Vertexpointer gesetzt. Der erste Parameter sagt, dass es 3 Komponenten pro Vertex sind (x, y, z), der zweite, dass es sich um Floats handelt, der dritte der Offset zwischen zwei Vertices im Array (Wie viele Bytes er überspringen muss um zum nächsten Vertex zu gelangen) und der Dritte ist ein Pointer auf das erste Vertex. Genau so sieht der Colorpointer aus.

Das eigentliche Zeichnen

Nun, da alles initialisiert ist, sollen die Quads auch auf den Bildschirm. Dazu folgende Funktion:

void drawVertexArray(void) {
	glDrawElements(GL_QUADS, /* Primitivtyp */
		indexCount, /* Anzahl Indizes */
		GL_UNSIGNED_INT, /* Typ der Indizes */
		indices); /*Index-Array*/
}

Als erstes wird gesagt, dass die gezeichneten Elemente Quads sind. Dann kommt die Anzahl der Indices sowie deren Typ. Als letztes das eigentliche Indexarray.
Das Ergebnis sollte nun so aussehen:
Varray.png

Die kompletten C-Dateien

Der Übersicht halber die komplette vertexarray.h:

#ifndef _VERTEXARRAY_H
#define _VERTEXARRAY_H

void initVertexArray(void);

void drawVertexArray(void);

#endif

Und die vertexarray.c:

#include "vertexArray.h"

#include <GL/glut.h>

typedef struct {
	GLfloat x, y, z;
	GLfloat r, g, b;
} Vertex;

static const Vertex vertices[18] = {
	0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
	0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f,
	1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
	1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
	2.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,
	2.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f
	};

static const GLuint indices[8] = {
	0, 2, 3, 1, 2, 4, 5, 3
	};

static int indexCount = 8;

void initVertexArray(void) {
	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_COLOR_ARRAY);
	glVertexPointer(3, /* Komponenten pro Vertex (x,y,z) */
			GL_FLOAT, /* Typ der Komponenten */
			sizeof(Vertex), /* Offset zwischen 2 Vertizes im Array */
			&(vertices[0].x)); /* Zeiger auf die 1. Komponente */

	glColorPointer (3, GL_FLOAT, sizeof(Vertex), &(vertices[0].r));
}

void drawVertexArray(void) {
	glDrawElements(GL_QUADS, /* Primitivtyp */
		indexCount, /* Anzahl Indizes */
		GL_UNSIGNED_INT, /* Typ der Indizes */
		indices); /* Index-Array */
}

Hinweis zu dem Array

In dem Minimalbeispiel wurde für Vertices und Farben das selbe Array verwendet. Meistens ist es sinnvoller, das in verschiedenen Arrays zu halten. Die entsprechenden Aufrufe, die OpenGL die Pointer mitteilen, müssen dann natürlich entsprechend angepasst werden.

Weiterführendes

Weitere Pointer

Bisher wurden nur Vertices und Farben als Array benutzt. Nützlich sind noch Pointer auf Normale und Texturkoordinaten. Dazu müssen die entsprechenden Clientstates aktiviert werden:

glEnableClientState(GL_NORMAL_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);

OpenGL muss natürlich im Init entsprechende Pointer mittels glNormalPointer und glTexCoordPointer bekommen. Die entsprechenden Werte funktionieren genau so wie bei dem Colorpointer.
Desweiteren gibt es noch diese Pointer:

Weitere Zeichenfunktionen

Neben glDrawElements gibt es noch weitere Funktionen, um ein Vertexarray zu zeichnen.

Anwendungshinweise

Vertex-Arrays sind zwar weitaus besser als der Immediate-Mode mit glBegin/glEnd, aber noch nicht das Wahre. Weitaus performanter sind Vertexbufferobjekte. Sie arbeiten ähnlich wie Vertexarrays, bloß dass die Daten nicht im Hauptspeicher liegen, sondern direkt im Grafikkartenspeicher. Es gibt im Wiki hier ein Tutorial dazu: Tutorial_Vertexbufferobject