Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Observação
Este tópico faz parte da série de tutoriais Criar um jogo simples da Plataforma Universal do Windows (UWP) com DirectX. O tópico nesse link define o contexto da série.
Até agora, abordamos como estruturar um jogo da Plataforma Universal do Windows (UWP) e como definir uma máquina de estado para lidar com o fluxo do jogo. Agora é hora de aprender a desenvolver a estrutura de renderização. Vamos ver como o jogo de exemplo renderiza a cena do jogo usando o Direct3D 11.
O Direct3D 11 contém um conjunto de APIs que fornecem acesso aos recursos avançados de hardware gráfico de alto desempenho que podem ser usados para criar gráficos 3D para aplicativos com uso intensivo de gráficos, como jogos.
Renderizar gráficos de jogos na tela significa basicamente renderizar uma sequência de quadros na tela. Em cada quadro, tu tens que renderizar objetos que são visíveis na cena, com base na vista.
Para renderizar um quadro, você tem que passar as informações de cena necessárias para o hardware para que ele possa ser exibido na tela. Se você quiser ter algo exibido na tela, você precisa começar a renderizar assim que o jogo começar a ser executado.
Objetivos
Para configurar uma estrutura de renderização básica para exibir a saída gráfica de um jogo UWP DirectX. Você pode dividir isso vagamente nessas três etapas.
- Estabeleça uma conexão com a interface gráfica.
- Crie os recursos necessários para desenhar os gráficos.
- Exiba os gráficos renderizando o quadro.
Este tópico explica como os gráficos são renderizados, abordando as etapas 1 e 3.
Estrutura de renderização II: Renderização de jogos cobre a etapa 2 — como estabelecer a estrutura de renderização e como os dados são preparados antes que a renderização possa ocorrer.
Comece
É uma boa ideia familiarizar-se com gráficos básicos e conceitos de renderização. Se você é novo no Direct3D e na renderização, consulte Termos e conceitos para obter uma breve descrição dos termos gráficos e de renderização usados neste tópico.
Para este jogo, a classe GameRenderer representa o renderizador para este jogo de exemplo. É responsável pela criação e manutenção de todos os objetos Direct3D 11 e Direct2D usados para gerar os visuais do jogo. Ele também mantém uma referência ao objeto Simple3DGame usado para recuperar a lista de objetos a serem renderizados, bem como o status do jogo para o heads-up display (HUD).
Nesta parte do tutorial, vamos nos concentrar na renderização de objetos 3D no jogo.
Estabelecer uma conexão com a interface gráfica
Para obter informações sobre como acessar o hardware para renderização, consulte o tópico
O método App::Initialize
A função std::make_shared, como mostrado abaixo, é usada para criar um shared_ptr para DX::DeviceResources, que também fornece acesso ao dispositivo.
No Direct3D 11, um dispositivo é usado para alocar e destruir objetos, renderizar primitivas e se comunicar com a placa gráfica por meio do driver gráfico.
void Initialize(CoreApplicationView const& applicationView)
{
...
// At this point we have access to the device.
// We can create the device-dependent resources.
m_deviceResources = std::make_shared<DX::DeviceResources>();
}
Exibir os gráficos renderizando o quadro
A cena do jogo precisa renderizar quando o jogo é iniciado. As instruções para renderização começam no método GameMain::Run, conforme mostrado abaixo.
O fluxo simples é este.
- Atualização
- Renderizar
- Presente
Método GameMain::Run
void GameMain::Run()
{
while (!m_windowClosed)
{
if (m_visible) // if the window is visible
{
switch (m_updateState)
{
...
default:
CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);
Update();
m_renderer->Render();
m_deviceResources->Present();
m_renderNeeded = false;
}
}
else
{
CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(CoreProcessEventsOption::ProcessOneAndAllPending);
}
}
m_game->OnSuspending(); // Exiting due to window close, so save state.
}
Atualização
Consulte o tópico
Renderização
A renderização é implementada chamando o método GameRenderer::Render de GameMain::Run.
Se a renderização estéreo estiver habilitada, haverá duas passagens de renderização — uma para o olho esquerdo e outra para o olho direito. Em cada passo de renderização, vinculamos o destino de renderização e a visualização de estêncil de profundidade ao dispositivo. Também limpamos a visão de estêncil de profundidade depois.
Observação
A renderização estéreo pode ser obtida usando outros métodos, como estéreo de passagem única usando instanciação de vértice ou sombreadores de geometria. O método de "duas passagens de renderização" é uma maneira mais lenta, mas mais conveniente, de obter renderização estéreo.
Assim que o jogo estiver em execução e os recursos forem carregados, atualizamos a matriz de projeção , uma vez por passe de renderização. Os objetos são ligeiramente diferentes em cada perspetiva. Em seguida, configuramos o pipeline de renderização de gráficos .
Observação
Consulte Criar e carregar recursos gráficos DirectX para obter mais informações sobre como os recursos são carregados.
Neste jogo de exemplo, o renderizador foi projetado para usar um layout de vértice padrão em todos os objetos. Isso simplifica o design do sombreador e permite mudanças fáceis entre sombreadores, independentemente da geometria dos objetos.
Método GameRenderer::Render
Definimos o contexto Direct3D para usar um layout de vértice de entrada. Os objetos de layout de entrada descrevem como os dados do buffer de vértice são transmitidos para o pipeline de renderização de .
Em seguida, definimos o contexto Direct3D para usar os buffers constantes definidos anteriormente, que são utilizados pelo estágio de pipeline do shader de vértice
Observação
Consulte Rendering framework II: Game rendering para obter mais informações sobre a definição dos buffers constantes.
Como o mesmo layout de entrada e conjunto de buffers constantes é utilizado para todos os shaders que estão no pipeline, a configuração é feita uma vez por frame.
void GameRenderer::Render()
{
bool stereoEnabled{ m_deviceResources->GetStereoState() };
auto d3dContext{ m_deviceResources->GetD3DDeviceContext() };
auto d2dContext{ m_deviceResources->GetD2DDeviceContext() };
int renderingPasses = 1;
if (stereoEnabled)
{
renderingPasses = 2;
}
for (int i = 0; i < renderingPasses; i++)
{
// Iterate through the number of rendering passes to be completed.
// 2 rendering passes if stereo is enabled.
if (i > 0)
{
// Doing the Right Eye View.
ID3D11RenderTargetView* const targets[1] = { m_deviceResources->GetBackBufferRenderTargetViewRight() };
// Resets render targets to the screen.
// OMSetRenderTargets binds 2 things to the device.
// 1. Binds one render target atomically to the device.
// 2. Binds the depth-stencil view, as returned by the GetDepthStencilView method, to the device.
// For more info, see
// https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-omsetrendertargets
d3dContext->OMSetRenderTargets(1, targets, m_deviceResources->GetDepthStencilView());
// Clears the depth stencil view.
// A depth stencil view contains the format and buffer to hold depth and stencil info.
// For more info about depth stencil view, go to:
// https://learn.microsoft.com/windows/uwp/graphics-concepts/depth-stencil-view--dsv-
// A depth buffer is used to store depth information to control which areas of
// polygons are rendered rather than hidden from view. To learn more about a depth buffer,
// go to: https://learn.microsoft.com/windows/uwp/graphics-concepts/depth-buffers
// A stencil buffer is used to mask pixels in an image, to produce special effects.
// The mask determines whether a pixel is drawn or not,
// by setting the bit to a 1 or 0. To learn more about a stencil buffer,
// go to: https://learn.microsoft.com/windows/uwp/graphics-concepts/stencil-buffers
d3dContext->ClearDepthStencilView(m_deviceResources->GetDepthStencilView(), D3D11_CLEAR_DEPTH, 1.0f, 0);
// Direct2D -- discussed later
d2dContext->SetTarget(m_deviceResources->GetD2DTargetBitmapRight());
}
else
{
// Doing the Mono or Left Eye View.
// As compared to the right eye:
// m_deviceResources->GetBackBufferRenderTargetView instead of GetBackBufferRenderTargetViewRight
ID3D11RenderTargetView* const targets[1] = { m_deviceResources->GetBackBufferRenderTargetView() };
// Same as the Right Eye View.
d3dContext->OMSetRenderTargets(1, targets, m_deviceResources->GetDepthStencilView());
d3dContext->ClearDepthStencilView(m_deviceResources->GetDepthStencilView(), D3D11_CLEAR_DEPTH, 1.0f, 0);
// d2d -- Discussed later under Adding UI
d2dContext->SetTarget(m_deviceResources->GetD2DTargetBitmap());
}
const float clearColor[4] = { 0.5f, 0.5f, 0.8f, 1.0f };
// Only need to clear the background when not rendering the full 3D scene since
// the 3D world is a fully enclosed box and the dynamics prevents the camera from
// moving outside this space.
if (i > 0)
{
// Doing the Right Eye View.
d3dContext->ClearRenderTargetView(m_deviceResources->GetBackBufferRenderTargetViewRight(), clearColor);
}
else
{
// Doing the Mono or Left Eye View.
d3dContext->ClearRenderTargetView(m_deviceResources->GetBackBufferRenderTargetView(), clearColor);
}
// Render the scene objects
if (m_game != nullptr && m_gameResourcesLoaded && m_levelResourcesLoaded)
{
// This section is only used after the game state has been initialized and all device
// resources needed for the game have been created and associated with the game objects.
if (stereoEnabled)
{
// When doing stereo, it is necessary to update the projection matrix once per rendering pass.
auto orientation = m_deviceResources->GetOrientationTransform3D();
ConstantBufferChangeOnResize changesOnResize;
// Apply either a left or right eye projection, which is an offset from the middle
XMStoreFloat4x4(
&changesOnResize.projection,
XMMatrixMultiply(
XMMatrixTranspose(
i == 0 ?
m_game->GameCamera().LeftEyeProjection() :
m_game->GameCamera().RightEyeProjection()
),
XMMatrixTranspose(XMLoadFloat4x4(&orientation))
)
);
d3dContext->UpdateSubresource(
m_constantBufferChangeOnResize.get(),
0,
nullptr,
&changesOnResize,
0,
0
);
}
// Update variables that change once per frame.
ConstantBufferChangesEveryFrame constantBufferChangesEveryFrameValue;
XMStoreFloat4x4(
&constantBufferChangesEveryFrameValue.view,
XMMatrixTranspose(m_game->GameCamera().View())
);
d3dContext->UpdateSubresource(
m_constantBufferChangesEveryFrame.get(),
0,
nullptr,
&constantBufferChangesEveryFrameValue,
0,
0
);
// Set up the graphics pipeline. This sample uses the same InputLayout and set of
// constant buffers for all shaders, so they only need to be set once per frame.
// For more info about the graphics or rendering pipeline, see
// https://learn.microsoft.com/windows/win32/direct3d11/overviews-direct3d-11-graphics-pipeline
// IASetInputLayout binds an input-layout object to the input-assembler (IA) stage.
// Input-layout objects describe how vertex buffer data is streamed into the IA pipeline stage.
// Set up the Direct3D context to use this vertex layout. For more info, see
// https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-iasetinputlayout
d3dContext->IASetInputLayout(m_vertexLayout.get());
// VSSetConstantBuffers sets the constant buffers used by the vertex shader pipeline stage.
// Set up the Direct3D context to use these constant buffers. For more info, see
// https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-vssetconstantbuffers
ID3D11Buffer* constantBufferNeverChanges{ m_constantBufferNeverChanges.get() };
d3dContext->VSSetConstantBuffers(0, 1, &constantBufferNeverChanges);
ID3D11Buffer* constantBufferChangeOnResize{ m_constantBufferChangeOnResize.get() };
d3dContext->VSSetConstantBuffers(1, 1, &constantBufferChangeOnResize);
ID3D11Buffer* constantBufferChangesEveryFrame{ m_constantBufferChangesEveryFrame.get() };
d3dContext->VSSetConstantBuffers(2, 1, &constantBufferChangesEveryFrame);
ID3D11Buffer* constantBufferChangesEveryPrim{ m_constantBufferChangesEveryPrim.get() };
d3dContext->VSSetConstantBuffers(3, 1, &constantBufferChangesEveryPrim);
// Sets the constant buffers used by the pixel shader pipeline stage.
// For more info, see
// https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-pssetconstantbuffers
d3dContext->PSSetConstantBuffers(2, 1, &constantBufferChangesEveryFrame);
d3dContext->PSSetConstantBuffers(3, 1, &constantBufferChangesEveryPrim);
ID3D11SamplerState* samplerLinear{ m_samplerLinear.get() };
d3dContext->PSSetSamplers(0, 1, &samplerLinear);
for (auto&& object : m_game->RenderObjects())
{
// The 3D object render method handles the rendering.
// For more info, see Primitive rendering below.
object->Render(d3dContext, m_constantBufferChangesEveryPrim.get());
}
}
// Start of 2D rendering
...
}
}
Renderização primitiva
Ao renderizar a cena, você percorre todos os objetos que precisam ser renderizados. As etapas abaixo são repetidas para cada objeto (primitivo).
- Atualize o buffer constante (m_constantBufferChangesEveryPrim) com a matriz de transformação mundial do modelo e informações de material.
- O m_constantBufferChangesEveryPrim contém parâmetros para cada objeto. Inclui a matriz de transformação objeto-mundo, bem como propriedades do material, como cor e expoente especular para cálculos de iluminação.
- Defina o contexto Direct3D para usar o layout de vértice de entrada para os dados do objeto de malha a serem transmitidos para o estágio de montagem de entrada (IA) do pipeline de renderização de .
- Defina o contexto Direct3D para usar um buffer de índice no estágio IA. Forneça as informações primitivas: tipo, ordem dos dados.
- Submeta uma chamada de desenho para desenhar a primitiva indexada, não instanciada. O método
GameObject::Render atualiza o buffer constante para a primitivacom dados específicos para uma primitiva dada. Isso resulta em uma chamada ao DrawIndexed do contexto para desenhar a geometria de cada primitivo. Especificamente, este desenho de chamadas enfileira comandos e dados para a unidade de processamento gráfico (GPU), conforme parametrizado pelos dados de buffer constante. Cada chamada de desenho executa o sombreador de vértice uma vez por vértice e, em seguida, o sombreador de pixel uma vez para cada pixel de cada triângulo na primitiva. As texturas fazem parte do estado que o sombreador de pixel usa para fazer a renderização.
Aqui estão as razões para usar vários buffers constantes.
- O jogo usa vários buffers constantes, mas só precisa atualizar esses buffers uma vez por primitivo. Como mencionado anteriormente, os buffers constantes são como entradas para os sombreadores que são executados para cada primitivo. Alguns dados são estáticos (m_constantBufferNeverChanges); Alguns dados são constantes sobre o quadro (m_constantBufferChangesEveryFrame), como a posição da câmera; e alguns dados são específicos do primitivo, como sua cor e texturas (m_constantBufferChangesEveryPrim).
- O renderizador do jogo separa essas entradas em diferentes buffers constantes para otimizar a largura de banda da memória que a CPU e a GPU usam. Essa abordagem também ajuda a minimizar a quantidade de dados que a GPU precisa acompanhar. A GPU tem uma grande fila de comandos, e cada vez que o jogo chama Draw, esse comando é enfileirado junto com os dados associados a ele. Quando o jogo atualiza o buffer constante primitivo e emite o próximo comando Draw, o driver gráfico adiciona esse próximo comando e os dados associados à fila. Se o jogo desenhar 100 primitivos, ele poderá ter 100 cópias dos dados de buffer constante na fila. Para minimizar a quantidade de dados que o jogo está enviando para a GPU, o jogo usa um buffer constante primitivo separado que contém apenas as atualizações para cada primitivo.
Método GameObject::Render
void GameObject::Render(
_In_ ID3D11DeviceContext* context,
_In_ ID3D11Buffer* primitiveConstantBuffer
)
{
if (!m_active || (m_mesh == nullptr) || (m_normalMaterial == nullptr))
{
return;
}
ConstantBufferChangesEveryPrim constantBuffer;
// Put the model matrix info into a constant buffer, in world matrix.
XMStoreFloat4x4(
&constantBuffer.worldMatrix,
XMMatrixTranspose(ModelMatrix())
);
// Check to see which material to use on the object.
// If a collision (a hit) is detected, GameObject::Render checks the current context, which
// indicates whether the target has been hit by an ammo sphere. If the target has been hit,
// this method applies a hit material, which reverses the colors of the rings of the target to
// indicate a successful hit to the player. Otherwise, it applies the default material
// with the same method. In both cases, it sets the material by calling Material::RenderSetup,
// which sets the appropriate constants into the constant buffer. Then, it calls
// ID3D11DeviceContext::PSSetShaderResources to set the corresponding texture resource for the
// pixel shader, and ID3D11DeviceContext::VSSetShader and ID3D11DeviceContext::PSSetShader
// to set the vertex shader and pixel shader objects themselves, respectively.
if (m_hit && m_hitMaterial != nullptr)
{
m_hitMaterial->RenderSetup(context, &constantBuffer);
}
else
{
m_normalMaterial->RenderSetup(context, &constantBuffer);
}
// Update the primitive constant buffer with the object model's info.
context->UpdateSubresource(primitiveConstantBuffer, 0, nullptr, &constantBuffer, 0, 0);
// Render the mesh.
// See MeshObject::Render method below.
m_mesh->Render(context);
}
Método MeshObject::Render
void MeshObject::Render(_In_ ID3D11DeviceContext* context)
{
// PNTVertex is a struct. stride provides us the size required for all the mesh data
// struct PNTVertex
//{
// DirectX::XMFLOAT3 position;
// DirectX::XMFLOAT3 normal;
// DirectX::XMFLOAT2 textureCoordinate;
//};
uint32_t stride{ sizeof(PNTVertex) };
uint32_t offset{ 0 };
// Similar to the main render loop.
// Input-layout objects describe how vertex buffer data is streamed into the IA pipeline stage.
ID3D11Buffer* vertexBuffer{ m_vertexBuffer.get() };
context->IASetVertexBuffers(0, 1, &vertexBuffer, &stride, &offset);
// IASetIndexBuffer binds an index buffer to the input-assembler stage.
// For more info, see
// https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-iasetindexbuffer.
context->IASetIndexBuffer(m_indexBuffer.get(), DXGI_FORMAT_R16_UINT, 0);
// Binds information about the primitive type, and data order that describes input data for the input assembler stage.
// For more info, see
// https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-iasetprimitivetopology.
context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
// Draw indexed, non-instanced primitives. A draw API submits work to the rendering pipeline.
// For more info, see
// https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-drawindexed.
context->DrawIndexed(m_indexCount, 0, 0);
}
Método DeviceResources::Present
Chamamos o método DeviceResources::P resent para exibir o conteúdo que colocamos nos buffers.
Usamos o termo swap chain para uma coleção de buffers que são usados para exibir fotogramas ao utilizador. Cada vez que um aplicativo apresenta um novo quadro para exibição, o primeiro buffer na cadeia de permuta substitui o buffer exibido. Este processo é chamado de permuta ou inversão. Para obter mais informações, consulte Cadeias de permuta.
- O método IDXGISwapChain1 interface Present instrui DXGI a bloquear até que a sincronização vertical (VSync) ocorra, colocando o aplicativo em suspensão até o próximo VSync. Isso garante que você não desperdice nenhum ciclo renderizando quadros que nunca serão exibidos na tela.
- O método
DiscardView da interfaceID3D11DeviceContext3 descarta o conteúdo do alvo de renderização. Esta é uma operação válida apenas quando o conteúdo existente será totalmente substituído. Se forem usados retângulos sujos ou de rolagem, esta chamada deve ser removida.
- Usando o mesmo método DiscardView, descarte o conteúdo da profundidade-estêncil .
- O método HandleDeviceLost é usado para gerenciar o cenário do dispositivo sendo removido. Se o dispositivo foi removido por uma desconexão ou uma atualização de driver, então você deve recriar todos os recursos do dispositivo. Para obter mais informações, consulte Manipular cenários removidos de dispositivos no Direct3D 11.
Sugestão
Para obter uma taxa de quadros suave, deves garantir que a quantidade de trabalho para renderizar um quadro se ajuste ao tempo entre os VSyncs.
// Present the contents of the swap chain to the screen.
void DX::DeviceResources::Present()
{
// The first argument instructs DXGI to block until VSync, putting the application
// to sleep until the next VSync. This ensures we don't waste any cycles rendering
// frames that will never be displayed to the screen.
HRESULT hr = m_swapChain->Present(1, 0);
// Discard the contents of the render target.
// This is a valid operation only when the existing contents will be entirely
// overwritten. If dirty or scroll rects are used, this call should be removed.
m_d3dContext->DiscardView(m_d3dRenderTargetView.get());
// Discard the contents of the depth stencil.
m_d3dContext->DiscardView(m_d3dDepthStencilView.get());
// If the device was removed either by a disconnection or a driver upgrade, we
// must recreate all device resources.
if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
{
HandleDeviceLost();
}
else
{
winrt::check_hresult(hr);
}
}
Próximos passos
Este tópico explicou como os gráficos são renderizados na exibição e fornece uma breve descrição para alguns dos termos de renderização usados (abaixo). Saiba mais sobre a renderização no tópico Rendering framework II: Game rendering e saiba como preparar os dados necessários antes da renderização.
Termos e conceitos
Cena de jogo simples
Uma cena de jogo simples é composta por alguns objetos com várias fontes de luz.
A forma de um objeto é definida por um conjunto de coordenadas X, Y, Z no espaço. A localização real da renderização no mundo do jogo pode ser determinada aplicando uma matriz de transformação às coordenadas posicionais X, Y, Z. Pode também ter um conjunto de coordenadas de textura — U e V — que especifica como um material é aplicado ao objeto. Isso define as propriedades da superfície do objeto e lhe dá a capacidade de ver se um objeto tem uma superfície áspera (como uma bola de tênis) ou uma superfície lisa brilhante (como uma bola de boliche).
As informações de cena e objeto são usadas pela estrutura de renderização para recriar a cena quadro a quadro, tornando-a ativa no monitor de exibição.
Pipeline de renderização
O pipeline de renderização é o processo pelo qual as informações da cena 3D são traduzidas para uma imagem exibida na tela. No Direct3D 11, esse pipeline é programável. Você pode adaptar os estágios para dar suporte às suas necessidades de renderização. Os estágios que apresentam núcleos de sombreador comuns são programáveis usando a linguagem de programação HLSL. Também é conhecido como "pipeline de renderização gráfica" , ou simplesmente, "pipeline" .
Para te ajudar a criar este pipeline, precisas estar familiarizado com estes detalhes.
- HLSL. Recomendamos o uso de HLSL Shader Model 5.1 e superior para jogos UWP DirectX.
- Sombreadores.
- sombreadores Vertex e sombreadores de pixel.
- Etapas do shader.
- Vários formatos de arquivo de sombreador.
Para obter mais informações, consulte Compreender o pipeline de renderização do Direct3D 11 e o pipeline de gráficos.
HLSL
HLSL é a linguagem de sombreador de alto nível para DirectX. Usando HLSL, você pode criar sombreadores programáveis do tipo C para o pipeline Direct3D. Para obter mais informações, consulte HLSL.
Sombreadores
Um sombreador pode ser considerado como um conjunto de instruções que determinam como a superfície de um objeto aparece quando renderizada. Aqueles que são programados usando HLSL são conhecidos como sombreadores HLSL. Os arquivos de código-fonte para sombreadores [HLSL])(#hlsl) têm a extensão de arquivo .hlsl. Esses shaders podem ser compilados em tempo de construção ou em tempo de execução, e configurados em tempo de execução no estágio de pipeline apropriado. Um objeto sombreador compilado tem uma extensão de arquivo .cso.
Os sombreadores Direct3D 9 podem ser projetados usando shader modelo 1, shader modelo 2 e shader modelo 3; Os sombreadores Direct3D 10 só podem ser projetados no sombreador modelo 4. Os shaders Direct3D 11 podem ser desenhados no modelo de shaders 5. O Direct3D 11.3 e o Direct3D 12 podem ser projetados no modelo de sombreador 5.1 e o Direct3D 12 também pode ser projetado no modelo de sombreador 6.
Sombreadores de vértice e sombreadores de pixel
Os dados entram no pipeline de gráficos como um fluxo de primitivos e são processados por vários sombreadores, como sombreadores de vértice e sombreadores de pixel.
Os sombreadores de vértice processam vértices, normalmente realizando operações como transformações, esfolamento e iluminação. Os sombreadores de pixel permitem técnicas avançadas de sombreamento, como iluminação por pixel e pós-processamento. Ele combina variáveis constantes, dados de textura, valores interpolados por vértice e outros dados para produzir saídas por pixel.
Estágios de sombreamento
Uma sequência destes vários sombreadores definidos para o processamento deste fluxo de primitivos é conhecida como etapas de sombreamento num pipeline de renderização. Os estágios reais dependem da versão do Direct3D, mas geralmente incluem os estágios de vértice, geometria e pixel. Há também outras etapas, como os sombreadores de casco e de domínio para tesselação, e o sombreador de cálculo. Todas estas etapas são completamente programáveis usando HLSL. Para obter mais informações, consulte pipeline de gráficos.
Vários formatos de arquivo de sombreador
Aqui estão as extensões de arquivo de código de sombreador.
- Um arquivo com a extensão
.hlslcontém o código-fonte [HLSL])(#hlsl). - Um arquivo com a extensão
.csocontém um objeto sombreador compilado. - Um arquivo com a extensão
.hé um arquivo de cabeçalho, mas em um contexto de código de sombreador, esse arquivo de cabeçalho define uma matriz de bytes que contém dados de sombreador. - Um arquivo com a extensão
.hlslicontém o formato dos buffers constantes. No jogo de exemplo, o arquivo é Shaders>ConstantBuffers.hlsli.
Observação
Você incorpora um sombreador carregando um arquivo .cso em tempo de execução ou adicionando um arquivo .h em seu código executável. Mas você não usaria os dois para o mesmo sombreador.
Compreensão mais profunda do DirectX
O Direct3D 11 é um conjunto de APIs que nos podem ajudar a criar gráficos para aplicações gráficas intensivas, como jogos, onde queremos ter uma boa placa gráfica para processar computação intensiva. Esta seção explica brevemente os conceitos de programação gráfica do Direct3D 11: recurso, subrecurso, dispositivo e contexto do dispositivo.
Recurso
Você pode pensar em recursos (também conhecidos como recursos de dispositivo) como informações sobre como renderizar um objeto, como textura, posição ou cor. Os recursos fornecem dados para a linha de processamento e definem o que é renderizado durante a sua cena. Os recursos podem ser carregados a partir da mídia do jogo ou criados dinamicamente em tempo de execução.
Um recurso é na verdade uma área na memória que pode ser acedida pelo pipeline Direct3D . Para que o pipeline acesse a memória de forma eficiente, os dados fornecidos ao pipeline (como geometria de entrada, recursos de sombreador e texturas) devem ser armazenados em um recurso. Há dois tipos de recursos dos quais todos os recursos Direct3D derivam: um buffer ou uma textura. Até 128 recursos podem estar ativos para cada estágio do pipeline. Para obter mais informações, consulte Recursos.
Subrecurso
O termo subrecurso refere-se a um subconjunto de um recurso. O Direct3D pode fazer referência a um recurso inteiro ou pode fazer referência a subconjuntos de um recurso. Para obter mais informações, consulte Subresource.
Estêncil de profundidade
Um recurso de estêncil de profundidade contém o formato e o buffer para armazenar informações de profundidade e estêncil. Ele é criado usando um recurso de textura. Para obter mais informações sobre como criar um recurso de estêncil de profundidade, consulte Configurando Depth-Stencil funcionalidade. Acessamos o recurso de estêncil de profundidade através da visualização de estêncil de profundidade implementada usando a interface ID3D11DepthStencilView
Informações de profundidade nos dizem quais áreas de polígonos estão atrás de outras, para que possamos determinar quais estão ocultas. Stencil info nos diz quais pixels são mascarados. Pode ser usado para produzir efeitos especiais, uma vez que determina se um pixel é desenhado ou não; define o bit como 1 ou 0.
Para obter mais informações, consulte de exibição de estêncil de profundidade, de buffer de profundidade e de buffer de estêncil.
Destino de renderização
Um destino de renderização é um recurso para o qual podemos escrever no final de um passo de renderização. É normalmente criado usando o método ID3D11Device::CreateRenderTargetView usando o buffer de retorno da cadeia de permuta (que também é um recurso) como o parâmetro de entrada.
Cada destino de renderização também deve ter uma exibição de estêncil de profundidade correspondente, porque quando usamos OMSetRenderTargets para definir o destino de renderização antes de usá-lo, ele também requer uma exibição de estêncil de profundidade. Acedemos ao recurso de alvo de renderização através da vista de alvo de renderização implementada usando a interface ID3D11RenderTargetView.
Dispositivo
Você pode imaginar um dispositivo como uma maneira de alocar e destruir objetos, renderizar primitivos e se comunicar com a placa gráfica através do driver gráfico.
Para uma explicação mais precisa, um dispositivo Direct3D é o componente de renderização do Direct3D. Um dispositivo encapsula e armazena o estado de renderização, executa transformações e operações de iluminação e rasteriza uma imagem para uma superfície. Para obter mais informações, consulte Dispositivos
Um dispositivo é representado pela interface ID3D11Device. Em outras palavras, a interface ID3D11Device representa um adaptador de vídeo virtual e é usada para criar recursos que são possuídos por um dispositivo.
Existem diferentes versões do ID3D11Device. ID3D11Device5 é a versão mais recente e adiciona novos métodos aos existentes no ID3D11Device4. Para obter mais informações sobre como o Direct3D se comunica com o hardware subjacente, consulte a arquitetura WDDM (Modelo de Controlador de Dispositivo do Windows).
Cada aplicação deve ter pelo menos um dispositivo; a maioria dos aplicativos cria apenas um. Crie um dispositivo para um dos drivers de hardware instalados em sua máquina chamando D3D11CreateDevice ou D3D11CreateDeviceAndSwapChain e especificando o tipo de driver com o sinalizador D3D_DRIVER_TYPE. Cada dispositivo pode usar um ou mais contextos de dispositivo, dependendo da funcionalidade desejada. Para obter mais informações, consulte função D3D11CreateDevice.
Contexto do dispositivo
Um contexto de dispositivo é utilizado para definir o estado do pipeline e gerar comandos de renderização usando os recursos de propriedade de um dispositivo .
O Direct3D 11 implementa dois tipos de contextos de dispositivo, um para renderização imediata e outro para renderização adiada; ambos os contextos são representados com um ID3D11DeviceContext interface.
As interfaces ID3D11DeviceContext têm versões diferentes; ID3D11DeviceContext4 adiciona novos métodos aos ID3D11DeviceContext3.
ID3D11DeviceContext4 foi introduzido na Atualização para Criadores do Windows 10 e é a versão mais recente da interface ID3D11DeviceContext. Os aplicativos destinados ao Windows 10 Creators Update e posteriores devem usar essa interface em vez de versões anteriores. Para obter mais informações, consulte ID3D11DeviceContext4.
DX::D eviceResources
A classe
Memória intermédia
Um recurso de buffer é uma coleção de dados totalmente tipados agrupados em elementos. Você pode usar buffers para armazenar uma grande variedade de dados, incluindo vetores de posição, vetores normais, coordenadas de textura em um buffer de vértice, índices em um buffer de índice ou estado do dispositivo. Os elementos de buffer podem incluir valores de dados compactados (como valores de superfície R8G8B8A8), inteiros únicos de 8 bits ou quatro valores de ponto flutuante de 32 bits.
Há três tipos de buffers disponíveis: buffer de vértice, buffer de índice e buffer constante.
Tampão de vértice
Contém os dados de vértice usados para definir sua geometria. Os dados de vértice incluem coordenadas de posição, dados de cor, dados de coordenadas de textura, dados normais e assim por diante.
Buffer de índice
Contém deslocamentos inteiros em buffers de vértice e são usados para renderizar primitivos com mais eficiência. Um buffer de índice contém um conjunto sequencial de índices de 16 bits ou 32 bits; Cada índice é usado para identificar um vértice em um buffer de vértice.
Buffer constante ou buffer constante de sombreador
Permite que você forneça dados de sombreador de forma eficiente para o pipeline. Você pode usar buffers constantes como entradas para os sombreadores que são executados para cada primitiva e armazenar resultados do estágio de fluxo-saída do pipeline de renderização. Conceitualmente, um buffer constante se parece com um buffer de vértice com um único elemento.
Conceção e implementação de buffers
Você pode projetar buffers com base no tipo de dados, por exemplo, como em nosso jogo de exemplo, um buffer é criado para dados estáticos, outro para dados constantes sobre o quadro e outro para dados específicos de uma primitiva.
Todos os tipos de buffer são encapsulados pela interface ID3D11Buffer e você pode criar um recurso de buffer chamando ID3D11Device::CreateBuffer. Mas um buffer deve ser vinculado ao pipeline antes que ele possa ser acessado. Os buffers podem ser associados a várias fases de pipeline simultaneamente para leitura. Um buffer também pode ser vinculado a um único estágio de pipeline para gravação; no entanto, o mesmo buffer não pode ser vinculado para leitura e gravação simultaneamente.
Estas são as maneiras pelas quais você pode vincular buffers.
- Para o estágio de montagem do assembler de entrada, ao invocar métodos de ID3D11DeviceContext, tais como ID3D11DeviceContext::IASetVertexBuffers e ID3D11DeviceContext::IASetIndexBuffer.
- No estágio de saída de fluxo, ao chamar ID3D11DeviceContext::SOSetTargets.
- Atingir o estágio de sombreador chamando métodos de sombreador, como ID3D11DeviceContext::VSSetConstantBuffers.
Para obter mais informações, consulte Introdução aos buffers no Direct3D 11.
DXGI
Microsoft DirectX Graphics Infrastructure (DXGI) é um subsistema que encapsula algumas das tarefas de baixo nível que são necessárias para o Direct3D. Cuidado especial deve ser tomado ao usar DXGI em um aplicativo multithreaded para garantir que impasses não ocorram. Para obter mais informações, consulte Multithreading e DXGI
Nível de funcionalidade
O nível de recurso é um conceito introduzido no Direct3D 11 para lidar com a diversidade de placas de vídeo em máquinas novas e existentes. Um nível de recurso é um conjunto bem definido de funcionalidade de unidade de processamento gráfico (GPU).
Cada placa de vídeo implementa um certo nível de funcionalidade DirectX, dependendo das GPUs instaladas. Em versões anteriores do Microsoft Direct3D, você poderia descobrir a versão do Direct3D a placa de vídeo implementada e, em seguida, programar seu aplicativo de acordo.
Com o nível de recurso, ao criar um dispositivo, você pode tentar criar um dispositivo para o nível de recurso que deseja solicitar. Se a criação do dispositivo funcionar, esse nível de recurso existirá, caso contrário, o hardware não suportará esse nível de recurso. Você pode tentar recriar um dispositivo em um nível de recurso mais baixo ou pode optar por sair do aplicativo. Por exemplo, o nível de recurso 12_0 requer Direct3D 11.3 ou Direct3D 12 e modelo de sombreador 5.1. Para obter mais informações, consulte níveis de recursos do Direct3D: Visão geral de cada nível de recurso.
Usando níveis de recursos, você pode desenvolver um aplicativo para Direct3D 9, Microsoft Direct3D 10 ou Direct3D 11 e, em seguida, executá-lo em hardware 9, 10 ou 11 (com algumas exceções). Para obter mais informações, consulte níveis de funcionalidade do Direct3D.
Renderização estéreo
A renderização estéreo é usada para aumentar a ilusão de profundidade. Ele usa duas imagens, uma do olho esquerdo e outra do olho direito para exibir uma cena na tela de exibição.
Matematicamente, aplicamos uma matriz de projeção estéreo, que é um ligeiro deslocamento horizontal para a direita e para a esquerda, da matriz de projeção mono regular para conseguir isso.
Fizemos dois passes de renderização para conseguir renderização estéreo neste jogo de amostra.
- Vincule ao alvo de renderização à direita, aplique a projeção correta e desenhe o objeto primitivo.
- Vincular ao destino de renderização esquerdo, aplicar projeção à esquerda e, em seguida, desenhar o objeto primitivo.
Câmera e espaço coordenado
O jogo tem o código para atualizar o mundo em seu próprio sistema de coordenadas (às vezes chamado de espaço do mundo ou espaço da cena). Todos os objetos, incluindo a câmara, estão posicionados e orientados neste espaço. Para obter mais informações, consulte sistemas de coordenadas.
Um sombreador de vértice faz o trabalho pesado de conversão das coordenadas do modelo para coordenadas do dispositivo com o seguinte algoritmo (onde V é um vetor e M é uma matriz).
V(device) = V(model) x M(model-to-world) x M(world-to-view) x M(view-to-device)
-
M(model-to-world)é uma matriz de transformação de coordenadas de modelo para coordenadas mundiais, também conhecida como matriz de transformação do mundo . Isso é fornecido pelo primitivo. -
M(world-to-view)é uma matriz de transformação para coordenadas mundiais para visualizar coordenadas, também conhecida como matriz de transformação View.- Isto é fornecido pela matriz de visão da câmara. É definido pela posição da câmara, juntamente com os vetores de visão (o vetor de visão olhar para, que aponta diretamente para a cena da câmara, e o vetor de visão para cima olhar para cima, que é perpendicular ao vetor de visão da cena).
- No jogo de exemplo, m_viewMatrix é a matriz de transformação de exibição e é calculada usando Camera::SetViewParams.
-
M(view-to-device)é uma matriz de transformação de coordenadas de visualização para coordenadas de dispositivo, também conhecida como matriz de transformação da projeção .- Isto é fornecido pela projeção da câmara fotográfica. Ele fornece informações sobre quanto desse espaço é realmente visível na cena final. O campo de visão (FoV), a proporção e os planos de recorte definem a matriz de transformação de projeção.
- No jogo de exemplo, m_projectionMatrix define a transformação para as coordenadas de projeção, calculadas usando Camera::SetProjParams (Para projeção estéreo, utilizam-se duas matrizes de projeção — uma para a visão de cada olho).
O código do sombreador no VertexShader.hlsl é carregado com esses vetores e matrizes dos buffers constantes e executa essa transformação para cada vértice.
Transformação de coordenadas
O Direct3D usa três transformações para alterar as coordenadas do modelo 3D em coordenadas de pixel (espaço na tela). Essas transformações são transformação do mundo, transformação da visão e transformação da projeção. Para saber mais, veja Visão geral do Transform.
Matriz de transformação mundial
Uma transformação de mundo muda coordenadas do espaço modelo, onde vértices são definidos em relação à origem local de um modelo, para o espaço mundo, onde vértices são definidos em relação a uma origem comum a todos os objetos em uma cena. Em essência, a transformação do mundo coloca um modelo no mundo; daí o seu nome. Para obter mais informações, consulte World transform.
Ver matriz de transformação
A transformação de visão localiza o espectador no espaço do mundo, transformando vértices em espaço de câmera. No espaço da câmara, a câmara, ou o observador, está na origem, olhando na direção positiva do eixo z. Para obter mais informações, vá para View transform.
Matriz de transformação de projeção
A transformada de projeção converte o frustum de visualização em uma forma cuboide. Um frustum de visualização é um volume 3D em uma cena posicionada em relação à câmera do visor. Um viewport é um retângulo 2D no qual uma cena 3D é projetada. Para mais informações, consulte Viewports e recorte.
Uma vez que a extremidade próxima do frustum de visualização é menor que a extremidade distante, isso provoca um efeito de ampliação dos objetos próximos da câmera, e é assim que a perspetiva é aplicada à cena. Assim, os objetos que estão mais próximos do jogador parecem maiores; objetos que estão mais longe parecem menores.
Matematicamente, a transformação de projeção é uma matriz que normalmente combina uma projeção em escala e perspetiva. Ele funciona como a lente de uma câmera. Para obter mais informações, consulte transformação de projeção.
Estado do amostrador
O estado do amostrador determina como os dados de textura são amostrados usando modos de endereçamento de textura, filtragem e nível de detalhes. A amostragem é feita cada vez que um pixel de textura (ou texel) é lido a partir de uma textura.
Uma textura contém uma matriz de texels. A posição de cada texel é denotada por (u,v), onde u é a largura e v é a altura, e é mapeada entre 0 e 1 com base na largura e altura da textura. As coordenadas de textura resultantes são usadas para acessar um texel quando se amostra uma textura.
Quando as coordenadas de textura estão abaixo de 0 ou acima de 1, o modo de endereço de textura define como a coordenada de textura aborda um local texel. Por exemplo, ao usar TextureAddressMode.Clamp , qualquer coordenada fora do intervalo 0-1 é fixada a um valor máximo de 1 e valor mínimo de 0 antes da amostragem.
Se a textura for muito grande ou muito pequena para o polígono, então a textura é filtrada para caber no espaço. Um filtro de ampliação amplia uma textura, um filtro de minificação reduz a textura para caber em uma área menor. A ampliação de textura repete o texel amostra para um ou mais endereços, resultando numa imagem mais desfocada. A minificação da textura é mais complicada porque requer a combinação de mais de um valor texel em um único valor. Isso pode causar aliasing ou bordas irregulares, dependendo dos dados de textura. A abordagem mais popular para a minificação é usar um mipmap. Um mipmap é uma textura de vários níveis. O tamanho de cada nível é uma potência de 2 menor do que o nível anterior até uma textura 1x1. Quando a minificação é usada, um jogo escolhe o nível de mipmap mais próximo do tamanho necessário no momento da renderização.
A classe BasicLoader
BasicLoader é uma classe de carregador simples que fornece suporte para carregar sombreadores, texturas e malhas de arquivos no disco. Ele fornece métodos síncronos e assíncronos. Neste jogo de exemplo, os ficheiros de BasicLoader.h/.cpp são encontrados na pasta Utilities.
Para obter mais informações, consulte Basic Loader.