Freigeben über


Ein einfaches Mesh erstellen und anzeigen

3D-Spiele für die universelle Windows-Plattform (UWP) verwenden in der Regel Polygone, um Objekte und Oberflächen im Spiel darzustellen. Die Listen der Scheitelpunkte, die zur Struktur dieser polygonalen Objekte und Oberflächen gehören, werden als Mesh bezeichnet. Hier erstellen wir ein einfaches Mesh für ein Würfelobjekt und stellen es der Shader-Pipeline zum Rendern und Anzeigen bereit.

Wichtig Der hier enthaltene Beispielcode verwendet Typen (z. B. DirectX::XMFLOAT3 und DirectX::XMFLOAT4X4) und Inlinemethoden, die in DirectXMath.h deklariert sind. Wenn Sie diesen Code ausschneiden und einfügen, #include <DirectXMath.h> in Ihr Projekt ein.

 

Was Sie wissen müssen

Technologieen

Voraussetzungen

  • Grundkenntnisse in linearer Algebra und 3D-Koordinatensystemen
  • Eine Direct3D-Vorlage in Visual Studio 2015 oder höher

Anweisungen

In diesen Schritten wird gezeigt, wie Sie einen einfachen Gitterwürfel erstellen.

Schritt 1: Erstellen des Gitters für das Modell

In den meisten Spielen wird das Gitter für ein Spielobjekt aus einer Datei geladen, die die spezifischen Vertexdaten enthält. Die Reihenfolge dieser Scheitelpunkte ist anwendungsabhängig, aber sie werden normalerweise als Streifen oder Fächer serialisiert. Vertexdaten können aus einer beliebigen Softwarequelle stammen oder manuell erstellt werden. Es liegt an Ihrem Spiel, die Daten so zu interpretieren, dass der Vertex-Shader sie effektiv verarbeiten kann.

In unserem Beispiel verwenden wir ein einfaches Gitter für einen Würfel. Der Würfel wird, wie jedes Objektgitter in dieser Phase der Pipeline, mithilfe eines eigenen Koordinatensystems dargestellt. Der Vertex-Shader erhält die Koordinaten und gibt, nachdem er die von Ihnen bereitgestellten Transformationsmatrizen angewendet hat, die endgültige 2D-Ansichtsprojektion in einem homogenen Koordinatensystem zurück.

Definieren Sie das Gitter für einen Würfel. (Oder laden Sie es aus einer Datei. Sie entscheiden!)

SimpleCubeVertex cubeVertices[] =
{
    { DirectX::XMFLOAT3(-0.5f, 0.5f, -0.5f), DirectX::XMFLOAT3(0.0f, 1.0f, 0.0f) }, // +Y (top face)
    { DirectX::XMFLOAT3( 0.5f, 0.5f, -0.5f), DirectX::XMFLOAT3(1.0f, 1.0f, 0.0f) },
    { DirectX::XMFLOAT3( 0.5f, 0.5f,  0.5f), DirectX::XMFLOAT3(1.0f, 1.0f, 1.0f) },
    { DirectX::XMFLOAT3(-0.5f, 0.5f,  0.5f), DirectX::XMFLOAT3(0.0f, 1.0f, 1.0f) },

    { DirectX::XMFLOAT3(-0.5f, -0.5f,  0.5f), DirectX::XMFLOAT3(0.0f, 0.0f, 1.0f) }, // -Y (bottom face)
    { DirectX::XMFLOAT3( 0.5f, -0.5f,  0.5f), DirectX::XMFLOAT3(1.0f, 0.0f, 1.0f) },
    { DirectX::XMFLOAT3( 0.5f, -0.5f, -0.5f), DirectX::XMFLOAT3(1.0f, 0.0f, 0.0f) },
    { DirectX::XMFLOAT3(-0.5f, -0.5f, -0.5f), DirectX::XMFLOAT3(0.0f, 0.0f, 0.0f) },
};

Das Koordinatensystem des Würfels legt die Mitte des Würfels am Ursprung fest, wobei die y-Achse in einem linkshändigen Koordinatensystem von oben nach unten verläuft. Koordinatenwerte werden als 32-Bit-Fließkommazahlen zwischen -1 und 1 ausgedrückt.

