代码地图
要找到实现所有间接绘制改进的代码,它位于 github 仓库的 “engine” 分支上。直接链接如下:仓库
代码库从第 5 章结束的地方继续,但添加了许多改进和抽象。 “额外” 章节中的所有内容都在这里,并且有一些内容在 “额外” 章节中没有文章。
- Imgui 支持:向引擎添加了 imgui UI,主要可以在 VulkanEngine 类中找到。说明这里
- CVars.h/cpp:为一些配置变量实现了一个 CVar 系统。说明在这里。这里的实现还支持在 Imgui 中调整变量。
- player_camera.h:小型相机系统,可以在地图上飞行。
- logger.h:日志系统,在控制台中显示更友好的信息/错误消息。
- vk_pushbuffer:将数据添加到缓冲区中,用于动态描述符。
- vk_profiler:添加一个计时分析器,以了解每个通道花费的时间,还可以显示 gpu 处理了多少三角形。连接到 Imgui。
- vk_engine_scenerender.cpp:将绘制和剔除逻辑从主 vk_engine.cpp 中分离出来。这里执行剔除和绘制命令。
- vk_scene:包含间接绘制缓冲区和不同网格通道的场景管理。
- material_system:新的材质系统,允许给定的材质在多个网格通道中渲染,并抽象化管线和描述符。
- vk_descriptors:描述符集的完全抽象,说明在这里
- vk_shaders:着色器编译代码。它使用 spirv-reflect 从着色器自动构建管线布局并获取其他信息。
- 资产系统和烘焙器:来自这里解释的那个。但它支持更优化的网格格式,并支持预制件和材质。它现在可以加载任意 GLTF 文件和 FBX 文件。预制件是场景节点列表,并在加载时转换为多个可渲染对象。
- 计算着色器:计算着色器的逻辑已添加到主 Vulkan Engine 类中。现在有一个 ComputePipelineBuilder 和更多关于内存同步的功能。
- 改进的缓冲区处理:统一缓冲区和存储缓冲区现在有一些改进,例如用于增长缓冲区的 Reallocate 函数。主要在 vk_engine.cpp 中
渲染流程
主引擎渲染循环与章节后的循环类似,但添加了很多内容。首先,可渲染对象的处理通过 vk_scene,并从预制件加载。
当引擎初始化时,它加载一些预制件并将它们作为 MeshObjects 生成到世界中,然后将其注入到 RenderScene 中,RenderScene 然后将根据材质和配置将对象添加到多个网格通道中。
处理了 3 个网格通道。 Forward 通道处理对象的“不透明”渲染,Transparent 通道处理半透明对象,并在不透明对象完成后绘制。然后是 Shadow 通道,它将渲染太阳阴影。 MeshObjects 将根据其设置注册到这 3 个通道中。不透明对象将被添加到 Forward 和 Shadow 通道,而半透明对象将仅注册到 Transparent 通道,因为我们不希望半透明对象投射阴影。
引擎加载完成后,在引擎初始化结束时调用 RenderScene::build_batches()
和 RenderScene::merge_meshes()
。 build_batches 将处理所有网格通道并准备它们的间接绘制命令。 merge_meshes 将抓取注册到 RenderScene 的每个网格的顶点缓冲区,并将它们合并到一个巨大的顶点缓冲区中。这允许我们每个网格通道绑定一次顶点缓冲区,并且永远不再触摸它。
初始化完成后,我们进入帧循环。
在帧循环开始时,我们刷新描述符缓存和帧删除队列,以确保动态事物被重置。然后它调用 ready_mesh_draw()
,它将处理对象数据的更改并将所有内容上传到 GPU。这是将 RenderScene 中处理的数据上传到 GPU 的主要步骤。
完成后,我们开始准备计算剔除通道的数据。为 RenderScene 中的每个网格通道调用 ready_cull_data
,这将将 gpu 绘制状态重置为其“默认”状态,准备好由剔除计算着色器写入。
当多个 ready_cull 调用完成后,我们执行一个管线屏障,以确保所有内存传输在计算着色器开始执行之前完成。
之后,对 3 个通道执行剔除。 Forward 和 Transparent 通道使用相同的设置,因为它们都从主相机视图进行剔除,而 shadow 通道使用不同的逻辑。
在执行了剔除的计算着色器后,我们在 GPU 上执行另一个屏障,以确保 gpu 在开始绘制命令之前完成计算着色器的执行。
然后我们执行绘制命令本身。首先执行 shadow_pass(),它将从太阳位置渲染场景深度到深度纹理中。然后执行 forward 通道,首先渲染所有不透明网格,然后渲染所有透明网格。
渲染结束后,深度缓冲区被转换为深度金字塔,这将用于下一帧的剔除。
我们还将渲染的图像复制到交换链中,使其准备好呈现。