第 2 章

共享的幻觉

把时间拨回到1987年秋天。路易斯安那州什里夫波特市,Softdisk公司的办公室在深夜还亮着灯。约翰·卡马克面前的屏幕上,一行行汇编代码正在EGA显卡的位平面内存中艰难地挪动像素。这不是他第一次试图让PC平滑滚动——在IBM PC兼容机上,EGA显卡的硬件设计根本没有为横向像素卷轴提供任何支持。

卡马克要做的事情,在当时的PC程序员圈子里被普遍认为是不可行的:用纯软件方式,在16色EGA模式下,实现每秒至少30帧的平滑横向卷轴,而且不能出现撕裂和闪烁。他面前的技术障碍是具体而残酷的。EGA显卡将256KB的显存划分为四个64KB的位平面,分别存储每个像素的蓝、绿、红和亮度分量。要移动一整个屏幕的像素,意味着要对这四个位平面同时进行精确到字节的移位操作。在1987年的286和386处理器上,用汇编语言逐字节搬移显存的速度,远不足以支撑实时游戏所需的帧率。

更致命的是,EGA的硬件卷轴寄存器只支持垂直方向的像素行滚动,水平方向完全没有硬件加速。这就是为什么当时PC平台上的动作游戏要么是静态画面切换,要么是垂直卷轴——横向卷轴在EGA上是一个硬伤。

卡马克的突破不是灵光一闪,而是一次系统性的工程围剿。他仔细研究了EGA显存的时序特性,发现如果在垂直消隐期间——也就是电子束从屏幕右下角回到左上角的那几十微秒里——直接对位平面寄存器进行写入,就可以避免画面撕裂。但这还不够快。

真正的加速来自一个数学洞察:如果屏幕上的大部分像素在帧与帧之间并没有改变,为什么要重新绘制整个画面?卡马克设计了一套脏矩形算法,只更新那些实际发生变化的屏幕区域。更进一步,他意识到横向卷轴的本质是像素的循环移位,而不是重新绘制——如果能把显存中的像素块按字节边界整体搬移,然后在露出的边缘上补绘新内容,速度就会提升一个数量级。这套技术在《指挥官基恩》中达到了实用化的顶点。

卡马克为游戏编写了一个EGA自适应图形层,它能在游戏启动时检测显卡的具体型号和显存配置,然后动态选择最优的绘制路径。在EGA模式下,它使用位平面直接写入和脏矩形更新;如果检测到VGA显卡,则切换到256色模式下的页翻转双缓冲。

这个抽象层的意义远远超出了技术本身——它意味着同一份游戏逻辑代码,可以在不同的图形硬件上运行,而不需要为每种显卡重写整个渲染循环。游戏世界开始从硬件的具体性中挣脱出来。但卡马克不是唯一在做这件事的人。在同一时期,大西洋两岸的个人电脑上,一场更激进的运动正在地下蔓延。1985年,Commodore公司发布了Amiga 1000。这台机器拥有一块名为Agnus的协处理器,它包含一个位块传输器——一种可以在显存中高速搬移矩形像素块的硬件电路。

Amiga的工程师们为这块芯片设计了操作系统的图形调用接口,但他们没有想到的是,欧洲的年轻程序员们会绕过操作系统,直接用汇编语言操纵Agnus的寄存器,在屏幕上演算出任何硬件规格表上都找不到的视觉效果。1985年底到1986年初,一个红白相间的旋转球体开始在Amiga用户之间流传。

这个程序叫Boing Ball,它只有几十KB大小,却在一块512KB的显存里,用光线追踪算法实时渲染出一个带纹理映射和阴影的旋转球体。这不是游戏,它不接受任何输入,也没有任何交互。它只是运行——一颗球在一个虚拟的三维空间里弹跳,反射着不存在的灯光,在Amiga的屏幕上以每秒30帧的速度旋转。

