目录
- Vulkan 渲染管线
- 顶点输入阶段
- 输入装配阶段
- 顶点着色器阶段
- 细分控制、评估着色器阶段(可选)
- 几何着色器阶段(可选)
- 图元装配阶段
- 光栅化阶段
- 片段着色器
- 片段测试阶段
- 混合阶段
Vulkan 渲染管线
渲染管线可以看作是一条生产流水线,定义了从输入顶点到输出图像的所有步骤,它包括一系列固定和可编程阶段,这些阶段按照顺序执行,以完成渲染任务
Vulkan
渲染管线的许多阶段是可选的,你可以禁用它们,或者 Vulkan
实现可能根本不支持这些功能
管线中唯一必须启用的阶段是顶点做着色器(vertex shader
),其余阶段和功能可以根据需要选择启用或者禁用,比如集合着色器、片段着色器和光栅化阶段等
Vulkan
中提供了高度的灵活性,让开发者能够根据需求配置渲染管线
顶点输入阶段
顶点输入阶段主要是从应用程序传递的顶端缓存区中读取顶点数据,并传递给后续阶段,顶点数据通常包括顶点位置、法线、颜色和纹理坐标等
顶点输入阶段需要配置顶点绑定描述符(描述顶点缓存区的布局)和顶点属性描述符(描述顶点数据的格式和偏移量)。开发者在此阶段定义如何从顶点缓冲区中获取数据
顶点绑定描述符对应的结构是 VkVertexInputBindingDescription
,顶点属性描述符对应的结构体 VkVertexInputAttributeDescription
,通过下面的代码体现其含义
// 定义顶点输入绑定描述符,描述顶点的布局
VkVertexInputBindingDescription vertex_input_bindings{
.binding = 0, // 绑定索引,指定顶点数据绑定的槽位(索引)
.stride = 8 * sizeof(float), // 每个顶点的数据跨度,即每个顶点数据的字节大小(8个float:3个float顶点坐标,2个float纹理坐标,3float个法向量)
.inputRate = VK_VERTEX_INPUT_RATE_VERTEX, // 指定输入率为每个顶点(每个顶点都有一组数据)
};
//定义顶点输入属性描述符数组,指定每个顶点属性的格式个偏移量
VkVertexInputAttributeDescription vertex_input_attributes[3]{
{
.location = 0, // 输入属性的位置,匹配顶点着色器中的输入变量
.binding = 0, // 顶点绑定索引,指定此属性从哪个绑定中获取数据
.format = VK_FORMAT_R32G32B32_SFLOAT, // 属性的数据格式,这里是3个float(X, Y, Z坐标)
.offset = 0, // 数据在顶点中的偏移量,这里从第0个字节开始
},
{
.location = 1, // 第二个输入属性的位置,通常用于纹理坐标
.binding = 0, // 绑定索引,仍然是从同一个绑定中获取数据
.format = VK_FORMAT_R32G32_SFLOAT, // 属性的数据格式,这里是2个float(纹理坐标U, V)
.offset = sizeof(float) * 3, // 数据在顶点中的偏移量,这里从第3个float开始(跳过XYZ坐标)
},
{
.location = 2, // 第三个输入属性的位置
.binding = 0, // 绑定索引
.format = VK_FORMAT_R32G32B32_SFLOAT, // 属性的数据格式,这里是3个float(X, Y, Z坐标)
.offset = sizeof(float) * 5, // 数据在顶点中的偏移量,这里从第5个float开始
},
};
// 创建并初始化 VkPipelineVertexInputStateCreateInfo 结构体
VkPipelineVertexInputStateCreateInfo vertexInputInfo{
.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, // 指定结构体类型
.pNext = nullptr, // 指定扩展指针(通常为nullptr)
.vertexBindingDescriptionCount = 1, // 顶点绑定描述符的数量
.pVertexBindingDescriptions = &vertex_input_bindings, // 指向顶点绑定描述符的指针
.vertexAttributeDescriptionCount = 3, // 顶点属性描述符的数量
.pVertexAttributeDescriptions = vertex_input_attributes, // 指向顶点属性描述符数组的指针
};
顶点着色器脚本中的 location 顶点和输入属性描述符的 location 属性一一对应
#version 400
layout (location = 0) in vec4 pos;//顶点坐标
layout (location = 1) in vec2 attr;//纹理坐标
layout (location = 2) in vec3 normal;//顶点法向量
layout (location = 0) out vec2 texcoord;
void main() {
texcoord = attr;
gl_Position = pos;
}
输入装配阶段
输入装配阶段将顶点数据组装成几何图元(比如点、线、三角形)。这是顶点着色器之前的最后一个阶段,定义了如何将顶点数据组合成图形基元
通过指定图元拓扑方式(如点列表、线条列表,三角形列表) 来控制如何将顶点组装成图元
VkPipelineInputAssemblyStateCreateInfo
是用于指定输入装配阶段(Input Assembly
)配置的结构体,用于将顶点数据组装成图元(如点、线、三角形),这些图元会被后续的管线阶段处理
typedef struct VkPipelineInputAssemblyStateCreateInfo {
VkStructureType sType;// 结构体类型
const void* pNext; // 扩展属性, 属性通常为nullptr
VkPipelineInputAssemblyStateCreateFlags flags; //保留字段, 当前必须为 0
VkPrimitiveTopology topology;// 图元拓扑类型(三角形,线,和点)
VkBool32 primitiveRestartEnable;// 图元重启功能
} VkPipelineInputAssemblyStateCreateInfo;
其中的 VkPrimitiveTopology topology
定义了图元拓扑类型,比较常用,用于控制图元类型(如点、线、三角形)
-
VK_PRIMITIVE_TOPOLOGY_POINT_LIST
每个输入的顶点位置表示一个点图元,对应触发的顶点索引为 i -
VK_PRIMITIVE_TOPOLOGY_LINE_LIST
每两个输入顶点构成一条直线,对应触发顶点索引为 2i -
VK_PRIMITIVE_TOPOLOGY_LINE_STRIP
每个顶点自身和前一个顶点之间构成一条直线,对应触发顶点索引为 i -
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST
每三个顶点构成一个填充三角形,对应触发顶点索引为 3i -
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP
每个顶点和之前的两个顶点一起构成一个填充三角形,对应触发索引顶点为 i
VkPipelineInputAssemblyStateCreateInfo inputAssemblyInfo{};
inputAssemblyInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
inputAssemblyInfo.pNext = nullptr;
inputAssemblyInfo.flags = 0;
inputAssemblyInfo.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; // 设置为三角形列表拓扑
inputAssemblyInfo.primitiveRestartEnable = VK_FALSE; // 不启用图元重启
顶点着色器阶段
顶点着色器阶段处理每个顶点,通常用于应用几何变化(如模型视图转换,投影变换), 并计算顶点的输出属性。如颜色、纹理坐标和法线等
开发者编写顶点着色器程序来定义每个顶点的处理逻辑,该阶段是必须启用的,是管线中唯一一个必不可少的阶段
Vulkan
中通过结构体 VkPipelineShaderStageCreateInfo
:
typedef struct VkPipelineShaderStageCreateInfo {
VkStructureType sType;// 结构点类型,必须是 VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO
const void* pNext; // 指向扩展结构的指针,一般设置为 nullptr
VkPipelineShaderStageCreateFlags flags;// 用于将来扩展的标志,当前必须为 0
VkShaderStageFlagBits stage; // 着色器阶段,是 VK_SHADER_STAGE_VERTEX_BIT 或 VK_SHADER_STAGE_FRAGMENT_BIT
VkShaderModule module; // 指向包含着色器代码的 VkShaderModule 对象
const char* pName; // 着色器入口函数的名称,通常 "main"
const VkSpecializationInfo* pSpecializationInfo;
// 指向 VkSpecializationInfo 的指针,用于指定特化常量,可以是 nullptr
} VkPipelineShaderStageCreateInfo;
示例代码:
VkPipelineShaderStageCreateInfo vertShaderStageInfo{};
vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
vertShaderStageInfo.module = vertShaderModule;
vertShaderStageInfo.pName = "main";
细分控制、评估着色器阶段(可选)
曲面细分是近代 GPU
的一项高级特性,可以在采用较少原始顶点数据的情况下绘制出如同采用海量数据描述的光滑曲面
曲面细分工作由细分控制着色器和细分求值着色器协同完成
几何着色器阶段(可选)
几何着色器(Geometry Shader
)是图形渲染的一个可选阶段,在顶点着色器和光栅化阶段之间运行
几何着色器的主要作用是处理图元(比如点、线或三角形),可以生成、修改或者删除图元
通过几何着色器,输入为一个图元,输出可以为一个或多个图元,图元的类型可以不同
比如输入三角形,输出三角形的三条边和法线共四根线。开发者能够对几何形状执行复杂的几何变换和操作,从而创建丰富的视觉效果
图元装配阶段
将来自顶点着色器、细分求值着色器或几何着色器的顶点按照指定的绘制模式(如点,线,三角形)
进行分组,以形成基本图元(如点、线段、三角形等),这些图元将被传递到光栅化阶段
在图元组装完成后,对其进行裁剪,若图元完全位于视景体或裁剪平面内部,则将完整的图元传递到下一个阶段
若完全位于视景体会自定义裁剪平面外部,则丢弃该图元,如果图元部分位于内部,部分位于外部,则需要对
对图元进行裁剪,以确保仅保留视景体内部的部分
光栅化阶段
光栅化是将图元(如三角形)转换为片段的过程,这些片段最终会被着色并显示到屏幕上
Vulkan
的光栅化阶段包括设置光栅化模式(如填充模式、线框模式、点模式),面剔除模式(如剔除背面或者正面)
以及深度偏移等,光栅化是将三维几何体转换为二维像素的关键步骤
VkPipelineRasterizationStateCreateInfo
是 Vulkan
渲染管线中用于配置光栅化阶段的结构体
typedef struct VkPipelineRasterizationStateCreateInfo {
VkStructureType sType;
const void* pNext;
VkPipelineRasterizationStateCreateFlags flags;
VkBool32 depthClampEnable;
VkBool32 rasterizerDiscardEnable;
VkPolygonMode polygonMode;
VkCullModeFlags cullMode;
VkFrontFace frontFace;
VkBool32 depthBiasEnable;
float depthBiasConstantFactor;
float depthBiasClamp;
float depthBiasSlopeFactor;
float lineWidth;
} VkPipelineRasterizationStateCreateInfo;
示例代码如下:
VkPipelineRasterizationStateCreateInfo rasterizer{};
rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterizer.pNext = nullptr; // 指向扩展结构的指针,通常为 nullptr
rasterizer.depthClampEnable = VK_FALSE; // 禁用深度钳制,深度值不会被限制在 [minDepth, maxDepth] 范围内
rasterizer.rasterizerDiscardEnable = VK_FALSE; // 禁用光栅化丢弃,光栅化阶段正常进行
rasterizer.polygonMode = VK_POLYGON_MODE_FILL; // 使用填充模式绘制多边形
rasterizer.lineWidth = 1.0f; // 线宽设置为 1.0. 单位为像素,注意这里应该使用浮点数
rasterizer.cullMode = VK_CULL_MODE_NONE;// 不进行面剔除,渲染正面和背面
rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; // 指定顺时针方向为正面
rasterizer.depthBiasEnable = VK_FALSE; // 禁用深度偏移,深度值不会被调整
片段着色器
片段着色器阶段对每个片段进行处理,决定最终的像素值,这一阶段通常用于纹理映射,光照计算和颜色处理
VkPipelineShaderStageCreateInfo fragShaderStageInfo{};
fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
fragShaderStageInfo.module = fragShaderModule;
fragShaderStageInfo.pName = "main";
片段测试阶段
在片段着色器执行后对片段进行深度和模板测试
深度测试中,它可以根据片段的深度值决定是否更新帧缓冲区中的深度和颜色值,而模板测试是将绘制区域
限定在任意形状的指定范围内
结构体 VkPipelineDepthStencilStateCreateInfo
用于设置深度和模板测试
VkPipelineDepthStencilStateCreateInfo depthStencil = {};
depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
depthStencil.depthTestEnable = VK_TRUE; // 启用深度测试
depthStencil.depthWriteEnable = VK_TRUE; // 启用深度写入
depthStencil.depthCompareOp = VK_COMPARE_OP_LESS; // 深度比较操作
depthStencil.depthBoundsTestEnable = VK_FALSE; // 禁用深度边界测试
depthStencil.stencilTestEnable = VK_FALSE; // 禁用模板测试
混合阶段
混合阶段是将片段着色器输出的颜色和帧缓冲区中现有的颜色进行混合,该阶段处理透明度和混合效果
开发者可以设置混合操作、混合因子和颜色掩码,以实现诸如半透明,叠加等效果, 混合阶段直接影响最终输出的图像
管线混合阶段设置:
VkPipelineColorBlendAttachmentState colorBlendAttachment{};
// 颜色写掩码
colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
// 启用颜色混合
colorBlendAttachment.blendEnable = VK_TRUE;
// src 的alphablend 系数为 src_alpha
colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
// 目标混合因子为 1 - src_alpha
colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
// 颜色混合方程为 加法
colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
// alpha 混合方程为加法
colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
VkPipelineColorBlendStateCreateInfo colorBlending{};
colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
colorBlending.logicOpEnable = VK_FALSE; // 禁用逻辑操作
colorBlending.logicOp = VK_LOGIC_OP_COPY; // 设置逻辑操作为复制
colorBlending.attachmentCount = 1; // 颜色缓和附件的数量
colorBlending.pAttachments = &colorBlendAttachment;
//colorBlending.blendConstants[0] = 0.0f;
//colorBlending.blendConstants[1] = 0.0f;
//colorBlending.blendConstants[2] = 0.0f;
//colorBlending.blendConstants[3] = 0.0f;