Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Observação
Este tópico faz parte da série de tutoriais Criar um jogo simples usando a 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 um computador 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 jogo na tela significa basicamente renderizar uma sequência de quadros na tela. Em cada quadro, você precisa renderizar objetos visíveis na cena, com base na exibição.
Para renderizar um quadro, você precisa 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, precisará começar a renderizar assim que o jogo começar a ser executado.
Objectivos
Para configurar uma estrutura de renderização básica para exibir a saída de gráficos para 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, abrangendo as etapas 1 e 3.
Estrutura de renderização II: a renderização de jogos abrange a etapa 2: como configurar a estrutura de renderização e como os dados são preparados antes que a renderização possa acontecer.
Comece agora
É uma boa ideia familiarizar-se com elementos gráficos básicos e conceitos de renderização. Se você não estiver familiarizado com o Direct3D e renderizando, consulte Termos e conceitos para obter uma breve descrição dos elementos gráficos e termos de renderização usados neste tópico.
Para este jogo, a classe GameRenderer representa o renderizador deste jogo de exemplo. Ele é responsável por criar e manter 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 display de informações (HUD).
Nesta parte do tutorial, nos concentraremos 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 Definir a estrutura de aplicativos UWP do jogo.
O método App::Initialize
A função std::make_shared, conforme 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 primitivos 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 ser renderizada 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 de gerenciamento de fluxo de jogos
Renderizar
A renderização é implementada chamando o método GameRenderer::Render de GameMain::Run.
Se a renderização estéreo estiver habilitada, haverá dois passes de renderização: um para o olho esquerdo e outro para o olho direito. Em cada passagem de renderização, associamos o alvo de renderização e a visualização de estêncil de profundidade ao dispositivo. Também limpamos a exibiçã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 conveniente, de obter renderização estéreo.
Depois que o jogo estiver em execução e os recursos forem carregados, atualizaremos a matriz de projeção , uma vez por passagem de renderização. Os objetos são ligeiramente diferentes de cada exibição. Em seguida, configuramos o pipeline de renderização gráfica .
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 alterações 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. Objetos de layout de entrada descrevem como os dados do buffer de vértice são transmitidos para o pipeline de renderização .
Em seguida, definimos o contexto direct3D para usar os buffers constantes definidos anteriormente, que são usados pelo sombreador de vértice estágio de pipeline e o sombreador de pixel estágio de pipeline.
Observação
Consulte Estrutura de renderização II: Renderização de jogos para obter mais informações sobre a definição de buffers constantes.
Como o mesmo layout de entrada e o conjunto de buffers constantes são usados para todos os sombreadores que estão no pipeline, isso é configurado uma vez por quadro.
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 do mundo do modelo e as informações do material.
- O m_constantBufferChangesEveryPrim contém parâmetros para cada objeto. Ele inclui a matriz de transformação objeto a mundo, bem como propriedades materiais, como cor e expoente especular para cálculos de iluminação.
- Defina o contexto Direct3D para usar o layout de vértices de entrada dos dados do objeto de malha a serem transmitidos para o estágio IA (input-assembler) do pipeline de renderização .
- Defina o contexto direct3D para usar um buffer de índice no estágio IA. Forneça as informações primitivas: tipo, ordem de dados.
- Submeta uma chamada de desenho para renderizar o primitivo indexado, não instanciado. O método GameObject::Renderizar atualiza o buffer de constante primitivo com os dados específicos de um determinado primitivo. Isso resulta em uma chamada DrawIndexed no contexto para desenhar a geometria de cada primitivo. Especificamente, essa chamada de desenho enfileira comandos e dados para a GPU, conforme parametrizado pelos dados constantes do buffer. Cada draw call executa o sombreador de vértice uma vez por vértice, e depois o sombreador de pixel uma vez para cada pixel de cada triângulo no primitivo. As texturas fazem parte do estado que o sombreador de pixel usa para fazer a renderização.
Aqui estão os motivos para usar vários buffers constantes.
- O jogo usa vários buffers constantes, mas só precisa atualizar esses buffers uma vez por primitiva. Conforme 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 para o primitivo, como sua cor e texturas (m_constantBufferChangesEveryPrim).
- O renderizador de jogos separa essas entradas em buffers constantes diferentes para otimizar a largura de banda de 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 toda 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 Desenhar, o driver gráfico adiciona este próximo comando e os dados associados à fila. Se o jogo desenhar 100 primitivas, é possível ter 100 cópias dos dados constantes do buffer na fila. Para minimizar a quantidade de dados que o jogo está enviando para a GPU, o jogo usa um buffer de constante primitiva 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::Present para exibir o conteúdo que colocamos nos buffers.
Usamos o termo cadeia de troca para uma coleção de buffers que são usados para exibir imagens ao usuário. Sempre que um aplicativo apresenta um novo quadro para exibição, o primeiro buffer na cadeia de troca assume o lugar do buffer exibido. Esse processo é chamado de troca ou inversão. Para obter mais informações, consulte cadeias de troca.
- 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 ID3D11DeviceContext3 interface DiscardView descarta o conteúdo do de destino de renderização. Essa é uma operação válida somente quando o conteúdo existente será totalmente substituído. Se retângulos sujos ou de rolagem forem usados, essa chamada deverá ser removida.
- Usando o mesmo método DiscardView, descarte o conteúdo do estêncil de profundidade .
- O método HandleDeviceLost é usado para gerenciar o cenário de o dispositivo ser removido. Se o dispositivo foi removido por uma desconexão ou uma atualização de driver, você deve recriar todos os recursos do dispositivo. Para obter mais informações, consulte Lidar com cenários de remoção do dispositivo no Direct3D 11.
Dica
Para obter uma taxa de quadros suave, você deve garantir que a carga de trabalho para renderizar um quadro se ajuste ao tempo entre 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óximas etapas
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 o tópico de renderização na estrutura de renderização II: Renderização de Jogos e aprenda 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 e Z no espaço. O local de renderização real no mundo do jogo pode ser determinado aplicando uma matriz de transformação às coordenadas posicionais X, Y, Z. Ele também pode ter um conjunto de coordenadas de textura — U e V — que especificam como um material é aplicado ao objeto. Isso define as propriedades da superfície do objeto e oferece a capacidade de ver se um objeto tem uma superfície áspera (como uma bola de tênis) ou uma superfície suave e 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, dando vida ao seu monitor.
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. Estágios que apresentam núcleos de sombreadores 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 ajudá-lo a criar esse fluxo de trabalho, você precisa estar familiarizado com esses detalhes.
- HLSL. Recomendamos o uso do Modelo de Sombreador HLSL 5.1 e superior para jogos UWP DirectX.
- Sombreadores.
- sombreadores de vértice e sombreadores de pixel.
- estágios de sombreador.
- Vários formatos de arquivo de sombreador.
Para obter mais informações, consulte Noções básicas sobre o pipeline de renderização do Direct3D 11 e pipeline gráfico.
HLSL
HLSL é a linguagem de sombreador de alto nível para DirectX. Usando o HLSL, você pode criar sombreadores programáveis semelhantes a C para o pipeline do 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 é exibida quando renderizada. Aqueles 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 sombreadores podem ser compilados em tempo de construção ou em tempo de execução e definidos em tempo de execução no estágio do pipeline apropriado. Um objeto de sombreador compilado tem uma extensão de arquivo .cso
.
Sombreadores Direct3D 9 podem ser projetados usando o modelo de sombreador 1, o modelo de sombreador 2 e o modelo de sombreador 3; Sombreadores Direct3D 10 só podem ser projetados no modelo de sombreador 4. Os shaders Direct3D 11 podem ser projetados com base no modelo de shader 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.
Sombreadores de vértice processam vértices, normalmente executando operações como transformações, esfolação e iluminação. 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 do sombreador
Uma sequência desses vários sombreadores definidos para processar esse fluxo de primitivos é conhecida como estágios de sombreador em um 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 outros estágios, como os shaders de casco e de domínio para tesselagem, e o shader de computação. Todos esses estágios são completamente programáveis usando HLSL. Para obter mais informações, consulte pipeline gráfico.
Vários formatos de arquivo de sombreador
Aqui estão as extensões de arquivo de código do sombreador.
- Um arquivo com a extensão
.hlsl
contém o código-fonte [HLSL])(#hlsl). - Um arquivo com a extensão
.cso
contém um objeto de 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
.hlsli
contém o formato dos buffers constantes. No jogo de exemplo, o arquivo é Shaders>ConstantBuffers.hlsli.
Observação
Você insira um sombreador carregando um arquivo .cso
em runtime ou adicionando um arquivo .h
no código executável. Mas você não usaria ambos para o mesmo sombreador.
Compreensão mais profunda do DirectX
O Direct3D 11 é um conjunto de APIs que pode nos ajudar a criar gráficos para aplicativos com uso intensivo de gráficos, como jogos, em que queremos ter uma boa placa gráfica para processar a computação intensiva. Esta seção explica brevemente os conceitos de programação gráfica do Direct3D 11: recurso, sub-fonte, dispositivo e contexto do dispositivo.
Recurso
Você pode pensar em recursos (também conhecidos como recursos do dispositivo) como informações sobre como renderizar um objeto, como textura, posição ou cor. Os recursos fornecem dados para o pipeline e definem o que é renderizado durante sua cena. Os recursos podem ser carregados da mídia do jogo ou criados dinamicamente em tempo de execução.
Um recurso é, de fato, uma área na memória que pode ser acessada pelo pipelinedo Direct3D
Sub-fonte
O termo sub-fonte refere-se a um subconjunto de um recurso. O Direct3D pode fazer referência a um recurso inteiro ou pode referenciar subconjuntos de um recurso. Para obter mais informações, consulte o sub-recurso .
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 Configuring Depth-Stencil Functionality. Acessamos o recurso de estêncil de profundidade por meio da exibição de estêncil de profundidade, implementada usando a interface ID3D11DepthStencilView de
Informações detalhadas nos dizem quais áreas de polígonos estão atrás de outras, para que possamos determinar quais estão ocultas. O estêncil nos diz quais pixels são mascarados. Ele pode ser usado para produzir efeitos especiais, pois determina se um pixel é desenhado ou não; define o bit como um 1 ou 0.
Para obter mais informações, consulte a exibição de estêncil de profundidade , o buffer de profundidade e o buffer de estêncil .
Destino de renderização
Um destino de renderização é um recurso que podemos gravar no final de uma passagem de renderização. Normalmente, ele é criado pelo método ID3D11Device::CreateRenderTargetView utilizando o buffer de retaguarda da cadeia de troca (que também é um recurso) como parâmetro de entrada.
Cada destino de renderização também deve ter uma exibição de estêncil de profundidade correspondente, pois 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. Acessamos o recurso de destino de renderização por meio da visualização de destino 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 por meio do driver gráfico.
Para obter 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 em uma superfície. Para obter mais informações, consulte Devices
Um dispositivo é representado pela interface ID3D11Device. Em outras palavras, a interface ID3D11Device representa um adaptador de exibição virtual e é usada para criar recursos que pertencem a um dispositivo.
Há diferentes versões do ID3D11Device. ID3D11Device5 é a versão mais recente e adiciona novos métodos aos métodos em ID3D11Device4. Para obter mais informações sobre como o Direct3D se comunica com o hardware subjacente, consulte arquitetura do WDDM (Windows Device Driver Model).
Cada aplicativo deve ter pelo menos um dispositivo; a maioria dos aplicativos cria apenas um. Crie um dispositivo para um dos drivers de hardware instalados em seu computador 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 é usado para definir o estado do pipeline e gerar comandos de renderização usando os recursos pertencentes a 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 uma interface ID3D11DeviceContext
As interfaces ID3D11DeviceContext têm versões diferentes; ID3D11DeviceContext4 adiciona novos métodos aos contidos em ID3D11DeviceContext3.
ID3D11DeviceContext4 foi introduzido na Atualização para Criadores do Windows 10 e é a versão mais recente da interface ID3D11DeviceContext. Os aplicativos direcionados ao Windows 10 Creators Update e posterior devem usar essa interface em vez de versões anteriores. Para obter mais informações, consulte ID3D11DeviceContext4.
DX::D eviceResources
A classe
Buffer
Um recurso de buffer é uma coleção de dados totalmente tipados agrupados em elementos. Você pode usar buffers para armazenar uma ampla 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 empacotados (como valores de superfície R8G8B8A8), inteiros de 8 bits únicos 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.
Buffer 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, que são usados para renderizar primitivos com mais eficiência. Um buffer de índice contém um conjunto sequencial de índices de 16 ou 32 bits; cada índice é usado para identificar um vértice em um buffer de vértice.
Buffer de constantes ou buffer de constantes do sombreador
Permite fornecer com eficiência dados de sombreador para o pipeline. Você pode usar buffers constantes como entradas para os sombreadores que são executados para cada primitivo e armazenar resultados do estágio de saída de fluxo do pipeline de renderização. Conceitualmente, um buffer constante se assemelha a um buffer de vértice de um único elemento.
Design e implementação de buffers
Você pode criar 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 que são constantes sobre o quadro e outro para dados específicos de um primitivo.
Todos os tipos de buffer são encapsulados pela interface de ID3D11Buffer
Você pode associar buffers destas maneiras.
- Para a fase de montagem de entrada, chamando métodos do ID3D11DeviceContext, como ID3D11DeviceContext::IASetVertexBuffers e ID3D11DeviceContext::IASetIndexBuffer.
- No estágio de saída de fluxo, chamando ID3D11DeviceContext::SOSetTargets.
- Para acessar o estágio do sombreador chamando métodos como ID3D11DeviceContext::VSSetConstantBuffers.
Para obter mais informações, consulte Introdução aos buffers no Direct3D 11.
DXGI
A DXGI (Infraestrutura Gráfica do Microsoft DirectX) é um subsistema que encapsula algumas das tarefas de baixo nível necessárias para o Direct3D. Cuidados especiais devem ser tomados ao usar DXGI em um aplicativo multithreaded para garantir que os deadlocks não ocorram. Para obter mais informações, consulte Multithread e DXGI
Nível de funcionalidade
O nível de funcionalidade é um conceito introduzido no Direct3D 11 para lidar com a diversidade de placas de vídeo em máquinas novas e antigas. Um nível de recurso é um conjunto bem definido de funcionalidade de GPU (unidade de processamento gráfico).
Cada placa de vídeo implementa um determinado nível de funcionalidade do DirectX dependendo das GPUs instaladas. Em versões anteriores do Microsoft Direct3D, você poderia descobrir a versão do Direct3D que a placa de vídeo implementou 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 oferecerá suporte a esse nível de recurso. Você pode tentar recriar um dispositivo em um nível de recurso inferior ou pode optar por sair do aplicativo. Por exemplo, o nível de recurso 12_0 requer Direct3D 11.3 ou Direct3D 12 e o modelo de sombreador 5.1. Para obter mais informações, consulte níveis de recursos do Direct3D: Visão geral para 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 executá-lo em hardware 9, 10 ou 11 (com algumas exceções). Para obter mais informações, consulte níveis de recursos do Direct3D.
Renderização estéreo
A renderização estéreo é usada para aprimorar a ilusão de profundidade. Ele usa duas imagens, uma do olho esquerdo e a outra do olho direito para exibir uma cena na tela de exibição.
Matematicamente, aplicamos uma matriz de projeção estéreo, que consiste em um leve deslocamento horizontal tanto para a direita quanto para a esquerda, na matriz de projeção mono regular para conseguir isso.
Fizemos dois passes de renderização para obter renderização estéreo neste jogo de exemplo.
- Associe ao destino de renderização direito, aplique a projeção direita e desenhe o objeto primitivo.
- Associe ao alvo de renderização esquerdo, aplique a projeção do lado esquerdo e desenhe o objeto primitivo.
Câmera e espaço de coordenadas
O jogo tem o código em vigor para atualizar o mundo em seu próprio sistema de coordenadas (às vezes chamado de espaço mundial ou espaço de cena). Todos os objetos, incluindo a câmera, são posicionados e orientados nesse 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 em coordenadas do dispositivo com o algoritmo a seguir (em que 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 a matriz de transformação de "World" . Isso é fornecido pela função primitiva. -
M(world-to-view)
é uma matriz de transformação de coordenadas mundiais para coordenadas de visualização, também conhecida como matriz de transformação de visualização .- Isso é fornecido pela matriz de exibição da câmera. Ele é definido pela posição da câmera junto com os vetores de visão (o vetor de olhar para que aponta diretamente para a cena a partir da câmera, e o vetor de visão para cima que é perpendicular a ele).
- 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 para coordenadas de visão para coordenadas do dispositivo, também conhecida como a matriz de transformação de projeção .- Isso é fornecido pela projeção da câmera. Ele fornece informações sobre quanto desse espaço é realmente visível na cena final. O campo de visão (FoV), a relação de aspecto e os planos de recorte definem a matriz de transformação projetiva.
- 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, você usa duas matrizes de projeção—uma para a visão de cada olho).
O código do shader em VertexShader.hlsl
é carregado com esses vetores e matrizes provenientes dos buffers constantes e realiza 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 de mundo, transformação de visão e transformação de projeção. Para obter mais informações, consulte Visão geral do Transform.
Matriz de transformação mundial
Uma transformação mundial muda as coordenadas do espaço do modelo, onde os vértices são definidos em relação à origem local de um modelo, para o espaço mundial, onde os 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 mundial posiciona um modelo no mundo; daí vem o nome. Para obter mais informações, consulte transformação do Mundo.
Exibir matriz de transformação
A transformação de visão localiza o visualizador no espaço do mundo, transformando vértices em espaço de câmera. No espaço da câmera, a câmera ou observador está na origem, olhando na direção positiva do eixo z. Para obter mais informações, acesse View Transformação.
Matriz de transformação de projeção
A transformação de projeção converte o frusto de exibição em uma forma cuboide. Um frusto de exibição é um volume 3D em uma cena posicionada em relação à câmera do visor. Uma viewport é um retângulo 2D no qual uma cena 3D é projetada. Para obter mais informações, consulte Visores e recorte
Como a extremidade próxima do campo de visão é menor do que a extremidade distante, isso causa a ampliação dos objetos que estão próximos da câmera; é assim que a perspectiva é aplicada à cena. Portanto, os objetos mais próximos do player aparecem maiores; os objetos que estão mais distantes parecem menores.
Matematicamente, a transformação de projeção é uma matriz que normalmente é uma escala e uma projeção de perspectiva. Ele funciona como a lente de uma câmera. Para obter mais informações, consulte transformação de projeção.
Estado do sampler
O estado do sampler determina como os dados de textura são amostrados usando modos de endereçamento de textura, filtragem e nível de detalhe. A amostragem é feita sempre que um pixel de textura (ou texel) é lido de uma textura.
Uma textura contém uma matriz de texels. A posição de cada texel é indicada por (u,v)
, em que 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 durante a amostragem de 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 se refere a uma posição de píxel de textura. Por exemplo, ao usar TextureAddressMode.Clamp, qualquer coordenada fora do intervalo de 0 a 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, a textura será filtrada para ajustar o 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 magnificação de textura repete o texel amostrado em um ou mais endereços, o que produz uma imagem mais borrada. A minificação de 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 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 3D dos arquivos armazenados em disco. Ele fornece métodos síncronos e assíncronos. Neste jogo de exemplo, os arquivos BasicLoader.h/.cpp
são encontrados na pasta de Utilitários .