第 5 章

可编程性的裂缝

渲染管线的所有权悬在半空。这个在软件渲染时代从未被质疑过的命题,在1998年的两个并排运行的游戏窗口里,显出了它全部的脆弱。硬件厂商用纹理填充率夺走了表面的定义权,引擎开发商用抽象层重新谈判,微软的DirectX团队则在雷德蒙德的办公室里规划着下一份规范——那份规范将引入硬件变换与光照,把战争推进到顶点管线。

但没有人预料到,真正的断裂不发生在顶点,而发生在更根本的地方:当GPU突然变得图灵完备,渲染管线本身就不再是一条管线了。

把时间拨回到1999年秋天。在NVIDIA的圣克拉拉总部,一群工程师正在完成一项将改写图形硬件定义的工作。他们不是在设计更快的固定功能单元,而是在设计一个指令集。这个指令集的名字在当时听起来平淡无奇——NV10架构的顶点程序扩展——但它所承载的逻辑是革命性的:GPU将不再仅仅执行硬件预设的变换与光照方程,它将接受开发者自行编写的程序,在每一个顶点通过管线的瞬间执行这些程序。

这个变化的技术前史可以追溯到1998年。当时,NVIDIA的竞争对手3dfx仍在沿着固定功能管线的路径推进——Voodoo3集成了更多的纹理单元,提高了填充率,但它的渲染管线逻辑仍然锁死在硬件中。ATI的Rage 128同样如此。整个产业的共识是:更快的固定功能硬件可以满足游戏画面的需求,而可编程性的成本太高,市场还没有准备好。

但NVIDIA的首席科学家戴维·科克(David Kirk)看到了另一条路径。他在内部技术备忘录中指出,固定功能管线的复杂度正在接近一个临界点——每增加一种新的光照模型或纹理混合模式,硬件就需要增加相应的固定功能单元,芯片面积和验证成本呈指数增长。如果继续沿着这条路走下去,GPU将变成一个臃肿的特征集合,每一个新增特征对应一小块硅片,而这些硅片在大多数渲染批次中处于闲置状态。科克提出的替代方案来自一个看似不相关的领域:数字信号处理器。

DSP芯片长期以来采用可编程架构——它们执行的不是固定功能的信号处理流程,而是由开发者加载的微码程序。如果GPU也采用类似的逻辑,将渲染管线中的关键阶段——顶点变换、光照计算、纹理采样——从固定功能单元替换为可编程的执行单元,那么硬件厂商就不再需要为每一种新的光照模型增加硅片。他们只需要提供一个足够快的通用计算单元,让开发者自己编写光照代码。

这个想法在1998年的NVIDIA内部引发了激烈争论。反对者的理由很直接:游戏开发者不是DSP程序员。他们习惯了通过设置参数来配置固定功能管线——设置光源类型为点光源,设置衰减系数,设置漫反射颜色——而不是为每一个顶点编写汇编级程序。如果NVIDIA推出一款要求开发者编写程序的GPU,市场接受度将是一个巨大的问号。

但科克有一个关键盟友:微软的DirectX团队。DirectX 7.0已经在规划硬件变换与光照,但DirectX团队同样看到了固定功能管线的极限。

在1999年初的一次技术评审会上,DirectX的架构师们做出了一个判断:下一代DirectX必须引入可编程着色器,否则API本身将成为画面进化的瓶颈。他们开始与NVIDIA密切合作,共同定义一套顶点着色器的指令集规范,这套规范将成为DirectX 8.0的核心。这意味着可编程性不是NVIDIA一家的技术冒险,而是整个PC图形生态的协同转向。当微软、NVIDIA和后来的ATI都朝着同一个方向移动时,引擎开发者们面对的不是一个可选的技术升级,而是一个即将到来的架构断裂。

2000年11月,NVIDIA发布了GeForce 3的初步技术规格。在面向开发者的技术简报会上,NVIDIA的工程师展示了一段演示程序,这段程序运行了一个在固定功能管线上不可能实现的效果:逐顶点位移映射。传统上,顶点在模型中的位置是固定的——它们在建模软件中被确定,然后通过变换矩阵映射到屏幕空间。

但在GeForce 3上,开发者编写了一段顶点着色器程序,在顶点通过管线时实时修改其位置——根据一张纹理贴图的亮度值沿法线方向偏移顶点,在GPU上生成了真正的几何细节。台下的引擎开发者们看着这个演示,他们看到的不是一个新的图形效果。他们看到的是过去五年积累的固定功能配置经验,从这一刻起开始贬值。