Boing Ball的作者是Commodore的工程师,他们写这个演示程序是为了展示Amiga的图形性能。但他们释放出的东西远远超出了营销演示的范畴。Boing Ball证明了一件事:在消费级硬件上,实时三维图形是可能的。

不是街机那种用精灵图缩放伪造的三维,不是《太空侵略者》那种平面位移,而是真正的、逐像素计算的、带有光照和透视的三维渲染。虽然Boing Ball只渲染了一个球体和一个平面,但它的技术含义是爆炸性的——如果一颗球可以实时渲染,那么一个房间为什么不可以?一个走廊?一个地牢?这个问题的答案并不在Amiga的硬件里,而在程序员的头脑中。

Boing Ball之后,欧洲的个人电脑上迅速兴起了一种独特的文化形态:演示场景。年轻的程序员们组成小组,用汇编语言编写自运行的图形演示程序,然后在聚会上互相比较谁能在更低的硬件配置上实现更惊人的视觉效果。这些演示程序不是商业软件,它们不卖钱,甚至不实用。它们唯一的目的是证明一件事:这台机器的极限还没有被触及。演示场景的程序员们发展出了一整套欺骗视觉的数学技巧。

他们发现,在一个画面上交替显示两个略微偏移的图像,人眼会自动把它们融合成更多的中间色——这就是所谓的隔行调色,它能让16色的EGA看起来像64色。精确计算每一条扫描线的渲染时间,在电子束扫过屏幕的过程中动态切换调色板寄存器,就可以在同一屏幕上显示超过硬件限制的颜色数量——这叫铜条效果,得名于Amiga的铜协处理器。把一个三维物体的顶点坐标预先计算好,存储在查找表里,然后用定点数运算代替浮点运算,就能在缺乏数学协处理器的CPU上实时旋转一个线框立方体——这叫预计算旋转,它把实时三维的门槛从工作站拉低到了家用电脑。

这些技巧的共同特征是:它们都在用数学欺骗硬件。硬件说,你只能显示16种颜色;数学说,如果我在每一行扫描线上切换颜色寄存器,人眼就会看到超过16种颜色。硬件说,你的CPU没有浮点运算器,做不了三维旋转;数学说,如果我用整数的加减和移位来近似三角函数,旋转一个立方体只需要几十条汇编指令。

硬件说,你的显存只有64KB,装不下一帧高分辨率的画面;数学说,如果我只更新那些发生变化的像素,64KB就够用了。

这种欺骗哲学是游戏引擎的第一块基石。它确立了一个根本性的信念:游戏世界的视觉呈现,不必是硬件的直接映射。在硬件和玩家眼睛之间,可以插入一层数学抽象——一套算法,一个渲染管线,一种视觉伪造术。这层抽象就是引擎的雏形。演示场景的程序员们没有发明引擎这个词,但他们在每一行汇编代码中实践着引擎的核心逻辑:用软件定义视觉,让硬件执行它从未被设计要执行的任务。

在商业游戏开发的另一条战线上,一个完全不同的问题正在逼迫程序员们走向同一个抽象层。1985年,Origin Systems发布了《创世纪IV》。这款角色扮演游戏试图在Apple II、Commodore 64和IBM PC上同时发售。当时的行业惯例是:为一个平台开发游戏,然后外包给第三方公司做移植,移植版通常质量低劣、bug丛生、延迟数月甚至数年。

Origin的创始人理查德·加里奥特不想这么做。他希望在三个平台上同时开发,让三个版本的玩家获得尽可能接近的体验。

这个需求在工程上制造了一个噩梦。Apple II使用6502处理器,Commodore 64使用6510处理器,IBM PC使用8088处理器。三者的CPU指令集完全不同。Apple II的图形模式基于位图帧缓冲,Commodore 64基于字符映射和精灵图,IBM PC的CGA显卡则是一个奇怪的混合体。三者的图形架构毫无共通之处。更不用说内存映射、键盘扫描码、磁盘格式和声音芯片——每一层硬件都是不同的。

