Meine Werkzeuge
Namensräume
Varianten

Terrain 101/Vertex- und Index-Buffer

Aus indiedev
Wechseln zu: Navigation, Suche
Terrain 101
Vertex- und Index-Buffer
TutorialTerrain101.png
Autor Roland "Glatzemann" Rosenkranz
Programmier­sprache C#
Kategorie XNA
Diskussion Thread im Forum
Lizenz indiedev article license
Terrain 101 auf MitOhneHaare.de


Da wir mittlerweile Profis im Transformieren von Vertices sind, können wir uns nun an ein neues Ufer begeben und uns in diesem Teil meiner Artikelreihe Terrain 101 den Buffern widmen. Ziel ist es natürlich nach wie vor, am Ende der Artikelreihe eine dreidimensionale Landschaft entwickeln zu können. Dabei möchte ich mich nicht nur auf eine winzig kleine Landschaft beschränken, wie dies in vielen anderen Tutorials gemacht wird, sondern ich möchte riesige, detaillierte Landschaften mit euch gemeinsam erschaffen, die soweit ausgebaut werden, daß man sie auch in einem Spiel verwenden kann.

Inhaltsverzeichnis

Hardware-Buffer

Diese Überschrift hatte ich in einem anderen Artikel, exakt 6 Monate bevor ich diese Zeile schreibe, bereits einmal verwendet. Im Mysterium RenderTarget wurde damals schon darüber geschrieben, was ein Hardwarebuffer ist. Im RenderTarget-Artikel ging es aber hauptsächlich um Pixel-Buffer und in diesem Artikel wird sich dies ändern. Hier werde ich jetzt über Index- und Vertex-Buffer schreiben.

Ein Hardware-Buffer ist ein einfaches Konstrukt. Es ist schlicht und einfach ein Speicherbereich, in dem bestimmte Daten gespeichert werden können. Dies können die unterschiedlichsten Daten sein, aber in der Regel sind dies Indexdaten, Vertexdaten und Pixeldaten. Man spricht dann von einem Index Buffer, einem Vertex Buffer, sowie von Texturen und/oder Render Targets. Diese Buffer können - wenn man natives DirectX verwendet - entweder im Arbeitsspeicher des Rechners liegen, oder im dedizierten Speicher der Grafikkarte. In XNA gehen wir immer davon aus, daß diese Daten im Speicher der Grafikkarte liegen. Wir gehen nur davon aus, da wir nicht die volle Kontrolle darüber haben. Der Grafikkarten-Treiber, DirectX und auch XNA können diese Entscheidung beeinflussen. Hat die Grafikkarte z.B. keinen eigenen Speicher oder einfach nur zuwenig, dann landet der jeweilige Buffer nicht zwangsweise dort, wo wir es erwarten, es wird aber auch nicht zwangsweise ein Fehler geworfen.

Warum nun dieser "Klimmzug"? Dies ist eine berechtigte Frage, die eine zum Glück nicht allzu komplizierte Antwort erfordert. Die Grafikkarte kann auf ihren eigenen Speicher besser zugreifen. Besser bedeutet hierbei in der Regel eine höhere Bandbreite. Warum dies technisch gesehen so ist, daß geht hier ein wenig zuweit und würde den Rahmen mehrfach sprengen. Der Hauptunterschied ist einfach, daß die Daten nicht über den PCI-Express Bus transportiert werden müssen. Das kann man sich gut merken. Der Transfer über den PCI-X Bus ist immer langsamer als der (lokale) Zugriff der GPU auf den eigenen Speicher.

Das ist aber nicht die einzige Besonderheit, die es mit diesen Buffern gibt. Wie man schon an den unterschiedlichen Arten erkennen kann, scheinen diese Buffer recht stark typisiert zu sein. Dies liegt daran, daß eine GPU recht "dumm" ist. Einen großen Teil der Geschwindigkeit bezieht sie daraus, daß sie nicht flexibel ist und nicht mit allen Arten von Daten so umgehen kann, wie dies eine CPU könnte. Viele Dinge müssen einfach auf die GPU zugeschnitten werden um ihr die Arbeit zu erleichtern. Dies ist auch bei den Hardwarebuffern so. Es gibt ganz spezielle Regeln, auf die ich in den nächsten beiden Abschnitten eingehen möchte.

