基于着色器的游戏中的渲染OO架构

12

我在构建游戏引擎时经常遇到这个问题,我的类想要看起来像这样:

interface Entity {
  draw();
}

class World {
  draw() {
    for (e in entities)
      e.draw();
  }
}
那只是伪代码,大致说明了绘图的过程。每个实体子类都会实现自己的绘制。游戏世界不按特定顺序循环遍历所有实体,并逐一告诉它们进行绘制。
但是在基于着色器的图形中,这种方法往往效率低下甚至不可行。每种实体类型可能都需要拥有自己的着色器程序。为了最小化程序更改,每个特定类型的实体都需要一起绘制。像粒子之类的简单实体还可以通过其他方式聚合它们的绘制,例如共享一个大的顶点数组。而在混合等方面则更加复杂,某些实体类型需要相对于其他实体类型在特定时间渲染,甚至需要在不同的通道上渲染多次。
我通常使用每个实体类的渲染器单例来保存所有实例并一次性绘制它们,这并不算太糟糕,因为它将绘制与游戏逻辑分离。但是渲染器需要找出要绘制的实体子集,并且需要访问图形管线的多个不同部分。这就是我的对象模型往往变得混乱的地方,存在许多重复的代码、紧密的耦合以及其他问题。
所以我的问题是:对于这种游戏绘图,有什么高效、灵活和模块化的好架构?

你将它与其他所有东西分开是一个好的开始,听起来它很混乱,因为它...嗯...很混乱。必要的混乱是不幸的,但仍然是必要的。但我怀疑你不会把这看作是一个答案... - Donal Fellows
2个回答

8

采用两阶段方法:首先循环遍历所有实体,但不是绘制它们,而是让它们将自己的引用插入到(其中)绘图批处理列表中。然后按照OpenGL状态和着色器使用对列表进行排序;在排序后,在每个状态转换处插入状态更改器对象。

最后通过遍历列表执行引用列表中每个对象的绘制例程。


如果你有不同的引擎,比如OpenGL和DirectX呢? - GorillaApe
谢谢您的回复。不过我的问题在这里。您说:“执行每个对象的绘图例程”。如果您想要使用多个API(direct2d、opengl等),该怎么办?我的意思是,每个对象中的draw()都应该对所有API进行实现吗?或者渲染器也负责绘图? - GorillaApe
@Parhs:当然,你不应该将渲染器代码混合到场景管理代码中。可以将其视为访问者模式:对于每个对象,渲染器代码都会要求场景/对象代码提供相关数据(网格、纹理ID等;这些数据已在帧设置期间使用),然后使用该数据进行实际绘制。对于每个想要支持的API,您都需要实现不同的渲染代码路径。虽然这项额外工作可能一开始会让人望而却步,但它允许您最大限度地利用所涉及的API。 - datenwolf
这可以在不同的类中实现,如OpenGLRenderer、DirectXRenderer等吗? - GorillaApe
@Parhs:是的,通常你需要一组“渲染器”虚函数,为每个要支持的渲染路径实现这些函数。场景类可以成为渲染器的友元,或者*Renderer成为场景类的访问者。 - datenwolf
显示剩余2条评论

1

这不是一个容易回答的问题,因为有很多方法来处理这个问题。一个好的想法是研究一些游戏/渲染引擎,看看它们是如何处理的。一个很好的起点是Ogre,因为它有很好的文档和开源。

据我所知,它通过内置的材质脚本将顶点数据与材质组件(着色器)分离。渲染器本身知道以什么顺序以及使用什么着色器(及其通道)绘制哪个网格。

我知道这个答案有点模糊,但我希望我能给你一个有用的提示。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接