游戏引擎RHI目前选用的是bgfx

  • Pros
    • 可以参考的代码比较多
      • Real-Time Polygonal-Light Shading with Linearly Transformed Cosines论文作者的原文实现也用了bgfx
      • Github上的LightMap开源烘焙工具XAtalas,也是基于bgfx作为3D查看器
      • 我的世界也使用了bgfx作为图形API,对bgfx的质量有一定保证
      • 更多例子可以查看Github bgfx的README.md
    • 兼容性,基本上所有图形API,平台都有支持,而且是经过测试的
  • Cons
    • 包袱多,对新旧两三代图形API都做了支持,抽象到同一套API中,让bgfx的上层API看起来太high level,与现代图形API违背
    • 虽然bgfx API是high level的,但实际上直接拿这些API去写图形效果会很累,仍然需要再次进行high level抽象

bgfx入门

  • 通过GENie编译
    • –with-windows指定使用的Windows SDK版本,–with-examples可以把bgfx的几十个参考例子生成出来,–with-tools可以生成bgfx工具层项目,比如基于glsl的shader跨平台预处理器,模型/贴图的二进制编译工具
      1
      2
      cd "bgfx"
      "../bx/tools/bin/windows/genie.exe" --with-windows=10.0 --with-examples --with-tools vs2022`
  • 打开生成的.build/projects/vs2022/bgfx.sln,工程分为三块
    • libs
      • 默认通过静态库的形式链接bgfx等库函数,也可以在make时指定动态库的形式
    • tools
      • shader/texture/model资源编译工具
    • examples
      • example-common是一个简单的例子程序框架,方便编写其他例子
        • 第三方库
          • dear-imgui :一个旧版ImGui版本,被bgfx修改后集成
          • meshoptimizer :模型优化工具,可以优化顶点,索引数据的布局,模型自动减面
      • 其他example工程代码是通过继承一个example-common提供的带有init,shutdown,update三个纯虚函数的接口类实现的
      • example-00-helloworld
        • 在init中,bgfx::init函数负责了图形API初始化的所有工作
          • 举一些重要的Init结构体参数做说明
            • type是RendererType::Enum枚举类型,罗列了bgfx支持的所有图形API类型,每一个类型都对应了libs/bgfx/src目录下的一份渲染后端代码.h/.cpp。比如DirectX11,就对应了renderer_d3d11.h和renderer_d3d11.cpp
            • vendorId可以指定使用某一个品牌的显卡,或者是软光栅模拟;如果同一品牌的有两张显卡,那么可以用deviceId来指定用哪一张
            • capabilities - 指定显卡特性开启的mask
            • debug和profile - 开发期间打开,输出更多的调试信息到Output窗口
            • platformData - 平台相关参数,比如想把图形API渲染的画面输出到一个Windows窗口上,那么可以指定platformData.nwh(native window handle)为该Windows窗口的句柄HWND。我个人推荐长命名的方式,nwh这类省略式的命名方式在大规模的工程中增大了理解难度,比如dir可以是direction,也可以是directory
            • resolution - 指定渲染目标的FrameBuffer尺寸,数量等
            • limits - 对Command Buffer,以及模型相关的Vertex Buffer/Index Buffer做限制
              • Command Buffer是现代图形API里的概念,主体思想是CPU和GPU的交流就像客户端和服务器异步通信一样,每一个图形API操作都可以理解成是发送一条命令/消息,进行一次图形操作的背后是多条图形API的组合。因为GPU算力是远大于CPU的,所以当CPU单核提交渲染命令时,GPU可能会因为CPU布置任务太慢太少而闲置下来。这种情况下,现代图形API的设计是每一帧的画面渲染需要的命令由多个线程去提交,每个线程都拥有一个Command List,去塞该线程提交的Command,然后一帧命令准备完毕,再从每个线程的Command List汇总所有Command塞到图形API的Command Buffer中,一次性提交给GPU,等待绘制
              • 在bgfx的概念里,bgfx::submit操作会将一次物体绘制需要的资源句柄收集起来,对应一个draw call;每个渲染线程可以分配一个bgfx::encoder,这个encoder就记录着draw call需要的资源句柄。等待bgfx::frame的调用,去一次性通过图形API提交渲染数据到GPU上
            • allocator可以指定一个定制化的内存分配器,但需要保证线程安全
        • 在init中,bgfx::setViewClear负责每帧设置清屏的初始颜色
          • bgfx的view是一个重要的概念,不同于常规理解的viewport,这里的view是一个draw call队列,每个view有自己的view id,也就是bgfx::setViewClear的第一个参数,你还会看到很多bgfx api要求指定view id,比如前面提到的bgfx::submit创建draw call,它就需要指定一个view id,来把draw call创建到某一个draw call队列里,也就是view。
          • view的作用有很多,比如图形引擎经常需要做的一件事情是对draw call对象进行各种排序,来达到渲染目的。比如说透明物体渲染,那么就可以把所有透明物体塞到一个view里,然后通过bgfx::setViewMode去指定队列内draw call优先级,没错,draw call队列是一个优先队列
          • view也可以用于区分不同目的draw call,保持渲染模块的整洁,比如ingame GUI的渲染可以指定ViewId为0,3D场景的不透明物体渲染指定ViewId为1,透明物体渲染指定ViewId为2,…,至多可以指定0~255一共256个view
        • 在init中,imguiCreate负责ImGui的初始化
          • 跟进到example-common里的ImGui框架,你会发现ImGui API调用产生的绘制数据在框架内转译成了bgfx API调用,再自行塞到前面提到的bgfx::encoder当中,等待bgfx::frame提交
        • init背后的事
          • 图形Device以及Context的创建
          • FrameBuffer创建完了之后,SwapChain的设置
        • shutdown时,注意先销毁ImGui把bgfx::encoder释放掉,最后再销毁bgfx
        • update时,imgui API的调用会让ImGui内部生成DrawCall,最后在example框架中会对接bgfx的各种设置材质,Vertex Buffer/Index Buffer的API,最后再由bgfx::frame提交到GPU
      • example-05-instancing : gpu instance渲染
      • example-12-lod : 一种edge collapse方式的模型自动减面算法
      • example-13-stencil : 模板测试
      • example-17-drawstress : 多个渲染线程
      • example-27-terrain : 地形
      • example-32-particles : GPU粒子实现
      • example-37-sky : 大气散射,天光
      • example-38-bloom : 后处理bloom
      • example-46-fsr : AMD FSR超采样
      • TBD

bgfx源码剖析

TBD

其他RHI

参考资料