【GAD翻译馆】如何制作渲染引擎

发表于2017-09-15
评论5 4.3k浏览

翻译:赵菁菁(轩语轩缘)审校:李笑达(DDBC4747


      今天我在推特上谈到了一个引擎,一些聪明的家伙发出了一些链接,我想在这里为后来人记录下来,这样一来每当有人使用场景图时,就真的存在一个那么网页,我可以推荐他们去参考。

记住,孩子们,在你的渲染循环中添加间接指针不是个明智之举。更严重的是,如果在DirectX 11中,降低渲染代码性能与GPU驱动程序无关,那么可能就是你的代码很糟糕。顺便提醒,你会发现,DX11中的多线程指令缓冲区让你的代码更慢,不会更快(因为它们可以用来提高引擎的并行性,但目前只是驱动用起来慢,你的瓶颈应该是驱动)。

      下面的链接都是关于抛弃状态机进行渲染的想法,每一次绘制都编码所有状态,并用固定的位串作为编码。我不会在这里描述概念,只要看看这些参考链接:

        ·  Christer Ericson's 文章

        ·  Aras为粗糙z排序使用位串 

        ·  FR的Werkkzeug 

        ·  BGFX引擎 看 

        ·  MT框架——日语,cedec2006 (也有关于它的翻译)

        ·  Intel Nulstein例子 

        ·  很久以前,我在博客上写过关于这个的文章的内容,但是那篇文章很糟糕,我不会把它链接到这里。:)


一些笔记/FAQ答案。因为每次我写一些关于这些系统的东西,人们就开始问同样的问题……我想是因为这么多的书仍然是关于“立即”和“保留”3D图形API的,“保留”通常是某种场景图……同样,场景图也是非常面向对象编程的,而且书籍都爱面向对象编程。

键中的位通常是一组状态矩阵的下标(如相机/视口/ 渲染目标状态、纹理设置,等等)或是直接指向底层3D API数据结构的指针。


o    所以下面是我们的指针,不管怎么说,不是吗?当然可以,但是魔术就是这样的,它不仅有助于最小化状态的变化,而且保证数组中的所有访问都(尽可能)是线性的!

o    当然,如果你是严格排序深度的(不是深度块里的),那么你必须接受每次绘制时在不同材质之间跳跃,而且这些访问很可能是随机的。

·  如果是这样的话,尽量避免这些的间接指向,在绘制结构中存储相关数据位。

·  本案例另一个解决方案是以一种粗糙深度一致的方法排序材质数据,即所有的材料在一个房间里彼此贴近地存储。从理论上讲,你还可以动态地排序和恢复指向游戏代码中材质数据的指针,但是我们现在太复杂了……

o    同样不能保证资源指针(GL,DX…),即使指针是线性排列的,它们可能在内存中离得很远,这是不可避免的。在控制台上,你甚至可以控制GPU东西资源分配的位置,这样你就可以将它们打包在一起,但更重要的是,你可以直接存储GPU所需的中间CPU数据结构的指针。




·  你不需要使用键数组并排序!


o    使用桶,也就是说,键的一些位会索引到具体的桶。为每个渲染目标/通道准备一个桶是明智的。

o    “桶”,即单独列表。换句话说,不要因每个子系统都有一个列表而害羞,没有人说引擎中的所有绘制都应该有一个解决方案。

·  这通常是一个好主意,也因为绘制发射工作可以而且应该按通道排序,例如:在延迟渲染器中,也许我们想先要一个粗略的深度光子图,然后g缓冲,然后阴影……这些可以从可见性系统中按通道顺序得到。


·  每个通道做一次发射意味着我们可以在第一个通道完成后就尽可能快地得到GPU。实际上,如果我们不关心完美的排序,我们只希望尽快进行绘制,我们甚至可以把每个通道分成片段,第一个片段完成之后就马上进行绘制。

·  我本不应该说,但是为了以防万一……这些系统允许并行绘制,很明显,也可以并行排序,并行生成GPU命令,非常容易。只保留每个线程的列表,对每个线程排序,然后将它们合并(唯一的同步点),然后分成块,每个线程创建GPU命令列表(如果你有一个API,完成这些步骤会很快……)


·  不需要为所有键使用相同的编码!

o    有些位可以决定其他位的含义。通常每个渲染目标/通道你需要做的是完全不同的东西,例如一个阴影贴图渲染通道不需要关心材质,但可能需要一些位作为深度排序的z-键。

o    同样,你也应有专门的解码循环。


·  并非键中的所有位都需要用于排序。

o    状态位直接映射到GPU,不需要额外设置状态而带来开销,状态位不应该是排序的一部分,它们只会降低排序速度。


·  剔除:让键成为可见系统的一部分。


o    当边界基元最终被认为可见时,它应该添加所有与绘制内容相关的键。

o    在这一点上,你还需要补上与投影深度相关的位,以便进行深度排序。


·  层次变换。

o    许多场景图用作层次变化。这是愚蠢的,在大多数引擎中,一小部分的对象需要,主要是动画/皮肤系统的骨头。骨头确实表达了一个图形,但是它不足以成为让你的整个渲染系统建立在层次变换之上的原因。


·  把那些(几乎)总是按相同位设置在一起的状态分为一组。

o    比如说,不为视口设置单独的位(指单独的状态结构)、渲染目标、视图世界投影常数等,在一个状态结构中把所有数据合并。


·  我的列表不需要其他渲染“命令”吗?清理呢?缓冲区拷贝呢?异步处理器的工作等待呢?计算着色器?异步计算着色器……


o    所有这些都可能是状态某些部分“绑定”属性的一部分。例如,当指向渲染目标/通道的指针改变时,我们会查看状态结构,看看新设置的渲染目标是否必须清除。

o    在实践中,你应该把你的键“装桶”,分在不同解码循环处理的不同阵列中,这些解码循环会知道做什么(例如:阴影贴图解码将确保CPU皮肤工作在绘制等工作之前完成)。


·  还有其他方法吗?

o    当然,但是这是一个非常好的起点……

o    视游戏而定。当你不知道你会拥有什么时,像这样的系统很好,通常是因为它们来自一个可见性系统,可能由于并行处理,这种系统不能以正确的顺序完成。

o    在游戏/系统中,你可以很轻松地按照正确的顺序生成GPU命令,你确切地知道需要哪些状态变化,显然可以避开所有这种架构。例如,国际足联是一个足球游戏,不需要做太多的可视性,并且确切地知道每个玩家是如何根据材质制作的,因此可以编写代码以正确的顺序处理事情……像这样的东西对寒霜是合理的,但你不会为国际足联使用寒霜引擎……

 

【版权声明】

原文作者未做权利声明,视为共享知识产权进入公共领域,自动获得授权;

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

标签: