【Vulkan 入门系列】创建纹理采样器、Uniform 缓冲区、描述符池(八)

原文首发微信公众号,微信搜索 非典型程序猿 即可关注。

这一节主要介绍创建纹理采样器,创建 Uniform 缓冲区和创建描述符池。

一、创建纹理采样器

createTextureSampler 用于在 Vulkan 中创建一个纹理采样器(Texture Sampler),它定义了在着色器中如何对纹理进行采样。

void HelloVK::initVulkan() {
  createInstance();
  createSurface();
  pickPhysicalDevice();
  createLogicalDeviceAndQueue();
  setupDebugMessenger();
  establishDisplaySizeIdentity();
  createSwapChain();
  createImageViews();
  createRenderPass();
  createDescriptorSetLayout();
  createGraphicsPipeline();
  createFramebuffers();
  createCommandPool();
  createCommandBuffer();
  decodeImage();
  createTextureImage();
  copyBufferToImage();
  createTextureImageViews();
  createTextureSampler();
  ...
}

void HelloVK::createTextureSampler() {
  VkSamplerCreateInfo createInfo{};
  createInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
  createInfo.magFilter = VK_FILTER_LINEAR;
  createInfo.minFilter = VK_FILTER_LINEAR;
  createInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
  createInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
  createInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
  createInfo.anisotropyEnable = VK_FALSE;
  createInfo.maxAnisotropy = 16;
  createInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
  createInfo.unnormalizedCoordinates = VK_FALSE;
  createInfo.compareEnable = VK_FALSE;
  createInfo.compareOp = VK_COMPARE_OP_ALWAYS;
  createInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
  createInfo.mipLodBias = 0.0f;
  createInfo.minLod = 0.0f;
  createInfo.maxLod = VK_LOD_CLAMP_NONE;

  VK_CHECK(vkCreateSampler(device, &createInfo, nullptr, &textureSampler));
}

1.1 结构体初始化

创建 VkSamplerCreateInfo 结构体实例 createInfo,用于配置采样器参数。指定结构体类型为 VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,这是 Vulkan 的固定要求。

VkSamplerCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;

1.2 过滤模式

配置放大和缩小时使用过滤模式为线性过滤(使用了双线性插值)。

createInfo.magFilter = VK_FILTER_LINEAR;  // 放大时使用线性过滤
createInfo.minFilter = VK_FILTER_LINEAR;  // 缩小时使用线性过滤
  1. 线性过滤(Linear Filtering)VK_FILTER_LINEAR:在放大(靠近纹理)或缩小(远离纹理)时,对周围像素进行插值,使纹理平滑。
  2. VK_FILTER_NEAREST:最近邻插值。

在这里插入图片描述

1.3 寻址模式

设置纹理坐标超出 [0, 1] 范围时的处理方式。

createInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
createInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
createInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;

可选值

VK_SAMPLER_ADDRESS_MODE_REPEAT:重复纹理(平铺)。
VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT:镜像重复。
VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE:截取到边缘颜色。
VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER:使用边界颜色(需定义 borderColor)。
VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE:镜像截取到边缘。

在这里插入图片描述

1.4 各向异性过滤

改善倾斜视角下的纹理清晰度(需硬件支持)。

此处禁用,但若启用,应确保 maxAnisotropy 不超过物理设备的 VkPhysicalDeviceProperties::limits.maxSamplerAnisotropy

createInfo.anisotropyEnable = VK_FALSE;  // 禁用各向异性过滤
createInfo.maxAnisotropy = 16;           // 若启用,最大各向异性级别为 16

1.5 边界颜色

当使用 VK_BORDER_COLOR_INT_OPAQUE_BLACK 模式时,超出范围的区域将填充此颜色(此处为不透明黑色)。

createInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;

可选值

VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK:透明黑(浮点格式)。
VK_BORDER_COLOR_INT_TRANSPARENT_BLACK:透明黑(整数格式)。
VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK:不透明黑(浮点格式)。
VK_BORDER_COLOR_INT_OPAQUE_BLACK:不透明黑(整数格式)。
VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE:不透明白(浮点格式)。
VK_BORDER_COLOR_INT_OPAQUE_WHITE:不透明白(整数格式)。

1.6 纹理坐标类型

使用归一化坐标 [0, 1)

createInfo.unnormalizedCoordinates = VK_FALSE;
  1. VK_FALSE:使用归一化坐标 [0, 1)
  2. VK_TRUE:使用实际像素坐标(需手动处理范围,较少用)。

1.7 比较操作

若启用 compareEnable,采样器将执行深度比较(用于阴影映射,此处未使用)。