Origin的程序员们被迫做出一项工程决策,这项决策在当时看起来只是权宜之计,但它的长期后果将重塑整个游戏产业。他们决定把游戏的核心逻辑——角色属性、地图数据、对话系统、战斗规则——写成与平台无关的C语言代码。然后把所有与硬件直接交互的部分——图形渲染、键盘输入、声音播放、文件读写——封装成一套与平台相关的底层函数。

在Apple II上,这套底层函数用6502汇编操作位图帧缓冲;在Commodore 64上,它用6510汇编操纵字符映射和精灵图寄存器;在IBM PC上,它用8088汇编在CGA的位平面上绘制像素。但上层的游戏逻辑代码,三个平台共用同一份。这就是可移植性作为一种工程纪律的第一次清晰呼吸。它不浪漫,不炫技,不追求在硬件极限上多榨出一帧。它的目标是实用主义的:让同一款游戏运行在不同的机器上,同时控制开发成本。

但它在工程上要求的抽象层,与演示场景用数学欺骗硬件所建立的抽象层,在结构上是同构的。两者都在硬件和游戏之间插入了一层软件定义的中介。前者是为了超越硬件,后者是为了跨越硬件。但它们共同确立了一个前提:游戏世界可以独立于运行它的机器而存在。到1980年代末,这两条谱系仍在各自的轨道上运行。

演示场景的程序员们在Amiga和Atari ST上继续推进视觉欺骗的边界,他们的演示程序越来越复杂——从旋转立方体到全屏纹理映射隧道,从静态调色板切换到每个扫描线动态切换数十次,从预计算动画到实时生成的分形图案。但他们写的代码完全不考虑可移植性。每一个演示程序都是为特定硬件的手工汇编杰作,一旦换一块显卡、换一颗CPU,整套代码就要重写。

演示场景锤炼了实时图形的数学技巧,但它没有产生可复用的引擎架构。商业游戏的可移植性实践则在另一个方向上受困。Origin的程序员们成功地让《创世纪》系列跨平台运行,但代价是巨大的:每一个新平台的移植都需要重新编写整个底层函数库,工作量和从头开发相差无几。而且,为了让三个版本体验一致,游戏的设计被迫向最低公分母靠拢——如果Commodore 64的精灵图硬件不支持某种视觉效果,那么即使Apple II的位图帧缓冲可以做到,设计上也不会采用。可移植性带来了市场覆盖,但付出了性能和设计灵活性的双重代价。

这种割裂有一个具体的注脚:当Amiga演示场景的程序员用铜条效果在屏幕上同时显示4096种颜色时,Origin的跨平台《创世纪》却不得不在所有平台上使用同一套缩水的16色调色板——因为Commodore 64的硬件限制被写进了共享的游戏逻辑层。演示场景证明了硬件可以被超越,可移植性实践却要求向最弱的硬件低头。

两条谱系各自握着拼图的一半,但拼图没有拼接——一边是超越硬件的技巧却无法跨平台复用,另一边是跨平台的抽象层却主动放弃了超越硬件的可能。1990年,Looking Glass Studios在《地下创世纪》中更进一步,试图在IBM PC上实现一个完全纹理映射的三维地牢,但它的软件渲染器在当时的硬件上步履蹒跚,帧率极低。可移植性的抽象层隔离了硬件差异,却没有释放硬件的潜力。两条谱系都在逼近同一个幻觉,但谁也没有完全抵达。

演示场景拥有超越硬件的数学技巧,但缺乏跨硬件的抽象结构。商业游戏拥有跨硬件的抽象结构,但缺乏超越硬件的渲染野心。它们各自握着拼图的一半。

转折发生在1990年。这一年,id Software从Softdisk分离出来,正式成为一家独立的游戏公司。卡马克和他的同事们已经完成了《指挥官基恩》三部曲,在PC平台上建立了平滑横向卷轴的技术声誉。但他们接下来要做的事情,将把演示场景的欺骗哲学和商业游戏的可移植性需求,焊接成一个不可分割的整体。