In jeder Klammerpaarung gibt die zweite DirectX::XMFLOAT3-Wertgruppe die Farbe an, die dem Scheitelpunkt als RGB-Wert zugeordnet ist. Beispielsweise hat der erste Scheitelpunkt bei (-0,5, 0,5, -0,5) eine volle grüne Farbe (der G-Wert ist auf 1,0 festgelegt, und die Werte "R" und "B" werden auf 0 festgelegt).

Daher haben Sie 8 Scheitelpunkte, jeweils mit einer bestimmten Farbe. Jedes Scheitelpunkt-/Farbpaar ist die vollständige Daten für einen Scheitelpunkt in unserem Beispiel. Wenn Sie unseren Vertexpuffer angeben, müssen Sie dieses spezifische Layout berücksichtigen. Wir stellen dieses Eingabelayout dem Vertex-Shader bereit, damit es Ihre Vertexdaten verstehen kann.

Schritt 2: Einrichten des Eingabelayouts

Jetzt haben Sie die Scheitelpunkte im Arbeitsspeicher. Ihr Grafikgerät verfügt jedoch über einen eigenen Speicher, und Sie verwenden Direct3D, um darauf zuzugreifen. Um Ihre Vertexdaten zur Verarbeitung in das Grafikgerät zu übertragen, müssen Sie den Weg freimachen: Sie müssen deklarieren, wie die Vertexdaten strukturiert sind, damit das Grafikgerät sie interpretieren kann, wenn es sie von Ihrem Spiel erhält. Dazu verwenden Sie ID3D11InputLayout.

Deklarieren Sie das Eingabelayout und legen Sie es für den Vertex-Puffer fest.

