Vulkan Tutorial 8 深度缓冲

news/2024/2/29 2:40:47

目录

26 三维几何图形

        深度图像和视图

27 显式转换深度图像

渲染通道

帧缓冲区

清除值

深度和模版状态

处理窗口调整大小


26 三维几何图形

到目前为止,我们所处理的几何体是投射到三维的,但它仍然是完全平面的。在这一章中,我们要给位置添加一个Z坐标,为三维网格做准备。我们将使用这第三个坐标在当前的正方形上放置一个正方形,看看几何体不按深度排序时出现的问题。

改变 “顶点”结构以使用三维矢量作为位置,并更新相应的VkVertexInputAttributeDescription中的format

attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT;

更新顶点着色器以接受和转换3D坐标作为输入。之后别忘了重新编译!

layout(location = 0) in vec3 inPosition;...void main() {gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0);fragColor = inColor;fragTexCoord = inTexCoord;
}//更新vertices容器以包括Z坐标: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}}//使用Z坐标为-0.5f,并为额外的正方形添加适当的索引:{{-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
};

现在运行你的程序,你会看到类似于埃舍尔插图的东西。

问题是,下层正方形的碎片被画在上层正方形的碎片之上,仅仅是因为它在索引数组中排在后面。有两种方法可以解决这个问题。

  • 按深度从后往前排序所有的绘制调用
  • 使用深度缓冲器进行深度测试

第一种方法通常用于绘制透明对象,因为与顺序无关的透明是一个难以解决的难题。然而,使用深度缓冲器更普遍地解决了按深度排序片段的问题。

深度缓冲区是一个额外的附件,它存储每个位置的深度,就像颜色附件存储每个位置的颜色一样。每次光栅化器产生一个片断时,深度测试将检查新片断是否比前一个片断更接近。如果不是,那么新的片段就会被丢弃。通过深度测试的片段会把它自己的深度写到深度缓冲区中。可以从片段着色器中操纵这个值,就像你可以操纵颜色输出一样。

#define GLM_FORCE_RADIANS
//由GLM生成的透视投影矩阵将使用OpenGL深度范围,默认为-1.0到1.0。我们需要使用
//GLM_FORCE_DEPTH_ZERO_TO_ONE'定义将其配置为使用Vulkan的0.0’到`1.0’范围。
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

深度图像和视图

深度附件是基于图像的,就像颜色附件一样。不同的是,互换链不会自动为我们创建深度图像。我们只需要一个深度图像,因为一次只运行一个绘制操作。深度图像将再次需要三方面的资源:图像、内存和图像视图。

void createDepthResources() {
//创建一个深度图像是相当直接的。它应该具有与颜色附件相同的分辨率,由交换链的范围定义,
//适合于深度附件的图像使用,最佳的平铺和设备本地内存}

与纹理图像不同,我们不一定需要一个特定的格式,因为我们不会直接从程序中访问纹理。它只需要有一个合理的精度,在现实世界的应用中,至少有24比特是常见的。有几种格式可以满足这个要求。

  • vk_format_d32_sfloat。用于深度的32位浮点数
  • vk_format_d32_sfloat_s8_uint: 32位带符号的浮点数,用于深度和8位模版组件
  • VK_FORMAT_D24_UNORM_S8_UINT:用于深度和8位网板组件的24位浮点数。

我们可以简单地采用VK_FORMAT_D32_SFLOAT格式,因为对它的支持是非常普遍的(见硬件数据库),但在可能的情况下,为我们的应用增加一些额外的灵活性也是不错的。我们要写一个函数findSupportedFormat,它按照从最理想到最不理想的顺序接收一个候选格式列表,并检查哪个是第一个被支持的:

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("failed to find supported format!");
}

VkFormatProperties 结构包含三个字段。

  • linearTilingFeatures:支持线性铺排的使用情况
  • optimalTilingFeatures: 支持最优铺排的使用情况
  • bufferFeatures: 支持缓冲区的用例

我们现在要用这个函数来创建一个findDepthFormat辅助函数,以选择一个有深度组件的格式,支持作为深度附件使用:

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);
}//添加一个简单的辅助函数,告诉我们所选择的深度格式是否包含模版组件:bool hasStencilComponent(VkFormat format) {return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT;
}//调用函数从createDepthResources中找到一个深度格式:VkFormat depthFormat = findDepthFormat();//现在我们有了调用createImage和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);

