在游戏中正确使用多线程

3

我正在开发一个3D游戏,旨在模拟可变形的3D物体。所谓可变形,是指3D物体的几何形状会从一帧到另一帧发生变化。在游戏中,有一个渲染循环,应尽可能频繁地执行(至少30次/秒),以获得良好的帧率。但是,在这个渲染循环中,我还要做两件事情:计算新的几何形状和计算轴对齐的边界框层次结构(AABVH)。我需要AABVH来进行碰撞检测。非常重要的是,在3D物体被渲染之前完成几何形状和AABVH的计算。由于计算新的几何形状和AABVH是一个耗时的任务,因此我的帧率迅速下降。因此,我的想法是在一个单独的线程中计算AABVH。

Thread t;
public void Render(Object3D o) // renders 3D object
{
  if (t != null) // wait until the new geometry got calculated
  {
     t.Join();
  }
  o.RenderGeometry();
  t = new Thread(() => o.CalcAABVH());
  t.Start();
}

我不是C#并行编程专家,但我肯定这不是一个好的解决方案。每帧都会创建、执行和销毁一个新的线程,造成了很大的开销。在我的情况下,一个好的解决方案会是什么样子呢?


2
这是在什么平台上?OpenGL?DirectX 11?您能否在GPU的着色器中进行几何修改?您能否简化变形网格,使其更合理并与GPU协作,例如使用蒙皮网格?多线程非常复杂,除非绝对必要,否则应该避免使用。现代DX使这变得更容易,因为您可以从多个线程访问DX资源等,但仍然相当困难。如果确实需要多线程,Gusdor的建议是避免大部分复杂性的好方法。 - Luaan
1
请注意,多线程不一定会提高应用程序的性能。只有在您拥有多个CPU核心并确保您的线程在不同的核心上调度时,才能实现显著的性能提升。显然,硬件图形加速器是实现这一目标的好方法。 :-) - Evil Dog Pie
@Luaan:这是在Windows上使用OpenGL。你说得没错,正确实现它的代价很高,也很复杂。问题在于我不仅要计算新几何体,还要为新几何体计算轴对齐边界框层次结构。而且我不能使用几何着色器来完成这个任务。 - NMO
@MikeofSST:绝对正确!硬件图形加速器?从未听说过。这是什么? - NMO
抱歉我没有表达得够清楚。实际上我正在计算两个东西:新的几何形状和一个边界体层次结构。我已经编辑了我的问题。 - NMO
2个回答

3
您不应在更新几何图形时阻止下一帧的渲染。您可以同步运行计算。阻塞渲染会增加每帧的总渲染时间,并对帧率产生负面影响 - 这是您特别想避免的效果!
相反,使用一个标志来指示新几何图形的计算是否正在进行,如果正在进行,则渲染旧几何图形。现在您需要2个几何集合 - 就像使用帧缓冲区时一样,在新几何图形完成时交换它们。
净效果将是,当您希望最新的几何图形可用时,某些帧可能没有。这将在几何图形计算时间较长的对象上更为明显。大多数游戏图形编程都是骗术,所以不要担心它。
实现
请使用线程池线程执行这些更新操作,因为创建成本较低。理想情况下,使用任务并行库。作为奖励,Task 对象会为您跟踪其运行状态!
    using System.Threading.Tasks;

    ...

    Task t;
    public void Render(Object3D o) // renders 3D object
    {
        if(t != null && t.Status == System.Threading.Tasks.TaskStatus.Running)
        {
            //render old geometry
        }
        else
        {
            t = Task.Factory.StartNew(o.CalcNewGeometry())
                    .ContinueWith(p => o.UpdateGeometry); //swap the new geometry in
        }
    }

在这里,你需要一些同步处理,以确保在渲染旧几何体时不会将新几何体换入。我将把这个任务留给你。


如果操作员想要例如弹跳球的固定步骤模拟,那么渲染旧几何可能不是一个选项。尽管如此,这将至少允许他使用线程池来安排工作,并且即使在最后一帧开始处理之前就可以安排工作,这可能会显着帮助延迟问题。 - Luaan
@Luaan,如果你想要一个固定步长的模拟,你需要同步进行每一步:计算物理->计算几何->渲染。精度比帧率更重要。这看起来像是基于实体的游戏编程,我会坚持这个主题。 - Gusdor
1
是的,那将是通常的方式。不过,如果您在“Update”步骤中更早地开始处理,可以轻松地削减1到2毫秒的延迟,这可以极大地帮助FPS。毕竟,OP的解决方案已经落后于任何输入一帧 - 他正在计算渲染上一个几何体之后的下一个几何体。 - Luaan
如前所述,我不是多线程专家。但我确信,使用您的解决方案会导致 NullReferenceException,因为当 if 子句第一次被执行时,t 为空。所以我必须事先创建一个任务,而不是在 else 子句中创建,对吗? - NMO
@NMO 这将强制几何图形在每个传递中进行绘制。我已经编辑了一个修复程序。 - Gusdor
1
谢谢你的努力。我按照那种方式实现了它。运行得很好。 - NMO

2
游戏中的多线程主要(取决于游戏类型、功能等)使用以下方式:
  • 1个线程 - 渲染

  • 1..x个线程 - 物理计算(包括变形)

  • x个线程 - AI
渲染对象的实际状态,在物理线程的副本中进行变形。根据Gusdor的建议,在变形后切换对象。
我建议您使用类似ConcurrentQueue的队列类来排队应该由物理线程计算的对象。这样,您不需要每次重新创建线程。只需让它们空闲并在有东西进入队列时进行计算即可。

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