createInfo.compareEnable = VK_FALSE;     // 禁用比较操作
createInfo.compareOp = VK_COMPARE_OP_ALWAYS;

1.8 Mipmap 设置

createInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; // Mipmap 间线性插值
createInfo.mipLodBias = 0.0f;          // Mipmap 级别偏移
createInfo.minLod = 0.0f;              // 最小 Mipmap 级别
createInfo.maxLod = VK_LOD_CLAMP_NONE; // 最大 Mipmap 级别无限制
  1. mipmapMode:控制 Mipmap 层级之间的过渡方式。

可选值

VK_SAMPLER_MIPMAP_MODE_NEAREST:选择最接近的 Mip 层级。
VK_SAMPLER_MIPMAP_MODE_LINEAR:在相邻 Mip 层级之间线性插值。

  1. maxLod = VK_LOD_CLAMP_NONE 表示使用所有可用 Mipmap 级别。

1.9 创建采样器

调用 vkCreateSampler 创建采样器,结果存储在 textureSampler

VK_CHECK(vkCreateSampler(device, &createInfo, nullptr, &textureSampler));

1.10 总结

此采样器配置为:

  1. 双线性过滤(平滑缩放)。
  2. 重复纹理寻址模式(平铺纹理)。
  3. 禁用各向异性过滤和深度比较。
  4. 使用线性 Mipmap 插值。
  5. 适用于常规纹理采样(如漫反射贴图)。

二、创建 Uniform 缓冲区

createUniformBuffers 用于 Vulkan 中管理 Uniform 缓冲区,确保了多帧渲染中的数据安全性和高效性。

void HelloVK::initVulkan() {
  createInstance();
  createSurface();
  pickPhysicalDevice();
  createLogicalDeviceAndQueue();
  setupDebugMessenger();
  establishDisplaySizeIdentity();
  createSwapChain();
  createImageViews();
  createRenderPass();
  createDescriptorSetLayout();
  createGraphicsPipeline();
  createFramebuffers();
  createCommandPool();
  createCommandBuffer();
  decodeImage();
  createTextureImage();
  copyBufferToImage();
  createTextureImageViews();
  createTextureSampler();
  createUniformBuffers();
  ...
}

/*
 *	Create a buffer with specified usage and memory properties
 *	i.e a uniform buffer which uses HOST_COHERENT memory
 *  Upon creation, these buffers will list memory requirements which need to be
 *  satisfied by the device in use in order to be created.
 */
void HelloVK::createBuffer(VkDeviceSize size, VkBufferUsageFlags usage,
                           VkMemoryPropertyFlags properties, VkBuffer &buffer,
                           VkDeviceMemory &bufferMemory) {
  VkBufferCreateInfo bufferInfo{};
  bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
  bufferInfo.size = size;
  bufferInfo.usage = usage;
  bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;

  VK_CHECK(vkCreateBuffer(device, &bufferInfo, nullptr, &buffer));

  VkMemoryRequirements memRequirements;
  vkGetBufferMemoryRequirements(device, buffer, &memRequirements);

  VkMemoryAllocateInfo allocInfo{};
  allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
  allocInfo.allocationSize = memRequirements.size;
  allocInfo.memoryTypeIndex =
      findMemoryType(memRequirements.memoryTypeBits, properties);

  VK_CHECK(vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory));

  vkBindBufferMemory(device, buffer, bufferMemory, 0);
}

void HelloVK::createUniformBuffers() {
  VkDeviceSize bufferSize = sizeof(UniformBufferObject);

  uniformBuffers.resize(MAX_FRAMES_IN_FLIGHT);
  uniformBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT);

  for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; 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]);
  }
}

2.1 createUniformBuffers

  1. 计算缓冲区大小。bufferSizeUniformBufferObject 结构体的大小,表示每帧需要传递的 Uniform 数据量(如 MVP 矩阵)。
  2. 预分配资源数组。uniformBuffersuniformBuffersMemory 是存储缓冲区和内存句柄的数组,大小为 MAX_FRAMES_IN_FLIGHT(值为 2,实现双缓冲)。
  3. 循环创建每帧的 Uniform 缓冲区。为每个帧创建独立的 Uniform 缓冲区,避免 CPU 和 GPU 同时访问同一资源的冲突。

