目录

      • UniformBuffer
        • DescriptorSetLayout 和 VkBuffer
        • 顶点着色器定义
        • 描述符布局(DescriptorSetLayout)
        • 创建 UniformBuffer
        • 描述符池(DescriptorSet Pool)
        • 描述符集(DescriptorSet)
        • 更新描述符集
        • 使用描述符集
        • 使用多个 Descriptor

UniformBuffer

本篇文档是通过 Uniform Buffer 的使用进一步加深对 DescriptorSet 的理解
Vulkan 中,描述符是一种在着色器中访问资源(比如缓冲区,图像,采样器等)的机制或者协议

每个描述符(Descriptor)对应一个资源,代表 GPU 内存中的资源,比如 Uniform Bufferstorage Buffer, TextureSampler

Vulkan 描述符集(VkDescriptorSet)表示着色器可以与之交互的资源的集合,着色器是通过描述符读取和解析资源中的数据,着色器中的绑定点和相应的描述符集中的绑定点必须一一对应
描述符集

DescriptorSetLayout 和 VkBuffer

现在我们已经可以传递顶点的属性(坐标和颜色等)给到顶点着色器,对于一些所有顶点都共享的属性,比如顶点的变换矩阵,将其作为顶点属性为每一个顶点都传递一份显然是很低效的
Vulkan 提供了资源描述符(resource descriptor)来解决这个问题,资源描述符是用来在着色器中访问缓冲和图像数据的一种方式,我们可以将变换矩阵存储在一个缓冲中,然后通过描述符在着色器中访问它,使用描述符需要进行下面三部分的设置:

  • 在管线(pipeline Creation)创建时指定描述符布局(DescriptorSetLayout)
  • 从描述符池(DescriptorSet Pool)中份分配描述符集(DescriptorSet)
  • 渲染时绑定描述符集(update DescriptorSet)

描述符布局(DescriptorSetLayout)用于指定可以被管线访问的资源类型,类似于渲染流程指定可以被访问的附着类型

描述符集指定要绑定到描述符上的缓冲和图像资源,类似于帧缓存指定绑定到渲染流程附着上的图像视图
(just like a framebuffer specifies the actual image views to bind to render pass attachments)

Note: 本质上是一种定义资源如何访问的机制或者协议

最后将描述符集绑定到绘制的指令上,类似绑定顶点缓冲和帧缓存到绘制指令上

有多种类型的描述符,在这里, 只使用到了 Uniform 缓冲对象(UBO), 也有其他类型的描述符,它们的使用方式和 Uniform 缓冲对象类似

我们先用结构体定义我们在着色器中使用的 Uniform 的数据:

struct UniformBufferObject {glm::mat4 model;glm::mat4 view;glm::mat4 proj;
}

我们将要使用的 uniform 数据复制到 VkBuffer 中,然后通过一个 uniform 缓冲对象描述符(DescriptorSet)在顶点着色器中访问它:

layout(binding = 0) uniform UniformBufferObejct {mat4 model;mat4 view;mat4 proj;
}void main() {gl_Position = ubo.proj * ubo.view *ubo.model*vec4(inPostion, 0.01.0)fragColor = inColor;
}

在现在的 demo 中,我们在每一帧更新模型(Model),视图(View),投影矩阵(Projection),可以让矩阵在三维空间内进行旋转

顶点着色器定义
#version 450
#extension GL_ARB_separate_shader_object :enablelayout(binding = 0) uniform UniformBufferObject {mat4 model;mat4 view;mat4 proj;
}layout(location = 0) in vec2 inPostion;
layout(location = 1) in vec3 inColor;layout(location = 0) out vec3 fragColorout gl_PerVertex {vec4 gl_Postion;
}void main() {gl_Position = ubo.proj + ubo.view + ubo.model * vec4(inPostion, 001.0);fragColor = inColor;
}

uniforminout 定义在着色器中出现的顺序可以是任意的,任意代码中 binding 修饰符类似于我们对顶点属性使用的 location 修饰符,我们会在描述符布局引用这个 binding

gl_Position 使用变换矩阵最终得到矩形在三维空间内的裁剪坐标

描述符布局(DescriptorSetLayout)

我们需要在管线创建的时候提供着色器使用的每一个描述符绑定信息,
首先需要使用 createDescriptorSetLayout 的函数,并在管线创建前调用

void initVulkan() {createDecriptorSetLayout();createGraphicPipeline();
}void createDescriptorSetLayout() {
}