const D3D11_INPUT_ELEMENT_DESC basicVertexLayoutDesc[] =
{
    { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0,  0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    { "COLOR",    0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};

ComPtr<ID3D11InputLayout> inputLayout;
m_d3dDevice->CreateInputLayout(
                basicVertexLayoutDesc,
                ARRAYSIZE(basicVertexLayoutDesc),
                vertexShaderBytecode->Data,
                vertexShaderBytecode->Length,
                &inputLayout)
);

In diesem Code geben Sie ein Layout für die Eckpunkte an, insbesondere welche Daten jedes Element in der Eckpunktliste enthält. Hier geben Sie in basicVertexLayoutDesc-zwei Datenkomponenten an:

  • POSITION: Dies ist eine HLSL-Semantik für Positionsdaten, die einem Shader bereitgestellt werden. In diesem Code ist es ein DirectX::XMFLOAT3, genauer gesagt eine Struktur mit 3 32-Bit-Gleitkommawerten, die einer 3D-Koordinate (x, y, z) entsprechen. Sie können auch einen Float4 verwenden, wenn Sie die homogene "w"-Koordinate bereitstellen, und in diesem Fall geben Sie DXGI_FORMAT_R32G32B32A32_FLOAT an. Ganz gleich, ob Sie DirectX::XMFLOAT3 oder float4 verwenden, hängt von den spezifischen Anforderungen Ihres Spiels ab. Stellen Sie einfach sicher, dass die Vertexdaten für Ihr Gitter dem verwendeten Format entsprechen!

    Jeder Koordinatenwert wird als Gleitkommawert zwischen -1 und 1 im Koordinatenbereich des Objekts ausgedrückt. Nach Abschluss des Vertex-Shaders befindet sich der transformierte Scheitelpunkt im homogenen (perspektivisch korrigierten) Ansichtsprojektionsbereich.

    "Aber der Enumerationswert gibt RGB an, nicht XYZ!", bemerken Sie klug. Guter Blick! In beiden Fällen von Farbdaten und Koordinatendaten verwenden Sie in der Regel 3 oder 4 Komponentenwerte, weshalb sie nicht dasselbe Format für beide verwenden? Die HLSL-Semantik, nicht der Formatname, gibt an, wie der Shader die Daten behandelt.

  • COLOR: Dies ist eine HLSL-Semantik für Farbdaten. Wie POSITIONbesteht sie aus 3 32-Bit-Gleitkommawerten (DirectX::XMFLOAT3). Jeder Wert enthält eine Farbkomponente: Rot (r), Blau (b) oder Grün (g), ausgedrückt als gleitende Zahl zwischen 0 und 1.

    COLOR Werte werden typischerweise als ein RGBA-Wert mit vier Komponenten am Ende der Shader-Pipeline zurückgegeben. In diesem Beispiel legen Sie den Alphawert "A" auf 1,0 (maximale Deckkraft) in der Shader-Pipeline für alle Pixel fest.

Eine vollständige Liste der Formate finden Sie unter DXGI_FORMAT. Eine vollständige Liste der HLSL Semantiken finden Sie unter Semantik.

Rufen Sie ID3D11Device::CreateInputLayout auf, und erstellen Sie das Eingabelayout auf dem Direct3D-Gerät. Jetzt müssen Sie einen Puffer erstellen, der die Daten tatsächlich enthalten kann!

Schritt 3: Auffüllen der Vertexpuffer

Vertexpuffer enthalten die Liste der Scheitelpunkte für jedes Dreieck im Gitter. Jeder Scheitelpunkt muss in dieser Liste eindeutig sein. In unserem Beispiel haben Sie 8 Scheitelpunkte für den Würfel. Der Vertex-Shader wird auf dem Grafikgerät ausgeführt und liest aus dem Vertexpuffer und interpretiert die Daten basierend auf dem eingabelayout, das Sie im vorherigen Schritt angegeben haben.

Im nächsten Beispiel geben Sie eine Beschreibung und eine Unterressource für den Puffer an, die Direct3D eine Reihe von Dingen über die physische Zuordnung der Vertexdaten und deren Behandlung im Arbeitsspeicher auf dem Grafikgerät mitteilen. Dies ist notwendig, da Sie eine generische ID3D11Bufferverwenden, die alles enthalten kann! Die D3D11_BUFFER_DESC- und D3D11_SUBRESOURCE_DATA-Strukturen werden bereitgestellt, um sicherzustellen, dass Direct3D das physische Speicherlayout des Puffers versteht, einschließlich der Größe jedes Vertexelements im Puffer sowie die maximale Größe der Vertexliste. Sie können hier auch den Zugriff auf den Pufferspeicher sowie die Art und Weise kontrollieren, wie er durchlaufen wird. Das geht jedoch etwas über den Rahmen dieses Tutorials hinaus.

Nachdem Sie den Puffer konfiguriert haben, rufen Sie ID3D11Device::CreateBuffer auf, um ihn tatsächlich zu erstellen. Offensichtlich, wenn Sie mehr als ein Objekt haben, erstellen Sie Puffer für jedes eindeutige Modell.

Deklarieren und erstellen Sie den Vertexpuffer.

D3D11_BUFFER_DESC vertexBufferDesc = {0};
vertexBufferDesc.ByteWidth = sizeof(SimpleCubeVertex) * ARRAYSIZE(cubeVertices);
vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vertexBufferDesc.CPUAccessFlags = 0;
vertexBufferDesc.MiscFlags = 0;
vertexBufferDesc.StructureByteStride = 0;

D3D11_SUBRESOURCE_DATA vertexBufferData;
vertexBufferData.pSysMem = cubeVertices;
vertexBufferData.SysMemPitch = 0;
vertexBufferData.SysMemSlicePitch = 0;

ComPtr<ID3D11Buffer> vertexBuffer;
m_d3dDevice->CreateBuffer(
                &vertexBufferDesc,
                &vertexBufferData,
                &vertexBuffer);

Eckpunkte geladen. Aber wie lautet die Reihenfolge der Verarbeitung dieser Eckpunkte? Dies wird behandelt, wenn Sie eine Liste der Indizes für die Scheitelpunkte bereitstellen – die Reihenfolge dieser Indizes ist die Reihenfolge, in der der Vertex-Shader sie verarbeitet.

Schritt 4: Auffüllen der Indexpuffer

Nun stellen Sie eine Liste der Indizes für jeden Scheitelpunkt bereit. Diese Indizes entsprechen der Position des Vertex im Vertexpuffer, beginnend mit 0. Um dies zu visualisieren, sollten Sie berücksichtigen, dass jedem eindeutigen Scheitelpunkt in Ihrem Gitter eine eindeutige Nummer zugewiesen ist, z. B. eine ID. Diese ID ist die ganzzahlige Position des Vertex im Vertexpuffer.

eines Würfels mit acht nummerierten Scheitelpunkten

In unserem Beispielwürfel haben Sie 8 Scheitelpunkte, die 6 Quads für die Seiten erstellen. Sie teilen die Quads in Dreiecke auf und erhalten insgesamt 12 Dreiecke, die unsere 8 Eckpunkte verwenden. Bei 3 Scheitelpunkten pro Dreieck haben Sie 36 Einträge in unserem Indexpuffer. In unserem Beispiel wird dieses Indexmuster als Dreiecksliste bezeichnet, und Sie geben es Direct3D als D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST an, wenn Sie die primitive Topologie einstellen.

Dies ist wahrscheinlich die ineffizienteste Methode zum Auflisten von Indizes, da es viele Redundanzen gibt, wenn Dreiecke Punkte und Seiten teilen. Wenn z. B. ein Dreieck eine Seite in einer Raute-Form teilt, führen Sie 6 Indizes für die vier Scheitelpunkte wie folgt auf:

Reihenfolge der Indizes beim Konstruieren einer Raute

  • Dreieck 1: [0, 1, 2]
  • Dreieck 2: [0, 2, 3]

In einer Streifen- oder Fantopologie ordnen Sie die Vertices so an, dass viele redundante Kanten während der Durchquerung reduziert werden (z. B. die Kante von Index 0 bis Index 2 im Bild). Bei großen Gittern wird dadurch die Anzahl der Ausführung des Vertex-Shaders erheblich verringert und die Leistung signifikant verbessert. Wir halten es jedoch einfach und bleiben bei der Dreiecksliste.

Deklarieren Sie die Indizes für den Vertexpuffer als einfache Dreieckslistentopologie.

unsigned short cubeIndices[] =
{   0, 1, 2,
    0, 2, 3,

    4, 5, 6,
    4, 6, 7,

    3, 2, 5,
    3, 5, 4,

    2, 1, 6,
    2, 6, 5,

    1, 7, 6,
    1, 0, 7,

    0, 3, 4,
    0, 4, 7 };

Sechsunddreißig Indexeinträge im Puffer sind sehr redundant, wenn Sie nur 8 Eckpunkte haben! Wenn Sie einige der Redundanzen entfernen und einen anderen Vertexlistentyp verwenden, z. B. einen Strip oder einen Lüfter, müssen Sie diesen Typ angeben, wenn Sie einen bestimmten D3D11_PRIMITIVE_TOPOLOGY Wert für die ID3D11DeviceContext::IASetPrimitiveTopology Methode angeben.

Weitere Informationen zu verschiedenen Indexlistentechniken finden Sie unter primitive Topologien.

Schritt 5: Erstellen eines Konstantenpuffers für Ihre Transformationsmatrizen

Bevor Sie mit der Verarbeitung von Vertexen beginnen können, müssen Sie die Transformationsmatrizen bereitstellen, die bei der Ausführung auf jeden Vertex angewendet (multipliziert) werden. Für die meisten 3D-Spiele gibt es drei davon:

  • Die 4x4-Matrix, die vom Objektkoordinatensystem (Modell) in das Gesamtkoordinatensystem der Welt transformiert wird.
  • Die 4x4-Matrix, die vom Weltkoordinatensystem in das Kamerakoordinatensystem (Ansicht) transformiert wird.
  • Die 4x4-Matrix, die vom Kamerakoordinatensystem in das 2-D-Projektionskoordinatensystem transformiert.

Diese Matrizen werden in einem Konstantenpufferan den Shader übergeben. Ein Konstantenpuffer ist ein Speicherbereich, der während der Ausführung des nächsten Durchlaufs der Shaderpipeline konstant bleibt und direkt von den Shadern aus dem HLSL-Code aufgerufen werden kann. Sie definieren jeden Konstantenpuffer zweimal: zuerst im C++-Code Ihres Spiels und (mindestens) einmal in der C-ähnlichen HLSL-Syntax für den Shadercode. Die beiden Deklarationen müssen direkt mit den Typen und der Datenausrichtung übereinstimmen. Es ist einfach, schwer zu findende Fehler einzuführen, wenn der Shader die HLSL-Deklaration verwendet, um in C++ deklarierte Daten zu interpretieren, und die Typen nicht übereinstimmen oder die Daten falsch ausgerichtet sind!

Konstantenpuffer werden nicht durch die HLSL geändert. Sie können sie ändern, wenn Ihr Spiel bestimmte Daten aktualisiert. Häufig erstellen Spieleentwickler vier Klassen von Konstantenpuffern: einen Typ für Updates pro Frame; ein Typ für Aktualisierungen pro Modell/Objekt; ein Typ für Updates pro Aktualisierung des Spielzustands; und ein Typ für Daten, die sich nie über die Lebensdauer des Spiels ändern.

In diesem Beispiel haben wir nur eine, die sich nie ändert: die DirectX::XMFLOAT4X4-Daten für die drei Matrizen.

Hinweis Der hier dargestellte Beispielcode verwendet spaltenweise angeordnete Matrizen. Sie können stattdessen Zeilenmatrizen verwenden, indem Sie das schlüsselwort "row_major" in HLSL verwenden und sicherstellen, dass die Quellmatrixdaten ebenfalls Zeilenmatrizen sind. DirectXMath verwendet Zeilenmatrizen und kann direkt mit HLSL-Matrizen verwendet werden, die mit dem schlüsselwort row_major definiert sind.

 

Deklarieren und erstellen Sie einen Konstantenpuffer für die drei Matrizen, die Sie zum Transformieren der einzelnen Scheitelpunkte verwenden.

struct ConstantBuffer
{
    DirectX::XMFLOAT4X4 model;
    DirectX::XMFLOAT4X4 view;
    DirectX::XMFLOAT4X4 projection;
};
ComPtr<ID3D11Buffer> m_constantBuffer;
ConstantBuffer m_constantBufferData;

// ...

// Create a constant buffer for passing model, view, and projection matrices
// to the vertex shader.  This allows us to rotate the cube and apply
// a perspective projection to it.

D3D11_BUFFER_DESC constantBufferDesc = {0};
constantBufferDesc.ByteWidth = sizeof(m_constantBufferData);
constantBufferDesc.Usage = D3D11_USAGE_DEFAULT;
constantBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
constantBufferDesc.CPUAccessFlags = 0;
constantBufferDesc.MiscFlags = 0;
constantBufferDesc.StructureByteStride = 0;
m_d3dDevice->CreateBuffer(
                &constantBufferDesc,
                nullptr,
                &m_constantBuffer
             );

m_constantBufferData.model = DirectX::XMFLOAT4X4( // Identity matrix, since you are not animating the object
            1.0f, 0.0f, 0.0f, 0.0f,
            0.0f, 1.0f, 0.0f, 0.0f,
            0.0f, 0.0f, 1.0f, 0.0f,
            0.0f, 0.0f, 0.0f, 1.0f);

);
// Specify the view (camera) transform corresponding to a camera position of
// X = 0, Y = 1, Z = 2.  