Vertex-Buffer

Der Vertex Buffer enthält - wie der Name unschwer erkennen lässt - Vertex-Daten. Dies sind Informationen über die einzelnen Stützpunkte unserer 3D-Objekte. Bisher hatten wir ein sogenanntes Vertex-Array. Wir haben also schlicht und einfach ein Array angelegt und darin unsere Vertex-Daten gespeichert. Dies sah im letzten Teil noch ungefähr so aus:


            vertices = new VertexPositionColor[] { new VertexPositionColor(new Vector3( 0.0f,  0.5f, 0.0f), Color.Red),
                                                   new VertexPositionColor(new Vector3( 0.5f, -0.5f, 0.0f), Color.Green),
                                                   new VertexPositionColor(new Vector3(-0.5f, -0.5f, 0.0f), Color.Blue),
                                                 };

Dies wollen wir nun "verbessern" und etwas flexibler gestalten. Dazu löschen wir erst mal unsere Member-Variable vertices, die am Anfang der Klasse dem Speichern der Vertices diente. Diese Variable benötigen wir nicht mehr, da wir die Daten ja nicht mehr in der Klasse, sondern in einem Vertex-Buffer speichern wollen.

Danach erzeugen wir eine neue Methode, die unseren Vertex-Buffer "aufsetzt".


private void SetupVertexBuffer()
{
            VertexPositionColor[] vertices = new VertexPositionColor[] { new VertexPositionColor(new Vector3( 0.0f,  0.5f, 0.0f), Color.Red),
                                                                         new VertexPositionColor(new Vector3( 0.5f, -0.5f, 0.0f), Color.Green),
                                                                         new VertexPositionColor(new Vector3(-0.5f, -0.5f, 0.0f), Color.Blue),
                                                                       };
}

Danach erzeugen wir eine Member-Variable die die Referenz auf unseren Vertex-Buffer enthält am Anfang der Klasse. Keine Angst, ich werde am Ende dieses Artikels - so wie in jedem Teil - nochmal den gesamten Quellcode dieses Teils auflisten. Wer also die Übersicht verliert, kann einfach runterscrollen und dort nachschauen.


VertexBuffer vertexBuffer;

In unserer SetupVertexBuffer-Methode erzeugen wir natürlich nun auch einen VertexBuffer. Dies erfolgt ganz einfach über den Konstruktor.


this.vertexBuffer = new VertexBuffer(GraphicsDevice, typeof(VertexPositionColor), 3, BufferUsage.WriteOnly);

Als ersten Parameter müssen wir eine Referenz auf das GraphicsDevice übergeben. Dies ist notwendig, da der Vertex-Buffer ja in den Grafikkartenspeicher hochgeladen werden soll. Ohne GraphicsDevice kann es daher keinen VertexBuffer geben. Der nächste Parameter gibt den Typ der Daten an, den wir in diesem Vertex-Buffer haben möchten. In diesem Fall ist das unsere bekannte VertexPositionColor-Vertex-Deklaration. Hier kann man auch andere Datentypen verwenden. Dazu aber in den Folgeartikeln noch mehr. Wir werden später noch unsere eigene Vertex-Deklaration und damit unseren eigenen Vertex-Typ erzeugen. Der dritte Parameter gibt schlicht und einfach an, wieviele Vertices im Vertex-Buffer landen sollen. Die Größe in Bytes berechnet XNA dabei für uns, so daß wir uns mit solchen Details nicht rumschlagen müssen, so wie es in nativem DirectX notwendig wäre.