在固定功能管线时代,图形编程的本质是状态管理——你知道硬件支持哪些光照模型、哪些纹理混合模式、哪些雾化方程,你的工作是选择正确的组合,在正确的渲染批次中设置正确的状态字。这是一种参数配置的范式。

但现在,GPU要求你为它编写程序。你不再告诉硬件“用点光源,衰减系数0.5,漫反射颜色白色”,你写一段代码计算光照,这段代码可以包含任意复杂的数学运算,可以采样任意数量的纹理,可以生成任意颜色的输出。

这意味着引擎架构师面对的不再是一个配置问题,而是一个编译问题。DirectX 8.0的规范文档在2000年12月发布。

文档中定义的顶点着色器指令集包含大约17条指令——加法、乘法、点积、指数、对数、纹理采样——这些指令不是参数表,是汇编语言。开发者必须手动分配寄存器,管理指令槽数量(顶点着色器1.0最多支持128条指令),处理精度损失。像素着色器1.0更为受限:仅支持4条纹理采样指令和8条算术指令,寄存器宽度只有8位或16位定点数,远非全精度浮点运算。

但限制本身恰恰暴露了本质。这些指令集虽然简陋,但在逻辑上是图灵完备的——在指令槽和寄存器限制内,它们可以执行任意算法。

固定功能管线时代的硬件,无论有多少种光照模式、多少级纹理混合,在逻辑上始终是一个有限状态机。可编程着色器将GPU变成了一个通用计算单元,尽管这个通用性在2000年还受到严格约束,但约束只是工程问题,不是逻辑问题。

逻辑上,断裂已经完成。对于引擎架构师而言,这道裂缝带来的是一个他们从未面对过的问题:当你可以为硬件编写任意程序时,你应该编写什么?这个问题听起来像是自由,但实际上是责任。

在固定功能管线时代,硬件厂商为渲染质量承担了一部分责任——他们定义了光照方程、纹理混合模式、雾化算法,引擎开发者只需要选择使用哪些。如果画面看起来不对,问题可能在硬件、在驱动、在API。但现在,当你自己编写光照代码时,每一行指令的精度损失、每一个边界条件的处理、每一个近似算法的取舍,都是你的责任。画面质量的最终定义权回到了引擎开发者手中,但回来的不是权力,是负担。

在德克萨斯州梅斯基特,约翰·卡马克比大多数人更早地理解了这种负担的性质。id Software在2000年已经开始了下一代引擎的开发。这个引擎后来被称为id Tech 4,它将驱动《毁灭战士3》。卡马克在项目早期做出了一个决定,这个决定将id Tech 4变成当时最纯粹的可编程管线宣言:引擎将完全抛弃固定功能路径。它不会回退到OpenGL的固定功能光照模型,不会为不支持顶点程序和片段程序扩展的GPU提供兼容模式。

引擎强制要求GPU支持ARB顶点程序扩展和ARB片段程序扩展——这两个扩展是OpenGL架构评审委员会(ARB)在2001年批准的可编程着色器标准,对应于DirectX 8.0的顶点着色器和像素着色器规范。这个决定的激进程度在当时被低估了。2001年,支持ARB顶点程序和片段程序扩展的GPU只有NVIDIA的GeForce 3系列和ATI的Radeon 8500系列。这两款GPU在2001年的市场占有率加起来不到百分之十五。绝大多数游戏玩家手中的显卡——GeForce 2、Radeon 7000系列、各种集成显卡——都不支持可编程着色器。如果id Tech 4强制要求这些扩展,它将在发布时与大量主流硬件不兼容。卡马克知道这个数字。他在内部邮件中讨论过兼容性问题,但他的判断是:等到引擎完成时——他预计在2003年左右——可编程硬件将成为主流。

这个判断基于一个计算:GPU的更新周期大约是十八个月,从2001年GeForce 3发布到2003年底,市场将经历至少两代硬件更新,届时支持可编程着色器的GPU将占据主导地位。

这个计算在逻辑上是正确的,但它低估了两个因素。第一个因素是OEM市场的惯性——品牌机厂商倾向于使用成熟、廉价的上一代GPU,这些GPU的库存消化周期远长于十八个月。第二个因素是笔记本电脑市场的崛起——笔记本GPU的可编程性升级比桌面GPU滞后一到两代。当《毁灭战士3》最终在2004年8月发布时,市场上仍有大量在售的笔记本和品牌台式机搭载着不支持ARB顶点程序的GPU。这些机器的用户购买了一款无法运行的游戏。

