Criar sombreadores e primitivos de desenho
Aqui, mostramos como usar arquivos de origem HLSL para compilar e criar sombreadores que você pode usar para desenhar primitivos na exibição.
Criamos e desenhamos um triângulo amarelo usando sombreadores de vértice e pixel. Depois de criarmos o dispositivo Direct3D, a cadeia de troca e a exibição de destino de renderização, lemos dados de arquivos de objeto de sombreador binário no disco.
Objetivo: Criar shaders e desenhar primitivas.
Pré-requisitos
Presumimos que você esteja familiarizado com C++. Você também precisa de experiência básica com conceitos de programação gráfica.
Também supomos que você passou pelo Início Rápido: configurando recursos do DirectX e exibindo uma imagem.
Tempo de conclusão: 20 minutos.
Instruções
1. Compilando arquivos de origem HLSL
O Microsoft Visual Studio usa o compilador de código HLSL fxc.exe para compilar os arquivos de origem .hlsl (SimpleVertexShader.hlsl e SimplePixelShader.hlsl) em arquivos de objeto de sombreador binário .cso (SimpleVertexShader.cso e SimplePixelShader.cso). Para obter mais informações sobre o compilador de código HLSL, consulte Ferramenta Effect-Compiler. Para obter mais informações sobre como compilar o código do sombreador, consulte Compilando sombreadores.
Aqui está o código em 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;
}
Aqui está o código em 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. Leitura de dados do disco
Usamos a função DX::ReadDataAsync do DirectXHelper.h no modelo do Aplicativo DirectX 11 (Universal do Windows) para ler dados de forma assíncrona de um arquivo no disco.
3. Criando sombreadores de vértice e pixel
Lemos dados do arquivo SimpleVertexShader.cso e atribuímos os dados à matriz de bytes vertexShaderBytecode . Chamamos ID3D11Device::CreateVertexShader com a matriz de bytes para criar o sombreador de vértice (ID3D11VertexShader). Definimos o valor da profundidade do vértice como 0,5 na origem SimpleVertexShader.hlsl para garantir que nosso triângulo seja desenhado. Preenchemos uma matriz de estruturas D3D11_INPUT_ELEMENT_DESC para descrever o layout do código do sombreador de vértice e, em seguida, chamamos ID3D11Device::CreateInputLayout para criar o layout. A matriz tem um elemento de layout que define a posição do vértice. Lemos dados do arquivo SimplePixelShader.cso e atribuímos os dados à matriz de bytes pixelShaderBytecode . Chamamos ID3D11Device::CreatePixelShader com a matriz de bytes para criar o sombreador de pixel (ID3D11PixelShader). Definimos o valor do pixel como (1,1,1,1) na origem SimplePixelShader.hlsl para tornar nosso triângulo amarelo. Você pode alterar a cor alterando esse valor.
Criamos buffers de vértice e índice que definem um triângulo simples. Para fazer isso, primeiro definimos o triângulo, em seguida, descrevemos os buffers de vértice e índice (D3D11_BUFFER_DESC e D3D11_SUBRESOURCE_DATA) usando a definição de triângulo e, por fim, chamamos ID3D11Device::CreateBuffer uma vez para cada buffer.
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
)
);
});
Usamos os sombreadores de vértice e pixel, o layout do sombreador de vértice e os buffers de vértice e índice para desenhar um triângulo amarelo.
4. Desenhando o triângulo e apresentando a imagem renderizada
Entramos em um loop infinito para renderizar e exibir continuamente a cena. Chamamos ID3D11DeviceContext::OMSetRenderTargets para especificar o destino de renderização como o destino de saída. Chamamos ID3D11DeviceContext::ClearRenderTargetView com { 0.071f, 0.04f, 0.561f, 1.0f } para limpar o destino de renderização para uma cor azul sólida.
No loop infinito, desenhamos um triângulo amarelo na superfície azul.
Para desenhar um triângulo amarelo
- Primeiro, chamamos ID3D11DeviceContext::IASetInputLayout para descrever como os dados do buffer de vértice são transmitidos para o estágio do assembler de entrada.
- Em seguida, chamamos ID3D11DeviceContext::IASetVertexBuffers e ID3D11DeviceContext::IASetIndexBuffer para associar os buffers de vértice e índice ao estágio do assembler de entrada.
- Em seguida, chamamos ID3D11DeviceContext::IASetPrimitiveTopology com o valor D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP a ser especificado para que o estágio do assembler de entrada interprete os dados de vértice como uma faixa de triângulo.
- Em seguida, chamamos ID3D11DeviceContext::VSSetShader para inicializar o estágio do sombreador de vértice com o código do sombreador de vértice e ID3D11DeviceContext::P SSetShader para inicializar o estágio do sombreador de pixel com o código do sombreador de pixel.
- Por fim, chamamos ID3D11DeviceContext::D rawIndexed para desenhar o triângulo e enviá-lo ao pipeline de renderização.
Chamamos IDXGISwapChain::P resent para apresentar a imagem renderizada à janela.
// 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)
);
Resumo e próximas etapas
Criamos e desenhamos um triângulo amarelo usando sombreadores de vértice e pixel.
Em seguida, criamos um cubo 3D em órbita e aplicamos efeitos de iluminação a ele.
Usando profundidade e efeitos em primitivos