Converter a estrutura de renderização
Resumo
- Parte 1: Inicializar o Direct3D 11
- Parte 2: Converter a estrutura de renderização
- Parte 3: Transferir o loop do jogo
Veja como converter uma estrutura de renderização simples do Direct3D 9 para o Direct3D 11. Saiba também como fazer a portabilidade de buffers de geometria, como compilar e carregar programas sombreadores HLSL e como implementar a cadeia de renderização no Direct3D 11. Parte 2 do passo a passo de Portar um aplicativo Direct3D 9 simples para DirectX 11 e UWP (Plataforma Universal do Windows).
Converter efeitos em sombreadores HLSL
O exemplo a seguir é uma técnica D3DX simples, escrita para a API de Efeitos herdada, para transformação de vértice de hardware e dados de cor de passagem.
Código do sombreador do Direct3D 9
// Global variables
matrix g_mWorld; // world matrix for object
matrix g_View; // view matrix
matrix g_Projection; // projection matrix
// Shader pipeline structures
struct VS_OUTPUT
{
float4 Position : POSITION; // vertex position
float4 Color : COLOR0; // vertex diffuse color
};
struct PS_OUTPUT
{
float4 RGBColor : COLOR0; // Pixel color
};
// Vertex shader
VS_OUTPUT RenderSceneVS(float3 vPos : POSITION,
float3 vColor : COLOR0)
{
VS_OUTPUT Output;
float4 pos = float4(vPos, 1.0f);
// Transform the position from object space to homogeneous projection space
pos = mul(pos, g_mWorld);
pos = mul(pos, g_View);
pos = mul(pos, g_Projection);
Output.Position = pos;
// Just pass through the color data
Output.Color = float4(vColor, 1.0f);
return Output;
}
// Pixel shader
PS_OUTPUT RenderScenePS(VS_OUTPUT In)
{
PS_OUTPUT Output;
Output.RGBColor = In.Color;
return Output;
}
// Technique
technique RenderSceneSimple
{
pass P0
{
VertexShader = compile vs_2_0 RenderSceneVS();
PixelShader = compile ps_2_0 RenderScenePS();
}
}
No Direct3D 11, ainda podemos usar nossos sombreadores HLSL. Colocamos cada sombreador em seu próprio arquivo HLSL para que o Visual Studio os compile em arquivos separados e, posteriormente, os carregaremos como recursos separados do Direct3D. Definimos o nível de destino como Shader Model 4 Level 9_1 (/4_0_level_9_1) porque esses shaders são escritos para GPUs DirectX 9.1.
Quando definimos o layout de entrada, garantimos que ele representasse a mesma estrutura de dados que usamos para armazenar dados por vértice na memória do sistema e na memória da GPU. Da mesma forma, a saída de um sombreador de vértice deve corresponder à estrutura usada como entrada para o sombreador de pixel. As regras não são as mesmas que passar dados de uma função para outra em C++; Você pode omitir variáveis não utilizadas no final da estrutura. Mas a ordem não pode ser reorganizada e você não pode pular o conteúdo no meio da estrutura de dados.
Observação As regras no Direct3D 9 para associar sombreadores de vértice a sombreadores de pixel eram mais relaxadas do que as regras no Direct3D 11. O arranjo do Direct3D 9 era flexível, mas ineficiente.
É possível que seus arquivos HLSL usem sintaxe mais antiga para semântica de sombreador – por exemplo, COLOR em vez de SV_TARGET. Nesse caso, você precisará habilitar o modo de compatibilidade HLSL (opção do compilador /Gec) ou atualizar a semântica do sombreador para a sintaxe atual. O sombreador de vértice neste exemplo foi atualizado com a sintaxe atual.
Aqui está nosso sombreador de vértice de transformação de hardware, desta vez definido em seu próprio arquivo.
Observação Os sombreadores de vértice são necessários para gerar a semântica do valor do sistema SV_POSITION. Essa semântica resolve os dados de posição do vértice para valores de coordenadas em que x está entre -1 e 1, y está entre -1 e 1, z é dividido pelo valor w da coordenada homogênea original (z/w) e w é 1 dividido pelo valor w original (1/w).
Sombreador de vértice HLSL (nível de recurso 9.1)
cbuffer ModelViewProjectionConstantBuffer : register(b0)
{
matrix mWorld; // world matrix for object
matrix View; // view matrix
matrix Projection; // projection matrix
};
struct VS_INPUT
{
float3 vPos : POSITION;
float3 vColor : COLOR0;
};
struct VS_OUTPUT
{
float4 Position : SV_POSITION; // Vertex shaders must output SV_POSITION
float4 Color : COLOR0;
};
VS_OUTPUT main(VS_INPUT input) // main is the default function name
{
VS_OUTPUT Output;
float4 pos = float4(input.vPos, 1.0f);
// Transform the position from object space to homogeneous projection space
pos = mul(pos, mWorld);
pos = mul(pos, View);
pos = mul(pos, Projection);
Output.Position = pos;
// Just pass through the color data
Output.Color = float4(input.vColor, 1.0f);
return Output;
}
Isso é tudo o que precisamos para nosso sombreador de pixel de passagem. Mesmo que o chamemos de passagem, na verdade ele está obtendo dados de cores interpolados com perspectiva correta para cada pixel. Observe que a semântica de valor do sistema SV_TARGET é aplicada à saída do valor de cor pelo sombreador de pixel, conforme exigido pela API.
Observação Os sombreadores de pixel de nível de sombreador 9_x não podem ler a semântica de valor do sistema SV_POSITION. Os sombreadores de pixel do modelo 4.0 (e superior) podem usar SV_POSITION para recuperar o local do pixel na tela, em que x está entre 0 e a largura do destino de renderização e y está entre 0 e a altura do destino de renderização (cada deslocamento em 0,5).
A maioria dos sombreadores de pixel é muito mais complexa do que uma passagem; observe que níveis mais altos de recursos do Direct3D permitem um número muito maior de cálculos por programa de sombreador.
Sombreador de pixel HLSL (nível de recurso 9.1)
struct PS_INPUT
{
float4 Position : SV_POSITION; // interpolated vertex position (system value)
float4 Color : COLOR0; // interpolated diffuse color
};
struct PS_OUTPUT
{
float4 RGBColor : SV_TARGET; // pixel color (your PS computes this system value)
};
PS_OUTPUT main(PS_INPUT In)
{
PS_OUTPUT Output;
Output.RGBColor = In.Color;
return Output;
}
Compilar e carregar sombreadores
Os jogos do Direct3D 9 geralmente usavam a biblioteca de efeitos como uma maneira conveniente de implementar pipelines programáveis. Os efeitos podem ser compilados em tempo de execução usando o método de função D3DXCreateEffectFromFile .
Carregando um efeito no Direct3D 9
// Turn off preshader optimization to keep calculations on the GPU
DWORD dwShaderFlags = D3DXSHADER_NO_PRESHADER;
// Only enable debug info when compiling for a debug target
#if defined (DEBUG) || defined (_DEBUG)
dwShaderFlags |= D3DXSHADER_DEBUG;
#endif
D3DXCreateEffectFromFile(
m_pd3dDevice,
L"CubeShaders.fx",
NULL,
NULL,
dwShaderFlags,
NULL,
&m_pEffect,
NULL
);
O Direct3D 11 funciona com programas de sombreador como recursos binários. Os sombreadores são compilados quando o projeto é criado e, em seguida, tratados como recursos. Portanto, nosso exemplo carregará o código de bytes do sombreador na memória do sistema, usará a interface do dispositivo Direct3D para criar um recurso Direct3D para cada sombreador e apontará para os recursos do sombreador Direct3D quando configurarmos cada quadro.
Carregando um recurso de sombreador no Direct3D 11
// BasicReaderWriter is a tested file loader used in SDK samples.
BasicReaderWriter^ readerWriter = ref new BasicReaderWriter();
// Load vertex shader:
Platform::Array<byte>^ vertexShaderData =
readerWriter->ReadData("CubeVertexShader.cso");
// This call allocates a device resource, validates the vertex shader
// with the device feature level, and stores the vertex shader bits in
// graphics memory.
m_d3dDevice->CreateVertexShader(
vertexShaderData->Data,
vertexShaderData->Length,
nullptr,
&m_vertexShader
);
Para incluir o bytecode do sombreador no pacote do aplicativo compilado, basta adicionar o arquivo HLSL ao projeto do Visual Studio. O Visual Studio usará a FXC (Effect-Compiler Tool ) para compilar arquivos HLSL em objetos de sombreador compilados (. CSO) e incluí-los no pacote do aplicativo.
Observação Certifique-se de definir o nível de recurso de destino correto para o compilador HLSL: clique com o botão direito do mouse no arquivo de origem HLSL no Visual Studio, selecione Propriedades e altere a configuração do Modelo de Sombreador em Compilador HLSL –> Geral. O Direct3D verifica essa propriedade em relação aos recursos de hardware quando seu aplicativo cria o recurso de sombreador Direct3D.
Esse é um bom lugar para criar o layout de entrada, que corresponde à declaração de fluxo de vértice no Direct3D 9. A estrutura de dados por vértice precisa corresponder ao que o sombreador de vértice usa; no Direct3D 11, temos mais controle sobre o layout de entrada; Podemos definir o tamanho da matriz e o comprimento de bits de vetores de ponto flutuante e especificar a semântica para o sombreador de vértice. Criamos uma estrutura D3D11_INPUT_ELEMENT_DESC e a usamos para informar ao Direct3D como serão os dados por vértice. Esperamos até depois de carregarmos o sombreador de vértice para definir o layout de entrada porque a API valida o layout de entrada em relação ao recurso de sombreador de vértice. Se o layout de entrada não for compatível, o Direct3D gerará uma exceção.
Os dados por vértice devem ser armazenados em tipos compatíveis na memória do sistema. Os tipos de dados DirectXMath podem ajudar; por exemplo, DXGI_FORMAT_R32G32B32_FLOAT corresponde a XMFLOAT3.
Observação Os buffers constantes usam um layout de entrada fixo que se alinha a quatro números de ponto flutuante por vez. XMFLOAT4 (e seus derivados) são recomendados para dados de buffer constantes.
Definindo o layout de entrada no Direct3D 11
// Create input layout:
const D3D11_INPUT_ELEMENT_DESC vertexDesc[] =
{
{ "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 },
};
Criar recursos de geometria
No Direct3D 9, armazenamos recursos de geometria criando buffers no dispositivo Direct3D, bloqueando a memória e copiando dados da memória da CPU para a memória da GPU.
Direct3D 9
// Create vertex buffer:
VOID* pVertices;
// In Direct3D 9 we create the buffer, lock it, and copy the data from
// system memory to graphics memory.
m_pd3dDevice->CreateVertexBuffer(
sizeof(CubeVertices),
0,
D3DFVF_XYZ | D3DFVF_DIFFUSE,
D3DPOOL_MANAGED,
&pVertexBuffer,
NULL);
pVertexBuffer->Lock(
0,
sizeof(CubeVertices),
&pVertices,
0);
memcpy(pVertices, CubeVertices, sizeof(CubeVertices));
pVertexBuffer->Unlock();
O DirectX 11 segue um processo mais simples. A API copia automaticamente os dados da memória do sistema para a GPU. Podemos usar ponteiros inteligentes COM para ajudar a facilitar a programação.
DirectX 11
D3D11_SUBRESOURCE_DATA vertexBufferData = {0};
vertexBufferData.pSysMem = CubeVertices;
vertexBufferData.SysMemPitch = 0;
vertexBufferData.SysMemSlicePitch = 0;
CD3D11_BUFFER_DESC vertexBufferDesc(
sizeof(CubeVertices),
D3D11_BIND_VERTEX_BUFFER);
// This call allocates a device resource for the vertex buffer and copies
// in the data.
m_d3dDevice->CreateBuffer(
&vertexBufferDesc,
&vertexBufferData,
&m_vertexBuffer
);
Implementar a cadeia de renderização
Os jogos do Direct3D 9 geralmente usavam uma cadeia de renderização baseada em efeitos. Esse tipo de cadeia de renderização configura o objeto de efeito, fornece a ele os recursos necessários e permite que ele renderize cada passagem.
Cadeia de renderização do Direct3D 9
// Clear the render target and the z-buffer.
m_pd3dDevice->Clear(
0, NULL,
D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
D3DCOLOR_ARGB(0, 45, 50, 170),
1.0f, 0
);
// Set the effect technique
m_pEffect->SetTechnique("RenderSceneSimple");
// Rotate the cube 1 degree per frame.
D3DXMATRIX world;
D3DXMatrixRotationY(&world, D3DXToRadian(m_frameCount++));
// Set the matrices up using traditional functions.
m_pEffect->SetMatrix("g_mWorld", &world);
m_pEffect->SetMatrix("g_View", &m_view);
m_pEffect->SetMatrix("g_Projection", &m_projection);
// Render the scene using the Effects library.
if(SUCCEEDED(m_pd3dDevice->BeginScene()))
{
// Begin rendering effect passes.
UINT passes = 0;
m_pEffect->Begin(&passes, 0);
for (UINT i = 0; i < passes; i++)
{
m_pEffect->BeginPass(i);
// Send vertex data to the pipeline.
m_pd3dDevice->SetFVF(D3DFVF_XYZ | D3DFVF_DIFFUSE);
m_pd3dDevice->SetStreamSource(
0, pVertexBuffer,
0, sizeof(VertexPositionColor)
);
m_pd3dDevice->SetIndices(pIndexBuffer);
// Draw the cube.
m_pd3dDevice->DrawIndexedPrimitive(
D3DPT_TRIANGLELIST,
0, 0, 8, 0, 12
);
m_pEffect->EndPass();
}
m_pEffect->End();
// End drawing.
m_pd3dDevice->EndScene();
}
// Present frame:
// Show the frame on the primary surface.
m_pd3dDevice->Present(NULL, NULL, NULL, NULL);
A cadeia de renderização do DirectX 11 ainda executará as mesmas tarefas, mas as passagens de renderização precisam ser implementadas de forma diferente. Em vez de colocar as especificidades em arquivos FX e deixar que as técnicas de renderização sejam mais ou menos opacas para nosso código C++, configuraremos toda a nossa renderização em C++.
Veja como nossa cadeia de renderização ficará. Precisamos fornecer o layout de entrada que criamos depois de carregar o sombreador de vértice, fornecer cada um dos objetos de sombreador e especificar os buffers constantes para cada sombreador a ser usado. Este exemplo não inclui várias passagens de renderização, mas se tivesse, faríamos uma cadeia de renderização semelhante para cada passagem, alterando a configuração conforme necessário.
Cadeia de renderização do Direct3D 11
// Clear the back buffer.
const float midnightBlue[] = { 0.098f, 0.098f, 0.439f, 1.000f };
m_d3dContext->ClearRenderTargetView(
m_renderTargetView.Get(),
midnightBlue
);
// Set the render target. This starts the drawing operation.
m_d3dContext->OMSetRenderTargets(
1, // number of render target views for this drawing operation.
m_renderTargetView.GetAddressOf(),
nullptr
);
// Rotate the cube 1 degree per frame.
XMStoreFloat4x4(
&m_constantBufferData.model,
XMMatrixTranspose(XMMatrixRotationY(m_frameCount++ * XM_PI / 180.f))
);
// Copy the updated constant buffer from system memory to video memory.
m_d3dContext->UpdateSubresource(
m_constantBuffer.Get(),
0, // update the 0th subresource
NULL, // use the whole destination
&m_constantBufferData,
0, // default pitch
0 // default pitch
);
// Send vertex data to the Input Assembler stage.
UINT stride = sizeof(VertexPositionColor);
UINT offset = 0;
m_d3dContext->IASetVertexBuffers(
0, // start with the first vertex buffer
1, // one vertex buffer
m_vertexBuffer.GetAddressOf(),
&stride,
&offset
);
m_d3dContext->IASetIndexBuffer(
m_indexBuffer.Get(),
DXGI_FORMAT_R16_UINT,
0 // no offset
);
m_d3dContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
m_d3dContext->IASetInputLayout(m_inputLayout.Get());
// Set the vertex shader.
m_d3dContext->VSSetShader(
m_vertexShader.Get(),
nullptr,
0
);
// Set the vertex shader constant buffer data.
m_d3dContext->VSSetConstantBuffers(
0, // register 0
1, // one constant buffer
m_constantBuffer.GetAddressOf()
);
// Set the pixel shader.
m_d3dContext->PSSetShader(
m_pixelShader.Get(),
nullptr,
0
);
// Draw the cube.
m_d3dContext->DrawIndexed(
m_indexCount,
0, // start with index 0
0 // start with vertex 0
);
A cadeia de troca faz parte da infraestrutura gráfica, portanto, usamos nossa cadeia de troca DXGI para apresentar o quadro concluído. O DXGI bloqueia a chamada até o próximo vsync; Em seguida, ele retorna e nosso loop de jogo pode continuar para a próxima iteração.
Apresentando um quadro na tela usando o DirectX 11
m_swapChain->Present(1, 0);
A cadeia de renderização que acabamos de criar será chamada de um loop de jogo implementado no método IFrameworkView::Run . Isso é mostrado na Parte 3: Janela de visualização e loop do jogo.