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 个几何体集 - 就像使用帧缓冲区一样,您在新几何体完成时交换它们。
最终结果是,某些帧可能没有您希望的最新几何图形可用。这在几何体计算时间较长的对象上会更加明显。无论如何,大多数游戏的图形编程都是烟雾缭绕的,所以不用担心。
执行
请为这些更新操作使用线程池线程,因为创建成本较低。理想情况下,使用任务并行库。作为奖励,
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 类的东西来对应该由物理线程计算的对象进行排队。这样你就不需要每次都重新创建线程。让他们空闲并在有东西进入队列的那一刻进行计算。