createImageView函数目前假定子资源总是VK_IMAGE_ASPECT_COLOR_BIT,所以我们需要把这个字段变成一个参数:

VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags) {...viewInfo.subresourceRange.aspectMask = aspectFlags;}//更新对该函数的所有调用,以使用正确的方面: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);...

27 显式转换深度图像

我们不需要将图像的布局显式转换为深度附件,因为我们将在渲染过程中处理这一点。

在 createDepthResources 函数的末尾调用 transitionImageLayout,如下所示:

transitionImageLayout(depthImage, depthFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);//未定义的布局可以用作初始布局,因为没有重要的现有深度图像内容。我们需要更新 transitionImageLayout 中的一些逻辑以使用正确的子资源方面: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;
}//最后,添加正确的访问掩码和管线阶段: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("unsupported layout transition!");
}

渲染通道

我们现在要修改createRenderPass以包括一个深度附件。首先指定VkAttachmentDescription:

VkAttachmentDescription depthAttachment{};
depthAttachment.format = findDepthFormat();
//format应该与深度图像本身相同。这次我们不关心存储深度数据(storeOp
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;
//我们不关心之前的深度内容,所以我们可以使用VK_IMAGE_LAYOUT_UNDEFINED作为initialLayout。
depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;VkAttachmentReference depthAttachmentRef{};
depthAttachmentRef.attachment = 1;
depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;为第一个(也是唯一的)子通道添加一个对附件的引用:subpass.pColorAttachments = &colorAttachmentRef;
subpass.pDepthStencilAttachment = &depthAttachmentRef;与颜色附件不同,一个子通道只能使用一个深度(+stencil)附件。在多个缓冲区上做深度测试其实没有任何意义。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;//更新VkRenderPassCreateInfo结构以引用两个附件。dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;

 

帧缓冲区

下一步是修改framebuffer的创建,将深度图像绑定到深度附件上。进入createFramebuffers并指定深度图像视图为第二个附件:

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;//你还需要移动对createFramebuffers的调用,以确保它是在深度图像视图真正被创建后调用的:void initVulkan() {...createDepthResources();createFramebuffers();...
}

清除值

因为我们现在有VK_ATTACHMENT_LOAD_OP_CLEAR的多个附件,我们也需要指定多个清除值。转到recordCommandBuffer并创建一个VkClearValue结构的数组:

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();
//注意clearValues的顺序应该与你的附件的顺序相同。

