关于c#:在游戏中使用正确的多线程

Using correct multithreading in a game

我目前正在开发一款模拟可变形 3D 对象的 3D 游戏。对于可变形,我的意思是 3D 对象的几何形状在逐帧变化。游戏中有一个渲染循环,应该尽可能频繁地执行(至少 30 次/秒)以获得下降的帧速率。但我还在这个渲染循环中做另外两件事:计算新几何和计算轴对齐边界框层次结构 (AABVH)。我需要 AABVH 进行碰撞检测。在渲染 3D 对象之前完成几何和 AABVH 计算非常重要。计算新几何和 AABVH 是一项耗时的任务,因此我的帧速率迅速下降。因此,我的想法是在单独的线程中计算 AABVH。
这看起来像这样:

1
2
3
4
5
6
7
8
9
10
11
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 个几何体集 - 就像使用帧缓冲区一样,您在新几何体完成时交换它们。

最终结果是,某些帧可能没有您希望的最新几何图形可用。这在几何体计算时间较长的对象上会更加明显。无论如何,大多数游戏的图形编程都是烟雾缭绕的,所以不用担心。

执行

请为这些更新操作使用线程池线程,因为创建成本较低。理想情况下,使用任务并行库。作为奖励,Task 对象会为您跟踪其运行状态!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    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
        }
    }

您需要在此处进行一些同步,以确保在渲染旧几何图形时不会交换新几何图形。我将把它留给你。


游戏中的多线程主要(取决于类型、功能……)以这种方式使用:

  • 1 线程 - 渲染

  • 1..x 线程 - 物理(你的变形所属的地方)

  • x 线程 - 人工智能

渲染对象的实际状态,在物理线程中将其变形为副本。
按照 Gusdor 的建议在变形后切换对象。

我建议您使用类似 ConcurrentQueue 类的东西来对应该由物理线程计算的对象进行排队。这样你就不需要每次都重新创建线程。让他们空闲并在有东西进入队列的那一刻进行计算。