深度
深度格式
有几种不同的深度格式,实现可能在 Vulkan 中公开支持。
对于从深度图像进行的读取,仅需要支持通过采样或 blit 操作读取 VK_FORMAT_D16_UNORM 和 VK_FORMAT_D32_SFLOAT。
对于向深度图像进行的写入,需要支持 VK_FORMAT_D16_UNORM。从这里开始,还必须支持至少一个 (VK_FORMAT_X8_D24_UNORM_PACK32 或 VK_FORMAT_D32_SFLOAT) 和 (VK_FORMAT_D24_UNORM_S8_UINT 或 VK_FORMAT_D32_SFLOAT_S8_UINT)。如果在同一格式中同时需要深度和模板,则这将涉及一些额外的逻辑来查找要使用的格式。
// Example of query logic
VkFormatProperties properties;
vkGetPhysicalDeviceFormatProperties(physicalDevice, VK_FORMAT_D24_UNORM_S8_UINT, &properties);
bool d24s8_support = (properties.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT);
vkGetPhysicalDeviceFormatProperties(physicalDevice, VK_FORMAT_D32_SFLOAT_S8_UINT, &properties);
bool d32s8_support = (properties.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT);
assert(d24s8_support | d32s8_support); // will always support at least one
作为 VkImage 的深度缓冲区
在谈论图形时,术语“深度缓冲区”被大量使用,但在 Vulkan 中,它只是一个 VkImage/VkImageView,VkFramebuffer 可以在绘制时引用它。创建 VkRenderPass 时,pDepthStencilAttachment 值指向帧缓冲区中的深度附件。
为了使用 pDepthStencilAttachment,必须使用 VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT 创建后备的 VkImage。
当执行图像屏障或清除等需要 VkImageAspectFlags 的操作时,VK_IMAGE_ASPECT_DEPTH_BIT 用于引用深度内存。
布局
当选择 VkImageLayout 时,有一些布局允许对图像进行读取和写入
-
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL
-
VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL
-
VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL
以及一些布局,仅允许对图像进行只读
-
VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL
-
VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL
-
VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_OPTIMAL
在进行布局转换时,请确保设置读取和写入深度图像所需的正确深度访问掩码。
// Example of going from undefined layout to a depth attachment to be read and written to
// Core Vulkan example
srcAccessMask = 0;
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 | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
// VK_KHR_synchronization2
srcAccessMask = VK_ACCESS_2_NONE_KHR;
dstAccessMask = VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT_KHR | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT_KHR;
sourceStage = VK_PIPELINE_STAGE_2_NONE_KHR;
destinationStage = VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT_KHR | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT_KHR;
|
如果不确定在应用程序中使用早期片段测试还是晚期片段测试,请同时使用两者。 |
光栅化前
下面概述了在光栅化之前发生的各种坐标名称和操作。
图元裁剪
除非使用 VK_EXT_depth_clip_enable 中的 depthClipEnable,否则如果图元位于视锥体之外,则始终会发生裁剪。在 Vulkan 中,深度裁剪表示为:
0 <= Zc <= Wc
当计算归一化设备坐标 (NDC) 时,任何超出 [0, 1] 范围的值都会被裁剪。
以下是一些示例,其中 Zd 是 Zc/Wc 的结果:
-
vec4(1.0, 1.0, 2.0, 2.0)- 未裁剪 (Zd==1.0) -
vec4(1.0, 1.0, 0.0, 2.0)- 未裁剪 (Zd==0.0) -
vec4(1.0, 1.0, -1.0, 2.0)- 已裁剪 (Zd==-0.5) -
vec4(1.0, 1.0, -1.0, -2.0)- 未裁剪 (Zd==0.5)
用户定义的裁剪和剔除
使用 ClipDistance 和 CullDistance 内置数组,预光栅化着色器阶段可以设置用户定义的裁剪和剔除。
在最后一个预光栅化着色器阶段,这些值将在图元上进行线性插值,并且插值距离小于 0 的图元部分将被认为在裁剪体积之外。如果片段着色器随后使用 ClipDistance 或 CullDistance,它们将包含这些线性插值的值。
|
|
从 OpenGL 移植
在 OpenGL 中,视锥体表示为:
-Wc <= Zc <= Wc
任何超出 [-1, 1] 范围的值都会被裁剪。
添加了 VK_EXT_depth_clip_control 扩展,以便在 Vulkan 上高效地分层 OpenGL。在创建 VkPipeline 时,通过将 VkPipelineViewportDepthClipControlCreateInfoEXT::negativeOneToOne 设置为 VK_TRUE,它将使用 OpenGL 的 [-1, 1] 视锥体。
如果 VK_EXT_depth_clip_control 不可用,则目前的解决方法是在预光栅化着色器中执行转换:
// [-1,1] to [0,1]
position.z = (position.z + position.w) * 0.5;
视口变换
视口变换是将归一化设备坐标转换为帧缓冲区坐标的变换,该变换基于视口矩形和深度范围。
管道中正在使用的视口列表由 VkPipelineViewportStateCreateInfo::pViewports 表示,而 VkPipelineViewportStateCreateInfo::viewportCount 设置正在使用的视口数量。如果未启用 VkPhysicalDeviceFeatures::multiViewport,则只能有 1 个视口。
|
可以使用 |
深度范围
每个视口都包含一个 VkViewport::minDepth 和 VkViewport::maxDepth 值,这些值设置视口的“深度范围”。
|
尽管名称如此, |
minDepth 和 maxDepth 被限制在 0.0 和 1.0 之间(包括端点)。如果启用了 VK_EXT_depth_range_unrestricted,则此限制将被解除。
帧缓冲区深度坐标 Zf 表示为:
Zf = Pz * Zd + Oz
-
Zd=Zc/Wc(参见 图元裁剪) -
Oz=minDepth -
Pz=maxDepth-minDepth
光栅化
深度偏移
可以通过为多边形计算的单个值来偏移由多边形的光栅化生成的所有片段的深度值。如果在绘制时 VkPipelineRasterizationStateCreateInfo::depthBiasEnable 为 VK_FALSE,则不应用深度偏移。
可以使用 VkPipelineRasterizationStateCreateInfo 中的 depthBiasConstantFactor、depthBiasClamp 和 depthBiasSlopeFactor 计算深度偏移。
|
需要支持 |
|
可以使用 |
后光栅化
片段着色器
内置输入 FragCoord 是帧缓冲区坐标。Z 分量是图元的插值深度值。如果着色器不写入 FragDepth,则此 Z 分量值将被写入 FragDepth。如果着色器动态写入 FragDepth,则必须声明 DepthReplacing 执行模式(这在 glslang 等工具中完成)。
|
|
|
在 SPIR-V 中使用 |
保守深度
DepthGreater、DepthLess 和 DepthUnchanged 执行模式允许对 依赖于在片段之前运行的早期深度测试的实现进行可能的优化。这可以通过使用适当的布局限定符声明 gl_FragDepth 在 GLSL 中轻松完成。
// assume it may be modified in any way
layout(depth_any) out float gl_FragDepth;
// assume it may be modified such that its value will only increase
layout(depth_greater) out float gl_FragDepth;
// assume it may be modified such that its value will only decrease
layout(depth_less) out float gl_FragDepth;
// assume it will not be modified
layout(depth_unchanged) out float gl_FragDepth;
违反该条件会产生未定义的行为。
按样本处理和覆盖掩码
以下后光栅化作为“按样本”操作发生。这意味着在执行多重采样和颜色附件时,所使用的任何“深度缓冲区” VkImage 也必须使用相同的 VkSampleCountFlagBits 值创建。
每个片段都有一个覆盖掩码,该掩码基于确定该片段中哪些样本位于生成该片段的图元区域内。如果片段操作导致覆盖掩码的所有位均为 0,则会丢弃该片段。
解析深度缓冲区
可以使用 VK_KHR_depth_stencil_resolve 扩展(在 1.2 中提升为 Vulkan 核心)在子通道中解析多重采样的深度/模板附件,方式与颜色附件类似。
深度边界
|
需要支持 |
如果使用 VkPipelineDepthStencilStateCreateInfo::depthBoundsTestEnable,则会检查深度附件中的每个 Za 是否在 VkPipelineDepthStencilStateCreateInfo::minDepthBounds 和 VkPipelineDepthStencilStateCreateInfo::maxDepthBounds 设置的范围内。如果值不在边界内,则将覆盖掩码设置为零。
|
深度边界值可以使用 动态 状态设置,通过 |
深度测试
深度测试将帧缓冲区深度坐标 Zf 与深度附件中的深度值 Za 进行比较。如果测试失败,则丢弃该片段。如果测试通过,则深度附件将使用片段的输出深度更新。 VkPipelineDepthStencilStateCreateInfo::depthTestEnable 用于启用/禁用管线中的测试。
以下是深度测试的高级概述。
深度比较操作
VkPipelineDepthStencilStateCreateInfo::depthCompareOp 提供用于深度测试的比较函数。
一个示例,其中 depthCompareOp == VK_COMPARE_OP_LESS (Zf < Za)
-
Zf= 1.0 |Za= 2.0 | 测试通过 -
Zf= 1.0 |Za= 1.0 | 测试失败 -
Zf= 1.0 |Za= 0.0 | 测试失败
|
|
深度缓冲区写入
即使深度测试通过,如果 VkPipelineDepthStencilStateCreateInfo::depthWriteEnable 设置为 VK_FALSE,它也不会将值写入到深度附件。 主要原因是深度测试本身会设置覆盖掩码,可用于某些渲染技术。
|
|