使用 vkDescriptorSetLayoutBinding 结构体来描述每一个绑定操作

void createDescriptorSetLayout() {VkSescriptorSetLayoutBinding ubolayoutBinding = {};uboLayoutBinding.binding = 0;uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;uboLayoutBinding.descriptorCount = 1;
}

bindingdescriptorType 用于指定着色器使用的描述符绑定和描述符类型,这里我们指定的是一个 uniform 缓冲对象,
也可以使用 uniform 数组传递到着色器中,我们可以使用数组来制定骨骼动画(skeletal aniamtion)中使用的所有变换矩阵,
我们的 MVP 矩阵只需要使用一个 uniform 缓冲对象,所以我们将 descriptorCount 的值设置为 1

uboLayoutBinding.pImmutableSamplers = nullptr;
uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;

我们还需要指定描述符在哪一个着色器阶段被使用,stageFlags 这里我们只是在 Vertex Shader 中使用,
pImmutableSamplers 成员变量仅用于和图像采样相关的描述符

调用 vkCreateDescriptorSetLayout 函数创建 VkDescriptorSetLayout 对象,vkCreateDescriptorSetLayout 函数以 VkDescriptorSetLayoutCreateInfo结构体作为参数

VkDescriptorSetLayoutCreateInfo layoutInfo = {};
layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
layoutInfo.bindingCount = 1;
layoutInfo.pBindings = &uboLayoutBinding;if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) {throw std::runtime_error("failed to create descriptor set layout!");
}

同时我们需要在创建 GraphicPipeline 的时候指定 DescriptorSetLayout,也可以指定多个 DescriptorSetLayout

VkPipelineLayoutCreateInfo pipelineLayoutInfo = {};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.setLayoutCount = 1;
pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout;
创建 UniformBuffer

我们需要创建包含 UniformBuffer 的缓冲对象(Uniform Buffer),然后在每一帧中将新的 UBO 数据复制到 uniform 缓冲,由于需要频繁的更新数据,使用暂存并不会带来性能的提升

由于我们需要并行渲染多帧的缘故,我们需要多个 uniform 缓冲,来满足多帧并行渲染的需要,我们可以并行渲染每一帧或者一个交换链图像使用独立的 uniform 缓冲对象

VkBuffer indexBuffer;
VkDeviceMemory indexBufferMemory;std::vector<VkBuffer> uniformBuffers;
std::vector<VkDeviceMemory> uniformBuffersMemory;void createUniformBuffer() {VkDeviceSize bufferSize = sizeof(UniformBufferObject);uniformBuffers.resize(swapChainImages.size());uniformBuffersMemory.resize(swapChainImages.size());for (size_t i = 0; i < swapChainImages.size(); i++) {createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBuffersMemory[i]);}
}

最后更新 UniformBuffer 只需要将数据拷贝到 UniformBuffer Memory 对象的虚拟地址空间中

void* data;
vkMapMemory(device, uniformBuffersMemory[currentImage]0sizeof(ubo)0&data);
memcpy(data, &ubo, sizeof(ubo));
vkUnmapMemory(device, uniformBuffersMemory[currentImage]);
描述符池(DescriptorSet Pool)

描述符集不能被直接创建,需要通过描述符池(DescriptorSet Pool)来分配,这里使用 createDescriptorPool 的函数来进行描述符池的创建

我们使用 VkDescriptorPoolSize 来决定 我们使用的 DescriptorSet 类型和数量
poolSize 是根据 swapChainImages 中的 image 的数量来决定的

VkDescriptorPoolSize poolSize = {};
poolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
poolSize.descriptorCount = static_cast<uint32_t>(swapChainImages.size());VkDescriptorPoolCreateInfo poolInfo = {};
poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
poolInfo.poolSizeCount = 1;
poolInfo.pPoolSizes = &poolSize;
poolInfo.maxSets = static_cast<uint32_t>(swapChainImages.size());VkDescriptorPool descriptorPool;...if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) {throw std::runtime_error("failed to create descriptor pool!");
}
描述符集(DescriptorSet)

DescriptorSet 的分配(Allocate)需要我们使用 vkAllocateDescriptorSets 分配出来,我们使用 VkDescriptorSetAllocateInfo 结构体
需要指定分配 DescriptorSet 使用的 DescriptorSetPool,需要分配的描述符集数量,以及它们使用的 DescriptorSetLayout

