Usar profundidade e efeitos em primitivos
Aqui, mostramos como usar profundidade, perspectiva, cor e outros efeitos em primitivos.
Objetivo: Criar um objeto 3D e aplicar iluminação e coloração básicas de vértices a ele.
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 e Criando sombreadores e desenhando primitivos.
Tempo de conclusão: 20 minutos.
Instruções
1. Definindo variáveis de cubo
Primeiro, precisamos definir as estruturas SimpleCubeVertex e ConstantBuffer para o cubo. Essas estruturas especificam as posições e cores dos vértices do cubo e como o cubo será exibido. Declaramos ID3D11DepthStencilView e ID3D11Buffer com ComPtr e declaramos uma instância de ConstantBuffer.
struct SimpleCubeVertex
{
DirectX::XMFLOAT3 pos; // Position
DirectX::XMFLOAT3 color; // Color
};
struct ConstantBuffer
{
DirectX::XMFLOAT4X4 model;
DirectX::XMFLOAT4X4 view;
DirectX::XMFLOAT4X4 projection;
};
// This class defines the application as a whole.
ref class Direct3DTutorialFrameworkView : public IFrameworkView
{
private:
Platform::Agile<CoreWindow> m_window;
ComPtr<IDXGISwapChain1> m_swapChain;
ComPtr<ID3D11Device1> m_d3dDevice;
ComPtr<ID3D11DeviceContext1> m_d3dDeviceContext;
ComPtr<ID3D11RenderTargetView> m_renderTargetView;
ComPtr<ID3D11DepthStencilView> m_depthStencilView;
ComPtr<ID3D11Buffer> m_constantBuffer;
ConstantBuffer m_constantBufferData;
2. Criando uma exibição de estêncil de profundidade
Além de criar a exibição de destino de renderização, também criamos uma exibição de estêncil de profundidade. A exibição de estêncil de profundidade permite que o Direct3D renderize objetos com eficiência mais próximos da câmera na frente de objetos mais distantes da câmera. Antes de criarmos uma exibição para um buffer de estêncil de profundidade, devemos criar o buffer de estêncil de profundidade. Preenchemos um D3D11_TEXTURE2D_DESC para descrever o buffer de estêncil de profundidade e, em seguida, chamamos ID3D11Device::CreateTexture2D para criar o buffer de estêncil de profundidade. Para criar a exibição de estêncil de profundidade, preenchemos uma D3D11_DEPTH_STENCIL_VIEW_DESC para descrever a exibição de estêncil de profundidade e passamos a descrição da exibição de estêncil de profundidade e o buffer de estêncil de profundidade para ID3D11Device::CreateDepthStencilView.
// Once the render target view is created, create a depth stencil view. This
// allows Direct3D to efficiently render objects closer to the camera in front
// of objects further from the camera.
D3D11_TEXTURE2D_DESC backBufferDesc = {0};
backBuffer->GetDesc(&backBufferDesc);
D3D11_TEXTURE2D_DESC depthStencilDesc;
depthStencilDesc.Width = backBufferDesc.Width;
depthStencilDesc.Height = backBufferDesc.Height;
depthStencilDesc.MipLevels = 1;
depthStencilDesc.ArraySize = 1;
depthStencilDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
depthStencilDesc.SampleDesc.Count = 1;
depthStencilDesc.SampleDesc.Quality = 0;
depthStencilDesc.Usage = D3D11_USAGE_DEFAULT;
depthStencilDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
depthStencilDesc.CPUAccessFlags = 0;
depthStencilDesc.MiscFlags = 0;
ComPtr<ID3D11Texture2D> depthStencil;
DX::ThrowIfFailed(
m_d3dDevice->CreateTexture2D(
&depthStencilDesc,
nullptr,
&depthStencil
)
);
D3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc;
depthStencilViewDesc.Format = depthStencilDesc.Format;
depthStencilViewDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
depthStencilViewDesc.Flags = 0;
depthStencilViewDesc.Texture2D.MipSlice = 0;
DX::ThrowIfFailed(
m_d3dDevice->CreateDepthStencilView(
depthStencil.Get(),
&depthStencilViewDesc,
&m_depthStencilView
)
);
3. Atualizando a perspectiva com a janela
Atualizamos os parâmetros de projeção em perspectiva para o buffer constante, dependendo das dimensões da janela. Fixamos os parâmetros em um campo de visão de 70 graus com uma faixa de profundidade de 0,01 a 100.
// 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. For a generalized camera class, see Lesson 5.
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
);
4. Criando sombreadores de vértice e pixel com elementos de cor
Neste aplicativo, criamos sombreadores de vértice e pixel mais complexos do que os descritos no tutorial anterior, Criando sombreadores e desenhando primitivos. O sombreador de vértice do aplicativo transforma cada posição de vértice em espaço de projeção e passa a cor do vértice para o sombreador de pixel.
A matriz de estruturas de D3D11_INPUT_ELEMENT_DESC do aplicativo que descrevem o layout do código do sombreador de vértice tem dois elementos de layout: um elemento define a posição do vértice e o outro elemento define a cor.
Criamos buffers de vértice, índice e constante para definir um cubo em órbita.
Para definir um cubo em órbita
- Primeiro, definimos o cubo. Atribuímos a cada vértice uma cor além de uma posição. Isso permite que o sombreador de pixel colora cada face de forma diferente para que a face possa ser distinguida.
- Em seguida, descrevemos os buffers de vértice e índice (D3D11_BUFFER_DESC e D3D11_SUBRESOURCE_DATA) usando a definição de cubo. Chamamos ID3D11Device::CreateBuffer uma vez para cada buffer.
- Em seguida, criamos um buffer constante (D3D11_BUFFER_DESC) para passar matrizes de modelo, exibição e projeção para o sombreador de vértice. Posteriormente, podemos usar o buffer constante para girar o cubo e aplicar uma projeção em perspectiva a ele. Chamamos ID3D11Device::CreateBuffer para criar o buffer constante.
- Em seguida, especificamos a transformação de exibição que corresponde a uma posição de câmera de X = 0, Y = 1, Z = 2.
- Finalmente, declaramos uma variável de grau que usaremos para animar o cubo, girando-o a cada quadro.
auto loadVSTask = DX::ReadDataAsync(L"SimpleVertexShader.cso");
auto loadPSTask = DX::ReadDataAsync(L"SimplePixelShader.cso");
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::XMFLOAT3 vector defining the vertex position, and
// a DirectX::XMFLOAT3 vector defining the vertex color.
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;
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 unit cube.
auto createCubeTask = (createPSTask && createVSTask).then([this] () {
// In the array below, which will be used to initialize the cube vertex buffers,
// each vertex is assigned a color in addition to a position. This will allow
// the pixel shader to color each face differently, enabling them to be distinguished.
SimpleCubeVertex cubeVertices[] =
{
{ float3(-0.5f, 0.5f, -0.5f), float3(0.0f, 1.0f, 0.0f) }, // +Y (top face)
{ float3( 0.5f, 0.5f, -0.5f), float3(1.0f, 1.0f, 0.0f) },
{ float3( 0.5f, 0.5f, 0.5f), float3(1.0f, 1.0f, 1.0f) },
{ float3(-0.5f, 0.5f, 0.5f), float3(0.0f, 1.0f, 1.0f) },
{ float3(-0.5f, -0.5f, 0.5f), float3(0.0f, 0.0f, 1.0f) }, // -Y (bottom face)
{ float3( 0.5f, -0.5f, 0.5f), float3(1.0f, 0.0f, 1.0f) },
{ float3( 0.5f, -0.5f, -0.5f), float3(1.0f, 0.0f, 0.0f) },
{ float3(-0.5f, -0.5f, -0.5f), float3(0.0f, 0.0f, 0.0f) },
};
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
};
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;
DX::ThrowIfFailed(
m_d3dDevice->CreateBuffer(
&vertexBufferDesc,
&vertexBufferData,
&vertexBuffer
)
);
D3D11_BUFFER_DESC indexBufferDesc;
indexBufferDesc.ByteWidth = sizeof(unsigned short) * ARRAYSIZE(cubeIndices);
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 = cubeIndices;
indexBufferData.SysMemPitch = 0;
indexBufferData.SysMemSlicePitch = 0;
ComPtr<ID3D11Buffer> indexBuffer;
DX::ThrowIfFailed(
m_d3dDevice->CreateBuffer(
&indexBufferDesc,
&indexBufferData,
&indexBuffer
)
);
// Create a constant buffer for passing model, view, and projection matrices
// to the vertex shader. This will allow 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;
DX::ThrowIfFailed(
m_d3dDevice->CreateBuffer(
&constantBufferDesc,
nullptr,
&m_constantBuffer
)
);
// Specify the view transform corresponding to a camera position of
// X = 0, Y = 1, Z = 2. For a generalized camera class, see Lesson 5.
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
);
});
// This value will be used to animate the cube by rotating it every frame.
float degree = 0.0f;
5. Girar e desenhar o cubo e apresentar a imagem renderizada
Entramos em um loop infinito para renderizar e exibir continuamente a cena. Chamamos a função embutida rotationY (BasicMath.h) com uma quantidade de rotação para definir valores que girarão a matriz do modelo do cubo em torno do eixo Y. Em seguida, chamamos ID3D11DeviceContext::UpdateSubresource para atualizar o buffer constante e girar o modelo de cubo. Chamamos ID3D11DeviceContext::OMSetRenderTargets para especificar o destino de renderização como o destino de saída. Nesta chamada OMSetRenderTargets, passamos a exibição de estêncil de profundidade. Chamamos ID3D11DeviceContext::ClearRenderTargetView para limpar o destino de renderização para uma cor azul sólida e chamamos ID3D11DeviceContext::ClearDepthStencilView para limpar o buffer de profundidade.
No loop infinito, também desenhamos o cubo na superfície azul.
Para desenhar o cubo
- 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.
- Em seguida, chamamos ID3D11DeviceContext::VSSetConstantBuffers para definir o buffer constante usado pelo estágio de pipeline do sombreador de vértice.
- Por fim, chamamos ID3D11DeviceContext::D rawIndexed para desenhar o cubo e enviá-lo ao pipeline de renderização.
Chamamos IDXGISwapChain::P resent para apresentar a imagem renderizada à janela.
// Update the constant buffer to rotate the cube model.
m_constantBufferData.model = XMMatrixRotationY(-degree);
degree += 1.0f;
m_d3dDeviceContext->UpdateSubresource(
m_constantBuffer.Get(),
0,
nullptr,
&m_constantBufferData,
0,
0
);
// Specify the render target and depth stencil we created as the output target.
m_d3dDeviceContext->OMSetRenderTargets(
1,
m_renderTargetView.GetAddressOf(),
m_depthStencilView.Get()
);
// Clear the render target to a solid color, and reset the depth stencil.
const float clearColor[4] = { 0.071f, 0.04f, 0.561f, 1.0f };
m_d3dDeviceContext->ClearRenderTargetView(
m_renderTargetView.Get(),
clearColor
);
m_d3dDeviceContext->ClearDepthStencilView(
m_depthStencilView.Get(),
D3D11_CLEAR_DEPTH,
1.0f,
0
);
m_d3dDeviceContext->IASetInputLayout(inputLayout.Get());
// 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);
// Set the vertex and pixel shader stage state.
m_d3dDeviceContext->VSSetShader(
vertexShader.Get(),
nullptr,
0
);
m_d3dDeviceContext->VSSetConstantBuffers(
0,
1,
m_constantBuffer.GetAddressOf()
);
m_d3dDeviceContext->PSSetShader(
pixelShader.Get(),
nullptr,
0
);
// Draw the cube.
m_d3dDeviceContext->DrawIndexed(
ARRAYSIZE(cubeIndices),
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
Usamos profundidade, perspectiva, cor e outros efeitos em primitivos.
Em seguida, aplicamos texturas a primitivos.
Aplicando texturas a primitivos