但卡马克的决定不是鲁莽的。它源自一个技术判断:如果引擎同时维护固定功能回退路径和可编程路径,那么可编程路径的设计将受到固定功能路径的约束。固定功能管线的光照模型是逐顶点的——光照计算在顶点着色器阶段完成,结果在光栅化阶段被线性插值到每个像素。

这意味着阴影和高光的细节受限于顶点密度。如果你想让一个表面呈现真正的逐像素光照——每一个像素都独立计算法线与光源方向的点积——你必须在可编程管线中抛弃逐顶点的光照逻辑,构建一个完全不同的光照架构。

卡马克的野心正是构建这样一个架构。《毁灭战士3》引擎的光照模型是统一的、全像素的、交互式的。每一个表面的每一个像素,每一帧,都经过完整的法线贴图采样、高光贴图采样、衰减体积光计算。法线贴图存储了表面微观几何的法向量,使得低多边形模型可以呈现高多边形模型的光照细节。高光贴图定义了表面不同区域的反光强度。

衰减体积光模拟了光源在空间中的衰减分布——不是简单的线性衰减或平方衰减,而是可以艺术家手动绘制的任意衰减体积。这个光照模型的统一性意味着引擎不需要区分“静态光照”和“动态光照”——在固定功能管线时代,静态光照通常通过光照贴图预计算并烘焙到纹理中,动态光照则在运行时逐顶点计算。

两者在视觉上很难融合:光照贴图呈现柔和的全局光照效果,但无法响应动态光源;逐顶点光照可以响应动态光源,但在顶点稀疏的表面上产生明显的马赫带效应。《毁灭战士3》的解决方案是用同一个全像素光照管线处理所有光源——无论是静态的室内灯光还是动态的枪口闪光,都经过相同的法线贴图、高光贴图和衰减体积计算。这消除了静态与动态光照之间的视觉断层,代价是每一个像素的计算量远超固定功能时代的任何渲染路径。

为了实现这个光照模型,id Software的工程师们用ARB汇编语言编写了数百行着色器代码。ARB顶点程序和片段程序扩展定义的是一种低级汇编语言——不是Cg或HLSL那样的高级着色语言。每一条指令直接对应GPU执行单元的一个操作:乘法、加法、纹理采样、寄存器移动。

开发者必须手动管理临时寄存器——片段程序最多支持6个通用寄存器——手动优化指令顺序以减少依赖停顿,手动处理精度:片段程序中的算术运算通常使用定点数而非浮点数,这意味着每一次乘法都可能引入精度损失。

这是一项艰苦的工作。id Software的图形程序员在内部邮件列表中讨论着色器代码的寄存器分配策略,争论某一段光照计算是否可以在6个通用寄存器的限制内完成。最终的光照着色器代码经过了反复的手动优化——不是编译器优化,是程序员逐条指令地重新排列顺序、复用寄存器、用近似算法替代精确计算。这段代码的最终版本是一段紧凑的汇编程序,它在逻辑上实现了完整的法线贴图光照模型,在物理上精确地适配了GeForce 3的寄存器文件和指令槽。这段代码的汇编清单至今仍可以在id Software发布的引擎源代码中找到。它是一份技术宣言。它说:我们不再配置硬件,我们为硬件编写程序。

固定功能管线的参数表——光源类型、衰减系数、纹理混合模式——被替换为一段由开发者完全控制的程序,这段程序定义了每一个像素的颜色如何从法线、光源方向、视角方向和高光参数中计算出来。

但这份宣言的代价是兼容性。《毁灭战士3》在2004年8月发布时,最低系统要求是GeForce 3或Radeon 8500级别的GPU——这两款显卡在三年前发布,但在2004年的主流OEM机型中仍不普及。大量玩家发现他们购买的《毁灭战士3》无法在笔记本或品牌台式机上运行,不是因为性能不足,而是因为硬件根本不支持ARB顶点程序和片段程序扩展。id Software在发布后被迫提供了一个回退路径——但这个回退路径不是固定功能管线,而是一个简化的可编程路径,使用更少的指令和更低精度的计算,勉强兼容一些部分支持可编程性的低端GPU。市场反馈是矛盾的。

