551 lines
22 KiB
Markdown
551 lines
22 KiB
Markdown
## Introduction
|
|
|
|
Jusqu'à présent nous avons projeté notre géométrie en 3D, mais elle n'est toujours définie qu'en 2D. Nous allons ajouter
|
|
l'axe Z dans ce chapitre pour permettre l'utilisation de modèles 3D. Nous placerons un carré au-dessus ce celui que nous
|
|
avons déjà, et nous verrons ce qui se passe si la géométrie n'est pas organisée par profondeur.
|
|
|
|
## Géométrie en 3D
|
|
|
|
Mettez à jour la structure `Vertex` pour que les coordonnées soient des vecteurs à 3 dimensions. Il faut également
|
|
changer le champ `format` dans la structure `VkVertexInputAttributeDescription` correspondant aux coordonnées :
|
|
|
|
```c++
|
|
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);
|
|
|
|
...
|
|
}
|
|
};
|
|
```
|
|
|
|
Mettez également à jour l'entrée du vertex shader qui correspond aux coordonnées. Recompilez le shader.
|
|
|
|
```glsl
|
|
layout(location = 0) in vec3 inPosition;
|
|
|
|
...
|
|
|
|
void main() {
|
|
gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0);
|
|
fragColor = inColor;
|
|
fragTexCoord = inTexCoord;
|
|
}
|
|
```
|
|
|
|
Enfin, il nous faut ajouter la profondeur là où nous créons les instances de `Vertex`.
|
|
|
|
```c++
|
|
const std::vector<Vertex> vertices = {
|
|
{{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}},
|
|
{{0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}},
|
|
{{0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}},
|
|
{{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}}
|
|
};
|
|
```
|
|
|
|
Si vous lancez l'application vous verrez exactement le même résultat. Il est maintenant temps d'ajouter de la géométrie
|
|
pour rendre la scène plus intéressante, et pour montrer le problème évoqué plus haut. Dupliquez les vertices afin qu'un
|
|
second carré soit rendu au-dessus de celui que nous avons maintenant :
|
|
|
|

