Crear sombreadores y dibujar primitivos
Aquí se muestra cómo usar archivos de origen HLSL para compilar y crear sombreadores que luego puede usar para dibujar primitivos en la pantalla.
Creamos y dibujamos un triángulo amarillo mediante sombreadores de vértices y píxeles. Después de crear el dispositivo Direct3D, la cadena de intercambio y la vista de destino de representación, leemos datos de archivos de objetos de sombreador binarios en el disco.
Objetivo: Para crear sombreadores y dibujar primitivos.
Requisitos previos
Se supone que está familiarizado con C++. También necesita experiencia básica con los conceptos de programación de gráficos.
También se supone que ha pasado por inicio rápido: configuración de recursos de DirectX y visualización de una imagen.
Tiempo de finalización: 20 minutos.
Instrucciones
1. Compilación de archivos de origen de HLSL
Microsoft Visual Studio usa el compilador de código HLSL de fxc.exe para compilar los archivos de código fuente .hlsl (SimpleVertexShader.hlsl y SimplePixelShader.hlsl) en archivos de objeto de sombreador binarios .cso (SimpleVertexShader.cso y SimplePixelShader.cso). Para obtener más información sobre el compilador de código HLSL, consulte Effect-Compiler Tool. Para obtener más información sobre cómo compilar código de sombreador, consulta Compilar sombreadores.
Este es el código de 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;
}
Este es el código de 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. Lectura de datos del disco
Usamos la función DX::ReadDataAsync de DirectXHelper.h en la plantilla Aplicación directX 11 (Windows universal) para leer de forma asincrónica los datos de un archivo en el disco.
3. Crear sombreadores de vértices y píxeles
Se leen datos del archivo SimpleVertexShader.cso y se asignan los datos a la matriz de bytes vertexShaderBytecode . Llamamos a ID3D11Device::CreateVertexShader con la matriz de bytes para crear el sombreador de vértices (ID3D11VertexShader). Establecemos el valor de profundidad del vértice en 0,5 en el origen SimpleVertexShader.hlsl para garantizar que se dibuja nuestro triángulo. Rellenamos una matriz de D3D11_INPUT_ELEMENT_DESC estructuras para describir el diseño del código del sombreador de vértices y, a continuación, llamamos a ID3D11Device::CreateInputLayout para crear el diseño. La matriz tiene un elemento de diseño que define la posición del vértice. Leemos datos del archivo SimplePixelShader.cso y asignamos los datos a la matriz de bytes pixelShaderBytecode . Llamamos a ID3D11Device::CreatePixelShader con la matriz de bytes para crear el sombreador de píxeles (ID3D11PixelShader). Establecemos el valor de píxel en (1,1,1,1) en el origen SimplePixelShader.hlsl para que nuestro triángulo sea amarillo. Puede cambiar el color cambiando este valor.
Creamos búferes de vértices e índices que definen un triángulo simple. Para ello, primero definimos el triángulo, a continuación se describen los búferes de vértices e índices (D3D11_BUFFER_DESC y D3D11_SUBRESOURCE_DATA) mediante la definición del triángulo y, por último, llamamos a ID3D11Device::CreateBuffer una vez para cada búfer.
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 los sombreadores de vértices y píxeles, el diseño del sombreador de vértices y los búferes de vértices e índices para dibujar un triángulo amarillo.
4. Dibujar el triángulo y presentar la imagen representada
Entramos en un bucle sin fin para representar y mostrar continuamente la escena. Llamamos a ID3D11DeviceContext::OMSetRenderTargets para especificar el destino de representación como destino de salida. Llamamos a ID3D11DeviceContext::ClearRenderTargetView con { 0.071f, 0.04f, 0.561f, 1.0f } para borrar el destino de representación en un color azul sólido.
En el bucle sin fin, dibujamos un triángulo amarillo en la superficie azul.
Para dibujar un triángulo amarillo
- En primer lugar, llamamos a ID3D11DeviceContext::IASetInputLayout para describir cómo se transmiten los datos del búfer de vértices a la fase del ensamblador de entrada.
- A continuación, llamamos a ID3D11DeviceContext::IASetVertexBuffers y ID3D11DeviceContext::IASetIndexBuffer para enlazar los búferes de vértices e índices a la fase del ensamblador de entrada.
- A continuación, llamamos a ID3D11DeviceContext::IASetPrimitiveTopology con el valor de D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP para especificar para la fase del ensamblador de entrada para interpretar los datos de vértices como una franja de triángulos.
- A continuación, llamamos a ID3D11DeviceContext::VSSetShader para inicializar la fase del sombreador de vértices con el código del sombreador de vértices y ID3D11DeviceContext::P SSetShader para inicializar la fase del sombreador de píxeles con el código del sombreador de píxeles.
- Por último, llamamos a ID3D11DeviceContext::D rawIndexed para dibujar el triángulo y enviarlo a la canalización de representación.
Llamamos a IDXGISwapChain::P resent para presentar la imagen representada en la ventana.
// 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)
);
Resumen y pasos siguientes
Hemos creado y dibujado un triángulo amarillo mediante sombreadores de vértices y píxeles.
A continuación, creamos un cubo 3D en órbita y aplicamos efectos de iluminación a él.
Uso de profundidad y efectos en primitivos