m_constantBufferData.view = DirectX::XMFLOAT4X4(
            -1.00000000f, 0.00000000f,  0.00000000f,  0.00000000f,
             0.00000000f, 0.89442718f,  0.44721359f,  0.00000000f,
             0.00000000f, 0.44721359f, -0.89442718f, -2.23606800f,
             0.00000000f, 0.00000000f,  0.00000000f,  1.00000000f);

Hinweis Sie deklarieren die Projektionsmatrix in der Regel, wenn Sie gerätespezifische Ressourcen einrichten, da die Ergebnisse der Multiplikation mit den aktuellen 2D-Viewportgrößenparametern übereinstimmen müssen (die häufig der Pixelhöhe und -breite der Anzeige entsprechen). Wenn sich diese ändern, müssen Sie die Werte für die x- und y-Koordinate entsprechend skalieren.

 

// Finally, update the constant buffer perspective projection parameters
// to account for the size of the application window.  In this sample,
// the parameters are fixed to a 70-degree field of view, with a depth
// range of 0.01 to 100.  

float xScale = 1.42814801f;
float yScale = 1.42814801f;
if (backBufferDesc.Width > backBufferDesc.Height)
{
    xScale = yScale *
                static_cast<float>(backBufferDesc.Height) /
                static_cast<float>(backBufferDesc.Width);
}
else
{
    yScale = xScale *
                static_cast<float>(backBufferDesc.Width) /
                static_cast<float>(backBufferDesc.Height);
}
m_constantBufferData.projection = DirectX::XMFLOAT4X4(
            xScale, 0.0f,    0.0f,  0.0f,
            0.0f,   yScale,  0.0f,  0.0f,
            0.0f,   0.0f,   -1.0f, -0.01f,
            0.0f,   0.0f,   -1.0f,  0.0f
            );