Der vierte Parameter ist eine kleine Besonderheit. Dieser gibt an, wie wir den Vertex-Buffer verwenden wollen. In diesem Fall haben wir BufferUsage.WriteOnly gesetzt. Dies bedeutet, daß wir lediglich Daten in den Buffer reinschreiben wollen. Dies ist gleichzeitig ein Zugriffsmodifizierer, was bedeuten soll, daß uns XNA daran hindert, daß wir Daten aus dem VertexBuffer auslesen. Es ist aber in erster Linie ein Hinweis zur Optimierung. Die Grafikkarte kann bei einem Buffer, in den nur geschrieben wird viel besser optimieren. Es müssen z.B. keine Lese-Caches aufgebaut werden, evtl. kann ein schneller beschreibbarer Speicherbereich verwendet werden, etc.

Abschliessend müssen wir natürlich den Vertex-Buffer in der SetupVertexBuffer Methode noch befüllen. Dies erfolgt mit der SetData Methode.


this.vertexBuffer.SetData<VertexPositionColor>(vertices);

Die Daten werden nun in den Grafikspeicher der Grafikkarte geladen.

Selbstverständlich müssen wir nun noch den Draw-Aufruf zum Rendern unseres Dreiecks ändern. Erstens weil jetzt einen Compiler-Fehler bekommen (die Variable vertices wurde ja gelöscht) und zweitens weil wir der Grafikkarte natürlich mitteilen müssen, daß sie aus einem Vertex-Buffer rendern soll. Dazu sind in der Draw-Methode zwei Aufrufe notwendig. Als erstes müssen wir unseren Vertex-Buffer als aktiven Vertex-Buffer setzen und als zweiten Aufruf rendern wir diesen. In der Draw-Methode ersetzen wir also den bisherigen DrawUserPrimitives Aufruf durch folgenden Code:


GraphicsDevice.SetVertexBuffer(this.vertexBuffer);
GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, 1);

Die Parameter sind sehr ähnlich dem bisherigen Aufruf, weshalb ich diese hier nicht nochmal erklären möchte. Zur Not bitte einfach im letzten Artikel nachschauen.

Wenn wir unser Programm nun starten, dann bekommen wir exakt das gleiche Bild, wie wir es schon im letzten Teil hatten.

Terrain101 transformations.jpg

Es gibt nun aber einen entscheidenden, wenn auch unsichtbaren Effekt. Bisher war es so, daß der Inhalt unseres Vertex-Arrays bei jedem einzelnen Draw-Aufruf vom Arbeitsspeicher zur Grafikkarte geschickt werden musste, damit diese gerendert werden konnten. Dies belastet den Bus zur Grafikkarte, also meist den PCI-X Bus bzw. früher den AGP-Bus. Das kostet Zeit und ist damit langsamer als die Vertex-Buffer-Variante. Es gibt hier jedoch eine Ausnahme. Auf Plattformen wie der XBox macht es praktisch keinen Unterschied. Die XBox hat keinen dedizierten Grafikspeicher, sondern CPU und GPU sind gleichzeitig an den gleichen Speicher angeschlossen. Solange beide nicht auf den gleichen Speicherbereich agieren, kommen sich diese nicht ins Gehege. Tatsächlich ist es auf der XBox so, daß der Vertex-Buffer einfach nur eine Kopie der Daten des Vertex-Arrays ist. Trotzdem sollte man auch auf dieser Plattform testen, was tatsächlich schneller ist. Zwar ist der Unterschied auf der XBox deutlich kleiner, aber in gewissen Fällen trotzdem noch vorhanden.

