ProfilProfil
 Registrieren
 Login
Bild der WocheBild der Woche

(von Backslider)
Kommentare (0)
****

Weitere
User onlineBenutzer online
Gäste online: 7
Mitglieder online: Keine
Registrierte Mitglieder: 2116
Neustes Mitglied: onkel_keks

Hardware Instancing

Autor: cheater
    Dieses Tutorial soll einen Einblick in die DrawInstancedPrimitives API geben.
    Vorausgesetzt werden Grundkentnisse in der Shader-Programmierung(HLSL), sowie etwas Verständnis für Vertex- und Indexbuffer.

    Hardware-Instancing bringt große Vorteile beim häufigen Zeichnen eines Objektes, in einem einzigen Draw-Call. Dabei muss nicht für jede Instanz des Objektes ein neues Vertex-Indexbuffer Paar erstellt werden. Der Trick ist, Vertex- und Indexbuffer nur einmal untransformiert zu erstellen, und zusätzlich noch einen Vertexbuffer zu beschreiben, in dem die Transformationen zu finden sind.

    Als erstes erstellen wir ein neues WindowsGame-Projekt mit dem Namen "HardwareInstancing".
    Bevor wir uns dem Zeichnen zuwenden kommt zuerst das gewohnte erstellen der World-, View- und Projection-Matrix. Zuerst müssen wir die Variablen deklarieren. Anschließend weisen wir ihnen in der LoadContent-Methode folgende Werte zu:
    World = Matrix.Identity;
    View = Matrix.CreateLookAt(new Vector3(4, 5, 10), new Vector3(4, 0, 4), Vector3.Up);
    Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, GraphicsDevice.Viewport.AspectRatio, 1, 100);


    Anstatt ein fertiges Modell zu nehmen, definieren wir einfach ein am Boden liegendes Quadrat.
    VertexBuffer Square;

    Um die Ordentlichkeit zu bewaren, schreiben wir uns eine Methode zum beschreiben des Vertexbuffers:
    private void SetUpVertexBuffer()
    {
        VertexPositionColor[] Vertices = new VertexPositionColor[4];
        Vertices[0].Position = new Vector3(0, 0, 0);
        Vertices[1].Position = new Vector3(1, 0, 0);
        Vertices[2].Position = new Vector3(0, 0, 1);
        Vertices[3].Position = new Vector3(1, 0, 1);
        Square = new VertexBuffer(GraphicsDevice, VertexPositionColor.VertexDeclaration, 4, BufferUsage.WriteOnly);
        Square.SetData(Vertices);
    }

    Color werden wir in diesem Fall nicht brauchen.
    Nicht vergessen diese Methode in LoadContent aufzurufen.

    Als nächstes fügen wir dem Content-Projekt ein neues Effect-File "InstancingEffect.fx" hinzu.
    Wir deklarieren einen Effect "effect" und beschreiben ihn anschließend in LoadContent:
    effect = Content.Load<Effect>("InstancingEffect")

    Jetzt können wir ihm unsere Variablen zuweisen:
    effect.Parameters["World"].SetValue(World);
    effect.Parameters["View"].SetValue(View);
    effect.Parameters["Projection"].SetValue(Projection);


    Jetzt können wir schon zum ersten Mal unser Quadrat begutachten:
    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.CornflowerBlue);
        GraphicsDevice.SetVertexBuffer(Square);

        foreach (EffectPass pass in effect.CurrentTechnique.Passes)
        {
            pass.Apply();

            GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleStrip, 0, 2);
        }

        base.Draw(gameTime);
    }

    Wenn wir jetzt auf F5 drücken, sollte es links hinten zu sehen sein.

    Die DrawInstancedPrimitives API verlangt zusätzlich noch einen IndexBuffer, den wir, da er nicht benötigt wird einfach als 0, 1, 2, 3 definieren. Zuerst die Variable:
    IndexBuffer Indices;

    Dann müsssen wir ihn noch beschreiben, was wir wieder in einer neuen einfachen Methode machen:
    private void SetUpIndexBuffer()
    {
        Indices = new IndexBuffer(GraphicsDevice, typeof(uint), 4, BufferUsage.WriteOnly);
        Indices.SetData(new uint[] { 0, 1, 2, 3 });
    }

    Nicht vergessen in LoadContent aufzurufen.
    Nun können wir die Draw-Methode ändern:
    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.CornflowerBlue);
        GraphicsDevice.SetVertexBuffer(Square);
        GraphicsDevice.Indices = Indices;

        foreach (EffectPass pass in effect.CurrentTechnique.Passes)
        {
            pass.Apply();

            GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleStrip, 0, 0, 4, 0, 2);
        }

        base.Draw(gameTime);
    }

    Glücklicherweise ist alles wie vorher.

    Jetzt wird es spannend:
    Wie bereits erwähnt, werden die Transformationen in einem eigenen Vertexbuffer gespeichert.
    Dabei könnten wir einfach einen Translations-Vektor übergeben, aber ich werde hier zur Veranschaulichung eine Matrix verwenden, in der wir auch noch eine Skalierung und eine Rotation speichern könnten.
    Hierfür werden wir eine eigene VertexDeclaration schreiben, die wie folgt aussieht:
    VertexDeclaration Transformation = new VertexDeclaration
    (
        new VertexElement(0, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 0),
        new VertexElement(16, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 1),
        new VertexElement(32, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 2),
        new VertexElement(48, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 3)
    );

    Da es bei VertexElementFormat keine Matrix gibt, verwenden wir stattdessen 4 Vector4. Statt TextureCoordinate kann man auch anderes verwenden, das einzig wichtige ist, dass Usage+Index in keiner anderen gleichzeitig verwendeten VertexDeclaration (hier VertexPositionColor.VertexDeclaration) vorkommt.
    Wir definieren einen zweiten VertexBuffer
    VertexBuffer Transformations;

    und schreiben eine Methode in der wir ihn füllen:
    private void SetUpTransformations()
    {
        Matrix[] Trans = new Matrix[2];
        Trans[0] = Matrix.CreateTranslation(0, 0, 0);
        Trans[1] = Matrix.CreateTranslation(2, 0, 0);

        Transformations = new VertexBuffer(GraphicsDevice, Transformation, Trans.Length, BufferUsage.WriteOnly);
        Transformations.SetData(Trans);
    }

    Wir definieren ein Array mit zwei Matrizen, von denen eine eine Translation um zwei Einheiten nach rechts beschreibt. Diese Methode rufen wir wieder in Load-Content auf.

    Als nächtes müssen wir die VertexBuffer zusammen der GraphicsDevice übergeben. Dazu ersetzten wir die zweite Zeile der Draw-Methode mit
    GraphicsDevice.SetVertexBuffers(new VertexBufferBinding(Square, 0, 0), new VertexBufferBinding(Transformations, 0, 1));

    Nun können wir bereits die Zeile mit dem DrawCall umschreiben:
    GraphicsDevice.DrawInstancedPrimitives(PrimitiveType.TriangleStrip, 0, 0, 4, 0, 2, Transformations.VertexCount);


    Wenn wir dass Programm nun starten, werden wir im Vergleich zu vorher keinen Unterschied feststellen können. Das hat auch einen Grund:
    Die Transformationen müssen im VertexShader vollzogen werden, und diese haben wir noch nicht definiert. Also wechseln wir zu unserem Effect-File.
    Hier gibt es erstaunlich wenig zu tun:
    VertexShaderOutput VertexShaderFunction(VertexShaderInput input, float4x4 Transformation : TEXCOORD0)
    {
        VertexShaderOutput output;
        float4 transPosition = mul(input.Position, transpose(Transformation));
        float4 worldPosition = mul(transPosition, World);
        float4 viewPosition = mul(worldPosition, View);
        output.Position = mul(viewPosition, Projection);

        return output;
    }

    Zuerst übernehmen wir den Parameter Transformation. Als ElementUsage nehmen wir TEXCOORD0, wie wir es in unserer VertexDeclaration festgelegt haben.
    anschließend multiplizieren wir die Position mit transponierten Transformations-Matrix. Das ergebnis ist die neue Position der Instanz.

    Jetzt funktioniert das Instancing zwar, aber noch ist es kein echtes HardwareInstancing. Dafür müssen wir noch das ShaderModel auf 3.0 ändern:
    technique Technique1
    {
        pass Pass1
        {
            VertexShader = compile vs_3_0 VertexShaderFunction();
            PixelShader = compile ps_3_0 PixelShaderFunction();
        }
    }


    Fertig sind die ersten zwei Instancen des Quadrats.

    Für das erste wird das hier auch reichen. Evt. werde ich noch eine Fortsetzung schreiben.
    Viel Spaß beim experimentieren, ich hoffe, dass ihr das vielleicht in irgendeinem eurer Programme brauchen könnt Very Happy

    Infos

    Name: Hardware Instancing
    Autor: cheater
    Kommentare: Thread