Während Sie hier sind, legen Sie die Vertex- und Indexpuffer für dieID3D11DeviceContext-sowie die verwendete Topologie fest.

// Set the vertex and index buffers, and specify the way they define geometry.
UINT stride = sizeof(SimpleCubeVertex);
UINT offset = 0;
m_d3dDeviceContext->IASetVertexBuffers(
                0,
                1,
                vertexBuffer.GetAddressOf(),
                &stride,
                &offset);

m_d3dDeviceContext->IASetIndexBuffer(
                indexBuffer.Get(),
                DXGI_FORMAT_R16_UINT,
                0);

 m_d3dDeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

Alles klar! Die Eingabe-Baugruppe ist fertiggestellt. Alles ist zum Rendern bereit. Lassen Sie uns den Vertex-Shader starten.

Schritt 6: Verarbeiten des Gitters mit dem Vertex-Shader

Nachdem Sie nun über einen Scheitelpunktpuffer mit den Scheitelpunkten verfügen, die Ihr Gitter definieren, und den Indexpuffer, der die Reihenfolge definiert, in der die Scheitelpunkte verarbeitet werden, senden Sie sie an den Vertex-Shader. Der Vertex-Shadercode, ausgedrückt als kompilierte High-Level-Shadersprache, wird einmal für jeden Vertex im Vertexpuffer ausgeführt, sodass Sie Ihre Transformationen pro Vertex ausführen können. Das Endergebnis ist in der Regel eine 2D-Projektion.