1991年初,卡马克开始研究一项在PC游戏界几乎无人问津的技术:射线投射。这项技术的理论基础来自学术界——1968年,IBM的科学家阿瑟·阿佩尔在一篇论文中描述了如何通过追踪从视点出发的射线来计算三维场景的可见面。

1980年,贝尔实验室的特纳·惠特德在另一篇论文中将射线追踪扩展到了递归反射和折射。但这些研究都是为离线渲染设计的,生成一帧图像需要几分钟甚至几小时。在消费级PC上实时运行射线投射算法,这听起来像是天方夜谭。

卡马克看到了别人没看到的东西。阿佩尔的原始算法是通用的——它可以在任意方向上投射射线,处理任意形状的几何体。

但如果把问题简化呢?如果场景不是任意的三维空间,而是一个由等大立方体组成的迷宫?如果墙壁总是垂直的,地板和天花板总是水平的?如果视点始终保持在同一高度?在这些约束条件下,射线投射算法可以大幅简化。不再需要为每一个像素投射一条射线——只需要为屏幕的每一列投射一条射线。不再需要处理任意几何体与射线的交点计算——只需要检查射线穿过了哪些网格单元。不再需要递归追踪反射和折射——只需要计算射线击中墙壁的距离,然后按比例缩放墙壁的纹理列。这些简化把计算量从“不可能实时”降低到了“可以在386上跑到每秒30帧”。

但仅仅是算法简化还不够。卡马克在实现《德军总部3D》的射线投射引擎时,动用了演示场景的全部武库。他用定点数运算代替浮点运算——在缺乏浮点协处理器的386上,整数运算比浮点运算快一个数量级。他为纹理的每一列预计算了不同距离下的缩放查找表——这样在渲染时就不需要做除法,只需要查表和内存复制。他精确计算了每条射线投射和每列纹理渲染所需的CPU周期数,然后把整个渲染循环编排进垂直消隐的时序窗口里,确保画面不会撕裂。

《德军总部3D》在1992年5月发布时,它做到的事情在当时看来几乎是魔术。一个全纹理映射的三维纳粹城堡,以每秒60帧的速度在386 PC上实时渲染。玩家可以在城堡中自由移动、转弯、射击,所有的墙壁、门、装饰物和敌人都带着透视正确的纹理。这不是《Boing Ball》那种单个物体的旋转演示,这是一个完整的、可交互的、有游戏逻辑的三维世界。但从引擎的角度看,《德军总部3D》真正的突破不在渲染,而在架构。

卡马克为这款游戏编写的代码,清晰地分离成了两个层次。底层是一套渲染引擎,它负责射线投射、纹理映射、精灵图绘制和画面输出。这套渲染引擎完全不关心游戏逻辑——不知道玩家有多少血,不知道敌人在哪里巡逻,不知道门后面藏着什么道具。上层是一套游戏逻辑,它负责管理关卡数据、敌人AI、武器系统和玩家状态。游戏逻辑不直接操作显存,不关心像素位平面,甚至不关心渲染是用射线投射还是别的什么算法。它只负责更新游戏世界的状态,然后调用渲染引擎的接口把这个世界画出来。

这个分离的意义怎么强调都不过分。在《德军总部3D》之前,游戏程序和图形渲染是纠缠在一起的——代码的每一部分都既管理游戏逻辑又操作硬件。要更换渲染算法,就要重写整个游戏。要把游戏移植到新平台,就要一行一行地改代码。卡马克的分离打破了这种纠缠。渲染引擎成了一个独立的、可替换的模块。游戏逻辑成了另一个独立的、可移植的模块。

两者之间的接口是抽象的——它不规定渲染引擎内部如何工作,只规定渲染引擎必须提供哪些画面。