std::vector<VkDescriptorSetLayout>
layouts(swapChainImages.size(), descriptorSetLayout);
VkDescriptorSetAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
allocInfo.descriptorPool = descriptorPool;
allocInfo.descriptorSetCount = static_cast<uint32_t>(swapChainImages.size());
allocInfo.pSetLayouts = layouts.data();VkDescriptorPool descriptorPool;
std::vector<VkDescriptorSet> descriptorSets;
...
descriptorSets.resize(swapChainImages.size());
if (vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets[0]) != VK_SUCCESS) {throw std::runtime_error("failed to allocate descriptor sets!");
}

DescriptorSet 会在 DescriptorSetPool 销毁的时候自动被销毁,所以不需要我们显式的清除
vkAllocateDescriptorSets 函数分配地描述符集对象,每一个都带有 uniform 缓冲描述符(对应一个 uniformvkBuffer)

我们通过 vkDescriptorBufferInfo 结构体来配置引用的 vkBuffer

VkDescriptorBufferInfo 结构体可指定缓冲对象和可以访问的数据范围

for (size_t i = 0; i < swapChainImages.size(); i++) {VkDescriptorBufferInfo bufferInfo = {};bufferInfo.buffer = uniformBuffers[i];bufferInfo.offset = 0;bufferInfo.range = sizeof(UniformBufferObject);
}

如果需要使用整个缓冲,可以使将 range 成员变量范围设置为 VK_WHOLE_SIZE

更新描述符集
VkWriteDescriptorSet descriptorWrite = {};
descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrite.dstSet = descriptorSets[i];
descriptorWrite.dstBinding = 0;
descriptorWrite.dstArrayElement = 0;
descriptorWrite.pBufferInfo = &bufferInfo;
descriptorWrite.pImageInfo = nullptr; // Optional
descriptorWrite.pTexelBufferView = nullptr; // Optional

dstSetdstBinding 成员变量用于指定要更新的 DescriptorSet 和 绑定点(bindings)
需要注意的是DescriptorSet可以使用数组,所以我们需要指定数组的第一个元素作为索引,这里我们没有使用,所以将索引指定为 0

pBufferInfo 成员变量用于指定描述符引用的缓冲数据,pImageInfo 成员变量用于指定描述符引用的图像数据,
pTexelBufferView 成员变量 用于指定描述符引用的缓冲视图,这里我们只使用了 pBufferInfo 成员变量

最后使用 vkUpdateDescriptorSets 更新描述符集

vkUpdateDescriptorSets(device, 1&descriptorWrite, 0, nullptr);

vkUpdateDescriptorSets 函数可以接受两个数组作为参数;
VkWriteDescriptorSet 结构体数组和 VkCopyDescriptorSet 结构体数组,后者被用来复制(copy)描述符对

使用描述符集

现在修改 createCommandBuffer 函数为每个交换链图像绑定对应的描述符集,这需要调用 cmdBindDescriptorSets 完成,需要在调用 vkCmdDrawIndexed 函数之前调用这个函数

vkCmdBindDescriptorSets(commandBuffers[i],
VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 01&descriptorSets[i]0, nullptr);
vkCmdDrawIndexed(commandBuffers[i], static_cast<uint32_t>(indices.size())1000);

和顶点缓冲,索引缓冲不同,描述符集合并不是图像管线所独有的,所以我们需要指定我们绑定的是图形管线还是计算管线,管线之后的参数是描述符使用的布局
后面的三个参数用于指定: 描述符集的第一个元素索引,绑定的描述符集的个数,以及用于绑定的描述符集数组,最后两个参数用于指定动态描述符的数组偏移

使用多个 Descriptor

DescriptorSet 本身就是集合的概念,也就是可以创建 Descriptor 数组对应到一个 DescriptorSet 的绑定点上

VkDescriptorSetLayoutBinding binding = {};
binding.binding = 0; // 绑定点
binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
binding.descriptorCount = 8; // 绑定了 8 个 uniform buffer
binding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;VkDescriptorSetLayoutCreateInfo layoutInfo = {};
layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
layoutInfo.bindingCount = 1;
layoutInfo.pBindings = &binding;
vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout);// glsl access descriptorset array
layout(set = 0, binding = 0) uniform UniformBuffer {mat4 model;vec4 color;
} ubo[8];void main() {mat4 modelMatrix = ubo[3].model; // 访问第4个元素vec4 objectColor = ubo[gl_InstanceIndex].color; // 按实例索引访问
}

