如果将1993年的《DOOM》看作一台精密的魔术机器,它最核心的戏法,莫过于将一张二维的建筑平面图,“变”成一个让无数玩家沉浸其中、血脉偾张的三维世界。更令人惊叹的是,这场宏大的视觉魔术,竟然能在当年主流的486处理器上流畅上演。这场戏法的诀窍,并非真正的3D多边形渲染,而是一套被称为“2.5D”的、充满工程智慧的视觉骗术。
本文将化身一名深入后台的探秘者,尝试拆解这台古老而优雅的机器,看看它的齿轮与杠杆是如何精密咬合,最终在屏幕上构建出那个我们既熟悉又陌生的地狱。
要理解DOOM的世界,我们首先要抛弃现代游戏中“万物皆由三角形构成”的观念。DOOM关卡的根基,并非三维空间中的多边形网格,而是一张纯粹的二维地图。这张地图由四个核心元素构成:VERTEXES(顶点,即XY坐标点)、LINEDEFS(线段,连接两个顶点)、SIDEDEFS(线段的“侧面”,定义了墙壁的贴图)以及SECTORS(扇区,由一圈封闭的线段围成的区域)。
在关卡编辑器(如Doom Builder)中,设计师绘制的其实就是这样一张由点、线、面构成的平面图。而三维信息的关键,则储存在SECTORS这个结构里。每一个扇区都记录着三个至关重要的属性:地板的高度、天花板的高度,以及它们各自使用的贴图和光照亮度。
当游戏引擎读取这张地图时,它做了一件极为巧妙的事情:它将每个二维的扇区,沿着Z轴“挤出”(Extrude),形成一个具有体积感的空间。 你的脚下踩着的地面,就是扇区的地板高度;你的头顶碰到的天花板,就是扇区的天花板高度;而两个扇区交界处的LINEDEFS,如果它们连接的两个扇区高度不同,自然就形成了一堵墙或一个台阶。
这个从2D“挤出”3D的设计,是DOOM 2.5D技术的基石,同时也带来了它最著名的几个“规则”:
墙体永远垂直于地面:因为墙体只是两个不同高度扇区之间的“断面”,它不可能倾斜。
地板和天花板永远是水平的:它们是扇区在Z轴上的两个平面,无法做出斜坡。
没有真正意义上的“楼上楼”:在一个XY坐标点上,永远只能存在一个扇区。你无法建一座桥,让玩家从桥下走过——因为这会在二维地图上造成扇区的重叠,这是DOOM的数据结构所不允许的。
这种设计的本质,是将一个复杂的三维渲染问题,降维成一个“在特定高度绘制二维平面”的问题。虽然牺牲了空间的自由度,但却为在有限机能上实现流畅运行铺平了道路。这份关卡的几何蓝图,主要由w_wad.c等文件负责从WAD数据包中加载并解释。
有了世界的蓝图,下一个问题随之而来:在一帧画面中,成百上千的墙壁、地面和天花板,哪些是玩家能看到的?哪些被挡住了?谁先画,谁后画?现代游戏引擎依赖一个名为“Z-Buffer”(深度缓冲)的硬件功能来解决这个问题,它会记录每个像素的深度,后画的物体如果更近,就覆盖先画的。但在1993年,Z-Buffer是奢侈品,每一分计算资源都必须用在刀刃上。
DOOM的解决方案堪称神来之笔:二叉空间分割树(Binary Space Partitioning Tree,简称BSP树)。
这棵树并非在游戏运行时动态计算,而是在关卡编译时(由一个叫node builder的程序)预先生成好的。它的构建过程很简单:在地图上选择一条LINEDEF作为分割线,将整个空间一分为二。然后在这两个子空间里,再各自选择分割线,如此递归下去,直到每个最终的子空间都是一个简单的、内部无遮挡的凸多边形区域,这个区域在源码中被称为“子扇区”(Subsector或SSector)。
这棵树的神奇之处在于它在渲染时的遍历方式。每一帧,引擎都会从代表整个地图的根节点开始,进行一次简单的递归判断:玩家当前位于当前节点的分割线的哪一侧?
自动排序:引擎总是优先遍历玩家所在的那一侧子树,然后再处理分割线,最后才遍历另一侧的子树。这意味着,从玩家视角看,所有物体都以一个近乎完美的“从近到远”的顺序被提交给渲染器。这天然地解决了绘制顺序问题,无需任何复杂的排序算法。
视锥剔除:在遍历过程中,如果一个节点的包围盒(Bounding Box)完全在玩家的视野范围(FOV)之外,那么这个节点及其所有子节点都可以被直接丢弃,极大地减少了需要处理的几何体数量。
无Z-Buffer的遮挡剔除:这是BSP树最核心的魔法。DOOM在绘制墙体时,会维护一个数据结构,记录屏幕上每个X坐标的垂直像素区间是否已经被“实心墙”所占据。可以把它想象成一张“屏幕剪裁图”。当引擎按“从近到远”的顺序绘制墙体时,它会先检查这堵墙在屏幕上的投影范围是否已经被之前的墙画过了。如果画过了,那部分就会被“剪掉”不画。这样一来,被远处墙体遮挡的更远处的墙体,就自然而然地被剔除了,完美避免了无效的重复绘制(Overdraw)。
整个渲染流程的核心,R_RenderPlayerView函数,其逻辑可以用以下伪代码清晰地展示:
而BSP树的遍历逻辑,则在r_bsp.c中以优雅的递归实现:
通过这套机制,DOOM将“可见性判断”、“绘制顺序”和“遮挡剔除”这三大难题,巧妙地捆绑在一次BSP树遍历中一并解决,其设计之精妙,至今读来仍令人拍案叫绝。
当BSP树这位“导演”确定了每一帧要画什么以及按什么顺序画之后,真正的“绘制”工作就交给了几位高度特化的“画家”。DOOM并没有一个统一的渲染器,而是为世界的不同组成部分——墙壁、地面/天花板、物体——分别设计了最优化的绘制管线。
DOOM的墙体渲染是其视觉风格的核心。它的贴图资源在存储时,就不是我们今天熟悉的按行存储的位图,而是按垂直列(Column)存储。渲染时,引擎将一面可见的墙段(seg)投影到屏幕上,计算出它占据的X坐标范围。然后,它会一列一列地扫描这个范围,为屏幕上的每一个像素垂直列,从贴图中采样对应的纹理列,并根据距离进行缩放,最后“刷”到屏幕缓冲区上。这个过程由r_draw.c中的R_DrawColumn等函数执行。
这种“列渲染”的方式与贴图的存储格式高度匹配,使得内存访问极为高效。但它也导致了一个经典特性:DOOM无法实现真正的向上/向下看(Look up/down)。因为无论玩家视角如何,墙体始终是严格垂直绘制的。后来的社区源码移植版(Source Ports)通过一种称为Y-Shearing的矩阵变换技巧,模拟出了“抬头/低头”的效果,但这已是对原始引擎的扩展了。
绘制广阔的地面和天花板,如果也像墙体一样逐个像素计算透视,开销将是巨大的。DOOM在这里引入了另一个天才设计:可见平面(Visplane)。
当引擎在绘制墙体时,墙的上下边缘会暴露出其背后的地面和天花板。引擎会收集这些暴露出来的“像素水平条带”(Span),并将具有相同高度、相同贴图、相同光照的条带合并在一起,形成一个visplane。在所有墙体都画完后,引擎会得到一个关于当前画面所有可见地面和天花板的visplane列表。
在r_plane.c的R_DrawPlanes函数中,引擎会一次性地处理这些visplane。它不再逐像素计算,而是按屏幕的水平线(Scanline),一行一行地绘制这些平面的横向跨度(Span),纹理坐标和光照也可以通过简单的线性插值快速计算。这种化零为整、按水平跨度批量绘制的方式,极大地提升了地面/天花板的渲染效率。
当然,这个设计也有其极限。原版DOOM引擎最多只能同时处理128个visplane。在一些极其复杂的、远景开阔的自制地图中,如果屏幕内同时出现了太多不同高度、不同贴图、不同光照的地面/天花板组合,就会超出这个上限,导致游戏崩溃并弹出臭名昭著的错误信息——“Visplane overflow”。
怪物、道具、爆炸效果等动态物体,在DOOM中被称为“精灵”(Sprite)。它们本质上是始终朝向镜头的二维图片,这种技术被称为“公告板”(Billboard)。在BSP遍历过程中,可见的精灵被收集到一个列表(vissprites)中。在墙体和地面都绘制完毕后,引擎会按照距离从远到近的顺序绘制这些精灵。
为了确保正确的遮挡关系,精灵的绘制同样会利用之前墙体渲染阶段生成的“屏幕剪裁图”。在绘制精灵的每一垂直列时,引擎都会检查这一列是否已经被实心墙遮挡,如果是,则跳过绘制。这套机制由r_things.c负责,它确保了小妖可以被完美地隐藏在柱子后面。
而为整个世界注入灵魂的光照,则完全依赖于一个巧妙的“骗术”。DOOM是一个256色的调色板游戏,它的光照并非实时计算,而是通过查表完成。在WAD文件中有一个名为COLORMAP的数据块,它本质上是34个预先计算好的、从亮到暗的256色调色盘。
渲染任何像素时,引擎会根据其所属扇区的光照等级和它与玩家的距离,从这34个“变暗表”中选择一个,然后用这个表去“翻译”贴图原本的颜色索引,从而得到最终的屏幕颜色。这种方式几乎零成本地实现了“越远越暗”的距离衰减(雾化)效果,营造出强烈的空间感。
此外,为了增强立体感,卡马克还加入了一个被称为“Fake Contrast”(伪对比度)的细节:引擎会根据墙体的朝向(正南/北或正东/西),微调其使用的COLORMAP,使得不同朝向的墙壁有细微的亮度差异,从而在没有复杂光照模型的情况下,强化了场景的几何轮廓。
最后,为了让这一切能在486那孱弱的浮点运算能力下飞驰,整个引擎的核心数学运算都构建在16.16定点数和查找表之上。三角函数(正弦、余弦)、角度变换等耗时操作,都在游戏启动时被预计算并存入tables.c的巨大数组中。渲染中的所有计算,都尽可能地被转化为了整数加法和查表,这才是DOOM能够流畅运行的终极秘密。
回看DOOM的渲染引擎,我们发现它并未“真正”地去模拟一个三维世界。相反,它像一位技艺高超的舞台设计师,用一套量身定制的规则来构建和呈现幻象:
它用“挤出”的2D扇区搭建舞台布景,限定了空间的表达方式。
它用BSP树作为总导演,精准控制着观众(玩家)的视野与场景的调度。
它为墙、地、物分别雇佣了最高效的“绘制专家”(列渲染、跨度渲染、公告板)。
它用一张神奇的调色盘为整个舞台打光,廉价而富有戏剧性。
这套方案的每一个环节,都充满了对当时硬件限制的深刻理解与巧妙妥协。它不是“不够3D”,而是在那个时代背景下,关于“如何在有限算力内创造出最大化沉浸感”这一问题的最优解。DOOM的2.5D技术,与其说是一种技术的局限,不如说是一种与硬件共谋的、朴素而锋利的工程美学。
时至今日,当我们再次穿行于地狱的走廊,或许可以多一分欣赏。当你利落地闪过一个拐角,远方的地面在visplane列表中悄然合并,墙壁的像素列在CPU缓存中飞速刷下,而一只地狱男爵的轮廓被solidsegs的边缘“咔哒”一声精确裁切——请记住,这便是1993年,那个属于天才程序员的最迷人的戏法。
评论区
共 条评论热门最新