这正是两条谱系在id Software交汇的产物。从演示场景那里,卡马克继承了用数学欺骗硬件的全部技巧——定点数运算、预计算查找表、垂直消隐同步、汇编级优化。从商业游戏的可移植性实践那里,他继承了将渲染从游戏逻辑中抽象出来的工程纪律。

但他把两者推进到了前人没有到达的地方:他让这个抽象层不仅可以跨越不同的硬件,而且可以主动地、创造性地定义视觉——不再只是消极地隔离差异,而是积极地用数学构造出一个硬件从未被设计要显示的世界。《德军总部3D》的纳粹城堡在物理上并不存在于386的显存里。显存里只有一组一维的深度值、一堆纹理列的像素数据、和一些精灵图的位置坐标。三维的城堡是一种共享的幻觉——它存在于渲染引擎的算法中,存在于玩家的视觉感知中,但不存在于任何硬件电路的实际状态中。

卡马克的代码用射线投射算法计算每一列墙壁的深度,用查找表把深度映射成纹理列的缩放比例,用汇编循环把缩放后的纹理列写入位平面——然后,在屏幕的荧光粉上,这些离散的数学运算被人的视觉系统重新组装成一个连续的、有深度的、可以穿行的空间。

这个幻觉在1992年还只是惊鸿一瞥。射线投射引擎的局限是明显的:它只能渲染由正交墙壁组成的迷宫,不能有倾斜的墙面,不能有不同高度的楼层,不能有天花板和地板的纹理映射。

但它的核心结构已经完整了。渲染引擎和游戏逻辑的分离,数学抽象和硬件执行的分离,视觉呈现和物理现实的分离——这些分离构成了游戏引擎的基本骨架。到1992年底,id Software内部已经在讨论下一步。如果射线投射可以实时渲染一个正交迷宫,那么更通用的算法能不能实时渲染一个真正的三维空间?如果渲染引擎可以独立于游戏逻辑,那么同一套引擎能不能驱动完全不同的游戏?

如果《德军总部3D》的城堡是一个由数学规则统治的抽象空间,那么这个空间能不能被扩展成一个完整的、有任意几何体的、有光照和阴影的三维世界?

这些问题指向的,是游戏引擎从渲染工具变成世界编辑器的那个临界点。但在1992年,临界点还没有被跨越。卡马克和他的同事们刚刚完成了《德军总部3D》的引擎,正在着手设计它的继任者。

他们知道射线投射的局限,但他们还不确定下一步应该走向哪里。学术界有更通用的渲染算法——扫描线光栅化、BSP树可见性判断、多边形的透视纹理映射——但这些算法在1992年的消费级CPU上能不能跑到可玩的帧率,没有人知道。

演示场景的程序员们已经在Amiga上实现了全屏纹理映射隧道和多边形物体的实时旋转,但这些演示程序不是游戏,它们不需要处理玩家输入、敌人AI、碰撞检测和关卡切换。把演示场景的数学技巧装进一个完整的游戏引擎,这需要一种不同的工程思维——不是追求单帧的极致效果,而是追求在任意玩家行为下都能保持稳定帧率的系统架构。

Origin和Looking Glass的程序员们已经把可移植性抽象层推进到了角色扮演游戏的全部子系统——渲染、输入、声音、存档、对话——但他们的引擎架构是为了跨平台兼容而设计的,不是为了极限性能。当id Software在PC上追求60帧每秒的流畅三维渲染时,Origin的引擎还在为不同平台之间的微妙时序差异而焦头烂额。可移植性的工程纪律是必要的,但它本身不能产生实时三维渲染的突破。

两条谱系各自走到了自己的边界。演示场景证明了数学可以欺骗硬件,但它的代码无法复用,无法扩展,无法支撑一个完整的游戏。商业游戏的可移植性实践证明了抽象层可以隔离硬件差异,但它的抽象层是保守的,是为了防御硬件多样性而建立的,不是为了主动征服硬件的极限。