Ein weiterer, interessanter Aspekt ist der Aufruf von SetVertexBuffer. Der ein oder andere fragt sich vielleicht, wozu dieser Befehl notwendig ist, könnte man ihn doch explizit in DrawPrimitives verpacken. Der Grund ist sehr simpel: In einem Vertex-Buffer können beliebige Vertex-Daten vorhanden sein, auch von mehreren Meshes gleichzeitig. Es kann nun notwendig sein, daß diese mit mehreren DrawPrimitives Aufrufen gerendert werden müssen. Warum dies so sein kann, sollte uns an dieser Stelle erstmal nicht interessieren, es ist halt einfach so. In den Folgeartikeln werde ich darauf noch mal genauer eingehen (müssen). In diesem Fall spart man sich das Setzen eines neuen Vertex-Buffers. Dies gilt nämlich, genau wie das Setzen einer Textur oder eines Effekts als RenderStateChange und ist solange gültig, bis der Grafikkarte etwas anderes mitgeteilt wird. Daher macht es Sinn seine Objekte unter anderem nach VertexBuffer zu sortieren, damit dieser so selten wie möglich gewechselt werden muss.

Das sollte als Einführung in Vertex-Buffer erstmal reichen. Sicherlich sind damit noch nicht alle Aspekte vollkommen beleuchtet worden und auch noch nicht alle Besonderheiten beschrieben, aber der Leser sollte nun wissen, wie Vertex-Buffer grundsätzlich funktionieren und wozu diese benötigt werden. Die späteren Artikel werden dieses Wissen voraussetzen und darauf weiter aufbauen, so daß am Ende dieser Artikelreihe nahezu alle Aspekte von Vertex-Buffern angesprochen wurden.

Kommen wir nun zu den Index-Buffern.

Index Buffer

Die Index Buffer sind etwas einfacher zu erklären, als Vertex Buffer, aber in der Handhabung sehr ähnlich. Erstmal gibt es zwei Arten von Index Buffern, solche mit 16 Bit Indices und welche mit 32 Bit Indices. Die Unterschiede liegen dabei schlicht und einfach darin, daß mit einem 16 Bit Index-Buffer 65.536 Elemente verwendet werden können (ushort) und mit einem 32 Bit Buffer 4.294.967.295 Elemente (uint). Die exakte Anzahl kann jedoch je nach eingesetzter DirectX-Version abweichen. Bei XNA ist dies DirectX 9.0c. Nähers dazu in der Beschreibung der Feature Level.

Warum verwendet man dann nicht einfach immer 32 Bit-Buffer? Dies liegt zum einen daran, daß nicht jede Grafikkarte 32 Bit-Buffer unterstützt und zum anderen daran, daß ein 32 Bit-Buffer natürlich doppelt soviel Speicher benötigt wie ein 16 Bit-Buffer.

Übrigens: Das Reach Profil von XNA ist auf 16 Bit-Index-Buffer beschränkt. Wer mehr benötigt, der muss auf das HiDef Profil umschalten, was aber nur mit neueren Grafikkarten und nicht auf dem Windows Phone funktioniert.

Die alles entscheidende Frage ist aber eigentlich folgende: Welchen Sinn hat nun dieser Index-Buffer? Stellen wir uns mal einen Würfel vor. Diesen können wir durch 8 Vertices definieren.

Terrain101 indexbuffer01.jpg

Soweit so gut, aber in den vergangenen Artikel hatte ich ja bereits erklärt, daß die Grafikkarte nur mit Dreiecken arbeitet. Dies bedeutet also, daß wir 12 Dreiecke erzeugen müssen, um diesen Würfel darzustellen. Und da ein Dreieck durch jeweils drei Vertices definiert wird, benötigen wir ganze 36 Vertices zur Darstellung dieses Würfels. Hierzu auch eine kleine Illustration.

Terrain101 indexbuffer02.jpg

Dies verschwendet natürlich eine Menge Speicherplatz, je nachdem wie groß die jeweilige Vertex-Deklaration für einen einzelnen Vertex ist. Um dieses Problem nun etwas abzuschwächen und Vertices wiederzuverwenden, ist der Index-Buffer geschaffen worden. Der Index-Buffer ist schlicht und einfach ein Index. Dies bedeutet, daß in diesem Buffer einfach vermerkt wird, an welcher Stelle der Vertex steht, der als nächstes verwendet werden soll. Wir würden also in den vorherigen Illustrationen nicht mehr 36 Vertices definieren müssen, sondern lediglich die 8 aus der ersten Abbildung. Um nun die Dreiecke zu formen, wird einfach im Index-Buffer angegeben, welchen Vertex wir verwenden wollen.