(Haben Sie den Vertex-Shader geladen? Andernfalls sollten Sie in nachlesen, wie Sie Ressourcen in Ihrem DirectX-Spiel laden.)

Hier erstellen Sie den Vertex-Shader...

// Set the vertex and pixel shader stage state.
m_d3dDeviceContext->VSSetShader(
                vertexShader.Get(),
                nullptr,
                0);

... und legen Sie die Konstantenpuffer fest.

m_d3dDeviceContext->VSSetConstantBuffers(
                0,
                1,
                m_constantBuffer.GetAddressOf());

Hier sehen Sie den Vertex-Shadercode, der die Transformation von Objektkoordinaten zu Weltkoordinaten und dann zum 2D-Ansichtsprojektionskoordinatensystem behandelt. Sie wenden auch eine einfache Beleuchtung pro Scheitelpunkt an, um Dinge schön zu machen. Dies geschieht in der HLSL-Datei Ihres Vertex-Shaders (EinfacherVertexShader.hlsl, in diesem Beispiel).

cbuffer simpleConstantBuffer : register( b0 )
{
    matrix model;
    matrix view;
    matrix projection;
};

struct VertexShaderInput
{
    DirectX::XMFLOAT3 pos : POSITION;
    DirectX::XMFLOAT3 color : COLOR;
};

struct PixelShaderInput
{
    float4 pos : SV_POSITION;
    float4 color : COLOR;
};

PixelShaderInput SimpleVertexShader(VertexShaderInput input)
{
    PixelShaderInput vertexShaderOutput;
    float4 pos = float4(input.pos, 1.0f);

    // Transform the vertex position into projection space.
    pos = mul(pos, model);
    pos = mul(pos, view);
    pos = mul(pos, projection);
    vertexShaderOutput.pos = pos;

    // Pass the vertex color through to the pixel shader.
    vertexShaderOutput.color = float4(input.color, 1.0f);

    return vertexShaderOutput;
}

Sehen Sie den cbuffer oben? Dies ist das HLSL-Analog zum gleichen Konstantenpuffer, den wir zuvor im C++-Code deklariert haben. Und die VertexShaderEingabestruktur? Warum, das sieht genauso aus wie dein Eingabelayout und deine Vertex-Daten-Deklaration! Es ist wichtig, dass die Deklarationen der Konstantenpuffer und Vertexdaten in Ihrem C++-Code genau den Deklarationen im HLSL-Code entsprechen – einschließlich Vorzeichen, Typen und Datenausrichtung.