它们共同构成了引擎概念的基因,但引擎本身还没有诞生。1992年秋天的某个时刻,在id Software的办公室里,卡马克开始阅读一篇关于二叉空间分割树的学术论文。

这篇论文的作者是亨利·福克斯,他在1980年提出了用BSP树来加速三维场景的可见性判断。福克斯的算法是通用的——它可以处理任意多边形组成的三维场景,精确地判断哪些多边形在当前视点下是可见的,哪些被遮挡了。这篇论文在图形学学术界已经流传了十多年,但从未有人在消费级硬件上实时实现过它。

卡马克在BSP树中看到了一种可能性。如果BSP树可以在预处理阶段把一个三维场景的几何体组织成一颗二叉树,那么在运行时,渲染引擎只需要沿着这棵树从视点出发进行遍历,就可以自动获得从近到远、从前到后的多边形顺序。这意味着不需要为每一个像素投射射线,不需要把场景限制在正交网格里,不需要放弃倾斜的墙面和不同高度的楼层。BSP树可以把射线投射的简化版三维渲染,升级成通用的多边形三维渲染——只要CPU跑得够快。1992年的386还跑不了这个。但1993年的486或许可以。

如果再加上演示场景的全部数学技巧——定点数运算、预计算查找表、汇编级优化、垂直消隐同步——也许,仅仅是也许,一个通用的、全纹理映射的、支持任意几何体的三维游戏引擎,可以在消费级PC上跑到每秒30帧。

这个可能性一旦被看见,就不可能再被无视。它悬在id Software的办公室里,悬在卡马克的屏幕上,悬在所有正在阅读同一篇论文、正在研究同一个问题的程序员们的头顶。它是一个共享的幻觉——一个关于游戏世界可以完全独立于硬件而存在的信念。

这个幻觉在1987年的EGA位平面卷轴中萌芽,在Amiga的Boing Ball中旋转,在Origin的跨平台抽象层中呼吸,在《德军总部3D》的射线投射中第一次成形。现在,它正在等待下一次跳跃。

卡马克合上了福克斯的论文。屏幕上的代码编辑器还开着,里面是《德军总部3D》引擎的汇编优化循环。那些代码曾经代表PC游戏图形的最前沿,但现在,在BSP树的视野中,它们已经变成了过时的东西。

不是因为这些代码写得不好——它们写到了386架构的极限。而是因为它们所服务的渲染算法,射线投射,已经触碰到了它自己的天花板。要突破这个天花板,需要一套全新的渲染范式,一套全新的数据结构,一套全新的数学抽象。

这就是引擎代际更替的真正节奏。它不是线性的技术进步,不是每年快几帧、多几个多边形。它是范式的断裂和重构。

当旧的渲染算法无法再满足新的视觉野心时,当旧的数据结构无法再承载新的空间想象时,当旧的硬件契约被新的算力增长撕毁时——下一代引擎就在断裂处诞生。1992年的秋天,断裂正在《德军总部3D》的成功中悄然累积。射线投射引擎达到了它的巅峰,但巅峰本身就是极限的证明。

下一场断裂已经在BSP树的阴影中酝酿,它将把游戏引擎从二维迷宫的渲染器,变成三维世界的编辑器。那块曾经让卡马克在深夜与EGA位平面搏斗的屏幕,现在已经能显示纳粹城堡的纹理映射走廊。但屏幕上的东西已经变了。它不再只是像素的集合,不再只是硬件的输出。

它变成了一个独立于硬件的、由数学规则统治的抽象空间。这个空间可以旋转,可以缩放,可以穿行,可以在不同的显卡上重建,可以在不同的游戏中复用。它还不是一个完整的引擎——那要等到BSP树和多边形渲染登场。但它已经是引擎概念的确凿证明:游戏世界,可以从运行它的机器中解放出来。这个解放的后果,当时还没有人完全理解。