Um dies zu verdeutlichen, werden wir in unserem Beispielprogramm nun ebenfalls einen Index-Buffer verwenden. Dies macht dort nicht viel Sinn, da wir lediglich ein einziges Dreieck haben und auch nur drei Vertices, aber es verdeutlicht zumindest die Anwendung, die sich nicht unterscheidet, egal ob 1000 Indices mit geteilten Vertices vorhanden sind, oder lediglich drei.

Zunächst erzeugen wir wieder ein Member-Variable für den IndexBuffer.


IndexBuffer indexBuffer;

Nachdem wir den Vertex-Buffer aufgesetzt haben, setzen wir auch den Index-Buffer durch Aufruf der neuen Methode SetupIndexBuffer auf. Diese Methode sieht wie folgt aus.


private void SetupIndexBuffer()
{
  uint[] indices = new uint[] { 0, 1, 2 };

  this.indexBuffer = new IndexBuffer(GraphicsDevice, IndexElementSize.ThirtyTwoBits, 3, BufferUsage.WriteOnly);

  this.indexBuffer.SetData<uint>(indices);
}

Im Grunde genommen ist dies fast das gleiche Vorgehen, wie bei den Vertex-Buffern. Wir füllen ein Index-Array mit unseren Indices. In diesem Fall verwenden wir dazu den uint-Datentyp. Die drei Ziffern im Array geben schlicht und einfach an, daß wir als erstes den Vertex an Index 0 verwenden möchten, gefolgt von den Vertices an den Positionen 1 und 2.

Danach erzeugen wir den Index-Buffer durch Aufruf des Konstruktors. Auch hier müssen wir wieder das GraphicsDevice angeben, da der Index-Buffer ja im Grafikkartenspeicher angelegt wird. Danach geben wir mit dem Parameter IndexElementSize an, ob der Index-Buffer 16 oder 32 Bit Element-Größe haben soll, gefolgt von der Anzahl der Elemente, die wir darin speichern wollen. Die Größe in Byte berechnet XNA wieder für uns, diese wäre nur bei nativem DirectX von uns zu berechnen.

Der letzte Parameter, die BufferUsage, verhält sich exakt so, wie sie sich auch beim Vertex-Buffer verhält. Dies hatte ich bereits weiter oben beschrieben.

Um nun den Index-Buffer beim Rendern zu verwenden, müssen wir dies wieder XNA mitteilen. Dazu setzen wir zunächst den Index-Buffer, den wir verwenden wollen, gefolgt von einem aktualisierten Draw-Aufruf. Dies sieht wie folgt aus.


GraphicsDevice.Indices = indexBuffer;

GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 3, 0, 1);

Bei einem Programmstart werden wir nun wieder keine optische Änderung feststellen. "Unter der Haube" hat sich jedoch einiges getan, denn nun werden alle Dreiecke unter Verwendung eines Index-Buffers gerendert.

Auf die Argumente von DrawIndexedPrimitives möchte ich an dieser Stelle noch nicht im Detail eingehen, da dies doch stark weiterführende Themen sind. Dazu mehr in der MSDN. Zu einem späteren Zeitpunkt werde ich dies jedoch wieder aufgreifen, denn wir werden dies noch benötigen. Wichtig ist momentan nur die 3 an drittletzter Stelle (Anzahl der verwendeten Indices) und das letzte Argument die 1 (Anzahl der zu rendernden Primitives).

XBox-Besonderheiten

