深度缓冲:
绘制多个图像时,会按顺序进行绘制并显示,而不是按空间坐标的关系,有两种方式可以解决这个问题:
• 按照深度坐标对绘制调用进行排序
• 使用深度测试
通常我们使用第一种方式来绘制需要透明的对象。使用第二种方式来绘制一般对象。
使用第二种方式需要用到深度缓冲。深度缓冲是用于存储片段深度值的缓冲对象。
光栅化后生成的片段包含了一个深度值可以被深度测试检查是否可以使用这一片段覆盖之前的数据。
深度图像的创建方法:
设置和颜色附着完全一样的图像分辨率,以及用于深度附着的使用标记,优化的 tiling 模式和设备内存。对于深度图像,只需要使用一个颜色通道。
和纹理图像不同,深度图像不需要特定的图像数据格式,这是因为实际上我们不需要直接访问深度图像。只需要保证深度数据能够有一个合理的精度即可,通常这一合理精度是至少为深度数据提供 24 位的位宽,下面这些值满足 24 位的需求:
• VK_FORMAT_D32_SFLOAT:32 位浮点深度值
• VK_FORMAT_D32_SFLOAT_S8_UINT:32 位浮点深度值和 8 位模板值
• VK_FORMAT_D24_UNORM_S8_UINT:24 位浮点深度值和 8 位模板值
模板值用于模板测试,我们之后说
使用深度缓冲的步骤:
1.修改Vertex 结构体,使用三维向量来表示顶点的位置信息
2.修改顶点着色器接收三维顶点位置输入
3.更新 vertices 中的顶点数据,添加 z 坐标信息
4.查询可用的深度图像数据格式
5.创建深度图像对象,们使用管线障碍来同步图像变换
6.设置深度附着信息,更新 VkRenderPassCreateInfo 结构体信息引用深度附着
7.修改创建帧缓冲的代码,绑定深度图像作为帧缓冲的深度附着
8.使用了多个使用 VK_ATTACHMENT_LOAD_OP_CLEAR标记的附着,需设置多个清除值
9.开启图形管线的深度测试功能
10.在窗口大小改变时时重建深度缓冲
示例:
#define GLM_FORCE_RADIANS//用来使 glm::rotate这些函数使用弧度作为参数的单位
/**
默认情况下,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 //线性代数库
//顶点结构体
struct Vertex{
glm::vec3 pos;
glm::vec3 color;
glm::vec2 texCoord;//纹理坐标
//返回 Vertex 结构体的顶点数据存放方式
static VkVertexInputBindingDescription getBindingDescription() {
VkVertexInputBindingDescription bindingDescription = {};
bindingDescription.binding = 0;
bindingDescription.stride = sizeof(Vertex);
bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
return bindingDescription;
}
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);
attributeDescriptions[1].binding = 0;
attributeDescriptions[1].location = 1;
attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT;
attributeDescriptions[1].offset = offsetof(Vertex, color);
//使用纹理坐标来将纹理映射到几何图元上
attributeDescriptions[2].binding = 0;
attributeDescriptions[2].location = 2;
attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT;
attributeDescriptions[2].offset = offsetof(Vertex, texCoord);
return attributeDescriptions;
}
};
//定义顶点数据--交叉顶点属性 (interleaving vertex attributes)。
const std::vector<Vertex> vertices = {
{{-0.5f ,-0.5f, 0.0f}, {1.0f , 0.0f , 0.0f}, {1.0f,0.0f}},
{{0.5f , -0.5f, 0.0f}, {0.0f , 1.0f , 0.0f}, {0.0f,0.0f}},
{{0.5f , 0.5f , 0.0f}, {0.0f , 0.0f , 1.0f}, {0.0f,1.0f}},
{{-0.5f , 0.5f, 0.0f}, {1.0f , 1.0f , 1.0f}, {1.0f,1.0f}},
{{-0.5f ,-0.5f,-0.5f}, {1.0f , 0.0f , 0.0f}, {1.0f,0.0f}},
{{0.5f , -0.5f,-0.5f}, {0.0f , 1.0f , 0.0f}, {0.0f,0.0f}},
{{0.5f , 0.5f ,-0.5f}, {0.0f , 0.0f , 1.0f}, {0.0f,1.0f}},
{{-0.5f , 0.5f,-0.5f}, {1.0f , 1.0f , 1.0f}, {1.0f,1.0f}}
};
//定义索引数据
const std::vector<uint16_t> indices = {
0,1,2,2,3,0,
4,5,6,6,7,4
};
/**
深度附着和颜色附着一样都是基于图像对象。区别是,交换链不会自
动地为我们创建深度附着使用的深度图像对象。我们需要自己创建深度图
像对象。使用深度图像需要图像、内存和图像视图对象这三种资源
*/
VkImage depthImage ;
VkDeviceMemory depthImageMemory ;
VkImageView depthImageView ;
//设置深度测试
VkPipelineDepthStencilStateCreateInfo depthStencil = {};
depthStencil.sType =
VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
/**
depthTestEnable 成员变量用于指定是否启用深度测试。
depthWriteEnable 成员变量用于指定片段通过深度测试后是否写入它的深度值到深度缓冲。
使用这两个成员变量可以实现透明效果。透明对象的片段的深度值需
要和之前不透明对象片段的深度值进行比较,但透明对象的片段的深度值不需要写入深度缓冲
*/
depthStencil.depthTestEnable = VK_TRUE;
depthStencil.depthWriteEnable = VK_TRUE;
/**
depthCompareOp 成员变量用于指定深度测试使用的比较运算。这里
我们指定深度测试使用小于比较运算,这一设置下,新的片段只有在它的
深度值小于深度缓冲中的深度值时才会被写入颜色附着
*/
depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
/**
depthBoundsTestEnable、minDepthBounds 和 maxDepthBounds 成员
变量用于指定可选的深度范围测试。这一测试开启后只有深度值位于指定
范围内的片段才不会被丢弃。这里我们不使用这一功能
*/
depthStencil.depthBoundsTestEnable = VK_FALSE;
depthStencil.minDepthBounds = 0.0f;
depthStencil.maxDepthBounds = 1.0f;
/**
stencilTestEnable、front 和 back 成员变量用于模板测试,在我们的教
程中没有用到。如果读者想要使用模板测试,需要注意使用包含模板颜色
通道的图像数据格式
*/
depthStencil.stencilTestEnable = VK_FALSE;
depthStencil.front = {};
depthStencil.back ={};
//引用我们刚刚设置的深度模板缓冲状态信息
//如果渲染流程包含了深度模板附着,那就必须指定深度模板状态信息。
pipelineInfo.pDepthStencilState = &depthStencil;
//设置深度附着信息
VkAttachmentDescription depthAttachment = {};
//format成员变量的值的设置应该和深度图像的图像数据格式相同
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;
VkAttachmentReference depthAttachmentRef = {};
depthAttachmentRef.attachment = 1;
depthAttachmentRef.layout =
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
//为我们仅有的子流程添加对深度附着的引用
subpass.pDepthStencilAttachment = &depthAttachmentRef;
/**
和颜色附着不同,一个子流程只可以使用一个深度 (或深度模板) 附着。
一般而言,也很少有需要多个深度附着的情况
*/
//更新 VkRenderPassCreateInfo 结构体信息引用深度附着
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();
/**
和每个交换链图像对应不同的颜色附着不同,使用我们这里的信号量
设置,同时只会有一个子流程在执行,所以,这里我们只需要使用一个深度附着即可
*/
//指定深度图像视图对象作为帧缓冲的第二个附着
std::array<VkImageView,2> attachments = {
swapChainImageViews[i],depthImageView
};
framebufferInfo.attachmentCount =
static_cast<uint32_t>(attachments.size());
//指定渲染流程对象用于描述附着信息的 pAttachment 数组
framebufferInfo.pAttachments = attachments.data();
//配置深度图像需要的资源
void 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,
VK_IMAGE_ASPECT_DEPTH_BIT) ;
/**
接着,就是创建深度图像。通常,我们在渲染流程开始后首先会清除
深度附着中的数据,不需要复制数据到深度图像中。我们需要对图像布局
进行变换,来让它适合作为深度附着使用。由于这一图像变换只需要进行
一次,这里我们使用管线障碍来同步图像变换:
因为不需要深度图像之前的数据,所以我们使用VK_IMAGE_LAYOUT_UNDEFINED
*/
transitionImageLayout(depthImage , depthFormat ,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
}
/**
可用的深度图像数据格式还依赖于tiling模式和使用标记,所以我们需要将这些信息作为函数参数,
通过调用 vkGetPhysicalDeviceFormatProperties 函数查询可用的深度图像数据格式:
*/
//查找一个既符合我们需求又被设备支持的图像数据格式
VkFormat findSupportedFormat(const std::vector<VkFormat>& candidates,
VkImageTiling tiling,
VkFormatFeatureFlags features){
for(VkFormat format : candidates){
/**
VkFormatProperties 结构体包含了下面这些成员变量:
• linearTilingFeatures:数据格式支持线性 tiling 模式
• optimalTilingFeatures:数据格式支持优化 tiling 模式
• bufferFeatures:数据格式支持缓冲
目前我们只用到上面前两个成员变量,通过它们对应的 tiling 模式检
测数据格式是否被支持:
*/
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!");
}
//查找适合作为深度附着的图像数据格式
VkFormat findDepthFormat(){
/**
需要注意这里使用的是 VK_FORMAT_FEATURE_ 类的标记而不是
VK_IMAGE_USAGE_ 类标记,这一类标记的格式都包含了深度颜色通
道,有一些还包含了模板颜色通道,但在这里,我们没有用到模板颜色通道。
但如果使用的格式包含模板颜色通道,在进行图像布局变换时就需要考虑这一点。
*/
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;
}
//销毁图像视图对象
vkDestroyImageView(device, depthImageView, nullptr);
//销毁深度图像
vkDestroyImage(device, depthImage, nullptr);
//释放深度图像内存
vkFreeMemory(device, depthImageMemory, nullptr);
顶点着色器:
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(binding = 0) uniform UniformBufferObject {
mat4 model;
mat4 view;
mat4 proj;
} ubo;
layout(location = 0) in vec3 inPostion;
layout(location = 1) in vec3 inColor;
layout(location = 2) in vec2 inTexCoord;
layout(location = 0) out vec3 fragColor;
layout(location = 1) out vec2 fragTexCoord;
out gl_PerVertex{
vec4 gl_Position;
};
void main(){
gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPostion,1.0);
fragColor = inColor;
fragTexCoord = inTexCoord;
}