也可以在一个绑定点上使用不同的 DescriptorSet index,对应的 glsl 代码如下

// 三个不同的descriptor set,但都使用binding = 0
layout(set = 0, binding = 0) uniform UniformBuffer { ... } cameraUBO;
layout(set = 1, binding = 0) uniform UniformBuffer { ... } modelUBO;  
layout(set = 2, binding = 0) uniform sampler2D albedoTexture;

最后再更新一下 DescriptorSet 的示意图,加深理解:
DescriptorSet

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/921510.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/921510.shtml
英文地址,请注明出处:http://en.pswp.cn/news/921510.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[光学原理与应用-461]:波动光学 - 波片实现偏振态的转换或调整

波片&#xff08;Wave Plate&#xff09;是一种基于双折射效应的光学元件&#xff0c;其核心功能是通过控制光波中寻常光&#xff08;o光&#xff09;和非寻常光&#xff08;e光&#xff09;的相位差&#xff0c;实现偏振态的转换或调整。以下是波片的主要功能及其原理的详细说…

Flutter之riverpod状态管理详解

一、riverpod状态管理中所涉及到的provider对比分析Provider 类型核心用途最佳适用场景优势劣势/注意事项Provider(v1)暴露一个恒定不变的&#xff08;或不需要Riverpod管理的&#xff09;对象或值。依赖注入&#xff08;如&#xff1a;Repository, Logger, ApiClient&#xff…

昇腾310i Pro固件说明

目录 驱动和固件 驱动固件文件 firware固件 24.2版本对应的固件 驱动和固件共同文件 烧结到flash中的固件 总结 启动流程 固件关系猜测 启动关键信息 efuse atu大小 GPU的bar 总结 驱动和固件 以最新的25.2 对应的驱动和固件为例说明&#xff1a; 驱动固件文件…

【LeetCode热题100道笔记】二叉树的右视图

题目描述 给定一个二叉树的 根节点 root&#xff0c;想象自己站在它的右侧&#xff0c;按照从顶部到底部的顺序&#xff0c;返回从右侧所能看到的节点值。 示例 1&#xff1a; 输入&#xff1a;root [1,2,3,null,5,null,4] 输出&#xff1a;[1,3,4] 解释&#xff1a;示例 2&am…

Redis《RedisSerializer》

文章目录RedisSerializer为什么要使用如何使用RedisSerializer总结RedisSerializer 为什么要使用 RedisTemplate 有默认的序列化器&#xff0c;但默认使用的 JdkSerializationRedisSerializer 存在一些问题&#xff1a; 序列化后的数据包含类信息等额外内容&#xff0c;导致…

基于开源AI大模型AI智能名片S2B2C商城小程序的文案引流与社交传播运营策略研究

摘要&#xff1a;本文聚焦开源AI大模型AI智能名片S2B2C商城小程序&#xff0c;探讨其文案引流与社交传播运营策略。阐述文案在引流中的重要性&#xff0c;分析开源AI大模型AI智能名片S2B2C商城小程序的特性&#xff0c;研究文案设计策略、社交传播机制及运营策略实施与效果评估…

NGINX vs HAProxy vs LVS:优势与选型分析

目录 1. 负载均衡的江湖:三巨头初探 2. NGINX:全能选手的多面魅力 NGINX 核心优势 NGINX 的短板 NGINX 实战案例 3. HAProxy:调度大师的精细之道 HAProxy 核心优势 HAProxy 的短板 HAProxy 实战案例 4. LVS:内核猛兽的极致性能 LVS 核心优势 LVS 的短板 LVS 实…

AI+ 行动意见解读:音视频直播SDK如何加速行业智能化

引言&#xff1a;国家战略、技术基座与行业落地 8 月底&#xff0c;国务院发布了《“人工智能”行动意见》&#xff0c;明确将人工智能提升为继“互联网”之后的新一轮国家级战略抓手。这份文件的关键词已经不再是“连接”与“优化”&#xff0c;而是“重塑”与“跃迁”&#…

2025年华为HCIA人工智能认证发展前景如何?客观分析!

大家好&#xff01;7月世界人工智能大会即将揭幕首款重载机器人&#xff0c;AI产业化进程再次加速。不少朋友开始转移关注到和它有一点点关系的——华为HCIA-AI Solution认证&#xff08;人工智能解决方案工程师&#xff09;&#xff0c;但它是否真能搭上这趟技术快车&#xff…