在Vulkan中,深度缓冲区的深度范围是0.01.0,其中1.0位于远视平面,0.0位于近视平面。深度缓冲区中每一点的初始值应该是最远的深度,也就是`1.0’。

深度和模版状态

深度附件现在已经可以使用了,但深度测试仍需要在图形管道中启用。它是通过VkPipelineDepthStencilStateCreateInfo结构配置的:

VkPipelineDepthStencilStateCreateInfo depthStencil{};
depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
//depthTestEnable字段指定是否应将新片段的深度与深度缓冲区进行比较,看它们是否应被丢弃。
//depthWriteEnable字段指定是否应该将通过深度测试的新片段的深度实际写入深度缓冲区。
depthStencil.depthTestEnable = VK_TRUE;
depthStencil.depthWriteEnable = VK_TRUE;//为保留或丢弃片段而进行的比较
depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;//可选的深度绑定测试。基本上,这允许你只保留落在指定深度范围内的片段
depthStencil.depthBoundsTestEnable = VK_FALSE;
depthStencil.minDepthBounds = 0.0f; // Optional
depthStencil.maxDepthBounds = 1.0f; // Optional。//
depthStencil.stencilTestEnable = VK_FALSE;
depthStencil.front = {}; // Optional
depthStencil.back = {}; // Optional
//最后三个字段配置了 stencil buffer 的操作pipelineInfo.pDepthStencilState = &depthStencil;

如果你现在运行你的程序,那么你应该看到几何体的碎片现在已经正确排序了: 

 

处理窗口调整大小

深度缓冲区的分辨率应该在窗口调整大小时改变,以匹配新的颜色附件分辨率。在这种情况下,扩展recreateSwapChain函数来重新创建深度资源。

void recreateSwapChain() {int width = 0, height = 0;while (width == 0 || height == 0) {glfwGetFramebufferSize(window, &width, &height);glfwWaitEvents();}

清理操作应该发生在交换链清理功能中:

void cleanupSwapChain() {vkDestroyImageView(device, depthImageView, nullptr);vkDestroyImage(device, depthImage, nullptr);vkFreeMemory(device, depthImageMemory, nullptr);...
}

 


https://www.jiucaihua.cn/news/show-4626277.html

相关文章

股票买卖篇(II,III,IV)--基础,详细!状态机简单应用

目录 股票买卖II 本题思路 关于异常值的解释 代码 股票买卖III 本题思路 (包括对交易过程的理解&#xff0c;需认真理解) 代码 股票买卖 IV 本题思路 代码 股票买卖II 输入样例 6 7 1 5 3 6 4 输出样例 7 输入样例 5 1 2 3 4 5 输出样例 4 本题思路 该题是最…

linux基础知识学习记录

这里写自定义目录标题 一、 计算机基础知识二 、 Linux操作系统的介绍三、 Linux的安装四、Linux命令使用汇总 一、 计算机基础知识 计算机组成&#xff1a;计算机主要硬件和软件2部分组成。计算机软硬件的概念&#xff1a;硬件是可以看得见的物理实体&#xff0c;软件是运行在…

Cubase12没有声音解决办法(Windows 11专用)

本文章由CSDN 不想加班呀 原创&#xff0c;转载请注明出处。 作者首页&#xff1a;不想加班呀的博客_CSDN博客-Python爬虫,电脑小知识,程序员剪视频领域博主 目录 前言 解决办法 第一步&#xff08;进入系统硬件和声音设置界面&#xff09; 第二步&#xff08;在声音设置中…

(学习日记)2023.04.27

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…

柔性车间作业调度

1柔性车间作业调度 n n n个工件 { J 1 , J 2 , ⋯ , J n } \{J_1,J_2,\cdots,J_n\} {J1​,J2​,⋯,Jn​}要在 m m m台机器 { M 1 , M 2 , ⋯ , M m } \{M_1,M_2,\cdots,M_m\} {M1​,M2​,⋯,Mm​}上加工。每个工件包含一道或多道工序&#xff0c;工序顺序是预先确定的&#xf…

微信小程序获取手机号码 phonenumber.getPhoneNumber 提示47001错误

微信小程序获取手机号码 phonenumber.getPhoneNumber 提示47001错误 微信小程序获取客户端手机号码&#xff0c;踩的坑。如下提示&#xff1a; {"errcode":47001,"errmsg":"data format error hint: [6kMDxSDNRa-hAwqia] rid: 6308d1b5-69935bc9-1d9…

大数据:spark环境搭建,local模式,standalone模式,zookeeper standby,yarn模式

大数据&#xff1a;spark环境搭建&#xff0c;local模式 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多算法学生都得去找开发&#xff0c;测开 测开的话&#xff0c;你就得学数据库&#xff0c;sql&#xff0c;or…

大专毕业,从6个月开发转入测试岗位的一些感悟——写在测试岗位3年之际

时光飞逝&#xff0c;我从前端开发岗位转入测试岗位已经三年了&#xff0c;这期间从迷茫到熟悉&#xff0c;到强化&#xff0c;到熟练&#xff0c;到总结&#xff0c;感受还是很深的&#xff01; 三年前的某一个晚上&#xff0c;我正准备下班回家&#xff0c;我们的项目经理把…

初阶二叉树的相关性质定理及题目练习

前言&#xff1a; 前面我们介绍了初阶二叉树的相关知识&#xff0c;二叉树常考的还是链式二叉树&#xff0c;而且二叉树也会考很多选择题&#xff0c;本文重点是在给出一些常考的二叉树的性质定理推导和经典练习题目配合强化巩固知识。 目录 一、二叉树的常见性质定理 二、常…

我的测试之路:从入坑测试到月薪15k...

“干过保险卖过房&#xff0c;做过销售做过网管”这是我毕业后前两年的真实写照&#xff0c;因为所学网络安全专业不好找工作&#xff0c;毕业之后为了生活只能将就的干着这种门槛低的工作。后来一次同学聚会被同学带下坑后&#xff0c;正式转行软件测试。 刚入坑的两年&#…