Auf der XBox gibt es - das möchte ich an dieser Stelle nicht verschweigen - aufgrund der speziellen Architektur eine Besonderheit. SetData von Index- und Vertex-Buffer darf hier niemals in der Draw-Methode aufgerufen werden. Der Grund liegt im sogenannten Predicated Tiling. Dies ist notwendig, da die XBox ein 10 MB großes, extrem schnelles ED-RAM hat. Dies ist eine Art Zwischenspeicher für Grafikoperationen, daß sich direkt im Chip der GPU befindet. Wird nun etwas gerendert, daß größer ist als diese 10MB, dann wird automatisch das Predicated Tiling aktiviert. Der Bildschirminhalt wird in mehrere Rechtecke aufgeteilt und es werden Teile der Szene gerendert. Dies führt im Grunde genommen dazu, daß die Befehle der Draw-Methode mehrfach aufgerufen werden, aber nur ein einziges mal die (interne) Present-Methode. Die Daten werden aber erst bei Aufruf von Present an die Grafikkarte geschickt. Selbstverständlich ist dabei nicht sichergestellt, daß zu jedem Zeitpunkt die korrekten Daten im Vertex- und/oder Index-Buffer enthalten sind. Daher kann es in diesem Fall zu Grafikfehlern oder sogar Abstürzen kommen.

Aufräumen

Sowohl der Vertex-Buffer, als auch der Index-Buffer implementieren die Dispose-Schnittstelle. Dies macht auch Sinn, da beide einen unmanaged Hardware-Buffer aus dem DirectX-Umfeld im Hintergrund verwenden. Jedes .NET-Objekt, daß eine unmanaged Resource verwendet, sollte Dispose implementieren, damit diese Resourcen explizit freigegeben werden können. Dies ist also in unserem Fall auch keine Aussnahme.

Was bedeutet dies nun für unsere Arbeit mit Index- und Vertex-Buffern? Sie müssen schlicht und einfach freigegeben werden, wenn sie nicht mehr verwendet werden und genau dies machen wir in der UnloadContent-Methode, denn diese wird aufgerufen, wenn der Content der Game-Klasse nicht mehr benötigt wird.


        protected override void UnloadContent()
        {
            if (indexBuffer != null)
            {
                indexBuffer.Dispose();
                indexBuffer = null;
            }

            if (vertexBuffer != null)
            {
                vertexBuffer.Dispose();
                vertexBuffer = null;
            }

            base.UnloadContent();
        }

Ein Wort noch zur Erzeugung und Freigabe von Grafikkarten-Resourcen: Diese Operationen sind teuer bis sehr teuer. Es macht im Grunde genommen niemals Sinn, daß in regelmäßigen Abständen neue Vertex- oder Index-Buffer erzeugt werden. Man sollte die benötigten Buffer vor Abarbeitung der Game-Loop (also z.B. in LoadContent) erzeugen und dann wiederverwenden. Das befüllen von Buffern ist ebenfalls eine teure Operation, aber nicht immer vermeidbar. Auch sind diese Kosten stark von der Datenmenge abhängig und diese Operation wird auch deutlich häufiger benötigt, eigentlich sogar regelmäßig.

Abschluss und Ausblick

Ich habe in diesem Artikel die letzten, notwendigen Grundlagen erklärt: Index- und Vertex-Buffer. Unseren bisherigen Code habe ich so abgeändert, daß dieser zunächst einen Vertex-Buffer und in der letzten Ausbaustufe auch einen Index-Buffer verwendet und so die notwendigen Daten für das Rendern niemals die Grafikkarte verlassen müssen. Damit haben wir die höchstmögliche Geschwindigkeit für einzelne Meshes erreicht. Ursprünglich hatte ich für diesen Artikel bereits geplant, daß wir die ersten Teile des Terrains darstellen. Ich habe mich jedoch kurzfristig beim schreiben dieses Beitrages dagegen entschieden, da wir jetzt bereits einen stattlichen Umfang erreicht haben und dies diesen Grundlagenartikel dennoch aufgebläht hätte. So versteht man sicherlich besser den Sinn und Zweck der Hardware-Buffer. Im nächsten Teil wird dann aber tatsächlich eine kleine Landschaft sichtbar werden.

