Welcome 欢迎

这里记录着技术探索与学习的思考与收获。

每一篇文章都是知识积累的一部分。

vulkan-engine-roadmap

Vulkan 学习与图形引擎进阶路线 现状定位 完成 vulkan-tutorial 的 Loading Models 章节是一个分水岭。到这一步,Vulkan API 的核心套路(instance、device、swap chain、render pass、pipeline、descriptor、buffer/image、command buffer、同步原语)已经基本掌握。 接下来的路径分两段:先把 vulkan-tutorial 剩余章节按需完成,然后从"会用 API"过渡到"会设计渲染器/引擎"。 第一阶段: 完成 vulkan-tutorial 剩余章节 剩下的三章按重要性排序: Generating Mipmaps —— 必学 不只是为了"知道怎么生成 mipmap"。这一章让你第一次真正用 vulkan 在运行时用 GPU 生成数据(vkCmdBlitImage 在不同 mip 层级间逐级缩小)。这套"用 transfer/compute 在 image 上写东西"的模式后面到处都是: HDR bloom、SSAO 多分辨率模糊、间接光照辐照度图预计算。不做 mipmap 的话纹理远处会闪烁、走样,任何稍微像样的渲染器都不能省。 工作量: 半天到一天。 Compute Shader —— 必学 PBR 渲染器、IBL 预计算、后处理(bloom、tonemapping、color grading)都需要 compute shader。光追也跟 compute pipeline 强相关。这章必修。 工作量: 一天。 Multisampling —— 可以缓一缓 教 MSAA。MSAA 在前向渲染里有用,但实战中渲染器很可能走延迟渲染(deferred shading)路线,延迟渲染下 MSAA 性能开销巨大,通常用 TAA 替代。 ...

2026年5月8日 · 3 min · 604 words · Blog

Vulkan-Texture Mapping

Image Loding Image 使用stb加载图片,使用vcpkg管理外部包:vcpkg install stb。 然后修改MakeLists: cmake_minimum_required(VERSION 3.20) project(vulkan LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) find_package(Vulkan REQUIRED) find_package(glfw3 CONFIG REQUIRED) find_package(glm CONFIG REQUIRED) find_package(Stb REQUIRED) // Add this line add_executable(vulkan main.cpp) target_link_libraries(vulkan PRIVATE Vulkan::Vulkan glfw glm::glm ) 最后include即可使用: #define STB_IMAGE_IMPLEMENTATION #include <stb_image.h>

2026年4月10日 · 1 min · 45 words · Blog

Vulkan-uniform buffers

顶点属性(vertex buffer)是per-vertex的,每个顶点不同。但MVP矩阵是所有顶点共享的,每帧可能变化。如果塞进vertex buffer,即浪费内存又要频繁更新整个buffer。vulkan的解决方案是使用descriptor,一种让shader访问buffer/image等资源的机制。 Vertex Shader 修改顶点着色器使其包含上述的descriptor对象: ...

2026年4月3日 · 5 min · 986 words · Blog

Vulkan-vertex buffers

Staging Buffer 暂存缓冲区 之前的方案是直接在CPU可访问的内存上创建顶点缓冲区,没有使用staging buffer。createVertexBuffer()中: 创建一个VK_BUFFER_USAGE_VERTEX_BUFFER_BIT的buffer 分配内存时请求的是VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,也就是CPU可见、自动同步的内存 然后直接vkMapMemory->memcpy->vkUnmapMemory,手动将顶点数据从CPU端拷贝进去 这个方案能正常工作,但是HOST_VISIBLE的内存在独显上通常位于RAM或者PCIe BAR区域。虽然memcpy只在初始化时执行一次,但数据一直"住"在系统内存中,GPU每帧渲染读取顶点数据时都要通过PCIe总线去访问。而staging buffer方案是初始化时多一次GPU拷贝,把数据搬到DEVICE_LOCAL显存里,之后每帧GPU直接从本地显存高速读取。 Transfer queue 转运队列 vkCmdCopyBuffer这样的传输命令需要队列支持VK_QUEUE_TRANSFER_BIT。现在已经在使用的Graphics队列族天生就支持传输操作。简单做法是直接用现有的Graphics队列来提交拷贝命令,不需要改任何队列相关的代码。 进阶做法是用一个专门的Transfer队列族来拷贝,某些显卡上存在专门的Transfer队列族,制作数据搬运不做图形渲染,GPU可以并行工作:Transfer队列搬运数据的同时,Graphics队列继续渲染,互不阻塞。 findQueueFamilies要额外找一个只有TRANSFER_BIT没有GRAPHICS_BIT的队列族,这样能找到专用的传输队列,而不是“顺便能传输”的队列 createLogicalDevice要多请求一个传输队列的handle 要创建第二个Command Pool,因为Command Pool和队列族绑定,不同队列族需要不同的Pool buffer的sharingMode要改成CONCURRENT。因为现在两个不同的队列族都要访问同一个buffer(Transfer队列写入,Graphics队列读取) 拷贝命令提交到Transfer队列而不是Graphics队列 Abstracting buffer creation 抽象缓冲区创建 如果每次都把“创建Buffer->查询内存需求->分配内存->绑定”这套代码写一遍会有大量重复,把通用逻辑抽成一个createBuffer函数: void 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; // 这个buffer是用来当顶点缓冲区的 // 缓冲区可以由特定队列族所有,或者在多个队列族之间共享。 // 顶点缓冲区只会从图形队列中使用,因此可以使用EXCLUSIVE模式。只有当不同队列族需要访问同一个缓冲区时,才需要使用CONCURRENT模式,并且还需要指定所有访问该缓冲区的队列族索引。 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) { throw std::runtime_error("failed to create vertex buffer!"); } // 分配内存 // step 1: 查询缓冲区需要什么样的内存。 // memRequirements告诉三件事: // 1. size: 需要分配的内存大小(字节数)。这个值可能会根据buffer的usage和format等属性而变化。 // 2. alignment: 内存的对齐要求。分配的内存地址必须是alignment的倍数。这个值可能会根据buffer的usage和format等属性而变化。 // 3. memoryTypeBits: 一个位掩码,表示哪些类型的内存可以用来分配这个buffer。每个位对应一个内存类型,如果某个位为1,表示该内存类型支持这个buffer的需求。这个值取决于buffer的usage和format等属性,以及物理设备的内存特性。 VkMemoryRequirements memRequirements; vkGetBufferMemoryRequirements(device, buffer, &memRequirements); VkMemoryAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; allocInfo.allocationSize = memRequirements.size; // step 2: 根据第一步查询到的内存需求,找到一个既满足buffer需求又具有指定属性的内存类型。比如我们需要一个既能当作顶点缓冲区又可以直接从CPU访问的内存类型。 // HOST_VISIBLE + HOST_COHERENT: CPU可写,且自动同步 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { throw std::runtime_error("failed to allocate vertex buffer memory!"); } vkBindBufferMemory(device, buffer, bufferMemory, 0); } Using a staging buffer 使用暂存缓冲区 - 改造createVertexBuffer 现在创建两个buffer而不是一个: ...

2026年4月1日 · 3 min · 516 words · Blog