VIII. Tampon de profondeurs▲
VIII-A. Introduction▲
Le modèle que nous affichons est certes projeté dans un monde 3D, mais il est complètement plat. Dans ce chapitre, nous allons ajouter une coordonnée Z aux positions afin de pouvoir afficher des modèles 3D. Nous allons utiliser cette troisième coordonnée pour placer un carré au-dessus du carré déjà existant et voir apparaître un problème lorsque les modèles ne sont pas triés selon leur profondeur.
VIII-B. Géométrie en 3D▲
Mettez à jour la structure Vertex pour utiliser des vecteurs 3D pour la position. Modifiez aussi le format dans la structure VkVertexInputAttributeDescription correspondant aux coordonnées :
struct
Vertex {
glm::
vec3 pos;
glm::
vec3 color;
glm::
vec2 texCoord;
...
static
std::
array<
VkVertexInputAttributeDescription, 3
>
getAttributeDescriptions() {
std::
array<
VkVertexInputAttributeDescription, 3
>
attributeDescriptions{}
;
attributeDescriptions[0
].binding =
0
;
attributeDescriptions[0
].location =
0
;
attributeDescriptions[0
].format =
VK_FORMAT_R32G32B32_SFLOAT;
attributeDescriptions[0
].offset =
offsetof
(Vertex, pos);
...
}
}
;
Modifiez le vertex shader pour accepter comme entrée des coordonnées 3D. N’oubliez pas de le recompiler après modification !
layout
(
location =
0
) in
vec3
inPosition;
...
void
main
(
) {
gl_Position
=
ubo.proj *
ubo.view *
ubo.model *
vec4
(
inPosition, 1
.0
);
fragColor =
inColor;
fragTexCoord =
inTexCoord;
}
Finalement, ajoutez la profondeur dans les données des sommets :
const
std::
vector<
Vertex>
vertices =
{
{{-
0.5
f, -
0.5
f, 0.0
f}
, {
1.0
f, 0.0
f, 0.0
f}
, {
0.0
f, 0.0
f}}
,
{{
0.5
f, -
0.5
f, 0.0
f}
, {
0.0
f, 1.0
f, 0.0
f}
, {
1.0
f, 0.0
f}}
,
{{
0.5
f, 0.5
f, 0.0
f}
, {
0.0
f, 0.0
f, 1.0
f}
, {
1.0
f, 1.0
f}}
,
{{-
0.5
f, 0.5
f, 0.0
f}
, {
1.0
f, 1.0
f, 1.0
f}
, {
0.0
f, 1.0
f}}
}
;
Si vous lancez l'application, vous verrez exactement le même résultat qu’auparavant. Il est temps d'ajouter une autre géométrie pour rendre la scène plus intéressante et surtout, pour montrer le problème que nous résolvons dans ce chapitre. Dupliquez les sommets pour définir la position d’un second carré qui sera juste au-dessus du carré actuel :
Utilisez la valeur -0.5f comme profondeur du second carré :
const
std::
vector<
Vertex>
vertices =
{
{{-
0.5
f, -
0.5
f, 0.0
f}
, {
1.0
f, 0.0
f, 0.0
f}
, {
0.0
f, 0.0
f}}
,
{{
0.5
f, -
0.5
f, 0.0
f}
, {
0.0
f, 1.0
f, 0.0
f}
, {
1.0
f, 0.0
f}}
,
{{
0.5
f, 0.5
f, 0.0
f}
, {
0.0
f, 0.0
f, 1.0
f}
, {
1.0
f, 1.0
f}}
,
{{-
0.5
f, 0.5
f, 0.0
f}
, {
1.0
f, 1.0
f, 1.0
f}
, {
0.0
f, 1.0
f}}
,
{{-
0.5
f, -
0.5
f, -
0.5
f}
, {
1.0
f, 0.0
f, 0.0
f}
, {
0.0
f, 0.0
f}}
,
{{
0.5
f, -
0.5
f, -
0.5
f}
, {
0.0
f, 1.0
f, 0.0
f}
, {
1.0
f, 0.0
f}}
,
{{
0.5
f, 0.5
f, -
0.5
f}
, {
0.0
f, 0.0
f, 1.0
f}
, {
1.0
f, 1.0
f}}
,
{{-
0.5
f, 0.5
f, -
0.5
f}
, {
1.0
f, 1.0
f, 1.0
f}
, {
0.0
f, 1.0
f}}
}
;
const
std::
vector<
uint16_t>
indices =
{
0
, 1
, 2
, 2
, 3
, 0
,
4
, 5
, 6
, 6
, 7
, 4
}
;
Si vous lancez le programme maintenant, vous obtiendrez quelque chose rappelant une illustration de Maurits Cornelis Escher :
Le problème apparaît, car les fragments du carré inférieur sont écrits par-dessus les valeurs du carré supérieur. Cet ordre dépend de l’ordre de rendu des carrés. Il y a deux manières de régler ce problème :
- trier nos géométries en fonction de la profondeur ;
- utiliser un tampon de profondeur.
La première approche est communément utilisée pour l'affichage d'objets transparents, car la transparence indépendante de l’ordre d’affichage est un problème difficile à résoudre. Toutefois, la problématique du tri des géométries est réglée en utilisant un tampon de profondeur (depth buffer). Un tampon de profondeur est une attache supplémentaire conservant la profondeur de chaque position, à l’instar d’une attache de couleurs qui sauvegarde la couleur pour chaque position. Chaque fois que le rastériseur produit un fragment, sa profondeur est lue pour savoir si le nouveau fragment se situe devant ou derrière le fragment déjà en place. S’il est plus loin, le fragment est ignoré. Si le fragment est plus près et qu’il réussit donc le test de profondeur, sa valeur est alors inscrite dans le tampon de profondeur. Il est possible de manipuler cette valeur dans le fragment shader, tout comme vous pouvez manipuler la couleur.
#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include
<glm/glm.hpp>
#include
<glm/gtc/matrix_transform.hpp>
La matrice de projection générée par GLM utilise par défaut l’échelle de profondeur d’OpenGL comprise en -1.0 et 1.0. Nous devons configurer GLM avec GLM_FORCE_DEPTH_ZERO_TO_ONE pour qu'elle utilise les valeurs correspondantes à Vulkan (soit, sur l’échelle 0.0 à 1.0).
VIII-C. Image de profondeur et vue▲
L’attache pour la profondeur repose sur une image, tout comme l’attache des couleurs. La différence est que celle-ci n'est pas créée automatiquement par la « swap chain ». Nous n’avons besoin que d’une seule image de profondeur, car il n’y a qu’une seule opération de rendu à la fois. L’image de profondeur nécessite le classique triplet de ressources : image, mémoire et vue d’image.
VkImage depthImage;
VkDeviceMemory depthImageMemory;
VkImageView depthImageView;
Créez une nouvelle fonction nommée createDepthResources pour mettre en place ces ressources :
void
initVulkan() {
...
createCommandPool();
createDepthResources();
createTextureImage();
...
}
...
void
createDepthResources() {
}
La création d'une image de profondeur est assez simple. Elle doit avoir la même résolution que l’attache des couleurs, définie par la zone d’échange de la « swap chain ». Son utilisation doit correspondre à une attache de profondeur, elle doit avoir un tiling optimal et une mémoire provenant du périphérique local. La seule question est : quel est le bon format pour une image de profondeur ? Le format doit contenir des profondeurs, indiquées par _D??_ dans les valeurs VK_FORMAT_.
Contrairement à la texture, nous n'avons pas nécessairement besoin d’un format précis, car nous n’accédons pas aux texels directement. L’image de profondeur doit avoir une précision suffisante : les applications classiques utilisent 24 bits de précision. Plusieurs formats correspondent à ce besoin :
- VK_FORMAT_D32_SFLOAT : des nombres flottants sur 32 bits pour la profondeur ;
- VK_FORMAT_D32_SFLOAT_S8_UINT : des nombres flottants signés sur 32 bits pour la profondeur et un entier non signé sur 8 bits pour le tampon de pochoir ;
- VK_FORMAT_D24_UNORM_S8_UINT : des nombres flottants signés sur 24 bits pour la profondeur et des entiers non signés sur 8 bits pour le tampon de pochoir.
La partie pour le tampon de pochoir est utilisée dans le test de pochoir (stencil). C'est un test additionnel qui peut être combiné avec le test de profondeur. Nous y reviendrons dans un prochain chapitre.
Nous pourrions nous contenter d'utiliser le format VK_FORMAT_D32_SFLOAT, car son support est pratiquement assuré (consultez la base de données des matériels), mais c’est toujours un plus d’écrire une application flexible. Dans ce but, créez une fonction nommée findSupportedFormat. Elle recevra une liste des formats candidats, du plus intéressant au moins intéressant et vérifiera quel est le premier format supporté par le matériel :
VkFormat findSupportedFormat(const
std::
vector<
VkFormat>&
candidates, VkImageTiling tiling, VkFormatFeatureFlags features) {
}
Le support de tel ou tel format dépend du mode de tiling et de l’utilisation. C’est pourquoi nous les passons en paramètres à la fonction. Le support d’un format donné peut être obtenu grâce à la fonction vkGetPhysicalDeviceFormatProperties() :
for
(VkFormat format : candidates) {
VkFormatProperties props;
vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &
props);
}
La structure VkFormatProperties contient trois champs :
- linearTilingFeatures : cas d’utilisations supportés avec le tiling linéaire ;
- optimalTilingFeatures : cas d’utilisations supportés avec le tiling optimal ;
- bufferFeatures : cas d’utilisations supportés avec les tampons.
Seuls les deux premiers cas nous intéressent ici. Nous vérifierons l’un ou l’autre suivant le mode de tiling fourni en paramètre :
if
(tiling ==
VK_IMAGE_TILING_LINEAR &&
(props.linearTilingFeatures &
features) ==
features) {
return
format;
}
else
if
(tiling ==
VK_IMAGE_TILING_OPTIMAL &&
(props.optimalTilingFeatures &
features) ==
features) {
return
format;
}
Si aucun des candidats ne supporte l'utilisation souhaitée, nous pouvons soit retourner une valeur particulière, soit lever une exception :
VkFormat findSupportedFormat(const
std::
vector<
VkFormat>&
candidates, VkImageTiling tiling, VkFormatFeatureFlags features) {
for
(VkFormat format : candidates) {
VkFormatProperties props;
vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &
props);
if
(tiling ==
VK_IMAGE_TILING_LINEAR &&
(props.linearTilingFeatures &
features) ==
features) {
return
format;
}
else
if
(tiling ==
VK_IMAGE_TILING_OPTIMAL &&
(props.optimalTilingFeatures &
features) ==
features) {
return
format;
}
}
throw
std::
runtime_error("Échec pour trouver un format supporté !"
);
}
Nous allons utiliser cette fonction depuis la fonction findDepthFormat() pour sélectionner un format avec une composante pour la profondeur qui supporte une utilisation dans une attache de profondeur :
VkFormat findDepthFormat() {
return
findSupportedFormat(
{
VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT}
,
VK_IMAGE_TILING_OPTIMAL,
VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT
);
}
Assurez-vous d’utiliser un indicateur VK_FORMAT_FEATURE_ et non VK_IMAGE_USAGE_. Tous les candidats contiennent une composante pour la profondeur, mais les deux derniers contiennent aussi une composante pour le pochoir. Pour le moment, nous ne l’utilisons pas, mais nous devons prendre cet aspect en compte lors des transitions d’agencement des images ayant de tels formats. Ajoutez une simple fonction qui nous indique si le format choisi pour la profondeur contient une composante de pochoir :
bool
hasStencilComponent(VkFormat format) {
return
format ==
VK_FORMAT_D32_SFLOAT_S8_UINT ||
format ==
VK_FORMAT_D24_UNORM_S8_UINT;
}
Appelez cette fonction depuis la fonction createDepthResources() pour déterminer le format de profondeur :
VkFormat depthFormat =
findDepthFormat();
Nous avons maintenant toutes les informations nécessaires pour appeler nos fonctions createImage() et createImageView() :
createImage(swapChainExtent.width, swapChainExtent.height, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, depthImage, depthImageMemory);
depthImageView =
createImageView(depthImage, depthFormat);
Cependant, la fonction createImageView() suppose que la sous-ressource (subresource) est toujours VK_IMAGE_ASPECT_COLOR_BIT. Ce champ devient donc un paramètre :
VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags) {
...
viewInfo.subresourceRange.aspectMask =
aspectFlags;
...
}
Modifiez tous les appels à cette fonction pour passer la bonne valeur au nouveau paramètre :
swapChainImageViews[i] =
createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT);
...
depthImageView =
createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT);
...
textureImageView =
createImageView(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_ASPECT_COLOR_BIT);
Voilà, pour la création de l'image de profondeur. Nous n'avons pas besoin d'y envoyer de données ou d’y copier une image, car nous allons l’initialiser au début de la passe de rendu, comme nous le faisons pour l’attache des couleurs.
VIII-C-1. Transition explicite de l’image de profondeur▲
Nous n'avons pas besoin d’effectuer une transition de l’agencement de l’image vers une attache de profondeur, car nous allons le faire dans la passe de rendu. Toutefois et pour des fins de complétude, je vais décrire le processus dans cette section. Vous pouvez la passer, si vous le souhaitez.
Appelez la fonction transitionImageLayout() à la fin de la fonction createDepthResources() :
transitionImageLayout(depthImage, depthFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
L’agencement indéfini peut être utilisé comme agencement initial, car il n’y a pas de contenu important dans l’image de profondeur. Nous devons mettre à jour la logique de la fonction transitionImageLayout() pour utiliser le bon aspect de sous-ressource ;
if
(newLayout ==
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) {
barrier.subresourceRange.aspectMask =
VK_IMAGE_ASPECT_DEPTH_BIT;
if
(hasStencilComponent(format)) {
barrier.subresourceRange.aspectMask |=
VK_IMAGE_ASPECT_STENCIL_BIT;
}
}
else
{
barrier.subresourceRange.aspectMask =
VK_IMAGE_ASPECT_COLOR_BIT;
}
Même si nous n'utilisons pas le composant de pochoir, nous devons l’inclure dans les transitions d’agencement de l’image de profondeur.
Enfin, ajoutez les bons masques d’accès et les étapes du pipeline :
if
(oldLayout ==
VK_IMAGE_LAYOUT_UNDEFINED &&
newLayout ==
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
barrier.srcAccessMask =
0
;
barrier.dstAccessMask =
VK_ACCESS_TRANSFER_WRITE_BIT;
sourceStage =
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
destinationStage =
VK_PIPELINE_STAGE_TRANSFER_BIT;
}
else
if
(oldLayout ==
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL &&
newLayout ==
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
barrier.srcAccessMask =
VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.dstAccessMask =
VK_ACCESS_SHADER_READ_BIT;
sourceStage =
VK_PIPELINE_STAGE_TRANSFER_BIT;
destinationStage =
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
}
else
if
(oldLayout ==
VK_IMAGE_LAYOUT_UNDEFINED &&
newLayout ==
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) {
barrier.srcAccessMask =
0
;
barrier.dstAccessMask =
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
sourceStage =
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
destinationStage =
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
}
else
{
throw
std::
invalid_argument("Transition d’agencement non supportée !"
);
}
Le tampon de profondeur sera lu lors du test de profondeur pour savoir si un fragment est visible et sera écrit lors du rendu d’un nouveau fragment. La lecture se produit à l’étage VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT et l'écriture à l’étape VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT. Vous devez choisir la première des étapes correspondant à l'opération indiquée, afin que tout soit prêt lors de l’utilisation de l’attache de profondeur.
VIII-D. Passe de rendu▲
Nous allons modifier la fonction createRenderPass() pour inclure une attache de profondeur. Définissez d'abord la description grâce à la structure VkAttachementDescription :
VkAttachmentDescription depthAttachment{}
;
depthAttachment.format =
findDepthFormat();
depthAttachment.samples =
VK_SAMPLE_COUNT_1_BIT;
depthAttachment.loadOp =
VK_ATTACHMENT_LOAD_OP_CLEAR;
depthAttachment.storeOp =
VK_ATTACHMENT_STORE_OP_DONT_CARE;
depthAttachment.stencilLoadOp =
VK_ATTACHMENT_LOAD_OP_DONT_CARE;
depthAttachment.stencilStoreOp =
VK_ATTACHMENT_STORE_OP_DONT_CARE;
depthAttachment.initialLayout =
VK_IMAGE_LAYOUT_UNDEFINED;
depthAttachment.finalLayout =
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
Le format doit être celui de l'image de profondeur. Cette fois, nous ne garderons pas les données de profondeur (storeOp), car nous n'en avons plus besoin après le rendu. Cela pourrait permettre au matériel d’effectuer des optimisations. Tout comme pour le tampon de couleurs, nous n’avons pas besoin des valeurs du rendu précédent. Nous pouvons donc utiliser VK_IMAGE_LAYOUT_UNDEFINED comme valeur pour initialLayout.
VkAttachmentReference depthAttachmentRef{}
;
depthAttachmentRef.attachment =
1
;
depthAttachmentRef.layout =
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
Ajoutez une référence à l’attache dans notre première (et unique) sous-passe :
VkSubpassDescription subpass{}
;
subpass.pipelineBindPoint =
VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount =
1
;
subpass.pColorAttachments =
&
colorAttachmentRef;
subpass.pDepthStencilAttachment =
&
depthAttachmentRef;
Contrairement aux attaches de couleurs, une sous-passe ne peut avoir qu’une attache de profondeur (et de pochoir). En effet, aucun intérêt de réaliser plusieurs tests de profondeur sur plusieurs tampons.
std::
array<
VkAttachmentDescription, 2
>
attachments =
{
colorAttachment, depthAttachment}
;
VkRenderPassCreateInfo renderPassInfo{}
;
renderPassInfo.sType =
VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderPassInfo.attachmentCount =
static_cast
<
uint32_t>
(attachments.size());
renderPassInfo.pAttachments =
attachments.data();
renderPassInfo.subpassCount =
1
;
renderPassInfo.pSubpasses =
&
subpass;
renderPassInfo.dependencyCount =
1
;
renderPassInfo.pDependencies =
&
dependency;
Finalement, mettez à jour la structure VkRenderPassCreateInfo pour référencer ces deux attaches.
VIII-E. Tampon d’images▲
L'étape suivante va consister à modifier la création du tampon d’images pour lier notre image de profondeur à l’attache de profondeur. Dans la fonction createFramebuffers(), indiquez la vue de l’image de profondeur comme deuxième attache :
std::
array<
VkImageView, 2
>
attachments =
{
swapChainImageViews[i],
depthImageView
}
;
VkFramebufferCreateInfo framebufferInfo{}
;
framebufferInfo.sType =
VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
framebufferInfo.renderPass =
renderPass;
framebufferInfo.attachmentCount =
static_cast
<
uint32_t>
(attachments.size());
framebufferInfo.pAttachments =
attachments.data();
framebufferInfo.width =
swapChainExtent.width;
framebufferInfo.height =
swapChainExtent.height;
framebufferInfo.layers =
1
;
L’attache de couleurs change pour chaque image de la « swap chain », mais la même attache de profondeur peut être utilisée pour toutes les images, car une seule sous-passe s’exécute à la fois grâce à nos sémaphores.
Nous devons également déplacer l'appel à la fonction createFramebuffers() pour que la fonction soit appelée après la création de l'image de profondeur :
void
initVulkan() {
...
createDepthResources();
createFramebuffers();
...
}
VIII-F. Valeurs de nettoyage des tampons▲
Comme nous avons plusieurs attaches avec VK_ATTACHMENT_LOAD_OP_CLEAR, nous devons spécifier plusieurs valeurs de nettoyage. Allez à la fonction createCommandBuffers() et créez un tableau de la structure VkClearValue :
std::
array<
VkClearValue, 2
>
clearValues{}
;
clearValues[0
].color =
{
0.0
f, 0.0
f, 0.0
f, 1.0
f}
;
clearValues[1
].depthStencil =
{
1.0
f, 0
}
;
renderPassInfo.clearValueCount =
static_cast
<
uint32_t>
(clearValues.size());
renderPassInfo.pClearValues =
clearValues.data();
Avec Vulkan, l’échelle des valeurs des profondeurs dans le tampon est de 0.0 à 1.0, où 1.0 indique le plan lointain et 0.0, le plan proche. La valeur initiale de chaque point dans le tampon doit être au plus loin possible, donc 1.0.
Notez que l’ordre des valeurs de clearValues doit correspondre à l’ordre de vos attaches.
VIII-G. Configuration des tests profondeur et de pochoir▲
L’attache de profondeur est prête à être utilisée, mais le test de profondeur n'a pas encore été activé dans le pipeline. Il se configure grâce à la structure de type VkPipelineDepthStencilStateCreateInfo :
VkPipelineDepthStencilStateCreateInfo depthStencil{}
;
depthStencil.sType =
VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
depthStencil.depthTestEnable =
VK_TRUE;
depthStencil.depthWriteEnable =
VK_TRUE;
Le champ depthTestEnable indique si la profondeur des nouveaux fragments doit être comparée avec la profondeur présente dans le tampon afin de savoir si les fragments vont être ignorés. Le champ depthWriteEnable indique si la nouvelle profondeur des fragments ayant réussi le test doit être écrite dans le tampon de profondeur. C’est utile pour l’affichage d’objets transparents. En effet, les fragments d’un objet transparent doivent passer le test, mais ne doivent pas empêcher les objets derrière d’être dessinés.
depthStencil.depthCompareOp =
VK_COMPARE_OP_LESS;
Le champ depthCompareOp fournit le test de comparaison utilisé pour conserver ou éliminer les fragments. Comme nous sommes dans un cas classique où une petite valeur de profondeur indique un objet proche, la profondeur des nouveaux fragments doit être inférieure.
depthStencil.depthBoundsTestEnable =
VK_FALSE;
depthStencil.minDepthBounds =
0.0
f; // Optionnel
depthStencil.maxDepthBounds =
1.0
f; // Optionnel
Les champs depthBoundsTestEnable, minDepthBounds et maxDepthBounds sont utilisés pour des tests de profondeur encadrés. Ils permettent de ne garder que des fragments dont la profondeur est comprise entre deux valeurs fournies ici. Nous n'utiliserons pas cette fonctionnalité.
depthStencil.stencilTestEnable =
VK_FALSE;
depthStencil.front{}
; // Optionnel
depthStencil.back{}
; // Optionnel
Les trois derniers champs configurent les opérations liées au tampon de pochoir, que nous n'utiliserons pas non plus dans ce tutoriel. Si vous voulez l'utiliser, vous devrez vous assurer que le format sélectionné pour la profondeur contient aussi une composante pour le pochoir.
pipelineInfo.pDepthStencilState =
&
depthStencil;
Mettez à jour la structure VkGraphicsPipelineCreateInfo pour référencer l'état des tests de profondeur et de pochoir que nous venons de créer. Un tel état doit toujours être spécifié si la passe de rendu contient au moins une attache de profondeur.
Si vous lancez le programme, vous verrez que les géométries apparaissent correctement :
VIII-H. Gestion des redimensionnements de la fenêtre▲
La résolution du tampon de profondeur doit changer avec la taille de la fenêtre quand elle est redimensionnée, afin de correspondre à la taille de l’attache des couleurs. Étendez la fonction recreateSwapChain() pour régénérer les ressources liées à la profondeur :
void
recreateSwapChain() {
int
width =
0
, height =
0
;
while
(width ==
0
||
height ==
0
) {
glfwGetFramebufferSize(window, &
width, &
height);
glfwWaitEvents();
}
vkDeviceWaitIdle(device);
cleanupSwapChain();
createSwapChain();
createImageViews();
createRenderPass();
createGraphicsPipeline();
createDepthResources();
createFramebuffers();
createUniformBuffers();
createDescriptorPool();
createDescriptorSets();
createCommandBuffers();
}
La libération des ressources doit avoir lieu dans la fonction de libération de la « swap chain » :
void
cleanupSwapChain() {
vkDestroyImageView(device, depthImageView, nullptr
);
vkDestroyImage(device, depthImage, nullptr
);
vkFreeMemory(device, depthImageMemory, nullptr
);
...
}
Félicitations, votre application est maintenant capable d’afficher correctement n’importe quelle géométrie 3D. Nous allons essayer dans le prochain chapitre en affichant un modèle texturé !