2.2 createBuffer

  1. 创建缓冲区对象
  • VkBufferCreateInfo 定义缓冲区的属性,如大小、用途和共享模式。
  • usage 参数指定为 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,表示该缓冲区用于 Uniform 数据。
  • sharingMode = VK_SHARING_MODE_EXCLUSIVE 表示缓冲区仅由一个队列族使用。
  1. 查询内存需求
  • vkGetBufferMemoryRequirements 获取缓冲区需要的内存大小、对齐和兼容的内存类型。
  1. 分配内存
  • VkMemoryAllocateInfo 指定分配的大小和内存类型。
  • findMemoryType 函数根据内存类型掩码和属性(VK_MEMORY_PROPERTY_HOST_VISIBLE_BITVK_MEMORY_PROPERTY_HOST_COHERENT_BIT)选择合适的内存类型索引。

VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT:允许 CPU 通过 vkMapMemory 映射内存直接写入数据。
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT:确保 CPU 写入的数据对 GPU 立即可见,无需手动调用 vkFlushMappedMemoryRanges

  • vkAllocateMemory 是 Vulkan 中用于分配设备内存的核心函数。
  1. 绑定内存到缓冲区
  • vkBindBufferMemory 将分配的内存与缓冲区关联,偏移量为 0。

2.3 流程总结

createUniformBuffers()
├─ 计算 Uniform 数据大小(bufferSize)
├─ 为每帧预分配缓冲区和内存数组
└─ 循环创建每帧的 Uniform 缓冲区:
   └─ createBuffer()
      ├─ 创建缓冲区对象(vkCreateBuffer)
      ├─ 查询内存需求(vkGetBufferMemoryRequirements)
      ├─ 分配设备内存(vkAllocateMemory)
      └─ 绑定内存到缓冲区(vkBindBufferMemory)

三、创建描述符池

createDescriptorPool 用于创建 Vulkan 的描述符池(Descriptor Pool)。

void HelloVK::initVulkan() {
  createInstance();
  createSurface();
  pickPhysicalDevice();
  createLogicalDeviceAndQueue();
  setupDebugMessenger();
  establishDisplaySizeIdentity();
  createSwapChain();
  createImageViews();
  createRenderPass();
  createDescriptorSetLayout();
  createGraphicsPipeline();
  createFramebuffers();
  createCommandPool();
  createCommandBuffer();
  decodeImage();
  createTextureImage();
  copyBufferToImage();
  createTextureImageViews();
  createTextureSampler();
  createUniformBuffers();
  createDescriptorPool();
  ...
}

void HelloVK::createDescriptorPool() {
  VkDescriptorPoolSize poolSizes[2];
  poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
  poolSizes[0].descriptorCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);
  poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
  poolSizes[1].descriptorCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);

  VkDescriptorPoolCreateInfo poolInfo{};
  poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
  poolInfo.poolSizeCount = 2;
  poolInfo.pPoolSizes = poolSizes;
  poolInfo.maxSets = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT) * 2;

  VK_CHECK(vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool));
}

3.1 描述符池大小配置

定义描述符池中每种类型的描述符数量。

VkDescriptorPoolSize poolSizes[2];
poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
poolSizes[0].descriptorCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);
poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
poolSizes[1].descriptorCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);

VkDescriptorPoolSize:结构体表示某类描述符的数量。

type:描述符类型。

  • VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:Uniform 缓冲区,用于传递全局数据(如 MVP 矩阵)到着色器。
  • VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:组合图像采样器,用于在着色器中采样纹理。

descriptorCount:此类描述符的数量,设为 MAX_FRAMES_IN_FLIGHT(2,表示双缓冲)。

3.2 描述符池创建信息

配置描述符池的创建参数。

VkDescriptorPoolCreateInfo poolInfo{};
poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
poolInfo.poolSizeCount = 2;
poolInfo.pPoolSizes = poolSizes;
poolInfo.maxSets = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT) * 2;
  • sType:结构体类型,固定为 VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO
  • poolSizeCount:描述符类型数量(这里是 2)。
  • pPoolSizes:指向 poolSizes 数组的指针。
  • maxSets:池中可分配的描述符集最大数量。这里设为 MAX_FRAMES_IN_FLIGHT * 2,表示每帧需要 2 个描述符集。

3.3 创建描述符池

调用 Vulkan API 创建描述符池。