|
|
|
|
Nous allons utiliser `-0.5f` comme coordonnée Z.
|
|
|
|
```c++
|
|
const std::vector<Vertex> vertices = {
|
|
{{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}},
|
|
{{0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}},
|
|
{{0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}},
|
|
{{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}},
|
|
|
|
{{-0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}},
|
|
{{0.5f, -0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}},
|
|
{{0.5f, 0.5f, -0.5f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}},
|
|
{{-0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}}
|
|
};
|
|
|
|
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 verrez que le carré d'en-dessous est rendu au-dessus de l'autre :
|
|
|
|

|
|
|
|
Ce problème est simplement dû au fait que le carré d'en-dessous est placé après dans le tableau des vertices. Il y a
|
|
deux manières de régler ce problème :
|
|
|
|
* Trier tous les appels en fonction de la profondeur
|
|
* Utiliser un buffer de profondeur
|
|
|
|
La première approche est communément utilisée pour l'affichage d'objets transparents, car la transparence non ordonnée
|
|
est un problème difficile à résoudre. Cependant, pour la géométrie sans transparence, le buffer de profondeur est un
|
|
très bonne solution. Il consiste en un attachement supplémentaire au framebuffer, qui stocke les profondeurs. La
|
|
profondeur de chaque fragment produit par le rasterizer est comparée à la valeur déjà présente dans le buffer. Si le
|
|
fragment est plus distant que celui déjà traité, il est simplement éliminé. Il est possible de manipuler cette valeur de
|
|
la même manière que la couleur.
|
|
|
|
```c++
|
|
#define GLM_FORCE_RADIANS
|
|
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
|
|
#include <glm/glm.hpp>
|
|
#include <glm/gtc/matrix_transform.hpp>
|
|
```
|
|
|
|
La matrice de perspective générée par GLM utilise par défaut la profondeur OpenGL comprise en -1 et 1. Nous pouvons
|
|
configurer GLM avec `GLM_FORCE_DEPTH_ZERO_TO_ONE` pour qu'elle utilise des valeurs correspondant à Vulkan.
|
|
|
|
## Image de pronfondeur et views sur cette image
|
|
|
|
L'attachement de profondeur est une image. La différence est que celle-ci n'est pas créée par la swap chain. Nous
|
|
n'avons besoin que d'un seul attachement de profondeur, car les opérations sont séquentielles. L'attachement aura
|
|
encore besoin des trois mêmes ressources : une image, de la mémoire et une image view.
|
|
|
|
```c++
|
|
VkImage depthImage;
|
|
VkDeviceMemory depthImageMemory;
|
|
VkImageView depthImageView;
|
|
```
|
|
|
|
Créez une nouvelle fonction `createDepthResources` pour mettre en place ces ressources :
|
|
|
|
```c++
|
|
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'attachement de couleur,
|
|
définie par l'étendue de la swap chain. Elle doit aussi être configurée comme image de profondeur, avoir un tiling
|
|
optimal et une mémoire placée sur la carte graphique. Une question persiste : quelle est l'organisation optimale pour
|
|
une image de profondeur? Le format contient un composant de profondeur, indiqué par `_Dxx_` dans les valeurs de type
|
|
`VK_FORMAT`.
|
|
|
|
Au contraire de l'image de texture, nous n'avons pas besoin de déterminer le format requis car nous n'accéderons pas à
|
|
cette texture nous-mêmes. Nous n'avons besoin que d'une précision suffisante, en général un minimum de 24 bits. Il y a
|
|
plusieurs formats qui satisfont cette nécéssité :
|
|
|
|
* `VK_FORMAT_D32_SFLOAT` : float signé de 32 bits pour la profondeur
|
|
* `VK_FORMAT_D32_SFLOAT_S8_UINT` : float signé de 32 bits pour la profondeur et int non signé de 8 bits pour le stencil
|
|
* `VK_FORMAT_D24_UNORM_S8_UINT` : float signé de 24 bits pour la profondeur et int non signé de 8 bits pour le stencil
|
|
|
|
Le composant de stencil est utilisé pour le [test de stencil](https://en.wikipedia.org/wiki/Stencil_buffer). C'est un
|
|
test additionnel qui peut être combiné avec le test de profondeur. Nous y reviendrons dans un futur chapitre.
|
|
|
|
Nous pourrions nous contenter d'utiliser `VK_FORMAT_D32_SFLOAT` car son support est pratiquement assuré, mais il est
|
|
préférable d'utiliser une fonction pour déterminer le meilleur format localement supporté. Créez pour cela la fonction
|
|
`findSupportedFormat`. Elle vérifiera que les formats en argument sont supportés et choisira le meilleur en se basant
|
|
sur leur ordre dans le vecteurs des formats acceptables fourni en argument :
|
|
|
|
```c++
|
|
VkFormat findSupportedFormat(const std::vector<VkFormat>& candidates, VkImageTiling tiling, VkFormatFeatureFlags features) {
|
|
|
|
}
|
|
```
|
|
|
|
Leur support dépend du mode de tiling et de l'usage, nous devons donc les transmettre en argument. Le support des
|
|
formats peut ensuite être demandé à l'aide de la fonction `vkGetPhysicalDeviceFormatProperties` :
|
|
|
|
```c++
|
|
for (VkFormat format : candidates) {
|
|
VkFormatProperties props;
|
|
vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props);
|
|
}
|
|
```
|
|
|
|
La structure `VkFormatProperties` contient trois champs :
|
|
|
|
* `linearTilingFeatures` : utilisations supportées avec le tiling linéaire
|
|
* `optimalTilingFeatures` : utilisations supportées avec le tiling optimal
|
|
* `bufferFeatures` : utilisations supportées avec les buffers
|
|
|
|
Seuls les deux premiers cas nous intéressent ici, et celui que nous vérifierons dépendra du mode de tiling fourni en
|
|
paramètre.
|
|
|
|
```c++
|
|
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 désirée, nous pouvons lever une exception.
|
|
|
|
```c++
|
|
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("aucun des formats demandés n'est supporté!");
|
|
}
|
|
```
|
|
|
|
Nous allons utiliser cette fonction depuis une autre fonction `findDepthFormat`. Elle sélectionnera un format
|
|
avec un composant de profondeur qui supporte d'être un attachement de profondeur :
|
|
|
|
```c++
|
|
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
|
|
);
|
|
}
|
|
```
|
|
|
|
Utilisez bien `VK_FORMAT_FEATURE_` au lieu de `VK_IMAGE_USAGE_`. Tous les candidats contiennent la profondeur, mais
|
|
certains ont le stencil en plus. Ainsi il est important de voir que dans ce cas, la profondeur n'est qu'une *capacité*
|
|
et non un *usage* exclusif. Autre point, nous devons prendre cela en compte pour les transitions d'organisation. Ajoutez
|
|
une fonction pour determiner si le format contient un composant de stencil ou non :
|
|
|
|
```c++
|
|
bool hasStencilComponent(VkFormat format) {
|
|
return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT;
|
|
}
|
|
```
|
|
|
|
Appelez cette fonction depuis `createDepthResources` pour déterminer le format de profondeur :
|
|
|
|
```c++
|
|
VkFormat depthFormat = findDepthFormat();
|
|
```
|
|
|
|
Nous avons maintenant toutes les informations nécessaires pour invoquer `createImage` et `createImageView`.
|
|
|
|
```c++
|
|
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 cette fonction part du principe que la `subresource` est toujours `VK_IMAGE_ASPECT_COLOR_BIT`, il nous faut
|
|
donc en faire un paramètre.
|
|
|
|
```c++
|
|
VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags) {
|
|
...
|
|
viewInfo.subresourceRange.aspectMask = aspectFlags;
|
|
...
|
|
}
|
|
```
|
|
|
|
Changez également les appels à cette fonction pour prendre en compte ce changement :
|
|
|
|
```c++
|
|
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à tout pour la création de l'image de profondeur. Nous n'avons pas besoin d'y envoyer de données ou quoi que ce soit
|
|
de ce genre, car nous allons l'initialiser au début de la render pass tout comme l'attachement de couleur.
|
|
|
|
### Explicitement transitionner l'image de profondeur
|
|
|
|
Nous n'avons pas besoin de faire explicitement la transition du layout de l'image vers un attachement de profondeur parce
|
|
qu'on s'en occupe directement dans la render pass. En revanche, pour l'exhaustivité je vais quand même vous décrire le processus
|
|
dans cette section. Vous pouvez sauter cette étape si vous le souhaitez.
|
|
|
|
Faites un appel à `transitionImageLayout` à la fin de `createDepthResources` comme ceci:
|
|
|
|
```c++
|
|
transitionImageLayout(depthImage, depthFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
|
|
```
|
|
|
|
L'organisation indéfinie peut être utilisée comme organisation intiale, dans la mesure où aucun contenu d'origine n'a
|
|
d'importance. Nous devons faire évaluer la logique de `transitionImageLayout` pour qu'elle puisse utiliser la
|
|
bonne subresource.
|
|
|
|
```c++
|
|
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 stencil, nous devons nous en occuper dans les transitions de l'image de
|
|
profondeur.
|
|
|
|
Ajoutez enfin le bon accès et les bonnes étapes pipeline :
|
|
|
|
```c++
|
|
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'organisation non supportée!");
|
|
}
|
|
```
|
|
|
|
Le buffer de profondeur sera lu avant d'écrire un fragment, et écrit après qu'un fragment valide soit traité. La lecture
|
|
se passe en `VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT` et l'écriture en `VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT`.
|
|
Vous devriez choisir la première des étapes correspondant à l'opération correspondante, afin que tout soit prêt pour
|
|
l'utilisation de l'attachement de profondeur.
|
|
|
|
## Render pass
|
|
|
|
Nous allons modifier `createRenderPass` pour inclure l'attachement de profondeur. Spécifiez d'abord un
|
|
`VkAttachementDescription` :
|
|
|
|
```c++
|
|
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. Pour cette fois nous ne garderons pas les données de profondeur,
|
|
car nous n'en avons plus besoin après le rendu. Encore une fois le hardware pourra réaliser des optimisations. Et
|
|
de même nous n'avons pas besoin des valeurs du rendu précédent pour le début du rendu de la frame, nous pouvons donc
|
|
mettre `VK_IMAGE_LAYOUT_UNDEFINED` comme valeur pour `initialLayout`.
|
|
|
|
```c++
|
|
VkAttachmentReference depthAttachmentRef{};
|
|
depthAttachmentRef.attachment = 1;
|
|
depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
|
```
|
|
|
|
Ajoutez une référence à l'attachement dans notre seule et unique subpasse :
|
|
|
|
```c++
|
|
VkSubpassDescription subpass{};
|
|
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
|
subpass.colorAttachmentCount = 1;
|
|
subpass.pColorAttachments = &colorAttachmentRef;
|
|
subpass.pDepthStencilAttachment = &depthAttachmentRef;
|
|
```
|
|
|
|
Les subpasses ne peuvent utiliser qu'un seul attachement de profondeur (et de stencil). Réaliser le test de profondeur
|
|
sur plusieurs buffers n'a de toute façon pas beaucoup de sens.
|
|
|
|
```c++
|
|
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;
|
|
```
|
|
|
|
Changez enfin la structure `VkRenderPassCreateInfo` pour qu'elle se réfère aux deux attachements.
|
|
|
|
## Framebuffer
|
|
|
|
L'étape suivante va consister à modifier la création du framebuffer pour lier notre image de profondeur à l'attachement
|
|
de profondeur. Trouvez `createFramebuffers` et indiquez la view sur l'image de profondeur comme second attachement :
|
|
|
|
```c++
|
|
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'attachement de couleur doit différer pour chaque image de la swap chain, mais l'attachement de profondeur peut être le
|
|
même pour toutes, car il n'est utilisé que par la subpasse, et la synchronisation que nous avons mise en place ne permet
|
|
pas l'exécution de plusieurs subpasses en même temps.
|
|
|
|
Nous devons également déplacer l'appel à `createFramebuffers` pour que la fonction ne soit appelée qu'après la création
|
|
de l'image de profondeur :
|
|
|
|
```c++
|
|
void initVulkan() {
|
|
...
|
|
createDepthResources();
|
|
createFramebuffers();
|
|
...
|
|
}
|
|
```
|
|
|
|
## Supprimer les valeurs
|
|
|
|
Comme nous avons plusieurs attachements avec `VK_ATTACHMENT_LOAD_OP_CLEAR`, nous devons spécifier plusieurs valeurs de
|
|
suppression. Allez à `createCommandBuffers` et créez un tableau de `VkClearValue` :
|
|
|
|
```c++
|
|
std::array<VkClearValue, 2> clearValues{};
|
|
clearValues[0].color = {{0.0f, 0.0f, 0.0f, 1.0f}};
|
|
clearValues[1].depthStencil = {1.0f, 0};
|
|
|
|
renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
|
|
renderPassInfo.pClearValues = clearValues.data();
|
|
```
|
|
|
|
Avec Vulkan, `0.0` correspond au plan near et `1.0` au plan far. La valeur initiale doit donc être `1.0`, afin que tout
|
|
fragment puisse s'y afficher. Notez que l'ordre des `clearValues` correspond à l'ordre des attachements auquelles les
|
|
couleurs correspondent.
|
|
|
|
## État de profondeur et de stencil
|
|
|
|
L'attachement de profondeur est prêt à être utilisé, mais le test de profondeur n'a pas encore été activé. Il est
|
|
configuré à l'aide d'une structure de type `VkPipelineDepthStencilStateCreateInfo`.
|
|
|
|
```c++
|
|
VkPipelineDepthStencilStateCreateInfo depthStencil{};
|
|
depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
|
|
depthStencil.depthTestEnable = VK_TRUE;
|
|
depthStencil.depthWriteEnable = VK_TRUE;
|
|
```
|
|
|
|
Le champ `depthTestEnable` permet d'activer la comparaison de la profondeur des fragments. Le champ `depthWriteEnable`
|
|
indique si la nouvelle profondeur des fragments qui passent le test doivent être écrite dans le tampon de profondeur.
|
|
|
|
```c++
|
|
depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
|
|
```
|
|
|
|
Le champ `depthCompareOp` permet de fournir le test de comparaison utilisé pour conserver ou éliminer les fragments.
|
|
Nous gardons le `<` car il correspond le mieux à la convention employée par Vulkan.
|
|
|
|
```c++
|
|
depthStencil.depthBoundsTestEnable = VK_FALSE;
|
|
depthStencil.minDepthBounds = 0.0f; // Optionnel
|
|
depthStencil.maxDepthBounds = 1.0f; // Optionnel
|
|
```
|
|
|
|
Les champs `depthBoundsTestEnable`, `minDepthBounds` et `maxDepthBounds` sont utilisés pour des tests optionnels
|
|
d'encadrement de profondeur. Ils permettent de ne garder que des fragments dont la profondeur est comprise entre deux
|
|
valeurs fournies ici. Nous n'utiliserons pas cette fonctionnalité.
|
|
|
|
```c++
|
|
depthStencil.stencilTestEnable = VK_FALSE;
|
|
depthStencil.front = {}; // Optionnel
|
|
depthStencil.back = {}; // Optionnel
|
|
```
|
|
|
|
Les trois derniers champs configurent les opérations du buffer de stencil, 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 un composant pour le stencil.
|
|
|
|
```c++
|
|
pipelineInfo.pDepthStencilState = &depthStencil;
|
|
```
|
|
|
|
Mettez à jour la création d'une instance de `VkGraphicsPipelineCreateInfo` pour référencer l'état de profondeur et de
|
|
stencil que nous venons de créer. Un tel état doit être spécifié si la passe contient au moins l'une de ces
|
|
fonctionnalités.
|
|
|
|
Si vous lancez le programme, vous verrez que la géométrie est maintenant correctement rendue :
|
|
|
|

|
|
|
|
## Gestion des redimensionnements de la fenêtre
|
|
|
|
La résolution du buffer de profondeur doit changer avec la fenêtre quand elle redimensionnée, pour pouvoir correspondre
|
|
à la taille de l'attachement. Étendez `recreateSwapChain` pour régénérer les ressources :
|
|
|
|
```c++
|
|
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.
|
|
|
|
```c++
|
|
void cleanupSwapChain() {
|
|
vkDestroyImageView(device, depthImageView, nullptr);
|
|
vkDestroyImage(device, depthImage, nullptr);
|
|
vkFreeMemory(device, depthImageMemory, nullptr);
|
|
|
|
...
|
|
}
|
|
```
|
|
|
|
Votre application est maintenant capable de rendre correctement de la géométrie 3D! Nous allons utiliser cette
|
|
fonctionnalité pour afficher un modèle dans le prohain chapitre.
|
|
|
|
[Code C++](/code/26_depth_buffering.cpp) /
|
|
[Vertex shader](/code/26_shader_depth.vert) /
|
|
[Fragment shader](/code/26_shader_depth.frag)
|