Ich hoffe auch diesmal wieder, daß dieser über 3000 Worte lange Artikel gut zu lesen und verständlich geschrieben war. Anregungen und Fragen könnt ihr natürlich gerne mit der Kommentarfunktion los werden.

Aufgaben für den Leser

In diesem Bereich möchte ich den Leser dazu ermutigen, sich ein wenig mit dem hier vermittelten Wissen zu beschäftigen und dieses auszubauen. Diese Aufgaben sind optional, aber sehr gut dazu geeignet das Erlernte zu festigen. Probleme diesbezüglich können in den Kommentaren natürlich gerne diskutiert werden.

  • Vergößere den Vertex-Buffer und erweitere ihn um weitere Dreiecke
  • Vergrößere den Index-Buffer und erweitere ihn um weitere Indices
  • Drehe das Backface-Culling mit Hilfe des Index-Buffer um
  • Erzeuge einen Würfel, so wie er im Artikel beschrieben wurde mit Hilfe von 8 Vertices und den passenden Indices

Der gesamte Sourcecode dieses Artikels

Wie auch schon im letzten Teil möchte ich durch eine zusammenhängende Darstellung des Quellcodes den Überblick erhöhen. Daher liste ich hier nochmal den gesamten Quellcode aus diesem Artikel auf.


using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace Terrain_101
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        Effect triangleEffect;

        VertexBuffer vertexBuffer;
        IndexBuffer indexBuffer;

        Matrix projectionMatrix;
        Matrix viewMatrix;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            this.Window.Title = "http://www.MitOhneHaare.de - Terrain 101 - Hardware Buffer (Teil 5)";
        }

        protected override void Initialize()
        {
            projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, GraphicsDevice.Viewport.AspectRatio, 1.0f, 1000.0f);
            viewMatrix = Matrix.CreateLookAt(new Vector3(2.0f, 2.0f, 2.0f), Vector3.Zero, Vector3.Up);

            base.Initialize();
        }

        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            triangleEffect = Content.Load<Effect>("Triangle");

            SetupVertexBuffer();

            SetupIndexBuffer();
        }

        protected override void UnloadContent()
        {
            if (indexBuffer != null)
            {
                indexBuffer.Dispose();
                indexBuffer = null;
            }

            if (vertexBuffer != null)
            {
                vertexBuffer.Dispose();
                vertexBuffer = null;
            }

            base.UnloadContent();
        }

        private void SetupVertexBuffer()
        {
            VertexPositionColor[] vertices = new VertexPositionColor[] { new VertexPositionColor(new Vector3( 0.0f,  0.5f, 0.0f), Color.Red),
                                                                          new VertexPositionColor(new Vector3( 0.5f, -0.5f, 0.0f), Color.Green),
                                                                          new VertexPositionColor(new Vector3(-0.5f, -0.5f, 0.0f), Color.Blue),
                                                                        };

            this.vertexBuffer = new VertexBuffer(GraphicsDevice, typeof(VertexPositionColor), 3, BufferUsage.WriteOnly);

            this.vertexBuffer.SetData<VertexPositionColor>(vertices);
        }

        private void SetupIndexBuffer()
        {
            uint[] indices = new uint[] { 0, 1, 2 };

            this.indexBuffer = new IndexBuffer(GraphicsDevice, IndexElementSize.ThirtyTwoBits, 3, BufferUsage.WriteOnly);

            this.indexBuffer.SetData<uint>(indices);
        }

        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            // TODO: Add your update logic here

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            triangleEffect.Parameters["World"].SetValue(Matrix.Identity);
            triangleEffect.Parameters["View"].SetValue(viewMatrix);
            triangleEffect.Parameters["Projection"].SetValue(projectionMatrix);

            triangleEffect.CurrentTechnique.Passes[0].Apply();

            GraphicsDevice.SetVertexBuffer(this.vertexBuffer);
            GraphicsDevice.Indices = indexBuffer;

            GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 3, 0, 1);

            base.Draw(gameTime);
        }
    }
}

Navigation
Tutorials und Artikel
Community Project
Werkzeuge