第 4 章
光与表面的契约
1996年秋天的一个深夜,达拉斯郊外id Software办公室的灯还亮着。约翰·卡马克坐在他的29英寸CRT显示器前,屏幕上并排开着两个窗口。左边是《雷神之锤》的软件渲染画面,运行在他手工优化的表面缓存光栅化器上——640×480分辨率,双线性过滤用MIP映射在CPU上模拟,每帧耗时精确到微秒级别,这是他花了六个月时间用汇编语言逐条指令抠出来的成果。右边是同一场景在3dfx Voodoo Graphics开发板上的输出——纹理清晰锐利,边缘平滑得不像话,帧率计数器跳动的数字让左边窗口显得像上个世代的遗物。
卡马克没有自言自语的习惯。他只是静静地坐着,盯着那两组数字。软件渲染器在Pentium Pro 200上跑到每秒二十八帧。Voodoo卡在Pentium 133上跑到每秒六十帧,而且画面质量高出一个数量级。这不是优化的问题。这不是算法选择的问题。
这是架构层面的碾压——一块售价三百美元的消费级显卡,用硬件纹理单元和像素填充率,把他手工编写的全部汇编优化变成了历史文物。这个时刻之所以成为第二代引擎范式的转折点,不是因为一个天才程序员被技术打败的故事,而是因为它暴露了引擎架构中一个根本性的权力问题:渲染管线究竟属于谁?
引擎开发者还是硬件厂商?这个问题的答案将在接下来的两年里,通过OpenGL与Glide的API战争、通过蒂姆·斯威尼在《虚幻》引擎中的并行路径策略、通过微软DirectX的标准化野心,被反复谈判和重新定义。要理解这场谈判的起点,需要把时间拨回1995年底。
3dfx Interactive还是一家成立不到两年的初创公司,由硅谷图形公司(SGI)的前工程师罗斯·史密斯、加里·塔罗利和斯科特·塞勒斯创立。他们的核心赌注很简单:三维图形的未来不在工作站,而在消费级PC游戏。
这个判断在当时并不被看好——1995年的PC游戏市场仍然被软件渲染统治,id Software的《毁灭战士》在386上跑出了每秒三十五帧的奇迹,证明CPU足够处理游戏所需的全部图形计算。但3dfx的创始人看到了不同的东西:纹理映射。软件渲染可以在低分辨率下做透视变换和可见性判定,但一旦涉及逐像素的纹理采样和双线性过滤,CPU的算术逻辑单元就会被海量的内存读取操作拖垮。
这不是理论推演。3dfx的工程师们有SGI工作站的实际经验——他们知道硬件纹理单元可以将纹理采样从数十个CPU周期压缩到单个时钟周期,因为纹理内存被直接焊在显卡上,数据总线宽度和时序都是为图形工作负载定制的。Voodoo Graphics芯片组的核心创新就在这里:它不只是一个更快的处理器,而是一套重新划分了计算边界的架构。芯片组由两个独立的ASIC组成——一个处理纹理映射,一个处理像素着色和帧缓冲操作。两者通过专用总线通信,完全不占用系统内存带宽。
这意味着在Voodoo上,纹理填充率可以达到每秒四千五百万像素,而同期最快的Pentium Pro用全部计算资源做同样的事,只能达到这个数字的二十分之一。1996年6月,当id Software发布《雷神之锤》时,这个差距还没有完全显现。《雷神之锤》本身是一个软件渲染优先的产品。卡马克在引擎中实现了完整的表面缓存系统——这是一种将屏幕空间划分为小块的算法,只对可见表面进行纹理映射,避免了对被遮挡像素的无效计算。配合MIP映射的预计算纹理链,软件渲染器可以在320×200分辨率下维持可玩的帧率。
在当时的游戏媒体评测中,《雷神之锤》的图形被认为是革命性的——动态光源在BSP树定义的走廊中投射出实时阴影,水面纹理有波浪变形的动画效果,火箭弹飞行时拖曳的粒子尾迹照亮了沿途的墙壁。这一切都在没有硬件加速的情况下实现。但Voodoo Graphics已经在路上了。
1996年10月,基于Voodoo芯片的Diamond Monster 3D显卡上市。这块卡有一个奇怪的特征:它只能做三维加速,不能做二维显示。用户必须把Monster 3D通过一根外部VGA线缆连接到主显卡上,平时桌面和二维游戏由主显卡处理,只有运行支持Glide API的三维应用时,Monster 3D才会接管输出。这个设计在今天看来笨拙得可笑,但在当时却是一个精明的战略选择——3dfx不需要去和Matrox或S3争夺二维显卡市场,他们只需要证明一件事:三维加速的价值大到消费者愿意额外掏三百美元,并且忍受硬件切换的麻烦。事实证明这个赌注是对的。但证明的方式出乎所有人的预料。
1997年1月,id Software在官网上放出了一个补丁文件:GLQuake。这是一个不到两兆字节的下载,但它的内容标志着引擎架构史上的一次范式断裂。卡马克将《雷神之锤》的整个渲染后端从手工汇编的软件光栅化器,改写为通过OpenGL驱动调用硬件管线。
源码中原本占据数万行的表面缓存代码、MIP映射生成器、透视校正纹理映射的汇编优化,全部被替换为一组OpenGL API调用——glBegin、glTexCoord2f、glVertex3f、glEnd。硬件做了剩下的一切。
这不是一次渐进式优化。这是渲染管线的所有权从引擎开发者转移到了硬件厂商。在软件渲染时代,引擎控制着从几何变换到像素着色的每一个步骤,每一行代码都是为特定场景类型手工调校的。卡马克可以在表面缓存中为室内走廊场景做特殊优化,可以在MIP映射生成时调整各层纹理的锐度以补偿透视变形,可以在光照计算中混合多个光源而不受固定管线限制。但在OpenGL硬件路径中,这些决策被交还给了显卡驱动——引擎告诉硬件“在这个位置有一个三角形,贴上这张纹理,用这个法线方向计算光照”,然后硬件按照自己的方式执行。
如果硬件的光照模型和引擎的软件路径不一致,如果纹理过滤的质量达不到卡马克的标准,如果某个特定场景的优化被通用驱动忽略——引擎开发者能做的事情非常有限。
但GLQuake的画面质量让这些担忧暂时显得无关紧要。在Voodoo卡上,GLQuake可以运行在640×480分辨率下,开启双线性过滤,帧率稳定在每秒三十帧以上。纹理不再有软件渲染器不可避免的像素锯齿,远处墙壁上的砖缝清晰可辨,水面反射的高光平滑过渡,爆炸的火球边缘柔和地融入黑暗。
对于1997年的玩家来说,这不仅仅是画质的提升——这是一种新的视觉真实感,一种“材质感”。石头看起来像石头,金属看起来像金属,不是因为几何精度提高了,而是因为纹理在表面上的投影方式终于符合了人眼对真实世界的预期。
这就是本章论断的核心:第二代引擎范式的战场从“空间”转向了“表面”。第一代范式——以《毁灭战士》和《雷神之锤》的软件渲染器为代表——的核心问题是“如何在屏幕上正确投影三维几何”。
BSP树、表面缓存、透视校正纹理映射,都是为这个问题服务的。但Voodoo Graphics和GLQuake回答了一个不同的问题:“如何让投影出来的表面看起来可信”。可信意味着纹理过滤、光照插值、透明混合、雾化衰减——这些效果在软件渲染中理论上可以实现,但计算成本高到无法实时运行。
硬件加速将它们的成本降到了零,代价是引擎必须放弃对渲染管线的精细控制。这个代价在当时很少有人讨论。游戏开发者和玩家都沉浸在硬件加速带来的视觉冲击中。但引擎架构师们——那些需要为未来数年产品做技术决策的人——看到了更深层的后果。如果渲染管线由硬件厂商通过驱动定义,那么引擎的创新空间就被限制在驱动暴露的API边界内。如果一家显卡厂商决定不支持某种光照模型,或者以特定方式实现纹理过滤,引擎开发者只能接受。更关键的是,不同厂商的硬件实现不同——3dfx的Glide API暴露了Voodoo芯片的独特功能,如可编程纹理组合器和逐像素雾化表;
SGI主导的OpenGL则是一套更通用的规范,设计时考虑的是工作站级的三维应用,对游戏引擎的特殊需求反应迟缓。
这就将我们带到了第二条叙事线:蒂姆·斯威尼和《虚幻》引擎的并行路径策略。1995年,当卡马克在达拉斯埋头于《雷神之锤》的软件渲染器时,斯威尼在罗克维尔市的Epic MegaGames办公室里做出了一个不同的架构决策。《虚幻》引擎从一开始就被设计为多渲染后端并存——软件渲染器、Glide路径、OpenGL路径,后来还加入了Direct3D路径。
这不是简单的“支持多种API”。斯威尼在引擎核心中构建了一个抽象的光照与材质系统,这个系统不直接调用任何硬件API,而是定义了一套引擎自己的材质描述语言:表面属性包括漫反射颜色、镜面反射指数、自发光强度、透明度和多个纹理层,光照模型支持动态点光源、方向光和聚光灯,阴影通过光照图预计算与动态模板缓冲混合实现。这个抽象层的意义在于,它重新定义了渲染管线的所有权归属。
斯威尼的策略不是抵抗硬件厂商,也不是投降——而是谈判。引擎保留了对“场景应该看起来如何”的定义权,硬件厂商保留了对“如何实现这个效果”的执行权。当《虚幻》引擎的场景需要在Voodoo卡上渲染时,Glide后端将引擎的光照模型翻译为Voodoo的纹理组合器操作;当同一个场景在Matrox G200上渲染时,OpenGL后端利用模板缓冲和多遍渲染来逼近同样的视觉效果;当NVIDIA Riva TNT出现时,Direct3D后端利用其双纹理单元在一次渲染中完成多纹理混合。
结果是在不同硬件上,同一个《虚幻》场景呈现出截然不同但各自合理的视觉效果。在Voodoo卡上,彩色光照通过纹理组合器实现,颜色饱和度高但精度受限;在Matrox卡上,环境凹凸贴图通过模板缓冲模拟,效果微妙但计算成本高;在NVIDIA卡上,三线性过滤和单周期双纹理带来了清晰度和性能的平衡。
斯威尼的抽象层确保了一个最低限度的视觉一致性——石墙在哪种硬件上都看起来像石墙,火把的光在哪种硬件上都投射出暖色调——同时允许每个硬件路径发挥其独特优势。这个策略的技术代价是巨大的。《虚幻》引擎的渲染代码量是《雷神之锤》的数倍,因为每个渲染后端都需要独立的优化和调试。当3dfx发布新的Glide版本时,斯威尼的团队需要更新Glide后端;当微软修改Direct3D的规范时,Direct3D后端需要重写;当NVIDIA和ATI各自在OpenGL驱动中实现不同的扩展时,引擎需要检测并适配这些差异。
这是一个永无止境的维护循环,但它换来了一个关键的战略优势:Epic不依赖任何单一硬件厂商。如果3dfx明天倒闭,Glide后端可以废弃,但引擎核心和场景资产不受影响。如果微软的DirectX路线偏离游戏开发者的需求,OpenGL后端仍然可以支撑产品线。
这个优势在1998年到1999年间变得至关重要,因为第三条叙事线正在展开:微软DirectX的崛起和API选边的生存焦虑。DirectX的起源可以追溯到1995年,当时微软意识到Windows 95作为游戏平台的短板——DOS游戏可以直接访问硬件,Windows游戏却被GDI图形接口的软件抽象层束缚。微软最初的解决方案不是DirectX,而是一个叫WinG的库,试图在Windows上提供类似DOS的帧缓冲访问。WinG失败了,因为它没有解决根本问题:游戏需要的不是帧缓冲,而是对整个图形管线的直接控制。DirectX 1.0在1995年9月发布,但它几乎没有被任何商业游戏采用。问题出在架构上:DirectX的图形组件DirectDraw只提供了二维表面的快速访问,三维功能几乎不存在。真正的转折点是1996年的DirectX 3.0,它引入了Direct3D——微软对三维图形API的第一次严肃尝试。
但Direct3D从诞生之初就陷入了路线之争。微软内部有两个团队在同时推进不同的设计:一个团队提出了“保留模式”,这是一个基于场景图的高层API,自动管理几何数据、变换层级和光照计算;另一个团队提出了“立即模式”,这是一个底层API,直接暴露硬件能力,引擎开发者需要手动管理所有渲染状态和几何提交。
保留模式的哲学接近SGI的OpenGL Performer或苹果的QuickDraw 3D——它假定开发者想要一个“描述场景,让系统渲染”的模型。立即模式的哲学更接近Glide或OpenGL的核心规范——它假定开发者想要精确控制每一帧的每一个渲染调用。对于引擎开发者来说,这个选择不是技术偏好问题,而是生存问题。选择保留模式意味着将引擎架构绑在微软的场景图设计上,如果微软改变设计或者优化不足,引擎无法绕过。选择立即模式意味着承担巨大的开发工作量,但保留了对渲染管线的实际控制。1997年,这个路线之争达到了白热化。
微软官方推荐保留模式,认为它更符合Windows的抽象哲学和COM组件模型。但游戏开发者几乎一致地选择了立即模式——或者说,他们试图选择立即模式,却发现微软的立即模式实现在初期性能极差,API调用开销大到抵消了硬件加速的优势。这导致了一个荒谬的局面:在1997年,用Direct3D立即模式渲染一个简单的三角形列表,比用Glide或OpenGL做同样的事慢得多,原因不是硬件不行,而是微软的驱动层效率低下。中小引擎团队被这个局面推入了生存焦虑。
如果你是一个1997年的独立游戏开发者,正在选择引擎技术路线,你面对的选择是这样的:Glide提供最好的性能,但只支持3dfx硬件,而3dfx的市场份额虽然增长迅速,却不是唯一的显卡厂商;OpenGL提供跨平台和跨硬件支持,但Windows上的OpenGL驱动质量参差不齐,而且微软明显在冷落OpenGL以推广Direct3D;
Direct3D有微软的全部营销资源支持,但立即模式性能差,保留模式不灵活,而且API规范在每个DirectX版本中都剧烈变化。这不是一个技术选择——这是一个赌注,赌哪家硬件厂商和哪个API会活过未来三年。
正是在这个焦虑的背景下,斯威尼的抽象层策略显示出了它的价值。《虚幻》引擎不需要赌——它可以同时支持所有API,因为引擎核心不依赖任何单一API的设计哲学。当Direct3D立即模式在1998年的DirectX 6.0中终于变得可用时,《虚幻》引擎的Direct3D后端已经准备好了。当NVIDIA的Riva TNT证明OpenGL可以在Windows上跑到接近Glide的性能时,《虚幻》引擎的OpenGL后端同样准备好了。当3dfx发布Voodoo2并引入SLI双卡并联技术时,Glide后端可以充分利用两块卡的填充率而无需修改引擎核心。但这不是一个皆大欢喜的结局。抽象层的代价在1998年开始显现。
随着硬件能力的快速增长——Voodoo2的填充率是Voodoo1的三倍,NVIDIA TNT支持单周期双纹理和二十四位深度缓冲,Matrox G200引入了环境凹凸贴图——引擎的抽象层需要不断扩展以暴露新硬件功能。斯威尼的团队发现自己陷入了一场军备竞赛:每六个月,显卡厂商发布新硬件,附带新的专有API扩展;引擎必须快速适配这些扩展,否则竞争对手就会在画面质量上取得优势。抽象层不再是保护墙,而变成了瓶颈——如果一个硬件功能没有在抽象层中定义,即使硬件支持,游戏也无法使用。
这引出了本章必须回应的反方解释:引擎代际更替本质是商业生态的收割周期——谁掌握平台分发权,谁就定义“下一代”,技术只是事后包装的叙事。按照这个观点,GLQuake的发布不是范式投降,而是id Software和3dfx之间的一次商业联盟——卡马克通过支持OpenGL和Glide,确保了《雷神之锤》能在所有主流显卡上运行,从而最大化市场份额;
3dfx通过让Voodoo成为《雷神之锤》的最佳运行平台,获得了杀手级应用来推动硬件销售。技术决策只是商业利益的外壳。
这个解释有它的证据。1997年的显卡市场确实是一场平台战争——3dfx、NVIDIA、ATI、Matrox、S3、Rendition都在争夺游戏开发者的支持,而游戏开发者的支持意味着为特定API和硬件优化。id Software选择OpenGL而不是Direct3D,部分原因是微软的Direct3D在1997年仍然不可用,部分原因是卡马克个人对开放标准的偏好,但也有部分原因是SGI和3dfx在OpenGL架构审查委员会中的影响力确保了OpenGL对游戏需求的响应速度。同样,斯威尼的多后端策略不仅是技术远见,也是商业理性——Epic MegaGames作为独立引擎开发商,不能承受绑定单一硬件厂商的风险,因为那会将引擎的授权市场限制在使用该厂商硬件的游戏开发商群体中。
但将技术决策完全还原为商业计算,会错过这场变革中真正具有历史意义的部分。GLQuake的发布之所以是范式断裂,不是因为卡马克选择了OpenGL而不是Direct3D——那只是一个战术选择,在1999年就被《雷神之锤3》的Direct3D支持所修正。
范式断裂在于渲染与游戏逻辑的分离。在软件渲染时代,引擎的渲染器和游戏逻辑在同一个地址空间、同一个执行线程中运行,共享数据结构,共享优化假设。当GLQuake将渲染调用交给OpenGL驱动后,渲染变成了一个黑箱——引擎提交几何和纹理,驱动返回帧缓冲,中间发生了什么引擎不知道。这意味着引擎架构师不能再假设渲染的精确时序和资源消耗,不能再为特定场景手工优化光栅化顺序,不能再依赖软件渲染器的确定性行为来同步游戏状态。
这个分离的直接后果是引擎架构的分层化。在《雷神之锤》的源码中,渲染代码和游戏逻辑代码交织在一起——一个函数可能同时处理光照计算和怪物AI状态更新。
在《雷神之锤3》中,渲染器被重构为一个独立的模块,通过明确定义的接口与游戏逻辑通信。在《虚幻》引擎中,这个分离更加彻底——渲染器不仅独立,而且有多个可替换的实现,游戏逻辑完全不知道也不关心底层用的是Glide、OpenGL还是Direct3D。这不是商业策略的副产品,而是硬件加速强加给引擎架构的结构性约束:当渲染管线由外部驱动控制时,引擎内部必须建立一个隔离层,否则每次显卡驱动更新都会破坏游戏逻辑的正确性。
这个隔离层的建立,反过来定义了引擎开发商和硬件厂商之间的权力格局。硬件厂商通过API规范和驱动实现,控制了“如何渲染”——纹理过滤的质量、光照模型的精度、透明混合的算法、反锯齿的实现。引擎开发商通过抽象层设计,控制了“渲染什么”——场景的几何组织、材质的语义定义、光照的艺术家意图、后处理的美学方向。这场谈判没有最终的赢家。
硬件厂商不断通过新功能扩展API边界,试图将更多的渲染决策纳入硬件控制——可编程着色器在2001年的出现将是这个趋势的终极表达。引擎开发商不断通过抽象层和中间件抵抗这种侵蚀,试图保留对视觉结果的最终定义权。1998年末的一个安静画面可以捕捉这场谈判的中间状态。在一间游戏开发工作室的测试间里,一台Pentium II 400的测试机内部插着两块显卡——一块3dfx Voodoo2用于Glide渲染,一块Matrox Millennium G200用于桌面显示和OpenGL渲染。屏幕上运行着《虚幻》引擎的场景编辑器,同一个中世纪城堡庭院的场景在Glide和OpenGL两个窗口中并排显示。在Glide窗口中,火把的光在石墙上投射出暖橙色的渐变,纹理组合器将光照图和漫反射纹理相乘,边缘有轻微的色阶过渡不自然。
在OpenGL窗口中,同样的石墙呈现出更精确的镜面高光,因为Matrox卡支持逐像素的模板缓冲操作,但纹理过滤的锐度略低于Voodoo2的硬件实现。两个画面都正确——它们都忠实地呈现了斯威尼在引擎抽象层中定义的“傍晚的城堡庭院”应该有的氛围。但它们是不同的正确。
在这两个窗口背后,引擎的抽象层在无声运转。它接收场景数据——BSP树节点、静态网格体引用、动态光源参数、材质定义——然后将它们翻译为两套不同的渲染调用。对于Glide后端,它调用grDrawTriangle和grTextureCombine;对于OpenGL后端,它调用glBegin/glEnd和glTexEnvi。翻译过程中有信息的损失——Glide的纹理组合器只有有限的几个操作模式,无法精确表达引擎定义的完整光照方程;OpenGL的固定功能管线同样有约束,多纹理混合需要多次渲染。但这些损失被硬件加速的性能收益所掩盖。
玩家看到的不是损失,而是之前软件渲染中不可能实现的材质感和光照质量。这个画面不评论自己。它只是1998年某个下午,一台测试机上同时运行的两个渲染窗口。
但它标记了第二代引擎范式的核心张力:表面已经到来,但表面的定义权还没有最终归属。硬件厂商用纹理填充率和像素着色器将“表面”从CPU的算术逻辑单元中解放出来,但解放的代价是引擎必须放弃对渲染管线的完全控制。引擎开发商用抽象层和多后端策略重新夺回了部分控制权,但抽象层本身变成了新的瓶颈——它必须不断追赶硬件创新的速度,否则就会在画面竞赛中落后。
而微软的DirectX团队正在雷德蒙德的办公室里规划DirectX 7.0,它将在一年后引入硬件变换与光照(T&L),彻底终结软件渲染时代,同时将这场谈判推进到下一个战场:顶点管线。那将是另一场断裂的开始。但在1998年的这个下午,在Voodoo2和Matrox G200并排运行的两个窗口里,第二代引擎范式的全部成就和全部矛盾都已经显现。
表面取代了空间成为渲染的核心问题。材质感取代了几何精度成为视觉真实的标准。硬件契约取代了软件优化成为引擎架构的基石。而渲染管线的所有权——这个在软件渲染时代从未被质疑过的问题——现在悬在半空,等待下一代硬件和下一代API来重新谈判。