图形子系统是渲染层中图形相关子系统的最高层. 它基本上是Mangalore图形子系统的下一个版本, 但是现在整合进了Nebula, 并且与低层的渲染代码结合得更加紧密. 最基本的思想是实现一个完全自治的图形”世界”, 它包含模型, 灯光, 还有摄像机实体, 而且只需要与外部世界进行最少的通信. 图形世界的最主要操作是加入和删除实体, 还有更新它们的位置.
因为Mangalore的图形子系统跟Nebula2的完全分界线从Nebula3中移除了, 很多设想都可以用更少的代码和交互来实现.
图形子系统也会为了异步渲染而多线程化, 它和所有的底层渲染子系统都会生存在它们自己的fat-thread中. 这本应是Nebula3层次结构中更高级的东西, 但是我选择了这个位置, 因为这是游戏跟渲染相关通信最少的一部分代码. 正是因为图形代码有了更多的”自治权”, 游戏相关的代码可以跟图形以完全不同的帧率来运行, 不过这需要实践来证明一下. 但是我一定会尝试, 因为完全没有必要让游戏逻辑代码运行在10帧以上(格斗游戏迷们可能会反对吧).
图形子系统中最重要的公有类有:
- ModelEntity
- CameraEntity
- LightEntity
- Stage
- View
一个ModelEnity表示了一个可见的图形对象, 它包括位置, 包围体和内嵌的Model资源. 一个Model资源是一个完全的3D模型, 包括几何体, 材质, 动画, 层级变换等…(后面会提到).
一个CameraEntity描述了图形世界中的一个视景体, 为渲染提供View和Project矩阵.
一个LightEntity描述了一个动态光源. Nebula3的光源属性还没有最终确定, 但是我的目标是一个相对灵活地近似(最后一个光源不会超过几个shader参数).
Stage和View是Nebula3图形子系统新增的内容. 在Mangalore中, 图形实体是生存在一个单独的图形Level类里, 任何时候只能有一个Level和一个摄像机. 这对于只需要渲染一个世界到帧缓存(frame buffer)的情况来说还是不错的. 但许多游戏程序需要更复杂的渲染, 如在GUI中渲染一个使用单独灯光的3D对象, 而它又跟其它的图形世界是隔离的. 还有反射或像监视器之类的东西都需要一个额外的视口, 诸如此类. 在Mangalore中, 这个问题通过OffscreenRenderer类得到解决, 虽说比较容易使用, 但是具有一些使用限制并且需要更多事后的思考.
Nebula3提供了一个基于State和View的更加简洁的解决方案. 一个Stage就是一个图形实体的容器, 表示一个图形世界. 同一时间可能存在多个Stage, 但是它们之间是互相隔绝的. 每个实体在一个时刻只连接到了一个Stage(虽说克隆一个已有实体是一件很简单的事情). 除了简单地把实体组织到一起外, Stage的主要工作是根据它们之间的关系来加速可见性查询. 应用程序可以派生Stage的子类来实现完全不同的可见性查询方案.
一个View对象通过一个CameraEnity渲染stage到一个RenderTarget. 任何stage都可以连接任意数量的View对象. View对象可能会互相依赖(也可能是连接到不同stage的View), 所以更新一个View会首先强制更新另一个View的RenderTarget(这在一个View渲染需要使用另一个View的RenderTarget做为纹理时很方便). View对象完全实现了自己的渲染循环. 应用程序可以在View的子类中方便地实现它自己的渲染策略(如每个light一个pass VS 每个pass多个light, 渲染到cubemap, 等等).
总而言之, 一个Stage完全控制了可见性查询流程, 而一个View则完全控制了渲染流程.
图形子系统的一个最主要的工作就是根据可见性查询的结果来决定哪些实体需要被渲染. 一个可见性查询在实体间建立了一个双向的链接, 它有两种形式: 摄像机链接和灯光链接. 摄像机链接把一个摄像机和在它视景体内的模型连接到了一起. 因为链接是双向的, 所以摄像机知道所有的在它视景体范围内的模型, 而模型也知道所有可以看到它的摄像机. 灯光链接在灯光与模型之间建立了相似的关系, 一个灯光具有所有受它影响的模型的链接, 一个模型也知道所有影响它的灯光.
加速可见性查询最重要的类就是Cell类. 一个Cell是一个图形实体和子Cell的可见性容器, 它必须遵循2条简单的规则:
- 如果一个Cell是完全可见的, 那么它所有的图形实体和子Cell都必须可见.
- 如果一个Cell是完全不可见的, 那么它所有的图形实体和子Cell都必须不可见.
Cell是附属于Stage的, 它们形成了一棵有根Cell的树形层次结构. 标准的Cell支持简单的空间划分方案, 如四叉树和八叉树, 但如果像其它的可见性方案, 如portal, 就需要派生Cell的子类来实现了. 子类唯一的功能限制就是上面标出的那两条规则.
当一个图形体连接到一个Stage时, 它会被插入”接受” (通常仅仅是容纳)它的最低级的Cell中. 当更新图形实体的变换信息或改变包围体时, 它会根据需要改变在Cell层次中的位置.
Stage居住在StageBuilder类当中, 应用程序应当派生StageBuilder来创建一个Stage的初始状态(通过加入Cell和实体). Nebula3会提供一些标准的StageBuilder集合, 这应该能够满足大多数应用程序的需要了.
这只是图形子系统的一个粗略的概述. 因为当前只有一个最基本的实现, 很多细节接下来可能会有所更改.