VK_CHECK(vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool));
<think>嗯,用户问的是在Vulkan创建完着色器之后应该做什么。首先,我得回忆一下Vulkan的图形管线流程。创建着色器模块之后,接下来应该处理管线的其他部分吧?记得Vulkan的图形管线需要多个步骤,比如顶点输入、着色器阶段、图元装配、视口和裁剪、光栅化、片元着色等等。所以创建完着色器模块后,可能需要把它们整合到管线里。 然后,可能需要创建管线布局,管理着色器中的uniform变量和推送常量。还有描述符集布局,用来定义资源如何绑定到着色器上。这部分用户可能会容易出错,特别是描述符布局和管线的结合。 另外,顶点输入相关的结构需要配置,比如顶点绑定和属性描述。用户可能已经创建了顶点缓冲区,所以需要确认这些结构是否正确设置到管线中。 还有渲染流程,可能需要创建渲染通道(Render Pass)和帧缓冲区(Framebuffer),这样才能确定颜色和深度附件的格式和使用方式。渲染通道的创建可能会影响管线的配置,所以在创建图形管线之前应该先有Render Pass。 然后图形管线的创建需要把所有这些东西组合起来,包括着色器阶段、顶点输入、视口状态、光栅化状态等等。用户可能在这一步需要填写很多结构体,容易遗漏某些配置,比如动态状态或者多重采样设置。 最后,创建完管线之后,用户可能需要在命令缓冲中绑定管线,然后记录绘制命令。这部分需要确保命令缓冲的正确录制,以及同步对象的管理,比如信号量或栅栏,确保渲染顺序正确。 可能还需要考虑描述符集的创建和更新,把实际的缓冲区图像资源绑定到着色器上。用户如果在这里没有正确设置描述符集,可能会导致着色器无法访问到需要的资源,出现黑屏或者其他渲染问题。 总之,步骤应该包括创建管线布局、设置顶点输入、配置渲染通道、创建图形管线,然后处理描述符集和命令缓冲的记录。用户可能需要按照这些步骤逐一检查,确保每个部分都正确配置,没有遗漏必要的结构体或状态。</think>在Vulkan创建着色器模块后,需要继续完成以下关键步骤以构建完整的渲染管线: 1. **创建管线布局(Pipeline Layout)** - 定义着色器使用的uniform资源和推送常量 - 使用`VkPipelineLayoutCreateInfo`结构体配置 - 示例代码: ```c VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipelineLayoutInfo.setLayoutCount = 1; // 关联描述符集布局 pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout; vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout); ``` 2. **创建渲染流程(Render Pass)** - 定义帧缓冲附件(颜色/深度附件)的格式和使用方式 - 配置子流程(subpass)及其依赖关系 - 关键结构体: ```c VkRenderPassCreateInfo renderPassInfo{}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; renderPassInfo.attachmentCount = 1; renderPassInfo.pAttachments = &colorAttachment; renderPassInfo.subpassCount = 1; renderPassInfo.pSubpasses = &subpass; ``` 3. **配置图形管线** - 组合以下组件: - **着色器阶段**:通过`VkPipelineShaderStageCreateInfo`指定顶点/片元着色器 - **顶点输入**:定义顶点数据格式(绑定描述和属性描述) - **输入装配**:指定图元类型(三角形/线等) - **视口和裁剪**:设置视图变换 - **光栅化**:配置多边形填充模式、面剔除等 - **多重采样**:配置抗锯齿参数 - **深度/模板测试**:配置深度测试逻辑 - **颜色混合**:设置颜色混合模式 4. **创建图形管线对象** - 使用`VkGraphicsPipelineCreateInfo`组合所有配置 - 示例结构: ```c VkGraphicsPipelineCreateInfo pipelineInfo{}; pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; pipelineInfo.stageCount = 2; // 顶点+片元着色器 pipelineInfo.pStages = shaderStages; pipelineInfo.pVertexInputState = &vertexInputInfo; pipelineInfo.pInputAssemblyState = &inputAssembly; pipelineInfo.pViewportState = &viewportState; pipelineInfo.pRasterizationState = &rasterizer; pipelineInfo.pMultisampleState = &multisampling; pipelineInfo.pDepthStencilState = &depthStencil; pipelineInfo.pColorBlendState = &colorBlending; pipelineInfo.layout = pipelineLayout; // 之前创建的管线布局 pipelineInfo.renderPass = renderPass; // 关联的渲染流程 vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline); ``` 5. **创建描述符(Descriptor Sets)** - 绑定uniform buffer、纹理等资源 - 需要先创建描述符(Descriptor Pool) - 分配并更新描述符集 6. **记录命令缓冲** - 在渲染循环中: ```c vkCmdBeginRenderPass(commandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); vkCmdBindDescriptorSets(...); vkCmdDraw(commandBuffer, vertexCount, 1, 0, 0); vkCmdEndRenderPass(commandBuffer); ``` **关键注意事项**: 1. 确保所有Vulkan对象的生命周期管理正确(先创建依赖对象) 2. 使用验证层(Validation Layers)检查配置错误 3. 合理管理管线缓存以提高性能 4. 对不同的渲染需求创建多个管线对象 建议通过`VkPipelineCache`重用管线创建数据以提高后续管线创建速度。同时,对于复杂的场景,应考虑使用管线变体来处理不同的渲染状态组合。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TYYJ-洪伟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值