《毁灭战士3》的画面在当时被广泛认为是技术奇迹——统一的逐像素光照、实时的阴影体积、法线贴图带来的几何细节——这些效果在2004年的任何其他游戏中都无法找到。但兼容性限制和极高的硬件需求也引发了批评。id Software的激进选择暴露了技术理想主义与市场渗透率之间的尖锐矛盾:你可以设计一个纯粹的可编程管线,但你不能强迫市场按照你的时间表升级硬件。在北卡罗来纳州卡里,蒂姆·斯威尼看着id Software的激进宣言,选择了一条不同的路径。

Epic Games在2002年发布了《虚幻引擎2》,驱动了《虚幻竞技场2003》和《虚幻竞技场2004》。这个引擎面对的是同一个可编程性裂缝,但斯威尼的应对策略与卡马克截然相反。《虚幻引擎2》保留了完整的固定功能回退路径。引擎可以在不支持可编程着色器的GPU上运行,使用OpenGL或Direct3D的固定功能光照模型、纹理混合和雾化。

同时,引擎提供了一个可编程着色器接口,允许在支持DirectX 8.0的GPU上使用顶点着色器和像素着色器。这种双路径策略带来了架构上的复杂性。引擎的渲染器必须维护两套代码路径——固定功能路径和可编程路径——并确保它们在视觉上尽可能一致。这不是简单的if-else分支:固定功能光照模型和可编程光照模型在数学上可能产生不同的结果,因为固定功能管线的光照计算使用硬件内置的近似算法,而可编程着色器使用开发者自己编写的算法。斯威尼的解决方案是限制可编程路径的自由度——引擎提供的着色器接口不是完全开放的,而是一组预定义的着色器模板,开发者可以调整参数但不能编写任意代码。

这在2002年是一个务实的折衷。它让《虚幻引擎2》可以运行在从GeForce 2到Radeon 9700的广泛硬件上,覆盖了当时几乎全部的游戏PC市场。

但模板化的着色器接口也意味着引擎没有真正释放可编程性的全部潜力——开发者被限制在Epic预定义的着色器框架内,无法实现《毁灭战士3》那种完全自定义的全像素光照模型。斯威尼意识到了这个限制。在2003年的GDC演讲中,他分析了可编程着色器给引擎架构带来的根本性挑战:DirectX 8.0引入的顶点着色器和像素着色器只是第一步。DirectX 9.0已经在规划更高级的着色器模型——Shader Model 2.0将大幅增加指令槽数量和寄存器宽度,Shader Model 3.0将引入动态分支和循环。同时,OpenGL阵营通过ARB扩展提供了自己的可编程性标准。NVIDIA推出了Cg——一种高级着色语言,可以编译到DirectX和OpenGL的可编程接口。微软推出了HLSL——DirectX的原生高级着色语言。OpenGL阵营则有GLSL——OpenGL着色语言。

引擎架构师面对的不再是一种可编程接口,而是至少四种:DirectX的HLSL/Shader Model体系、OpenGL的GLSL/ARB扩展体系、NVIDIA的Cg跨平台方案、以及即将到来的PlayStation 3和Xbox 360各自的专有着色器架构。每一种接口有不同的指令集、不同的寄存器限制、不同的精度模型、不同的编译工具链。如果你想让引擎运行在所有这些平台上,你必须为每一种接口编写不同的着色器代码——或者找到一种方法,将着色器的编写抽象到引擎层之上。斯威尼的答案是“UnrealShader”——一套在《虚幻引擎2.5》中开始构建的元语言。UnrealShader不是一种新的着色语言,而是一个抽象层。它定义了一套统一的着色器描述语法,引擎在加载着色器时将这套语法翻译为目标平台的着色器代码。如果目标GPU支持DirectX 9.0的HLSL,UnrealShader将生成HLSL代码;

如果目标GPU支持OpenGL的GLSL,它将生成GLSL代码;如果目标GPU只支持固定功能管线,它将把着色器描述降级为固定功能参数配置。这个抽象层的架构图在Epic的内部文档中呈现出清晰的层次结构。最上层是艺术家和设计师使用的材质编辑器——一个可视化的节点图界面,允许非程序员通过连接节点来定义表面的外观。材质编辑器的输出是一套UnrealShader描述——不是可执行的着色器代码,而是一个中间表示,记录了表面需要哪些纹理采样、哪些数学运算、哪些光照计算。