PixelShaderInput gibt das Layout der Daten an, die von der Hauptfunktion des Vertex Shaders zurückgegeben werden. Wenn Sie die Verarbeitung eines Scheitelpunkts abgeschlossen haben, geben Sie eine Eckpunktposition im 2D-Projektionsraum und eine Farbe zurück, die für die Eckpunktbeleuchtung verwendet wird. Die Grafikkarte verwendet die Datenausgabe des Shaders, um die "Fragmente" (mögliche Pixel) zu berechnen, die farbig sein müssen, wenn der Pixelshader in der nächsten Phase der Pipeline ausgeführt wird.

Schritt 7: Übergeben des Gitters an den Pixelshader

In der Regel führen Sie in dieser Phase der Grafik-Pipeline Operationen auf Pixelbasis auf den sichtbaren projizierten Oberflächen Ihrer Objekte durch. (Menschen mögen Texturen.) Für die Zwecke des Beispiels lassen Sie es jedoch einfach durch diese Phase laufen.

Als Erstes erstellen wir eine Instanz des Pixelshaders. In der 2D-Projektion Ihrer Szene wird der Pixelshader für jedes Pixel ausgeführt, wobei diesem Pixel eine Farbe zugewiesen wird. In diesem Fall übergeben wir die Farbe für das Pixel direkt durch den Vertex-Shader.

Legen Sie den Pixelshader fest.

m_d3dDeviceContext->PSSetShader( pixelShader.Get(), nullptr, 0 );

Definieren Sie einen Passthrough-Pixelshader in HLSL.

struct PixelShaderInput
{
    float4 pos : SV_POSITION;
};

float4 SimplePixelShader(PixelShaderInput input) : SV_TARGET
{
    // Draw the entire triangle yellow.
    return float4(1.0f, 1.0f, 0.0f, 1.0f);
}

Platzieren Sie diesen Code in einer HLSL-Datei, die von der HLSL-Datei des Vertex-Shaders getrennt ist (z. B. SimplePixelShader.hlsl). Dieser Code wird für jedes sichtbare Pixel in Ihrem Viewport einmal ausgeführt (eine speicherinterne Darstellung des Bildschirmteils, dem Sie zeichnen), der in diesem Fall dem gesamten Bildschirm zugeordnet ist. Jetzt ist Ihre Grafikpipeline vollständig definiert!

Schritt 8: Rasterisieren und Anzeigen des Netzes

Führen wir die Pipeline aus. Dies ist einfach: Rufen Sie ID3D11DeviceContext::DrawIndexedauf.

Zeichnen Sie diesen Würfel!

// Draw the cube.
m_d3dDeviceContext->DrawIndexed( ARRAYSIZE(cubeIndices), 0, 0 );
            

Innerhalb der Grafikkarte wird jeder Vertex in der im Indexpuffer angegebenen Reihenfolge verarbeitet. Nachdem Ihr Code den Vertex-Shader ausgeführt hat und die 2D-Fragmente definiert sind, wird der Pixelshader aufgerufen und die Dreiecke gefärbt.

Setzen Sie nun den Würfel auf den Bildschirm.

Stellen Sie diesen Framepuffer für die Anzeige bereit.

// Present the rendered image to the window.  Because the maximum frame latency is set to 1,
// the render loop is generally  throttled to the screen refresh rate, typically around
// 60 Hz, by sleeping the app on Present until the screen is refreshed.

m_swapChain->Present(1, 0);

Und du bist fertig! Verwenden Sie für eine Szene voller Modelle mehrere Vertex- und Indexpuffer, und Sie haben möglicherweise sogar unterschiedliche Shader für verschiedene Modelltypen. Denken Sie daran, dass jedes Modell über ein eigenes Koordinatensystem verfügt und sie mithilfe der Matrizen, die Sie im Konstantenpuffer definiert haben, in das Koordinatensystem der gemeinsamen Welt transformieren müssen.

Bemerkungen

In diesem Thema wird das Erstellen und Anzeigen einfacher Geometrie behandelt, die Sie selbst erstellen. Weitere Informationen zum Laden komplexerer Geometrie aus einer Datei und zum Konvertieren in das beispielspezifische Vertexpufferobjekt (VBO)-Format finden Sie unter Laden von Ressourcen in Ihrem DirectX-Spiel.