/EqnoEngine

Cpp and rust codes for glfw and vulkan test.

Primary LanguageC++

架构

框架层

  • RHI 与引擎层分离,分别由 Game / Render 线程驱动。
  • 借助可变参数模板,封装实例的创建和销毁(工厂模式),实现 class owner 的递归注册。
  • 实现异步加载队列,只需将函数 bind 后 push 入队列,便可在单独的 resource 线程执行。
  • 引擎层使用 shared_ptr 持有 Object 实例,RHI 层使用 weak_ptr 持有引擎数据。
    • RHI 层借此实现 mesh 的生命周期管理,保证线程安全。
    • 当引擎层销毁 Object 时,其包含的 mesh 将在下一帧开始时销毁。

RHI 层

  • 使用 Subpass 和 InputAttachment 实现 One Pass Defer,解决多光照的性能问题。
    • 在 GBuffer 中记录 pipeline id,在 Fragment Shader 中 discard 片段,实现 BlinnPhong 与 PBR 混合渲染。
  • 使用 Z-PrePass 进行深度测试优化,减少 Fragment Shader 的执行次数,提高渲染性能。
  • 支持为 Z-PrePass、Shadow Map、Deferred Shading 和 MSAA 在 Config 中配置开启和关闭。
  • 提供了多套 Lit/Unlit 内置 Shader,其中包括 Blinn-Phong 和基于 Cook-Torrance 的 PBR。
  • 使用 Fence、Barrier 和 Semaphore 进行设备、内存、管线、命令和 Render Pass 的同步。
  • 支持 Multiple Frames In Flight,可以为交换链指定 Frame 数量,减少 CPU 和 GPU 同步带来的性能浪费。
  • 支持 Texture 的 Mipmap 生成,使用线性插值对相邻的 Mipmap 进行采样,防止渲染远处的物体时出现摩尔纹。
  • 支持 MSAA 反走样,在 Configs/Graphics 中配置最大采样倍数,自动查询并使用设备支持且最接近配置的倍数。
  • 使用 Shaderc 自动编译 Shader 文件并创建 Shader Stage。
    • 支持 Shader 外定义宏,Shader 内直接使用。
    • 支持 Shader Fallback 功能,在 Material 文件中填写多个 Shader 路径以使用。
    • 支持 Shader Include 的功能,设置 Include Path,使用 File Finder 自动寻找头文件并引入编译。
  • 同 Shader Pipeline 合并,减少 DST 和 Pipeline 的创建。
    • 使用同一 LightChannel 的 mesh Storage Buffer 合并。
    • 使用同一 Camera 或 Material 的 mesh Uniform Buffer 合并。
  • 支持 LightChannel,指定多光源作用于 SceneObject。
    • 多光源着色,光照通道信息使用 Storage Buffer,以动态大小传递给 Fragment Shader,避免 GPU 内存的浪费。
    • 多光源阴影,生成 Shadow Map 时,利用动态 DST 传递光源 VP Matrix,只需在记录 Command Buffer 时换绑。

引擎层

  • 使用文件路径,缓存并复用 Texture 的数据,使材质加载更高效。
  • 重载 operator new 实现 Object 内存池,优化内存分配和销毁的性能消耗。
  • 借助 RapidJson 实现简易文件系统,支持 Config 和 Object 模板的序列化和反序列化。
  • 使用匿名函数和闭包,返回元素随时可能失效的序列迭代器,动态维护当前场景内的有效灯光。
  • 实现基于基向量、与 Unity 类似的 Relative / Absolute Transform 系统。
    • 设置某一 SceneObject 的 Transform 属性时,其他属性也会进行同步。
    • SceneObject 进行 Transform 变换时,子 SceneObject 同时进行递归变换。
  • 使用协程为 RHI 层 ParseMesh 并创建管线,减少面数较多的模型加载时的卡顿。
  • 使用单独的线程异步加载 Model,场景中 Model、Light 和 Camera 等任何 SceneObject 均可在运行时动态添加或删除。
  • 实现 InputSystem 输入监听系统,无论在一帧内的何时按下按键,一定在下一帧开始时触发,实现一帧内的监听结果统一。
  • 实现 Object 的生命周期管理和事件调用,其中 OnStart 的调用位于下一帧的开始,保证晚于同一帧内的所有 OnCreate。

Q & A

框架层

Q:为什么部分指针下转不使用 dynamic_cast 和 dynamic_pointer_cast?

A:dynamic_cast 匹配虚表,性能堪忧,如果可以确定下转目标类型,使用 static_cast。

RHI 层

Q:Deferred Shading 相比 Forward + ZPrePass 优化的性能优势在哪?

A:延迟管线的第二个 Subpass,不会对每一个 mesh DrawIndexed,而是仅仅 Draw Fullscreen Quad。

Q:为 Dscriptor 指定 ImageInfo 时,为什么不可以合并两次遍历为一次?

A:Vector 的 push_back 会导致扩容时内存被整体移动,先前指定的指针失效,变为野指针。

Q:为什么要为每个光源的 ShadowMap 生成创建独立的 UBO,而非通过切换来复用同一个?

A:RecordCommandBuffer 的顺序不是 Pipeline 在 GPU 内的执行顺序,真正的执行是【乱序】且【异步】的。

Q:上一个问题的延伸,为什么不使用 MemoryBarrier 或 Fence 进行同步?

A:MemoryBarrier 是 GPU 与 GPU 之间的内存屏障,Fence 虽然可以同步但会影响性能。

Q:为什么会有一种说法 —— Deferred Shading 和 MSAA 不兼容?

A:使用 MultiSample 输出 GBuffer,占用显存太大了,后面的 Process 还要取 Average,性能堪忧。

Q:为什么在 Deferred Process Subpass 中,Draw Fullscreen Quad 多次?

A:每一次 Draw Fullscreen Quad 都是在渲染一种着色模型,故而支持多着色模型混合渲染。