引擎的渲染器在加载材质时,调用UnrealShader编译器,将这个中间表示翻译为目标平台的原生着色器代码。UnrealShader编译器是这套架构的核心。它必须理解至少三种目标语言的语法和语义——HLSL、GLSL、以及固定功能状态配置——并生成在视觉上尽可能一致的输出。这不是一项简单的翻译工作。不同的着色语言有不同的数学库、不同的精度模型、不同的纹理采样语义。

HLSL的纹理采样函数与GLSL的纹理采样函数参数顺序不同。Shader Model 2.0不支持动态分支,而Shader Model 3.0支持。固定功能管线根本没有“着色器代码”这个概念——它只有状态字。

斯威尼的团队花费了大量精力处理这些差异。UnrealShader编译器的早期实现包含大量条件编译逻辑:目标平台支持Shader Model 3.0时生成使用动态分支的代码,不支持时生成使用条件赋值模拟分支的代码;目标平台支持全精度浮点运算时使用高精度算法,只支持定点数时使用近似算法并在材质编辑器中标记可能的精度损失。这种策略的代价是抽象层的厚度。UnrealShader生成的着色器代码通常不如手写优化的汇编代码高效——编译器必须生成能在多种平台上正确运行的通用代码,无法针对特定GPU的寄存器文件和指令延迟做深度优化。在2004年,这种性能损失在高端GPU上可能达到百分之二十到三十。

但斯威尼的判断是,随着GPU性能的指数增长,抽象层的性能损失将被硬件进步所吸收,而跨平台兼容性带来的市场覆盖优势将远超过性能损失。这个判断在商业上被证明是正确的。《虚幻引擎2》和《虚幻引擎2.5》授权给了数十款游戏,覆盖了PC、Xbox和PlayStation 2平台。授权厂商不需要为每一个平台编写不同的着色器代码——他们在UnrealEditor的材质编辑器中创建材质,引擎自动处理跨平台翻译。这种工具链的便利性成为虚幻引擎在商业竞争中的关键优势,而id Tech 4的纯粹可编程宣言尽管在技术上令人敬畏,却因为兼容性限制和陡峭的学习曲线而未能获得同样广泛的授权。

但在伦敦,第三条路径正在被开辟。这条路径不经过DirectX或OpenGL的可编程着色器规范,不经过高级着色语言的编译器,不经过任何抽象层。它直接面对硬件,用汇编微代码实现可编程性。

Criterion Software的RenderWare引擎在2000年代初期是全球最广泛授权的游戏引擎之一。它的客户包括Rockstar Games——使用RenderWare驱动了《侠盗猎车手III》、《罪恶都市》和《圣安地列斯》。RenderWare的成功部分源于它的跨平台能力:它同时支持PlayStation 2、Xbox、GameCube和PC,允许开发者用一套代码库覆盖所有主流平台。但PlayStation 2是一个特殊的平台。它的图形硬件不是PC风格的GPU,而是索尼自行设计的图形合成器——一套高度并行的固定功能光栅化引擎,配合两个可编程的向量处理单元。这两个向量单元被称为VU0和VU1,它们是PlayStation 2的核心计算资源:每个向量单元可以同时执行四条浮点运算指令,以微码程序的形式控制。索尼提供了底层的向量单元汇编语言,允许开发者直接为VU编写微码。

在PlayStation 2上实现类似可编程着色器的效果,意味着用向量单元的汇编微码模拟顶点着色器和像素着色器的功能。这不是一个官方支持的路径——索尼从未将VU微码称为“着色器”——但Criterion的工程师们发现,VU1向量单元与图形合成器之间的数据通路可以被重新编程。传统上,VU1负责顶点变换和光照计算,将结果传递给图形合成器进行光栅化。

但Criterion的工程师编写了一套微码,让VU1在完成顶点处理后,继续执行类似像素着色器的计算——对每一个即将进入光栅化的像素块进行额外的颜色修正、纹理混合和光照调整。这套微码的指令序列特征与PC上的像素着色器汇编代码惊人地相似:加载纹理样本、执行乘法累加、根据条件码选择输出颜色。但相似性掩盖了本质差异。PC上的像素着色器运行在GPU内部,与光栅化引擎紧密耦合,可以高效地访问纹理缓存。

