Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Remarque
Cette rubrique fait partie de la série de tutoriels Créer un jeu simple pour la plateforme universelle Windows (UWP) avec DirectX. La rubrique à ce lien définit le contexte de la série.
Jusqu’à présent, nous avons abordé comment structurer un jeu de 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 image, vous devez rendre les objets visibles dans la scène, selon la vue.
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.
Framework 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.
Commencez
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 termes 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 à afficher, 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 Définir l’infrastructure d’application UWP du jeu rubrique.
Méthode App::Initialize
La fonction std ::make_shared, comme indiqué ci-dessous, permet de créer un shared_ptr pour 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, afficher 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é. Les instructions pour le rendu commencent dans la méthode GameMain ::Run, comme indiqué ci-dessous.
Le flux simple est celui-ci.
- Mettre à jour
- Rendre
- Présent
Méthode '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.
}
Mise à jour
Consultez la rubrique gestion des flux de jeu pour plus d’informations sur la façon dont les états de jeu sont mis à jour dans la méthode GameMain ::Update.
Rendu
Le rendu est implémenté en appelant la méthode GameRenderer ::Render à partir de GameMain ::Run.
Si de rendu stéréo est activé, il existe deux passes de rendu : une pour l’œil gauche et une pour la droite. Dans chaque passe de rendu, nous liez la cible de rendu et la vue profondeur-gabarit à l’appareil. Nous effaçons également la vue profondeur-stencil ensuite.
Remarque
Le rendu stéréo peut être obtenu à l’aide d’autres méthodes telles que la stéréo à passage unique à l’aide d’instanciations de vertex ou de shader 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 selon chaque vue. Ensuite, nous avons configuré le pipeline de rendu graphique .
Remarque
Consultez créer et charger des ressources graphiques DirectX pour plus d’informations sur le chargement des ressources.
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.
Méthode GameRenderer::Render
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 tampon de sommet sont diffusées 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 le nuanceur de vertex étape de pipeline et le nuanceur de pixels étape du pipeline.
Remarque
Consultez Cadre de rendu II : rendu de jeu pour plus d’informations sur la définition des tampons constants.
Étant donné que la même disposition des entrées et l'ensemble de tampons constants sont utilisés pour tous les nuanceurs du pipeline, elle est configurée une fois par trame.
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 du vertex d’entrée pour que les données d’objet de maillage soient diffusées en continu à 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 primitive constante 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 de rendu aligne des commandes et des données pour l'unité de traitement graphique (GPU), selon la paramétrisation par les données du tampon constant. 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), comme la position de la caméra ; et certaines données sont spécifiques à la primitive, comme 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 Dessiner, cette commande est mise en file d’attente avec les données associées. Lorsque le jeu met à jour la mémoire tampon de constante primitive et émet la commande de dessin suivante, le pilote graphique ajoute cette commande 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.
Méthode 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éthode 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);
}
DeviceResources::Present, Méthode
Nous appelons la méthode DeviceResources::Present pour afficher le contenu que nous avons placé dans la mémoire tampon.
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é échange ou basculement. Pour plus d’informations, consultez swap chains.
- La méthode Present de l'interface IDXGISwapChain1 indique à DXGI de bloquer jusqu'à ce que la synchronisation verticale (VSync) se produise, en mettant l'application en pause 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 rejette 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 rectangles sales ou de défilement sont utilisés, il faut supprimer cet appel.
- En utilisant 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 du retrait 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 de retrait de périphériques dans Direct3D 11.
Conseil / Astuce
Pour obtenir une fréquence d’images fluide, vous devez vous assurer que la quantité de travail à effectuer pour afficher une image correspond au temps entre les synchronisations verticales.
// 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, U 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 la scène image par image, la faisant prendre vie sur votre écran.
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.
- shaders.
- Nuanceurs de vertex et de pixels.
- étapes du nuanceur.
- différents formats de fichiers shader.
Pour plus d’informations, consultez Comprendre le pipeline de rendu de Direct3D 11 et pipeline graphique.
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.
Ombreurs
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 shaders [HLSL](#hlsl) portent l'extension de fichier .hlsl
. Ces shaders peuvent être compilés à la compilation ou à l'exécution, et configurés à l'exécution dans l'étape de pipeline appropriée. Un objet nuanceur compilé a une extension de fichier .cso
.
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 déformation par 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 shader
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 tessellation, et de calcul. Toutes ces étapes sont entièrement programmables à l’aide de HLSL. Pour plus d’informations, consultez pipeline graphique.
Différents formats de fichier de shader
Voici les extensions de fichier de code de shader.
- 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 fichier .cso
au moment de l’exécution ou en ajoutant un fichier .h
dans votre code exécutable. Mais vous n’utiliseriez pas les deux pour le même shader.
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 dans 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 que le pipeline puisse accéder efficacement à la mémoire, les données fournies au pipeline (telles que la géométrie d’entrée, les ressources de nuanceur et les textures) doivent être stockées dans une ressource. Il existe deux types de ressources dont dérivent toutes les ressources Direct3D : une mémoire tampon ou une texture. Jusqu’à 128 ressources peuvent être actives pour chaque phase de pipeline. Pour plus d’informations, consultez Resources.
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 la sous-ressource .
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 Depth-Stencil. Nous accédons à la ressource de gabarit de profondeur par le biais de la vue de gabarit de profondeur 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 vue profondeur-stencil, mémoire tampon de profondeuret mémoire tampon stencil.
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 appareil encapsule et stocke l’état de rendu, effectue des transformations et des opérations d’éclairage, et raté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 sert à 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 'architecture wdDM (Windows Device Driver Model).
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 de l’appareil
Un contexte de l'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 de ID3D11DeviceContext3.
ID3D11DeviceContext4 est introduit dans Windows 10 Creators Update et est la dernière version de l’interface ID3D11DeviceContext. Les applications ciblant Windows 10 Creators Update 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.
Mémoire tampon
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 sommet
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
Permet de fournir efficacement des données de shader 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
Vous pouvez lier des mémoires tampons de ces manières.
- Pour l'étape d'assemblage des entrées en appelant des méthodes telles que ID3D11DeviceContext, ID3D11DeviceContext::IASetVertexBuffers et ID3D11DeviceContext::IASetIndexBuffer.
- Pour parvenir à l'étape de sortie du flux en appelant ID3D11DeviceContext::SOSetTargets.
- À 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 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 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.
- Lier à la cible de rendu de droite, appliquer la projection à droite, puis dessiner 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 des coordonnées du modèle en coordonnées mondiales, également appelée matrice de transformation du monde . Ceci est fourni par la primitive. -
M(world-to-view)
est une matrice de transformation des coordonnées du monde vers les coordonnées de vue, également appelée matrice de transformation de vue .- Ceci est fourni par la matrice d’affichage de la caméra. Il est défini par la position de l’appareil photo avec les vecteurs visuels (le vecteur regarder vers qui pointe directement dans la scène depuis l'appareil photo, et le vecteur regarder vers le haut qui est perpendiculaire et dirigé vers le haut par rapport à 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 dans VertexShader.hlsl
est 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 du monde, la transformation de vue 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. Essentiellement, la transformation mondiale place un modèle dans le monde; d'où son nom. Pour plus d’informations, consultez Transformation du monde.
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, orientée vers la direction positive de l’axe z. 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 fenêtres d’affichage et de découpage
Étant donné que la fin proche du frustum d’affichage est plus petite que l’extrémité éloignée, cela a pour effet d'agrandir les 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 la 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
est la hauteur, et est mappée entre 0 et 1 en fonction de la largeur et de la hauteur de texture. 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, lorsque vous utilisez 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 le texel d'échantillon pour une ou plusieurs adresses, ce qui produit une image plus 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 de l'aliasing ou des bords dentelés 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'à atteindre 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 de nuanceurs, de textures et de maillages à partir de fichiers sur disque. Il fournit à la fois des méthodes synchrones et asynchrones. Dans cet exemple de jeu, les fichiers