Infrastructure de rendu I : Présentation du rendu
Remarque
Cette rubrique fait partie de la série de tutoriels Créer un jeu simple de plateforme Windows universelle simple (UWP) avec DirectX. La rubrique accessible via ce lien définit le contexte de la série.
Jusqu’à présent, nous avons abordé comment structurer un jeu plateforme Windows universelle (UWP) et comment définir un ordinateur d’état pour gérer le flux du jeu. Maintenant, il est temps d’apprendre à développer l’infrastructure de rendu. Examinons comment l’exemple de jeu restitue la scène de jeu à l’aide de Direct3D 11.
Direct3D 11 contient un ensemble d’API qui permettent d’accéder aux fonctionnalités avancées du matériel graphique hautes performances qui peuvent être utilisés pour créer des graphiques 3D pour des applications gourmandes en graphiques, telles que des jeux.
Le rendu des graphiques de jeu à l’écran signifie essentiellement le rendu d’une séquence d’images à l’écran. Dans chaque frame, vous devez afficher les objets visibles dans la scène, en fonction de l’affichage.
Pour afficher un cadre, vous devez transmettre les informations de scène requises au matériel afin qu’elle puisse être affichée à l’écran. Si vous souhaitez afficher quelque chose sur l’écran, vous devez commencer le rendu dès que le jeu commence à s’exécuter.
Objectifs
Pour configurer une infrastructure de rendu de base pour afficher la sortie graphique d’un jeu DirectX UWP. Vous pouvez décomposer cela en trois étapes.
- Établissez une connexion à l’interface graphique.
- Créez les ressources nécessaires pour dessiner les graphiques.
- Affichez les graphiques en affichant le cadre.
Cette rubrique explique comment les graphiques sont rendus, couvrant les étapes 1 et 3.
Infrastructure de rendu II : le rendu de jeu couvre l’étape 2 : comment configurer l’infrastructure de rendu et comment les données sont préparées avant que le rendu puisse se produire.
Bien démarrer
Il est judicieux de vous familiariser avec les concepts de base des graphismes et du rendu. Si vous débutez avec Direct3D et le rendu, consultez conditions générales et concepts pour obtenir une brève description des termes graphiques et de rendu utilisés dans cette rubrique.
Pour ce jeu, la classe GameRenderer représente le renderer pour cet exemple de jeu. Il est responsable de la création et de la maintenance de tous les objets Direct3D 11 et Direct2D utilisés pour générer les visuels de jeu. Il conserve également une référence à l’objet Simple3DGame utilisé pour récupérer la liste des objets à restituer, ainsi que l’état du jeu pour l’affichage tête-haut (HUD).
Dans cette partie du tutoriel, nous allons nous concentrer sur le rendu d’objets 3D dans le jeu.
Établir une connexion à l’interface graphique
Pour plus d’informations sur l’accès au matériel pour le rendu, consultez la rubrique Définir l’infrastructure d’application UWP du jeu.
Méthode App::Initialize
La fonction std ::make_shared , comme indiqué ci-dessous, est utilisée pour créer un shared_ptr à DX ::D eviceResources, qui fournit également l’accès à l’appareil.
Dans Direct3D 11, un appareil est utilisé pour allouer et détruire des objets, restituer des primitives et communiquer avec la carte graphique via le pilote graphique.
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>();
}
Afficher les graphiques en affichant le cadre
La scène de jeu doit s’afficher lorsque le jeu est lancé. Instructions pour le rendu du début dans la méthode GameMain ::Run , comme indiqué ci-dessous.
Le flux simple est celui-ci.
- Update
- Render
- Present
GameMain ::Run, méthode
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.
}
Update
Pour plus d’informations sur la façon dont les états de jeu sont mis à jour dans la méthode GameMain ::Update, consultez la rubrique de gestion des flux de jeu.
Rendu
Le rendu est implémenté en appelant la méthode GameRenderer ::Render à partir de GameMain ::Run.
Si le rendu stéréo est activé, il existe deux passes de rendu : l’un pour l’œil gauche et l’autre pour la droite. Dans chaque passe de rendu, nous liez la cible de rendu et la vue profondeur-gabarit à l’appareil. Nous décochons également la vue profondeur-gabarit par la suite.
Remarque
Le rendu stéréo peut être obtenu à l’aide d’autres méthodes telles que la transmission stéréo unique à l’aide d’instanciations de vertex ou de nuanceurs géométriques. La méthode à deux passes de rendu est un moyen plus lent mais plus pratique d’obtenir un rendu stéréo.
Une fois le jeu en cours d’exécution et les ressources chargées, nous mettons à jour la matrice de projection, une fois par passe de rendu. Les objets sont légèrement différents de chaque vue. Ensuite, nous avons configuré le pipeline de rendu graphique.
Remarque
Pour plus d’informations sur la façon dont les ressources sont chargées, consultez Créer et charger des ressources DirectX.
Dans cet exemple de jeu, le renderer est conçu pour utiliser une disposition de vertex standard sur tous les objets. Cela simplifie la conception du nuanceur et permet de modifier facilement les nuanceurs, indépendamment de la géométrie des objets.
GameRenderer ::Render, méthode
Nous définissons le contexte Direct3D pour utiliser une disposition de vertex d’entrée. Les objets de disposition d’entrée décrivent comment les données de mémoire tampon de vertex sont diffusées en continu dans le pipeline de rendu.
Ensuite, nous définissons le contexte Direct3D pour utiliser les mémoires tampons constantes définies précédemment, qui sont utilisées par l’étape du pipeline du nuanceur de vertex et l’étape de pipeline du nuanceur de pixels.
Remarque
Consultez l’infrastructure de rendu II : Rendu de jeu pour plus d’informations sur la définition des mémoires tampons constantes.
Étant donné que la même disposition d’entrée et l’ensemble de mémoires tampons constantes sont utilisés pour tous les nuanceurs qui se trouvent dans le pipeline, il est configuré une fois par image.
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
...
}
}
Rendu primitif
Lors du rendu de la scène, vous parcourez tous les objets qui doivent être rendus. Les étapes ci-dessous sont répétées pour chaque objet (primitif).
- Mettez à jour la mémoire tampon constante (m_constantBufferChangesEveryPrim) avec la matrice de transformation mondiale du modèle et les informations matérielles.
- Le m_constantBufferChangesEveryPrim contient des paramètres pour chaque objet. Il inclut la matrice de transformation d’objet à monde ainsi que les propriétés matérielles telles que la couleur et l’exposant spéculaire pour les calculs d’éclairage.
- Définissez le contexte Direct3D pour utiliser la disposition de vertex d’entrée pour que les données d’objet de maillage soient diffusées en continu dans l’étape d’assembleur d’entrée (IA) du pipeline de rendu.
- Définissez le contexte Direct3D pour utiliser une mémoire tampon d’index à l’étape IA. Fournissez les informations primitives : type, ordre des données.
- Envoyez un appel de dessin pour dessiner la primitive indexée et non instancenée. La méthode GameObject ::Render met à jour la mémoire tampon de constante primitive avec les données spécifiques à une primitive donnée. Cela entraîne un appel DrawIndexed sur le contexte pour dessiner la géométrie de chaque primitive. Plus précisément, cet appel dessine des commandes et des données vers l’unité de traitement graphique (GPU), comme paramétré par les données de mémoire tampon constantes. Chaque appel de dessin exécute le nuanceur de vertex une fois par sommet, puis le nuanceur de pixels une fois pour chaque pixel de chaque triangle dans la primitive. Les textures font partie de l’état que le nuanceur de pixels utilise pour effectuer le rendu.
Voici les raisons de l’utilisation de plusieurs mémoires tampons constantes.
- Le jeu utilise plusieurs mémoires tampons constantes, mais il ne doit mettre à jour ces mémoires tampons qu’une seule fois par primitive. Comme mentionné précédemment, les mémoires tampons constantes sont similaires aux entrées des nuanceurs qui s’exécutent pour chaque primitive. Certaines données sont statiques (m_constantBufferNeverChanges) ; certaines données sont constantes sur le cadre (m_constantBufferChangesEveryFrame), telles que la position de la caméra ; et certaines données sont spécifiques à la primitive, telles que sa couleur et ses textures (m_constantBufferChangesEveryPrim).
- Le convertisseur de jeu sépare ces entrées en différentes mémoires tampons constantes pour optimiser la bande passante mémoire utilisée par le processeur et le GPU. Cette approche permet également de réduire la quantité de données dont le GPU a besoin pour effectuer le suivi. Le GPU dispose d’une grande file d’attente de commandes, et chaque fois que le jeu appelle Draw, cette commande est mise en file d’attente avec les données associées. Lorsque le jeu met à jour la mémoire tampon constante primitive et émet la commande Draw suivante, le pilote graphique ajoute cette commande suivante et les données associées à la file d’attente. Si le jeu dessine 100 primitives, il peut potentiellement contenir 100 copies des données de mémoire tampon constantes dans la file d’attente. Pour réduire la quantité de données que le jeu envoie au GPU, le jeu utilise une mémoire tampon constante primitive distincte qui contient uniquement les mises à jour pour chaque primitive.
GameObject ::Render, méthode
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);
}
MeshObject ::Render, méthode
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);
}
DeviceResources ::P resent, méthode
Nous appelons la méthode DeviceResources ::P resente pour afficher le contenu que nous avons placé dans les mémoires tampons.
Nous utilisons la chaîne d’échange de termes pour une collection de mémoires tampons utilisées pour afficher des images à l’utilisateur. Chaque fois qu’une application présente une nouvelle image pour l’affichage, la première mémoire tampon de la chaîne d’échange prend la place de la mémoire tampon affichée. Ce processus est appelé permutation ou détourage. Pour plus d’informations, consultez Chaînes d’échange.
- La méthode Present de l’interface IDXGISwapChain1 indique à DXGI de bloquer jusqu’à ce que la synchronisation verticale (VSync) se produise, en plaçant l’application en veille jusqu’à la prochaine synchronisation VSync. Cela garantit que vous ne gaspillez aucun cycle de rendu des images qui ne seront jamais affichées à l’écran.
- La méthode DiscardView de l’interface ID3D11DeviceContext3 ignore le contenu de la cible de rendu. Il s’agit d’une opération valide uniquement lorsque le contenu existant sera entièrement remplacé. Si des rects incorrects ou de défilement sont utilisés, cet appel doit être supprimé.
- À l’aide de la même méthode DiscardView, ignorez le contenu du gabarit de profondeur.
- La méthode HandleDeviceLost est utilisée pour gérer le scénario de suppression de l’appareil. Si l’appareil a été supprimé par une déconnexion ou une mise à niveau de pilote, vous devez recréer toutes les ressources d’appareil. Pour plus d’informations, consultez Gérer les scénarios supprimés des appareils dans Direct3D 11.
Conseil
Pour obtenir une fréquence d’images fluide, vous devez vous assurer que la quantité de travail à effectuer pour afficher une trame correspond au temps entre les synchronisations virtuelles.
// 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);
}
}
Étapes suivantes
Cette rubrique a expliqué comment les graphiques sont rendus sur l’affichage et fournissent une brève description de certains termes de rendu utilisés (ci-dessous). En savoir plus sur le rendu dans l’infrastructure de rendu II : rubrique de rendu de jeu et découvrez comment préparer les données nécessaires avant le rendu.
Termes et concepts
Scène de jeu simple
Une scène de jeu simple est composée de quelques objets avec plusieurs sources de lumière.
La forme d’un objet est définie par un ensemble de coordonnées X, Y, Z dans l’espace. L’emplacement de rendu réel dans le monde du jeu peut être déterminé en appliquant une matrice de transformation aux coordonnées positionnelles X, Y, Z. Il peut également avoir un ensemble de coordonnées de texture , vous et V, qui spécifient la façon dont un matériau est appliqué à l’objet. Cela définit les propriétés de surface de l’objet et vous donne la possibilité de voir si un objet a une surface rugueuse (comme une balle de tennis) ou une surface brillante lisse (comme une boule de bowling).
Les informations de scène et d’objet sont utilisées par l’infrastructure de rendu pour recréer le cadre de scène par image, ce qui le rend actif sur votre moniteur d’affichage.
Pipeline de rendu
Le pipeline de rendu est le processus par lequel les informations de scène 3D sont traduites en image affichée à l’écran. Dans Direct3D 11, ce pipeline est programmable. Vous pouvez adapter les étapes pour prendre en charge vos besoins de rendu. Les étapes qui présentent des cœurs de nuanceur courants sont programmables à l’aide du langage de programmation HLSL. Il est également appelé pipeline de rendu graphique, ou simplement pipeline.
Pour vous aider à créer ce pipeline, vous devez être familiarisé avec ces détails.
- HLSL. Nous vous recommandons d’utiliser HLSL Shader Model 5.1 et versions ultérieures pour les jeux UWP DirectX.
- Nuanceurs.
- Nuanceurs de vertex et nuanceurs de pixels.
- Étapes du nuanceur.
- Différents formats de fichier de nuanceur.
Pour plus d’informations, consultez Comprendre le pipeline de rendu Direct3D 11 et le pipeline Graphics.
HLSL
HLSL est le langage de nuanceur de haut niveau pour DirectX. À l’aide de HLSL, vous pouvez créer des nuanceurs programmables de type C pour le pipeline Direct3D. Pour plus d’informations, consultez HLSL.
Nuanceurs
Un nuanceur peut être considéré comme un ensemble d’instructions qui déterminent la façon dont la surface d’un objet apparaît lors du rendu. Ceux qui sont programmés à l’aide de HLSL sont appelés nuanceurs HLSL. Les fichiers de code source pour les nuanceurs [HLSL])(#hlsl) ont l’extension .hlsl
de fichier. Ces nuanceurs peuvent être compilés au moment de la génération ou au moment de l’exécution et définis au moment de l’exécution dans l’étape de pipeline appropriée. Un objet nuanceur compilé a une .cso
extension de fichier.
Les nuanceurs Direct3D 9 peuvent être conçus à l’aide du modèle de nuanceur 1, du modèle de nuanceur 2 et du modèle de nuanceur 3 ; Les nuanceurs Direct3D 10 peuvent être conçus uniquement sur le modèle de nuanceur 4. Les nuanceurs Direct3D 11 peuvent être conçus sur le modèle de nuanceur 5. Direct3D 11.3 et Direct3D 12 peuvent être conçus sur le modèle de nuanceur 5.1, et Direct3D 12 peut également être conçu sur le modèle de nuanceur 6.
Nuanceurs de vertex et nuanceurs de pixels
Les données entrent dans le pipeline graphique en tant que flux de primitives et sont traitées par différents nuanceurs tels que les nuanceurs de vertex et les nuanceurs de pixels.
Les nuanceurs de vertex traitent les sommets, effectuant généralement des opérations telles que les transformations, la peau et l’éclairage. Les nuanceurs de pixels permettent de riches techniques d’ombrage telles que l’éclairage par pixel et le post-traitement. Il combine des variables constantes, des données de texture, des valeurs interpolées par vertex et d’autres données pour produire des sorties par pixel.
Étapes du nuanceur
Une séquence de ces différents nuanceurs définis pour traiter ce flux de primitives est appelée étapes de nuanceur dans un pipeline de rendu. Les étapes réelles dépendent de la version de Direct3D, mais incluent généralement les étapes de vertex, de géométrie et de pixels. Il existe également d’autres étapes, telles que les nuanceurs de coque et de domaine pour la pavage, et le nuanceur de calcul. Toutes ces étapes sont entièrement programmables à l’aide de HLSL. Pour plus d’informations, consultez pipeline Graphics.
Différents formats de fichier de nuanceur
Voici les extensions de fichier de code du nuanceur.
- Un fichier avec l’extension
.hlsl
contient le code source [HLSL])(#hlsl). - Un fichier avec l’extension
.cso
contient un objet nuanceur compilé. - Un fichier avec l’extension
.h
est un fichier d’en-tête, mais dans un contexte de code de nuanceur, ce fichier d’en-tête définit un tableau d’octets qui contient des données de nuanceur. - Un fichier avec l’extension
.hlsli
contient le format des mémoires tampons constantes. Dans l’exemple de jeu, le fichier est Shaders>ConstantBuffers.hlsli.
Remarque
Vous incorporez un nuanceur en chargeant un .cso
fichier au moment de l’exécution ou en ajoutant un .h
fichier dans votre code exécutable. Mais vous n’utiliseriez pas les deux pour le même nuanceur.
Compréhension approfondie de DirectX
Direct3D 11 est un ensemble d’API qui peuvent nous aider à créer des graphiques pour des applications graphiques intensives telles que des jeux, où nous voulons disposer d’une bonne carte graphique pour traiter des calculs intensifs. Cette section décrit brièvement les concepts de programmation graphique Direct3D 11 : ressource, sous-ressource, appareil et contexte d’appareil.
Ressource
Vous pouvez considérer les ressources (également appelées ressources d’appareil) comme des informations sur le rendu d’un objet, comme la texture, la position ou la couleur. Les ressources fournissent des données au pipeline et définissent ce qui est rendu pendant votre scène. Les ressources peuvent être chargées à partir de votre média de jeu ou créées dynamiquement au moment de l’exécution.
En fait, une ressource est une zone en mémoire accessible par le pipeline Direct3D. Pour permettre un accès efficace du pipeline à la mémoire, les données fournies au pipeline (géométrie d’entrée, ressources des nuanceurs et textures) doivent être stockées dans une ressource. Il existe deux types de ressource dont dérivent l’ensemble des ressources Direct3D : la mémoire tampon et la texture. Jusqu’à 128 ressources peuvent être actives à chaque phase du pipeline. Pour plus d’informations, consultez Ressources.
Sous-ressource
Le terme sous-ressource fait référence à un sous-ensemble d’une ressource. Direct3D peut référencer une ressource entière ou référencer des sous-ensembles d’une ressource. Pour plus d’informations, consultez Subresource.
Gabarit de profondeur
Une ressource de gabarit de profondeur contient le format et la mémoire tampon pour contenir les informations de profondeur et de gabarit. Elle est créée à l’aide d’une ressource de texture. Pour plus d’informations sur la création d’une ressource de gabarit de profondeur, consultez Configuration des fonctionnalités de profondeur-gabarit. Nous accédons à la ressource de gabarit de profondeur via la vue profondeur-gabarit implémentée à l’aide de l’interface ID3D11DepthStencilView .
Les informations de profondeur nous indiquent quelles zones de polygones se trouvent derrière d’autres, afin que nous puissions déterminer celles qui sont masquées. Les informations de gabarit nous indiquent quels pixels sont masqués. Il peut être utilisé pour produire des effets spéciaux, car il détermine si un pixel est dessiné ou non ; définit le bit sur 1 ou 0.
Pour plus d’informations, consultez la vue de gabarit de profondeur, la mémoire tampon de profondeur et la mémoire tampon de gabarit.
Afficher la cible
Une cible de rendu est une ressource que nous pouvons écrire à la fin d’une passe de rendu. Il est généralement créé à l’aide de la méthode ID3D11Device ::CreateRenderTargetView à l’aide de la mémoire tampon de la chaîne d’échange (qui est également une ressource) comme paramètre d’entrée.
Chaque cible de rendu doit également avoir une vue de gabarit de profondeur correspondante, car lorsque nous utilisons OMSetRenderTargets pour définir la cible de rendu avant de l’utiliser, elle nécessite également une vue de gabarit de profondeur. Nous accédons à la ressource de cible de rendu via la vue cible de rendu implémentée à l’aide de l’interface ID3D11RenderTargetView .
Appareil
Vous pouvez imaginer un appareil comme un moyen d’allouer et de détruire des objets, de restituer des primitives et de communiquer avec la carte graphique via le pilote graphique.
Pour une explication plus précise, un appareil Direct3D est le composant de rendu de Direct3D. Un périphérique encapsule et stocke l’état de rendu, effectue des transformations et des opérations d’éclairage, et rastérise une image sur une surface. Pour plus d’informations, consultez Appareils
Un appareil est représenté par l’interface ID3D11Device . En d’autres termes, l’interface ID3D11Device représente une carte d’affichage virtuelle et est utilisée pour créer des ressources appartenant à un appareil.
Il existe différentes versions d’ID3D11Device. ID3D11Device5 est la dernière version et ajoute de nouvelles méthodes à celles de ID3D11Device4. Pour plus d’informations sur la façon dont Direct3D communique avec le matériel sous-jacent, consultez l’architecture du modèle de pilote de périphérique Windows (WDDM).
Chaque application doit avoir au moins un appareil ; la plupart des applications ne créent qu’une seule. Créez un appareil pour l’un des pilotes matériels installés sur votre ordinateur en appelant D3D11CreateDevice ou D3D11CreateDeviceAndSwapChain et en spécifiant le type de pilote avec l’indicateur D3D_DRIVER_TYPE. Chaque appareil peut utiliser un ou plusieurs contextes d’appareil, en fonction de la fonctionnalité souhaitée. Pour plus d’informations, consultez la fonction D3D11CreateDevice.
Contexte d’appareil
Un contexte d’appareil est utilisé pour définir l’état du pipeline et générer des commandes de rendu à l’aide des ressources appartenant à un appareil.
Direct3D 11 implémente deux types de contextes d’appareil, un pour le rendu immédiat et l’autre pour le rendu différé ; les deux contextes sont représentés avec une interface ID3D11DeviceContext .
Les interfaces ID3D11DeviceContext ont des versions différentes ; ID3D11DeviceContext4 ajoute de nouvelles méthodes à celles dans ID3D11DeviceContext3.
ID3D11DeviceContext4 est introduit dans le Mise à jour Windows 10 Créateurs et est la dernière version de l’interface ID3D11DeviceContext. Les applications ciblant Mise à jour Windows 10 Créateurs et versions ultérieures doivent utiliser cette interface au lieu des versions antérieures. Pour plus d’informations, consultez ID3D11DeviceContext4.
DX ::D eviceResources
La classe DX ::D eviceResources se trouve dans les fichiers DeviceResources.cpp.h/ et contrôle toutes les ressources d’appareil DirectX.
Buffer
Une ressource de mémoire tampon est une collection de données entièrement typées regroupées en éléments. Vous pouvez utiliser des mémoires tampons pour stocker une grande variété de données, notamment des vecteurs de position, des vecteurs normaux, des coordonnées de texture dans une mémoire tampon de vertex, des index dans une mémoire tampon d’index ou un état d’appareil. Les éléments de mémoire tampon peuvent inclure des valeurs de données packées (telles que des valeurs de surface R8G8B8A8 ), des entiers 8 bits uniques ou quatre valeurs à virgule flottante 32 bits.
Il existe trois types de mémoires tampons disponibles : mémoire tampon de vertex, mémoire tampon d’index et mémoire tampon constante.
Mémoire tampon de vertex
Contient les données de vertex utilisées pour définir votre géométrie. Les données de vertex incluent les coordonnées de position, les données de couleur, les données de coordonnées de texture, les données normales, et ainsi de suite.
Mémoire tampon d’index
Contient des décalages entiers dans des mémoires tampons de vertex et sont utilisés pour afficher les primitives plus efficacement. Une mémoire tampon d’index contient un ensemble séquentiel d’index 16 bits ou 32 bits ; chaque index est utilisé pour identifier un sommet dans une mémoire tampon de vertex.
Mémoire tampon constante ou mémoire tampon de nuanceur
Vous permet de fournir efficacement des données de nuanceur au pipeline. Vous pouvez utiliser des mémoires tampons constantes en tant qu’entrées aux nuanceurs qui s’exécutent pour chaque primitive et stockent les résultats de l’étape de sortie de flux du pipeline de rendu. Conceptuellement, une mémoire tampon constante ressemble à une mémoire tampon de vertex à élément unique.
Conception et implémentation de mémoires tampons
Vous pouvez concevoir des mémoires tampons basées sur le type de données, par exemple, comme dans notre exemple de jeu, une mémoire tampon est créée pour les données statiques, une autre pour les données constantes sur l’image, et une autre pour les données spécifiques à une primitive.
Tous les types de mémoires tampons sont encapsulés par l’interface ID3D11Buffer et vous pouvez créer une ressource de mémoire tampon en appelant ID3D11Device ::CreateBuffer. Toutefois, une mémoire tampon doit être liée au pipeline avant d’être accessible. Les mémoires tampons peuvent être liées simultanément à plusieurs étapes de pipeline pour la lecture. Une mémoire tampon peut également être liée à une seule étape de pipeline pour l’écriture ; toutefois, la même mémoire tampon ne peut pas être liée simultanément à la lecture et à l’écriture.
Vous pouvez lier des mémoires tampons de ces manières.
- Pour l’étape d’assembleur d’entrée en appelant des méthodes ID3D11DeviceContext telles que ID3D11DeviceContext ::IASetVertexBuffers et ID3D11DeviceContext ::IASetIndexBuffer.
- Pour l’étape de sortie de flux en appelant ID3D11DeviceContext ::SOSetTargets.
- Pour l’étape du nuanceur en appelant des méthodes de nuanceur, telles que ID3D11DeviceContext ::VSSetConstantBuffers.
Pour plus d’informations, consultez Présentation des mémoires tampons dans Direct3D 11.
DXGI
Microsoft DirectX Graphics Infrastructure (DXGI) est un sous-système qui encapsule certaines des tâches de bas niveau nécessaires par Direct3D. Des précautions particulières doivent être prises lors de l’utilisation de DXGI dans une application multithread pour s’assurer que les interblocages ne se produisent pas. Pour plus d’informations, consultez Multithreading et DXGI
Niveau de fonctionnalité
Le niveau de fonctionnalité est un concept introduit dans Direct3D 11 pour gérer la diversité des cartes vidéo dans les machines nouvelles et existantes. Un niveau de fonctionnalité est un ensemble bien défini de fonctionnalités d’unité de traitement graphique (GPU).
Chaque carte vidéo implémente un certain niveau de fonctionnalités DirectX en fonction des GPU installés. Dans les versions antérieures de Microsoft Direct3D, vous pouvez découvrir la version de Direct3D la carte vidéo implémentée, puis programmer votre application en conséquence.
Avec le niveau de fonctionnalité, lorsque vous créez un appareil, vous pouvez tenter de créer un appareil pour le niveau de fonctionnalité que vous souhaitez demander. Si la création de l’appareil fonctionne, ce niveau de fonctionnalité existe, sinon, le matériel ne prend pas en charge ce niveau de fonctionnalité. Vous pouvez essayer de recréer un appareil à un niveau de fonctionnalité inférieur, ou vous pouvez choisir de quitter l’application. Par exemple, le niveau de fonctionnalité 12_0 nécessite Direct3D 11.3 ou Direct3D 12 et le modèle de nuanceur 5.1. Pour plus d’informations, consultez les niveaux de fonctionnalités Direct3D : Vue d’ensemble de chaque niveau de fonctionnalité.
À l’aide des niveaux de fonctionnalités, vous pouvez développer une application pour Direct3D 9, Microsoft Direct3D 10 ou Direct3D 11, puis l’exécuter sur du matériel 9, 10 ou 11 (avec certaines exceptions). Pour plus d’informations, consultez les niveaux de fonctionnalités Direct3D.
Rendu stéréo
Le rendu stéréo est utilisé pour améliorer l’illusion de profondeur. Il utilise deux images, l’une de l’œil gauche et l’autre de l’œil droit pour afficher une scène sur l’écran d’affichage.
Mathématiquement, nous appliquons une matrice de projection stéréo, qui est un léger décalage horizontal à droite et à gauche, de la matrice de projection mono régulière pour y parvenir.
Nous avons effectué deux passes de rendu pour obtenir un rendu stéréo dans cet exemple de jeu.
- Liez à la cible de rendu de droite, appliquez la projection droite, puis dessinez l’objet primitif.
- Liez à la cible de rendu gauche, appliquez la projection gauche, puis dessinez l’objet primitif.
Appareil photo et espace de coordonnées
Le jeu a le code en place pour mettre à jour le monde dans son propre système de coordonnées (parfois appelé espace du monde ou espace de scène). Tous les objets, y compris la caméra, sont positionnés et orientés dans cet espace. Pour plus d’informations, consultez Systèmes de coordonnées.
Un nuanceur de vertex effectue le gros travail de conversion des coordonnées du modèle en coordonnées d’appareil avec l’algorithme suivant (où V est un vecteur et M est une matrice).
V(device) = V(model) x M(model-to-world) x M(world-to-view) x M(view-to-device)
M(model-to-world)
est une matrice de transformation pour les coordonnées de modèle en coordonnées mondiales, également appelée matrice de transformation mondiale. Ceci est fourni par la primitive.M(world-to-view)
est une matrice de transformation pour les coordonnées mondiales pour afficher les coordonnées, également appelée matrice de transformation d’affichage.- Ceci est fourni par la matrice d’affichage de la caméra. Il est défini par la position de la caméra avec les vecteurs d’apparence (le vecteur de regard qui pointe directement dans la scène de la caméra, et le vecteur de recherche vers le haut perpendiculaire à celui-ci).
- Dans l’exemple de jeu, m_viewMatrix est la matrice de transformation d’affichage et est calculée à l’aide de Camera ::SetViewParams.
M(view-to-device)
est une matrice de transformation pour les coordonnées d’affichage en coordonnées d’appareil, également appelée matrice de transformation de projection.- Ceci est fourni par la projection de la caméra. Il fournit des informations sur la quantité de cet espace réellement visible dans la scène finale. Le champ d’affichage (FoV), les proportions et les plans de découpage définissent la matrice de transformation de projection.
- Dans l’exemple de jeu, m_projectionMatrix définit la transformation en coordonnées de projection, calculée à l’aide de Camera ::SetProjParams (pour la projection stéréo, vous utilisez deux matrices de projection, une pour la vue de chaque œil).
Le code du nuanceur est VertexShader.hlsl
chargé avec ces vecteurs et matrices à partir des mémoires tampons constantes et effectue cette transformation pour chaque vertex.
Transformation de coordonnées
Direct3D utilise trois transformations pour modifier vos coordonnées de modèle 3D en coordonnées de pixels (espace d’écran). Ces transformations sont la transformation mondiale, la transformation d’affichage et la transformation de projection. Pour plus d’informations, consultez Vue d’ensemble de la transformation.
Matrice de transformation mondiale
Une transformation mondiale change les coordonnées de l’espace du modèle, où les sommets sont définis par rapport à l’origine locale d’un modèle, à l’espace mondial, où les sommets sont définis par rapport à une origine commune à tous les objets d’une scène. En essence, le monde transforme un modèle dans le monde ; donc son nom. Pour plus d’informations, consultez La transformation mondiale.
Afficher la matrice de transformation
La transformation de vue localise la visionneuse dans l’espace du monde, transformant les sommets en espace de caméra. Dans l’espace de la caméra, l’appareil photo ou la visionneuse est à l’origine, en regardant dans la direction z positive. Pour plus d’informations, accédez à Afficher la transformation.
Matrice de transformation de projection
La transformation de projection convertit le frustum d’affichage en forme cuboïde. Un frustum d’affichage est un volume 3D dans une scène positionnée par rapport à la caméra de la fenêtre d’affichage. Une fenêtre d’affichage est un rectangle 2D dans lequel une scène 3D est projetée. Pour plus d’informations, consultez Affichage et découpage
Étant donné que la fin proche du frustum d’affichage est plus petite que l’extrémité éloignée, cela a l’effet de développer des objets proches de la caméra ; c’est ainsi que la perspective est appliquée à la scène. Ainsi, les objets plus proches du joueur apparaissent plus grands ; les objets qui sont plus éloignés apparaissent plus petits.
Mathématiquement, la transformation de projection est une matrice qui est généralement à la fois une échelle et une projection de perspective. Il fonctionne comme l’objectif d’une caméra. Pour plus d’informations, consultez Transformation de projection.
État de l’échantillonneur
L’état de l’échantillonneur détermine le mode d’échantillonnage des données de texture à l’aide des modes d’adressage de texture, du filtrage et du niveau de détail. L’échantillonnage est effectué chaque fois qu’un pixel de texture (ou texel) est lu à partir d’une texture.
Une texture contient un tableau de texels. La position de chaque texel est indiquée par (u,v)
, où u
est la largeur et v
la hauteur, et est mappée entre 0 et 1 en fonction de la largeur de texture et de la hauteur. Les coordonnées de texture résultantes sont utilisées pour traiter un texel lors de l’échantillonnage d’une texture.
Lorsque les coordonnées de texture sont inférieures à 0 ou supérieures à 1, le mode d’adresse de texture définit la façon dont la coordonnée de texture traite un emplacement texel. Par exemple, lors de l’utilisation de TextureAddressMode.Clamp, toute coordonnée en dehors de la plage 0-1 est limitée à une valeur maximale de 1 et la valeur minimale de 0 avant l’échantillonnage.
Si la texture est trop grande ou trop petite pour le polygone, la texture est filtrée pour ajuster l’espace. Un filtre d’agrandissement agrandit une texture, un filtre de minification réduit la texture pour s’adapter à une zone plus petite. L’agrandissement de texture répète l’exemple de texel pour une ou plusieurs adresses qui génère une image floue. La minification de texture est plus compliquée, car elle nécessite la combinaison de plusieurs valeurs texel en une seule valeur. Cela peut entraîner des alias ou des arêtes en jaquet en fonction des données de texture. L’approche la plus populaire pour la minification consiste à utiliser un mipmap. Un mipmap est une texture à plusieurs niveaux. La taille de chaque niveau est une puissance de 2 plus petite que le niveau précédent jusqu’à une texture 1x1. Lorsque la minification est utilisée, un jeu choisit le niveau mipmap le plus proche de la taille nécessaire au moment du rendu.
Classe BasicLoader
BasicLoader est une classe de chargeur simple qui prend en charge le chargement des nuanceurs, des textures et des maillages à partir de fichiers sur le disque. Il fournit à la fois des méthodes synchrones et asynchrones. Dans cet exemple de jeu, les BasicLoader.h/.cpp
fichiers se trouvent dans le dossier Utilitaires .
Pour plus d’informations, consultez Chargeur de base.