小型游戏引擎需要高效、轻量的渲染后端,Vulkan API 以其低开销和高控制力成为理想选择。本文聚焦基础渲染器实现,覆盖描述符集(Descriptor Sets)、管线状态(Pipeline States)、命令缓冲(Command Buffers)及同步机制(Synchronization),构建从三角形渲染到纹理输出的闭环。不同于高级 bindless 或动态管线,本文强调初学者友好的核心设置,提供可直接落地的 C++ 代码片段与参数配置。
Vulkan 初始化基础
首先,建立 Vulkan 上下文:创建 VkInstance、选择物理设备(VkPhysicalDevice),并创建逻辑设备(VkDevice)。优先选择支持 graphics 和 present 队列族的设备。
// 伪代码示例
VkInstanceCreateInfo instanceInfo{};
// ... 启用验证层
vkCreateInstance(&instanceInfo, nullptr, &instance);
uint32_t deviceCount = 0;
vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
std::vector<VkPhysicalDevice> devices(deviceCount);
vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
// 选择支持 VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_PRESENT_BIT 的设备
创建交换链(Swapchain)时,设置 imageCount=2(双缓冲),format=SwapchainKHR 的 surfaceFormat,presentMode=VK_PRESENT_MODE_FIFO_KHR(VSync)。对于小引擎,extent 设置为窗口尺寸,minImageCount=2 避免撕裂。
描述符集:资源绑定接口
描述符集是着色器与资源(如 Uniform Buffer、纹理)的桥梁。小引擎中,用于传递 MVP 矩阵和纹理采样器。
- 创建描述符集布局(VkDescriptorSetLayout):
- Binding 0: Uniform Buffer (VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, stage=VK_SHADER_STAGE_VERTEX_BIT)
- Binding 1: Combined Image Sampler (VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, stage=VK_SHADER_STAGE_FRAGMENT_BIT)
VkDescriptorSetLayoutBinding uboLayoutBinding{};
uboLayoutBinding.binding = 0;
uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
uboLayoutBinding.descriptorCount = 1;
uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
VkDescriptorSetLayoutCreateInfo layoutInfo{};
layoutInfo.bindingCount = 2; // UBO + Sampler
vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout);
- 创建描述符池(VkDescriptorPool),poolSize 为 uniformBuffers=MAX_FRAMES_IN_FLIGHT (2),samplers=1。
- 分配描述符集(VkDescriptorSet),更新(vkUpdateDescriptorSets)绑定 Uniform Buffer 和纹理视图。
参数建议:MAX_FRAMES_IN_FLIGHT=2,减少内存占用;使用 staging buffer 上传 Uniform 数据,每帧更新模型矩阵。
管线状态:固定渲染配置
VkGraphicsPipelineCreateInfo 定义管线状态,包括顶点输入、着色器阶段、光栅化、混合等。
- 顶点着色器:输入位置(VK_FORMAT_R32G32B32_SFLOAT),输出到 gl_Position。
- 片段着色器:采样纹理或简单颜色。
- 输入装配:VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST。
- 视口:动态状态(VK_DYNAMIC_STATE_VIEWPORT),scissor 匹配 swapchain extent。
- 多采样:disabled(小引擎简化)。
- 深度测试:disabled(平面三角形)。
创建管线缓存(VkPipelineCache)加速后续管线创建。渲染通道(RenderPass):颜色附件 VK_ATTACHMENT_LOAD_OP_CLEAR 到 VK_ATTACHMENT_STORE_OP_STORE,subpass 无依赖。
VkGraphicsPipelineCreateInfo pipelineInfo{};
pipelineInfo.pVertexShaderModule = vertexShader;
pipelineInfo.pFragmentShaderModule = fragmentShader;
pipelineInfo.pVertexInputState = &vertexInputInfo; // stride=6*sizeof(float) for pos+uv
pipelineInfo.pInputAssemblyState = &inputAssembly;
pipelineInfo.pViewportState = &viewportInfo; // dynamic viewport
pipelineInfo.pRasterizationState = &rasterizer; // cullMode=VK_CULL_MODE_BACK_BIT
vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineInfo, nullptr, &graphicsPipeline);
落地参数:rasterizer.lineWidth=1.0f;depthBias=0;polygonMode=VK_POLYGON_MODE_FILL。
命令缓冲:绘制指令录制
命令池(VkCommandPool)绑定 graphics queue family。预分配命令缓冲(vkAllocateCommandBuffers),一级缓冲(primary)。
录制流程:
- vkCmdBeginRenderPass:clearValue color=(0.0f,0.0f,0.0f,1.0f)。
- vkCmdBindPipeline:graphicsPipeline。
- vkCmdBindDescriptorSets:slot=0。
- vkCmdBindVertexBuffers /vkCmdBindIndexBuffer(若索引)。
- vkCmdDraw (3,1,0,0) 或 vkCmdDrawIndexed。
- vkCmdEndRenderPass。
对于纹理渲染:创建 offscreen RenderPass(颜色附件为纹理 Image,VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT),独立 Framebuffer。命令中切换布局(vkCmdPipelineBarrier,VK_IMAGE_LAYOUT_UNDEFINED 到 VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL)。
小引擎优化:命令缓冲复用,每帧 vkResetCommandBuffer + 重新录制;使用 secondary buffers 嵌套复杂场景。
同步机制:帧间安全
双重缓冲需同步:
- 信号量(VkSemaphore):imageAvailableSemaphore(acquire 后信号),renderFinishedSemaphore(submit 后信号)。
- 栅栏(VkFence):inFlightFence [inFlightIndex],等待上一帧完成。
渲染循环:
while (!glfwWindowShouldClose(window)) {
vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX);
uint32_t imageIndex;
vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex);
vkResetFences(device, 1, &inFlightFences[currentFrame]);
vkResetCommandBuffer(commandBuffers[currentFrame], 0);
recordCommandBuffer(commandBuffers[currentFrame], imageIndex); // 包含纹理渲染
VkSubmitInfo submitInfo{};
VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]};
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = waitSemaphores;
vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]);
VkPresentInfoKHR presentInfo{};
presentInfo.pWaitSemaphores = &renderFinishedSemaphores[currentFrame];
vkQueuePresentKHR(presentQueue, &presentInfo);
currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
}
参数:超时 UINT64_MAX;fenceCreateInfo.flags=VK_FENCE_CREATE_SIGNALED_BIT(初次)。
三角形到纹理渲染循环
- 创建纹理 Image(VK_IMAGE_TYPE_2D, extent=512x512, format=VK_FORMAT_R8G8B8A8_SRGB, usage= COLOR_ATTACHMENT | TRANSFER_SRC)。
- 分配 DeviceLocal 内存,transition 布局。
- RenderPass1:渲染三角形到纹理(clear red)。
- Barrier:纹理从 COLOR_ATTACHMENT_OPTIMAL 到 FRAGMENT_SHADER_READ_ONLY_OPTIMAL。
- RenderPass2:全屏 quad 采样纹理显示到 swapchain。
监控要点:RenderDoc 捕获帧,检查 barrier 布局错误;GPU 负载 <80%;帧时 <16ms。
回滚策略:fallback 到单缓冲(imageCount=1),禁用多采样。
此实现总代码~1500 行,适合小引擎原型。扩展时添加 push constants 减少描述符更新。
资料来源:Elias Daler 的 Vulkan 小游戏引擎经验(edw.is/learning-vulkan),Vulkan Tutorial 基础流程,vkguide.dev 同步最佳实践。