AutoGPT 原理与实践:从AI助理到“自主任务完成者” (人工智能入门系列)

Elon Musk 曾预言&#xff0c;“AIAgent 终将比人类聪明&#xff0c;并能自动完成大部分工作&#xff0c;这既是机遇也是威胁。” 而 AutoGPT&#xff0c;正是当前 AI 领域涌现出的、最能体现这一预言雏形的产品。它不再是那个需要你一句一句精确指令的“AI助手”&#xff0c;而…

自适应滤波器:Ch4 最小均方(LMS)算法

随机梯度下降算法简介 之前的章节中介绍了利用最速下降算法可以实现维纳滤波器的最优解&#xff08;LMMSE&#xff09;&#xff0c;其最优解的形式为&#xff1a; w0R−1Pw_{0} R^{- 1}Pw0​R−1P 它基于两个假设&#xff1a;环境的联合平稳&#xff0c;即输入u(n)u(n)u(n)以及…

AI生成内容的版权问题解析与实操指南

针对个人使用AI工具生成视频/音乐的版权问题深度解析&#xff0c;从法律归属、侵权边界到确权实操&#xff0c;结合最新司法实践提炼核心要点&#xff1a; 一、版权归属核心逻辑&#xff1a;人类智力投入的可视化 当用户深度参与创作过程时&#xff0c;可主张版权。关键看操作…

4.2 机器学习 - 欠拟合和过拟合

模型训练的核心挑战是让模型既 “学好” 训练数据&#xff0c;又能 “适应” 新数据。欠拟合&#xff08;Underfitting&#xff09;和过拟合&#xff08;Overfitting&#xff09;是阻碍这一目标的两大典型问题&#xff0c;其本质是 “模型复杂度” 与 “数据复杂度” 不匹配。本…

LeetCode 468. 验证IP地址 - 详细解析

文章目录LeetCode 468. 验证IP地址 - 详细解析题目描述IPv4验证规则&#xff1a;IPv6验证规则&#xff1a;最优Java解决方案&#xff08;注释完整版&#xff09;关键变量含义及代码技巧代码技巧详解1. 前导零检查的最佳实践2. IPv6为什么不能用Character.isDigit()3. 针对性注释…

新能源研发,用新型实验记录本:ELN

新能源&#xff08;材料&#xff09;研发如火如荼&#xff0c;竞争激烈。以电池为例&#xff0c;新能源汽车的崛起、储能技术的突破&#xff0c;让电池成为了能源领域的“新宠”。电池研发已经成为热门赛场&#xff0c;各研发团队都在与时间赛跑&#xff0c;试图维持优势或弯道…

大语言模型领域最新进展

CSDN大礼包《人工智能大模型课程》 CSDN大礼包《人工智能平台设计开发课程课程》

【网安干货】--计算机网络知识梳理总结(二)

这是计算机网络知识梳理的第二篇&#xff0c;真正去梳理才发现内容好多好多好多好多好多啊…怕是预计要写四篇 注意&#xff1a;如果看不清可以右键复制图片链接到浏览器访问或另存为照片并放大查看 计算机网络2 计算机网络协议2.1 网络协议的定义与核心要素2.1.1 协议的定义2.…

百度前端社招面经二

社招 百度 前端开发 二面 base 北京 react 17 和 18 的差异react的响应式原理&#xff0c;js是如何驱动模块的webpacke 4 和 5 差异webpacke 热更新原理。Tree Shaking 是干嘛的import 和 require 区别&#xff0c;都会被Tree Shaking吗隐藏元素的几种方式三栏布局&#xff0c;…

结合prompt分析NodeRAG的build过程

之前介绍了NodeRAG的节点类型和安装过程。 linux环境conda安装NodeRAG示例-CSDN博客 这里尝试从prompt代码角度分析NodeRAG如何将文档转化为节点、关系。 1 整体处理流程 NodeRAG定义了如下所示状态及处理流程。 # define the state to pipeline mapping self.state_pipelin…

我改写的二分法XML转CSV文件程序速度追上了张泽鹏先生的

以下是美团龙猫初稿&#xff0c;我改正&#xff0c;DeepSeek重新格式化的代码。 重要改正点&#xff1a; 1.二分查找用goto控制迭代&#xff0c;返回<row的正确位置 2.在缓冲区头填上父标签使expat能连续解析不报错 #include <stdio.h> #include <stdlib.h> #in…