Erstellen von Shadern und Zeichnen von Grundtypen
Hier zeigen wir Ihnen, wie Sie HLSL-Quelldateien zum Kompilieren und Erstellen von Shadern verwenden können, die Sie dann zum Zeichnen von Grundtypen auf der Anzeige verwenden können.
Wir erstellen und zeichnen ein gelbes Dreieck mithilfe von Vertex- und Pixel-Shadern. Nachdem wir das Direct3D-Gerät, die Swapchain und die Renderzielansicht erstellt haben, lesen wir Daten aus Binär-Shaderobjektdateien auf dem Datenträger.
Ziel: Erstellen von Shadern und Zeichnen von Grundtypen
Voraussetzungen
Wir gehen davon aus, dass Sie mit C++ vertraut sind. Außerdem benötigen Sie grundlegende Erfahrungen mit Grafikprogrammierungskonzepten.
Wir gehen auch davon aus, dass Sie schnellstarts ausgeführt haben: Einrichten von DirectX-Ressourcen und Anzeigen eines Bilds.
Zeit bis zum Abschluss: 20 Minuten.
Anweisungen
1. Kompilieren von HLSL-Quelldateien
Microsoft Visual Studio verwendet den fxc.exe HLSL-Codecompiler, um die HLSL-Quelldateien (SimpleVertexShader.hlsl und SimplePixelShader.hlsl) in CSO-Binär-Shaderobjektdateien (SimpleVertexShader.cso und SimplePixelShader.cso) zu kompilieren. Weitere Informationen zum HLSL-Codecompiler finden Sie unter Effect-Compiler Tool. Weitere Informationen zum Kompilieren von Shadercode finden Sie unter Kompilieren von Shadern.
Hier ist der Code in SimpleVertexShader.hlsl:
struct VertexShaderInput
{
DirectX::XMFLOAT2 pos : POSITION;
};
struct PixelShaderInput
{
float4 pos : SV_POSITION;
};
PixelShaderInput SimpleVertexShader(VertexShaderInput input)
{
PixelShaderInput vertexShaderOutput;
// For this lesson, set the vertex depth value to 0.5, so it is guaranteed to be drawn.
vertexShaderOutput.pos = float4(input.pos, 0.5f, 1.0f);
return vertexShaderOutput;
}
Hier ist der Code in SimplePixelShader.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);
}
2. Lesen von Daten vom Datenträger
Wir verwenden die DX::ReadDataAsync-Funktion von DirectXHelper.h in der DirectX 11-App-Vorlage (Universelle Windows-App), um Daten asynchron aus einer Datei auf dem Datenträger zu lesen.
3. Erstellen von Vertex- und Pixelshadern
Wir lesen Daten aus der Datei SimpleVertexShader.cso und weisen die Daten dem VertexShaderBytecode-Bytearray zu. Wir rufen ID3D11Device::CreateVertexShader mit dem Bytearray auf, um den Vertex-Shader (ID3D11VertexShader) zu erstellen. Wir legen den Vertextiefewert in der Quelle SimpleVertexShader.hlsl auf 0,5 fest, um sicherzustellen, dass unser Dreieck gezeichnet wird. Wir füllen ein Array von D3D11_INPUT_ELEMENT_DESC Strukturen auf, um das Layout des Vertex-Shadercodes zu beschreiben, und rufen dann ID3D11Device::CreateInputLayout auf, um das Layout zu erstellen. Das Array verfügt über ein Layoutelement, das die Vertexposition definiert. Wir lesen Daten aus der Datei SimplePixelShader.cso und weisen die Daten dem PixelShaderBytecode-Bytearray zu. Wir rufen ID3D11Device::CreatePixelShader mit dem Bytearray auf, um den Pixelshader (ID3D11PixelShader) zu erstellen. Wir legen den Pixelwert auf (1,1,1,1) in der Quelle SimplePixelShader.hlsl fest, um unser Dreieck gelb zu machen. Sie können die Farbe ändern, indem Sie diesen Wert ändern.
Wir erstellen Vertex- und Indexpuffer, die ein einfaches Dreieck definieren. Dazu definieren wir zunächst das Dreieck, beschreiben als Nächstes die Vertex- und Indexpuffer (D3D11_BUFFER_DESC und D3D11_SUBRESOURCE_DATA) mithilfe der Dreiecksdefinition und rufen schließlich ID3D11Device::CreateBuffer einmal für jeden Puffer auf.
auto loadVSTask = DX::ReadDataAsync(L"SimpleVertexShader.cso");
auto loadPSTask = DX::ReadDataAsync(L"SimplePixelShader.cso");
// Load the raw vertex shader bytecode from disk and create a vertex shader with it.
auto createVSTask = loadVSTask.then([this](const std::vector<byte>& vertexShaderBytecode) {
ComPtr<ID3D11VertexShader> vertexShader;
DX::ThrowIfFailed(
m_d3dDevice->CreateVertexShader(
vertexShaderBytecode->Data,
vertexShaderBytecode->Length,
nullptr,
&vertexShader
)
);
// Create an input layout that matches the layout defined in the vertex shader code.
// For this lesson, this is simply a DirectX::XMFLOAT2 vector defining the vertex position.
const D3D11_INPUT_ELEMENT_DESC basicVertexLayoutDesc[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
ComPtr<ID3D11InputLayout> inputLayout;
DX::ThrowIfFailed(
m_d3dDevice->CreateInputLayout(
basicVertexLayoutDesc,
ARRAYSIZE(basicVertexLayoutDesc),
vertexShaderBytecode->Data,
vertexShaderBytecode->Length,
&inputLayout
)
);
});
// Load the raw pixel shader bytecode from disk and create a pixel shader with it.
auto createPSTask = loadPSTask.then([this](const std::vector<byte>& pixelShaderBytecode) {
ComPtr<ID3D11PixelShader> pixelShader;
DX::ThrowIfFailed(
m_d3dDevice->CreatePixelShader(
pixelShaderBytecode->Data,
pixelShaderBytecode->Length,
nullptr,
&pixelShader
)
);
});
// Create vertex and index buffers that define a simple triangle.
auto createTriangleTask = (createPSTask && createVSTask).then([this] () {
DirectX::XMFLOAT2 triangleVertices[] =
{
float2(-0.5f, -0.5f),
float2( 0.0f, 0.5f),
float2( 0.5f, -0.5f),
};
unsigned short triangleIndices[] =
{
0, 1, 2,
};
D3D11_BUFFER_DESC vertexBufferDesc = {0};
vertexBufferDesc.ByteWidth = sizeof(float2) * ARRAYSIZE(triangleVertices);
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 = triangleVertices;
vertexBufferData.SysMemPitch = 0;
vertexBufferData.SysMemSlicePitch = 0;
ComPtr<ID3D11Buffer> vertexBuffer;
DX::ThrowIfFailed(
m_d3dDevice->CreateBuffer(
&vertexBufferDesc,
&vertexBufferData,
&vertexBuffer
)
);
D3D11_BUFFER_DESC indexBufferDesc;
indexBufferDesc.ByteWidth = sizeof(unsigned short) * ARRAYSIZE(triangleIndices);
indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
indexBufferDesc.CPUAccessFlags = 0;
indexBufferDesc.MiscFlags = 0;
indexBufferDesc.StructureByteStride = 0;
D3D11_SUBRESOURCE_DATA indexBufferData;
indexBufferData.pSysMem = triangleIndices;
indexBufferData.SysMemPitch = 0;
indexBufferData.SysMemSlicePitch = 0;
ComPtr<ID3D11Buffer> indexBuffer;
DX::ThrowIfFailed(
m_d3dDevice->CreateBuffer(
&indexBufferDesc,
&indexBufferData,
&indexBuffer
)
);
});
Wir verwenden die Vertex- und Pixelshader, das Vertex-Shaderlayout und die Vertex- und Indexpuffer, um ein gelbes Dreieck zu zeichnen.
4. Zeichnen des Dreiecks und Darstellen des gerenderten Bilds
Wir geben eine Endlosschleife ein, um die Szene kontinuierlich zu rendern und anzuzeigen. Wir rufen ID3D11DeviceContext::OMSetRenderTargets auf, um das Renderziel als Ausgabeziel anzugeben. Wir rufen ID3D11DeviceContext::ClearRenderTargetView mit { 0.071f, 0.04f, 0.561f, 1.0f } auf, um das Renderziel auf eine vollblaue Farbe zu löschen.
In der Endlosschleife zeichnen wir ein gelbes Dreieck auf der blauen Oberfläche.
So zeichnen Sie ein gelbes Dreieck
- Zunächst rufen wir ID3D11DeviceContext::IASetInputLayout auf, um zu beschreiben, wie Vertexpufferdaten in die Eingabeassemblerphase gestreamt werden.
- Als Nächstes rufen wir ID3D11DeviceContext::IASetVertexBuffers und ID3D11DeviceContext::IASetIndexBuffer auf, um die Vertex- und Indexpuffer an die Eingabeassemblerphase zu binden.
- Als Nächstes rufen wir ID3D11DeviceContext::IASetPrimitiveTopology mit dem D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP Wert auf, der für die Eingabeassemblerphase angegeben werden soll, um die Vertexdaten als Dreiecksstreifen zu interpretieren.
- Als Nächstes rufen wir ID3D11DeviceContext::VSSetShader auf, um die Vertex-Shaderphase mit dem Vertex-Shadercode und ID3D11DeviceContext::P SSetShader zu initialisieren, um die Pixelshaderphase mit dem Pixelshadercode zu initialisieren.
- Schließlich rufen wir ID3D11DeviceContext::D rawIndexed auf, um das Dreieck zu zeichnen und an die Renderingpipeline zu übermitteln.
Wir rufen IDXGISwapChain::P resent auf, um das gerenderte Bild im Fenster darzustellen.
// Specify the render target we created as the output target.
m_d3dDeviceContext->OMSetRenderTargets(
1,
m_renderTargetView.GetAddressOf(),
nullptr // Use no depth stencil.
);
// Clear the render target to a solid color.
const float clearColor[4] = { 0.071f, 0.04f, 0.561f, 1.0f };
m_d3dDeviceContext->ClearRenderTargetView(
m_renderTargetView.Get(),
clearColor
);
m_d3dDeviceContext->IASetInputLayout(inputLayout.Get());
// Set the vertex and index buffers, and specify the way they define geometry.
UINT stride = sizeof(float2);
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);
// Set the vertex and pixel shader stage state.
m_d3dDeviceContext->VSSetShader(
vertexShader.Get(),
nullptr,
0
);
m_d3dDeviceContext->PSSetShader(
pixelShader.Get(),
nullptr,
0
);
// Draw the cube.
m_d3dDeviceContext->DrawIndexed(
ARRAYSIZE(triangleIndices),
0,
0
);
// Present the rendered image to the window. Because the maximum frame latency is set to 1,
// the render loop will generally be throttled to the screen refresh rate, typically around
// 60 Hz, by sleeping the application on Present until the screen is refreshed.
DX::ThrowIfFailed(
m_swapChain->Present(1, 0)
);
Zusammenfassung und nächste Schritte
Wir haben ein gelbes Dreieck mithilfe von Vertex- und Pixel-Shadern erstellt und gezeichnet.
Als Nächstes erstellen wir einen orbitierenden 3D-Würfel und wenden Lichteffekte darauf an.
Verwenden von Tiefe und Effekten auf Grundtypen