From 2fb12abd6307154c380cb40cedd631bc9677c0ff Mon Sep 17 00:00:00 2001 From: Xingyu Wang Date: Mon, 15 Aug 2022 11:02:09 +0800 Subject: [PATCH] R PART 3 --- ...Using Binary Space Partitioning in Doom.md | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/translated/talk/20191106 How Much of a Genius-Level Move Was Using Binary Space Partitioning in Doom.md b/translated/talk/20191106 How Much of a Genius-Level Move Was Using Binary Space Partitioning in Doom.md index 7212ac8e09..8235192a40 100644 --- a/translated/talk/20191106 How Much of a Genius-Level Move Was Using Binary Space Partitioning in Doom.md +++ b/translated/talk/20191106 How Much of a Genius-Level Move Was Using Binary Space Partitioning in Doom.md @@ -12,7 +12,7 @@ 1993 年,游戏开发公司 id Software 发行了一款第一人称射击游戏 《毁灭战士DOOM》,游戏一经发行迅速爆火。在今天看来,《毁灭战士》可谓有史以来最具影响力的游戏之一。 -《毁灭战士》发行之后的第十年(2003 年),记者大卫·库什纳David Kushner出版了一本关于 id Software 的书,书名为 《Doom 启示录Masters of Doom》,后被奉为记录毁灭战士创作史的典范读物。几年前我曾读过这本书,如今内容已记得不太真切了,但是书中有一个关于 id Software 首席程序员约翰·卡马克John Carmack的故事,我印象特别深刻。这里只对故事做粗略描述(具体情节请往下阅读)。实际上,早在 《毁灭战士》 开发前期,卡马克就发现自己为这款游戏编写的 3D 渲染器在渲染某些关卡时慢得像爬一样。对于 《毁灭战士》 这一对动感和速度有着相当高要求的射击游戏来说,这是一个非常严重的问题。意识到了这一问题的严重性,卡马克需要一个更加有效的渲染算法,于是他开始阅读相关论文。最后,他实现了一种叫做“二叉空间分割binary space partitioning”的技术,极大地提升了《毁灭战士》游戏引擎的运行速度,而这项技术此前从未用于电子游戏当中。 +《毁灭战士》发行之后的第十年(2003 年),记者大卫·库什纳David Kushner出版了一本关于 id Software 的书,书名为 《Doom 启示录Masters of Doom》,后被奉为记录毁灭战士创作史的典范读物。几年前我曾读过这本书,如今内容已记得不太真切了,但是书中有一个关于 id Software 首席程序员约翰·卡马克John Carmack的故事,我印象特别深刻。这里只对故事做粗略描述(具体情节请往下阅读)。实际上,早在 《毁灭战士》 开发前期,卡马克就发现自己为这款游戏编写的 3D 渲染器在渲染某些关卡时慢得像爬一样。对于 《毁灭战士》 这一对动感和速度有着相当高要求的射击游戏来说,这是一个非常严重的问题。意识到了这一问题的严重性,卡马克需要一个更加有效的渲染算法,于是他开始阅读相关论文。最后,他实现了一种叫做“二叉空间分割binary space partitioning(BSP)”的技术,极大地提升了《毁灭战士》游戏引擎的运行速度,而这项技术此前从未用于电子游戏当中。 一直以来,我对这个故事的印象十分深刻。卡马克将学术前沿研究运用于电子游戏之中,我觉得这正是他之所以成为传奇人物的原因。无论从哪个角度来看,卡马克都应该是电子游戏行业中人尽皆知的典型的天才程序员,只不过上面这个故事是我最先能够想到的理由。 @@ -54,41 +54,47 @@ BSP 树是计算机图形领域最具挑战性难题的解决方案之一。举 ### 二叉空间分割 -二叉空间分割会提前将 3D 场景分割为若干部分,借以解决 VSD 问题。讲到这里,你需要先了解一下为什么分割场景可以奏效:如果你在场景上画条线(对应 3D 空间里的一个平面),你就可以指出玩家或者摄像机视角在这条线的哪一侧,在这条线另一侧的物体无法遮挡玩家所在一侧的物体。如果多次重复这一操作,该 3D 场景最终会被分割为多个区域。最后,你要明白场景中不同的部分是会相互遮挡的,这样才能理解为什么这些区域可以起到优化原来场景的作用。 +二叉空间分割binary space partitioning(BSP)会提前将 3D 场景分割为若干部分,使 VSD 问题易于解决。讲到这里,你需要先了解一下为什么分割场景可以奏效:如果你在场景上画条线(对应 3D 空间里的一个平面),你就可以指出玩家或者摄像机视角在这条线的哪一侧,在这条线另一侧的物体无法遮挡玩家所在一侧的物体。如果多次重复这一操作,该 3D 场景最终会被分割为多个区域,这并不是对原始场景的改进,只是现在你知道了更多关于场景的不同部分如何相互阻挡。 -首次阐述上述 3D 场景分割的是美国空军的研究员,他们曾尝试向美国空军证明计算机图形已经非常先进,可以应用到飞行模拟器领域。1969 年,他们将研究发现发表在一份题为《计算机生成图像在图形仿真中的应用研究》的报告中。该报告的总结部分指出,计算机图形可用于训练飞行员,但其实际应用可能会受制于 VSD 问题: +首次阐述上述 3D 场景分割的是美国空军的研究员,他们曾尝试向美国空军证明计算机图形已经非常先进,可以应用到飞行模拟器领域。1969 年,他们将研究发现发表在一份题为《计算机生成图像在图形仿真中的应用研究》的报告中。该报告的总结部分指出,计算机图形可用于训练飞行员,但也警告说,其实际应用可能会受制于 VSD 问题: -> 实时图像处理需要解决的一个关键问题就是优先级问题或者隐藏线问题。在我们平时用眼睛观察外界时,大自然替我们轻易地解决了这一问题:注视不透明物体上一点时,同一视觉方向的物体以及距离较远的物体就会变得模糊。但在计算机中,这项任务却非常困难。图像处理通常需要解决优先级问题,随着环境复杂程度的增加,计算量会呈指数级增长,随即就会超过绘制物体透视图所需得计算负载。[2][3] +> 实时图像处理需要解决的一个关键问题就是优先级问题,或称隐藏线问题。在我们平时用眼睛观察外界时,大自然替我们轻易地解决了这一问题:不透明物体上的一个点,掩盖了同一视觉方向上,且距离较远的所有其它物体。但在计算机中,这项任务却非常困难。图像处理需要解决的优先级问题,随着环境复杂程度的增加,计算量会呈指数级增长,随即就会超过绘制物体透视图所需得计算负载。[^2] -他们在报告中提出了一项基于构造“遮挡矩阵”的方案,这一方案早些时候曾被应用于 NASA 的项目当中。研究员指出,平面将场景一分为二,可用来解决平面两侧物体之间存在的“任何优先级问题”】。通常情况下,可能需要将实实在在的平面添加到场景中,但是有了几何图形,只需借助几何物体的表面即可。他们举了一个例子,如下图:\\(p_1\\)、\\(p_2\\) 以及 \\(p_3\\) 是三个不同的平面,如果摄像机视角位于其中一个平面的前方或“正”面,\\(p_i\\) 的值就等于 1。这种矩阵展示出基于三个不同平面和摄像机视角位置的三个物体之间的关系——如果物体 \\(a_i\\) 遮挡了物体 \\(a_j\\),那么 \\(a_{ij}\\) 在此矩阵中的数值等于 1。 +他们在报告中提出了一项基于构造“遮挡矩阵”的方案,这一方案据说早些时候曾被应用于 NASA 的项目当中。研究员指出,平面将场景一分为二,可用来解决平面两侧物体之间存在的“任何优先级问题”。通常情况下,可能需要明确将这些平面添加到场景中,但对某些几何体,只需借助你已经拥有的几何体的表面即可。他们举了一个例子,如下图:p~1~、p~2~ 以及 p~3~ 是三个不同的平面,如果摄像机视角位于其中一个平面的前方,即“正”面,p~i~ 的值就等于 1。这种矩阵展示出基于三个不同平面和摄像机视角位置的三个物体之间的关系 —— 如果物体 a~i~ 遮挡了物体 a~j~,那么 a~ij~ 在此矩阵中的数值等于 1。 ![][4] -研究员指出,这种矩阵可以应用到硬件中,对每一帧进行重新评估。该矩阵基本上可以用作大型交换器,或者一种预置的 Z 缓冲器。在绘制给定的物体时,如果在物体所在列上得出数值 1,并且所在行已经在绘制中,那么物体被遮挡的部分就不会绘制出来。 +研究人员指出,这种矩阵可以应用到硬件中,对每一帧进行重新评估。该矩阵基本上可以用作大型的开关,或者一种预置的 Z 缓冲区。在绘制给定的物体时,如果在物体所在列上得出数值 1,并且所在行已经在绘制中,那么物体被遮挡的部分就不会绘制出来。 -不过,该矩阵方法的主要缺点在于,为了在场景中表示出 \\(n\\) 个物体,需要将矩阵的尺寸调整为 \\(n^2\\)。于是,研究员们继续深入,探究使用遮挡矩阵作为“优先级顺序表”的可行性。遮挡矩阵的尺寸还是 \\(n\\),可确定物体绘制的顺序。他们随即发现,诸如上图此类场景根本无法确定顺序(因为它存在循环阻塞的现象)。因此,他们不遗余力,讲明“合适”与“不合适”场景之间在数学方面的区别。最后,他们得出了一个结论:在“合适的”场景下,优先级顺序表是可以制作出来的;而对场景设计师来说,避免设计出“不合适”的场景也不是一件难事。但是,他们并没有说明如何生成顺序表。可以说,这份研究的首要贡献在于提出了可以采用平面分割的方法,对场景中的物体进行渲染排顺。至少,这在 _理论上_ 是可行的。 +不过,该矩阵方法的主要缺点在于,为了在场景中表示出 n 个物体,你需要一个尺寸为 n^2^ 的矩阵。于是,研究人员们继续深入,探究将遮挡矩阵表示为“优先级列表”的可行性,该列表的尺寸是 n,可确定物体绘制的顺序。他们随即发现,诸如上图此类场景根本无法确定顺序(因为它存在循环阻塞的现象)。因此,他们花了很多时间来阐明“合适”与“不合适”场景之间的数学区别。最后,他们得出了一个结论:至少对于“合适的”场景下,优先级列表是可以制作出来的;而对场景设计师来说,避免设计出“不合适”的场景也不是一件难事。但是,他们并没有说明如何生成该列表。可以说,这份 1969 年的研究的首要贡献在于提出了,至少,在 _理论上_,可以采用平面分割的方法,对场景中的物体进行渲染排序。 -直到 1980 年,一份题为《基于优先级树结构的可见表面生成》的论文提出了解决该问题的具体算法。在这份论文中,作者亨利·福克斯、泽维·凯德姆以及布鲁斯·内勒介绍了 BSP 树。他们指出这种新的数据结构“可以替代十年前首次使用但由于一些问题未得到广泛发展的方案”(此处即前文 1969 年美国空军相关研究中的方案)。[3][5] BSP 树一经生成,即可用于确定场景中物体的优先级顺序。 +直到 1980 年,一份题为《基于优先级树结构的可见表面生成》的论文提出了解决该问题的具体算法。在这份论文中,作者亨利·福克斯Henry Fuchs泽维·凯德姆Zvi Kedem以及布鲁斯·内勒Bruce Naylor介绍了 BSP 树。他们指出,这种新的数据结构“可以替代十年前首次使用,但由于一些问题未得到广泛发展的方案”(即前文 1969 年美国空军相关研究中的方案)。[^3] BSP 树一经生成,即可用于确定场景中物体的优先级顺序。 -三人在论文中详细明了地解释了 BSP 树的工作原理。在本文,我将尝试使用更加通俗具体的语言,介绍给大家。 +三人在论文中对 BSP 树的工作原理给出了相当可读的解释。但在本文,我将尝试使用更加通俗的语言,介绍给大家。 首先,在场景中选定一个多边形,将该多边形所在的平面作为分割平面。同时,该多边形充当树的根节点。场景中剩下的多边形会分散在分割平面的两侧。位于分割表面“前方”或者与分割平面相交后位于“前”半部分的多边形落在了根节点左侧的左子树上;位于分割表面“后方”或者与分割平面相交后位于“后”半部分的多边形落在了右子树上。接着,递归重复这一过程:在左子树和右子树上各选定一个多边形,作为各自空间新的分割平面,继而二分出来更多的子空间和子树。等到全部的多边形均选定之后,二叉空间分割也就结束了。 -由后向前将场景中的几何图形进行渲染就是所谓的“画家算法”。因为在绘制时,距离摄像机较远的多边形会被距离摄像机较近的多边形所覆盖,借此正确进行渲染任务。如果想要实现这一算法,必须按中序遍历 BSP 树,左右子树的渲染顺序由摄像机视角与节点所在分割平面的位置关系决定的。因此,针对树上的每个节点,首先渲染距离分割平面较“远”一侧的所有多边形,接着是位于平面上的多边形,最后是距离平面较“近”一侧的所有多边形——“远”与“近”相对于摄像机视角而言。根据前文,距离分割平面较远一侧的多边形无法遮挡近侧的物体,所以这种方法可以解决 VSD 问题。 +假设你想由后向前将场景中的几何图形进行渲染。(这就是所谓的“画家算法”。因为在绘制时,距离摄像机较远的多边形会被距离摄像机较近的多边形所覆盖,借此正确进行渲染任务。)如果想要实现这一算法,必须按顺序遍历 BSP 树,左右子树的渲染顺序由摄像机视角与节点所在分割平面的位置关系决定的。因此,针对树上的每个节点,首先渲染距离分割平面较“远”一侧的所有多边形,接着是位于平面上的多边形,最后是距离平面较“近”一侧的所有多边形 —— “远”与“近”相对于摄像机视角而言。根据前文,距离分割平面较远一侧的多边形无法遮挡近侧的物体,所以这种方法可以解决 VSD 问题。 下图表示一个简单的 2D 场景的 BSP 树的构造与遍历过程。在 2D 中,分割平面变成了分割线,但就基本原理而言,与复杂的 3D 场景并无二质。 -![][6] _第一步:根分割线落在 D 墙上,将剩下的几何图形分为两组。_ +![][6] -![][7] _第二步:继续分割位于 D 墙两侧的空间。C 墙是其中一侧的唯一一堵墙壁,因此无需再分。另一侧,B 墙形成新的分割平面。因为 A 墙与新的分割平面相交,所以必须将其分割为两堵墙。_ +第一步:根分割线落在 D 墙上,将剩下的几何图形分为两组。 -![][8] _第三步:参照右上方视角,由后向前对墙壁进行排序,对执行画家算法很有帮助。这就是树的中序遍历过程。_ +![][7] -福克斯、凯德姆以及内勒多次强调了 BSP 树的优势:无需重复构建。可能有些难以置信,但实际上无论摄像机视角位于何处,场景的渲染只需一棵 BSP 树。只要场景中的多边形没有移动,BSP 树就不会失效。因此,BSP 树在实时渲染任务中非常实用——构建树时的所有艰巨任务都可以在渲染工作开展之前完成。 +第二步:继续分割位于 D 墙两侧的空间。C 墙是其中一侧的唯一一堵墙壁,因此无需再分。另一侧,B 墙形成新的分割平面。因为 A 墙与新的分割平面相交,所以必须将其分割为两堵墙。 -同时,三人也提到了一项需要进一步深入研究的问题:究竟怎样才能构建出一棵“高质量的” BSP 树?BSP 树的质量取决于用作分割平面的多边形的选择。我在前文跳过了这一问题,不过如果用作分割平面的多边形与其他多边形相交,那么为了避免 BSP 算法失效,必须将相交的多边形一分为二,这样两部分就可以分在不同的空间。但是如果这种现象反复出现,BSP 树的构建势必会大幅增加场景中多边形的数量。 +![][8] -内勒后来在其 1993 年的论文《构建高质量的分割树Constructing Good Partitioning Trees》中提及这一问题。与卡马克一同建立 id Software 的约翰·罗梅洛指出,这篇论文是卡马克在 《毁灭战士》 中引入 BSP 树时读到的论文之一。[4][9] +第三步:参照右上方视角,由后向前对墙壁进行排序,对执行画家算法很有帮助。这就是树的中序遍历过程。 + +福克斯、凯德姆以及内勒多次强调了 BSP 树的优势:它只需构建一次。可能有些难以置信,但实际上无论摄像机视角位于何处,同一棵 BSP 树都可以用来渲染一个场景。只要场景中的多边形没有移动,BSP 树就不会失效。因此,BSP 树在实时渲染任务中非常实用 —— 构建树时的所有艰巨任务都可以在渲染工作开展之前完成。 + +同时,三人也提到了一项需要进一步深入研究的问题:究竟怎样才能构建出一棵“高质量的” BSP 树?BSP 树的质量取决于用作分割平面的多边形的选择。我在前文跳过了这一问题,不过如果用作分割平面的多边形与其他多边形相交,那么为了让 BSP 算法发挥作用,必须将相交的多边形一分为二,这样两部分就可以分在不同的空间。但是如果这种现象反复出现,BSP 树的构建势必会大幅增加场景中多边形的数量。 + +内勒后来在其 1993 年的论文《构建高质量的分割树》中提及这一问题。卡马克的同事,id Software 的共同创始人约翰·罗梅洛John Romero指出,这篇论文是卡马克在 《毁灭战士》 中引入 BSP 树时读到的论文之一。[^4] ### 《毁灭战士》中的 BSP 树