PlayStation 2的VU1是通用向量处理器,它的微码必须手动管理数据在VU内存、图形合成器缓存和主内存之间的传输——每一次纹理采样都涉及显式的DMA传输指令,每一次颜色混合都必须考虑VU的寄存器限制和指令延迟。这是一项极度艰苦的工作。RenderWare的PS2渲染路径包含数千行手写向量单元汇编代码,每一行都经过精心优化以适配VU1的四路并行执行单元。Criterion的工程师在内部文档中记录了寄存器分配策略、指令调度优化和缓存管理技巧——这些文档读起来更像是DSP固件开发手册,而不是游戏引擎技术文档。

但RenderWare在PS2上的汇编微代码路径证明了一个关键事实:可编程性不是PC独有的。在主机领域,可编程性的实现路径比PC更加直接——也更接近硬件。没有DirectX或OpenGL的抽象层,没有高级着色语言编译器,只有汇编微码和硬件寄存器。

主机开发者面对可编程性裂缝的方式不是选择拥抱还是恐惧,而是别无选择——PS2的向量单元天生就是可编程的,你必须为它编写程序,否则就无法发挥硬件的全部能力。RenderWare的PS2汇编路径在商业上取得了巨大成功。《侠盗猎车手III》在PS2上的画面——动态光影、粒子效果、屏幕空间特效——在2001年看起来像是来自另一个时代。这些效果不是通过固定功能管线配置出来的,而是通过数千行手写向量单元微码计算出来的。Rockstar的开发者利用RenderWare提供的微码框架,编写了自定义的视觉效果代码,实现了在PC固定功能管线上不可能实现的效果。

但当RenderWare试图将这些汇编级优化移植到PC平台时,它遇到了与id Software和Epic Games相反方向的问题。PC上的可编程性通过DirectX和OpenGL的抽象层实现——这些抽象层隐藏了硬件的具体细节,提供了统一的编程接口。

RenderWare的汇编级优化无法直接映射到这些抽象层上。Criterion的工程师必须重新编写PC平台的着色器代码,使用HLSL或GLSL,而不是向量单元汇编。这意味着引擎必须维护两套完全不同的渲染后端——一套用于PS2的汇编微码路径,一套用于PC的高级着色语言路径——它们实现相同的视觉效果,但代码完全不同。

这种分裂暴露了可编程性裂缝的另一个维度:当硬件变得可编程时,不同平台的可编程性实现方式开始分叉。PC走向了高级着色语言和编译器工具链的路径——开发者编写HLSL或GLSL代码,驱动程序将其编译为GPU的原生指令。主机走向了直接硬件编程的路径——开发者编写汇编微码,直接控制向量单元或GPU的执行单元。这两种路径在逻辑上实现了相同的可编程性,但在工程上产生了不可调和的差异:为一条路径优化的代码无法移植到另一条路径。到2003年,引擎架构师们面对的是一个被可编程性撕裂的景观。

id Software选择了纯粹的可编程宣言——抛弃固定功能,强制要求ARB顶点程序和片段程序,用汇编代码实现统一的全像素光照模型。Epic Games选择了渐进的抽象策略——保留固定功能回退路径,构建UnrealShader元语言,将着色语言的差异隐藏在引擎层之下。Criterion Software在PS2上开辟了汇编微码的第三条道路——直接面对硬件,用手写向量单元代码实现可编程效果。三条路径,三种回应。但它们共享一个共同的底层事实:可编程性不是一项功能升级。它是一道裂缝。它撕开了固定功能管线时代那层舒适的抽象膜——那层将硬件实现细节封装在参数表后面的膜——迫使引擎架构师直面一个他们从未承担过的责任。从此以后,他们不是在配置硬件,而是在为硬件编写编译器。

这个责任的重量在2003年还没有完全显现。DirectX 9.0刚刚发布,引入了Shader Model 2.0和HLSL编译器。OpenGL阵营正在推进GLSL标准化。NVIDIA和ATI在竞相扩展指令集——更多的指令槽、更宽的寄存器、动态分支、循环。可编程性的裂缝还在扩大,而引擎架构师们刚刚开始理解,他们签署的是一份没有终止日期的合同。

《毁灭战士3》引擎的源代码中有一段注释写在光照着色器汇编代码的顶部。它只有一行:“// 这曾经是一个参数。”

它标记的不是一段代码的功能。它标记的是一个时代的终结。在固定功能管线时代,光照是一个参数——你设置光源类型,硬件完成其余工作。在可编程管线时代,光照是一段程序——你编写每一行计算,你为每一处精度损失负责,你决定每一个像素的最终颜色。这不是自由。这是从配置者到编译者的身份转换。而这个转换一旦完成,就再也无法逆转。