在上期的CBS探索与发现节目中,我们已经揭示了AMD和NVIDIA双方自统一构架出现以来所推出的每一代桌面硬件构架与API的关系。从中不难发现,单纯以硬件设计的角度出发,NVIDIA的每一代构架都更加符合微软在对应的API中所提出的要求。在适应的API环境下,NVIDIA的硬件执行效率应该更高才对。实际上在微软推出的每一代DirectX SDK的runtime测试中,NVIDIA确实在各种场合大幅领先竞争对手。
然而,当场合一旦转移到游戏领域,情况就变了。事实上除了个别情况之外,我们并没有看到NVIDIA在大多数游戏中取得DirectX SDK测试上的领先幅度,这究竟是为什么呢?遵循CBS之眼的原则,本期CBS探索与发现节目将继续为您展现其中的奥秘,带您领略现象背后的真实。
● 多边形,像素,纹理,流水线
要了解事情的真相,我们首先要带大家回顾一些最基本的东西,比如游戏画面和程序的构成。
作为最基本的要素,画面的构成其实很简单——覆盖在多边形表面的材质,以及构成材质颜色的像素。画面的形成过程要说复杂确实非常复杂,但如果以通俗的“人类语言”来解释其实也很简单——先用多边形搭好物体的“架子”,然后在上面蒙上一层作为“表皮”的纹理材质,最后用像素刷好颜色,mission accomplished。
没错,我们每天都能看到的经由无数人以千万甚至亿为单位记得美元编写出来的,运行在以亿甚至十亿为单位记得美元开发出来的硬件上的游戏过程,其实就这么简单。很多事情就是这样,过程表面上看起来复杂无比,但实际上本质却简单的要命。
这里的本质甚至还不止您想象的那样只是游戏图形生成的本质,这还是图形流水线工作过程的基本描述。那些滚滚美元流淌所汇合成的极为复杂的过程,本质上的工作顺序也是这样的。
OK,既然已经基本上了解了游戏渲染的基本过程,那就让我们深入到过程之中,一边进一步了解为什么NVIDIA比AMD的构架更贴近API需求,一边看看这些过程跟所谓的真实有什么联系吧。
顾杰所写过的其他相关文章:《谁是微软忠实随从 A/N统一构架历史回顾》
《曝你不知道的DX11 解析GPU通用计算妙用》
● 框架,脚手架,骨架,随便你怎么叫它 作为流水线的起点,多边形的生成是整个图形过程的起点。当顶点信息被送入流水线之后,硬件需要根据需求将顶点“连起来”变成多边形。完成这个工作的,就是我们所说的几何处理单元或者说几何引擎。生成顶点的工作一般都是由CPU来完成的,所以当固定数量的顶点被泵送到流水线之后,决定整个图形渲染效率的第一关就出现了,那就是几何引擎的效率。
相比于后面我们要面对的环节来说,几何引擎的意义描述以及其效率的衡量标准要简单很多。更多的多边形有助于模型细节的细腻表现,而在顶点数一定的前提下,谁能够在单位周期内将更多的顶点绘制成多边形,谁的效率就更高。虽然伴随着着色器的发展,几何引擎中出现了诸如Domain Shader、Geometry Shader甚至是Hull Shader之类的单元,但是本质上来讲几何引擎依旧是以输出多边形为最根本任务的。
GPU经过十余年的发展,其本身的处理能力有了飞跃式的进步。但作为整个GPU最前端的单元,几何引擎的发展却一直不如GPU的其他部分来的迅猛。究其原因其实非常简单——作为图形过程的起点,构成多边形的顶点是由CPU生成的。
相比于GPU爆炸式的规模增长,CPU的发展明显更加缓慢。受限于频率压力、单位流水线效率压力以及摩尔定律导致的规模压力,CPU在过去几年里不得不向并行化方向去发展。但是长久以来一致困扰数学界的任务的无损拆解和网格化又成了限制CPU向并行化方向发展的重要因素。来自材料学,逻辑电路设计甚至是纯数学领域的压力使得CPU的发展举步维艰。
“得益”于“上级单位”的缓慢,长期以来GPU的几何引擎部分压力都不是很大,甚至自支持DirectX 10的GPU开始,几何引擎的多边形输出能力相对于CPU能够输出的顶点来说竟然已经到了绝对过剩的地步。因此在DirectX 11出现之前,不论是AMD还是NVIDIA,大家的几何引擎都没有值得大书特书的能够产生对效率起决定性影响的设计差异。
● 靠不了CPU,那我们就靠自己
CPU发展的缓慢并不是一时半刻可以解决的,即便是微软这种大号商人也没有办法。不过商人毕竟是商人,变通的处世之道帮助微软化解了很多看似无法解决的矛盾,这次也不例外。既然无法从CPU获得更多有效的顶点来描绘物体的细腻表面细节,那我们就自己想办法吧。于是,商人再次发威,DirectX 11引入了重要的几何处理过程——Tessellation。
一个完整的Tessellation过程包含如下步骤:几何引擎自CPU得到初始顶点参数设置,然后将这些顶点连接成模型的初始框架。接下来几何引擎会开始读取跟随顶点参数被一起传送过来的程序中的Tessellation系数,其中包括细分等级以及修补参数。根据细分等级的不同,几何引擎中的Tessellator会计算新的顶点数量,然后再根据修补参数的要求,与几何引擎中的Domain Shader一起将新的顶点放置在正确的位置上。最后以这些顶点为基础,几何引擎会再次生成新的多边形。
虽然过程看上去很复杂,Tessellation过程的本质其实依旧非常的简单。CPU送来的顶点先被连成多边形,然后硬件再在这些多边形中设置新的作为顶点的操作点,通过将这些新的顶点与原有的顶点相连把多边形“打碎”,完毕。
本质简单有一个好处,我们可以很轻松的从本质中抽离出过程所要的需求是什么。作为Tessellation过程来说,实际需求其实只有两个:负责根据Tessellation系数计算到底需要多少新的顶点并安置他们的Tessellator单元,以及多边形输出能力。
● 司马光?不,我们叫它短板效应
从几何阶段这里开始,我们将引入并且不停面对一个横亘整个体系并且导致了最终结局的重要影响因素——短板效应。
有人说司马光砸缸之所以名留青史是因为司马光自小有正义感见义勇为,也有人说这表明司马光从小就很聪明果断,知道不能用舀水的方式慢慢的将水放光于是当机立断把缸砸了。我以为以上都不对,司马光砸缸这件事最威武的地方,在于它是人类历史上第一次儿童自发的对短板效应应用的描述。
试想一下,如果司马光拿起石头直扑缸口,一石头下去却没多少水流出来,结局会怎样么?司马光选择在水线以下把缸砸开,缸里的水位自然会降到破洞以下,这就是短板效应的真髓——体系中最低的位置永远都由最低的限制因素决定。
水尚且如此,显卡又会怎样呢?
让我们再试想一下,假定我造了一块显卡,里面有几百个Tessellator单元,每秒钟可以生成几百亿个新的顶点,然后几何引擎还维持每秒几百万的多边形生成能力,又或者把情况对调过来,几何引擎每秒钟可以输出几百亿个多边形,然后体系只有一个Tessellator单元来生成新顶点,结局会怎样?
答案很简单,等待。
传统结构的硬盘就是电脑中的最短板
这就是短板效应最无情的地方。在单进单出体系中,不论某个单元的速度有多快,体系的效率始终由速度最慢的那个单元决定。我们电脑中的传统硬盘就是很好的例子。
作为几何引擎来说,顶点和数据流进来,多边形流出去,这就是单进单出。要想让它快起来,要么解决最短的那块板子,要么改变单进单出的过程。从DirectX 11开始,几何引擎有了值得书写的东西。
● 并行度决定一切
我们前面提到过,无论AMD还是NVIDIA,在DirectX 11以前的几何引擎设计上都没有本质上能够导致性能极大差异的设计。双方基本上都维持着单几何引擎和单周期单引擎搏出单多边形的方案。这种情况一直持续到DirectX 11才发生改变。
DirectX 11引入的Tessellation过程会导致巨量的新生顶点的出现,要完成几何过程,将这些顶点生成出来以及将他们连接成多边形都是必不可少的步骤。这直接导致了几何引擎的效率界定从传统的由CPU顶点输出能力变成了由Tessellator单元以及多边形生成能力。我们前面提到过,单进单出体系会严重受制于最短板性能的制约,那么AMD和NVIDIA又是如何应对这个问题的呢。
由于RV870构架的几何部分改进过于简单,甚至可以说有些幼稚,所以在这里我们直接引入AMD最新的构架RV970进行说明和比较。
从图中不难发现,不论GF100还是RV970,双方都选择了双管齐下的解决方案——增加单位周期的多边形生成数,同时将几何处理过程并行化。这样做即从体系内部解决了短板效应的问题,又将整个体系单进单出的结构进行了改进。但在细部,双方的设计上还是有着很大的差别的。
GF100采用了将几何引擎完全融入到流水线中同时将流水线并行化的GPC结构设计,而RV970则采用了在RV870基础上继续维持几何引擎的独立,但几何引擎内部拆分成对等的两部分并实现并行化的结构设计。相对来说,GF100的GPC设计导致了几何引擎的并行度和内部结构搭配更加合理,几何融入和GPC并行的结构不仅使得GF100在整个流水线执行过程中更具并行度,让几何过程与流水线结合的更加紧密,而且最关键的优势在于使其具备了4套独立的完全可并行的几何引擎,这个结构在单周期可以同时输出4个完整的多边形。相比之下,RV970的双图形引擎只能输出2个完整多边形,同时几何过程与后续流水线过程的结合也远不如竞争对手来的紧密。
这就是真相,GF100与RV970在Tessellation效率上的差异其实并不是简单的仅由Tessellator个数所导致的。实际上以RV970的多边形生成能力来说,即便搭配了与竞争对手相同的Tessellator单元,其效率也不会提升到GF100的水平上,单周期能够输出地多边形数量差异成了几何引擎效率差异的决定性因素。
在整个图形过程的起点上,传统的结构并没有区别。当API发展到DirectX 11之后,双方的差异一目了然。
我要提醒各位,如果看到这里你就开始认定我是在为谁歌功颂德,那就错了。请记住我们的宗旨——我们在秉承CBS之眼的传统,为您揭露真相。同时还要请您记住一个前面提到的有助于理解真相的关键点——短板效应是横亘在整个体系之上的重要影响因素。
盖楼用砖,盖帐篷用布,画画用材质
● 盖楼用砖,盖帐篷用布,画画用材质
完成了多边形部分也就是几何引擎之后,我们顺着流水线过程来到了第二个步骤,材质。在经历了相对简单的操作之后,生成好的多边形顺着流水线来到了材质处理单元,也就是纹理单元TMU/TA。如果说几何单元是过程复杂本质简单,那么纹理单元可以称得上是过程和本质都简单了。因为根据多边形的形状不同,纹理单元只完成两个步骤——定址和操作。
所谓定址,就是为不同的多边形寻找合适的材质,而所谓操作则更加简单——粘贴。程序员会根据游戏的需要在编程阶段事先烘焙好所需要的材质块,将这些材质块以材质库的形式放置在程序中,当多边形操作完成之后,硬件的纹理单元会访问材质库,在其中寻找哪些材质是当前多边形所需要的,并将其从材质库中抓取出来贴到多边形上。
过程简单,本质简单,操作也简单,那是不是意味着硬件在这里也很简单呢?
● 融合流水线,NVIDIA的阵痛
从DX9.0C开始,为了提高纹理操作效率,TMU内部开始将完成定址工作的TA和完成纹理抓取操作的TF独立成两组阵列,双方各司其职,互不干扰。
我们认为自G80开始到GF100,NVIDIA的整个图形构架发展是一个事先规划好的一体化的完整构架路线,每代构架的发展都目标明确而且对后代构架产生影响,最重要的证据之一,就是G80以来NVIDIA所使用的TMU结构。
G80立项之初,NVIDIA在当下和未来的构架发展路线上选择了并行化的方向。要达到这个目的,图形处理器的各个部分必须充分融合在一起,只有融合完整的流水线过程,才能够在未来实现多流水线并行的设计。因此NVIDIA自G80构架起便开始了将纹理操作单元融入处理单元基础SM中的努力。
将TF自传统的TMU单元中完全剥离出来放置在SM中比较简单,只要将对应的专有的L1 Tex Cache和TF绑定好之后放进SM即可。而将TA融合进去的过程则出现了当时无法克服的困难。与只需要专门的少量独享的缓冲资源的拾取操作不同,纹理定址过程对缓冲空间极为敏感。在具备一致性的unifield shared也就是统一cache构架出现之前,TA单元暂时还只能跟L2 Tex Cache绑定并独立于SM之外。拆分之后的纹理处理单元显然不再是一个完整的可以被称作TMU的单元了,因此这种拆分之后的纹理处理部分被命名为了Texture Arroy。
这种不完全的融合过程虽然为未来的纹理单元和几何单元的彻底融合以及GPC并行设计打下了坚实的基础,但是其坏处也是显而易见的——由于功能单元的分离,TA和TF不再能够共享一套缓存系统,之间的操作也需要经过比过去更多的PASS来完成,再加上不同的运行频率导致的效率差异。这些都直接影响了G80及沿用设计的GT200在纹理操作能力上的表现。
相对于NVIDIA,AMD的构架发展虽然没有那么明确的指向性和前后关联性,更多的是对之前问题的修补和对即将到来的需求的简单迎合,但也正是由于这种见招拆招的发展模式,AMD自R600以来的构架在纯纹理性能方面的扩展一直非常方便。因为避开了被拆分的命运,TMU的整体效率也一直维持在了起码不低于前代的水准之上。尤其是RV770的纹理单元,不仅效率和规模都非常可观,而且还得到了来自RBE单元的进一步辅助。这使得RV770在面对材质的操作过程中轻松取得了比竞争对手GT200高得多的效率。
● 阵痛依旧没有过去
随着时间的推移,NVIDIA在GF100中正式完成了并行化设计。透过真正的多级cache体系的引入,数据和指令真正体会到了一致性和不强制要求延迟透明所带来的好处。正是得益于cache的引入,一致性的L2使得TA单元即便在SM内部也可以方便的实现各种定址和共享操作。GF100的TA单元终于可以和TF单元再次“团聚”了,在GF100中,我们再次看到了久违的单一完整的TMU设计。再次统一的纹理单元因为与流水线的高度融合以及构架并行度的成功而获得了比过去更高的执行效率,这也算是对过去为了收获而进行的艰苦耕耘的一种回报吧。
但是,如果你觉得好日子终于要来了,那就错了。
实际上我们之前曾多次提到过,为了迎合微软追求更高效shader的需求,NVIDIA在ALU集群部分的设计一直是不惜血本的。让ALU具备更加灵活的操作方式和更高的效率,充足的周边资源必不可少。与此同时,为了不被对手在绝对吞吐能力上甩开太远,NVIDIA还要不断地放大ALU的整体规模,这进一步加大了周边资源的需求负担。一颗半导体芯片在其使用的工艺阶段所能够承受的晶体管上限是一定的,超过上限将使芯片丧失可制造性以及成本的可控性。总晶体管上限既然无法打破,这里多了,那里自然就要少。
按照NVIDIA对DirectX、光栅化过程以及图形业界发展历程的理解,对shader的灵活应用才是未来发展的必由之路,其原因我们会在后面的像素阶段进行分析。基于这种判断,再加上自身其他发展方向如通用计算在HPC领域的需求,NVIDIA在shader和材质的天平中选择了前者。于是自G80开始,NVIDIA构架的纹理资源和运算资源的比例一直维持在一个很低的水准上,甚至发展到GF100的时候,其运算资源相对于之前的GT200放大了100%,但绝对纹理资源却还出现了缩减,这使得其比例还出现了巨大的下降。
反观AMD,其简单的直接扩展资源的发展方式被同时应用在了ALU和纹理单元部分。尽管采用了最为传统而且直接的设计,但方便的可扩展性使其在绝对数量上达到了一个空前的规模。与此同时,纹理资源相对运算资源的比例也一直维持在一个比较高的水平之上。
显而易见的,如果应用场合集中在传统的重材质领域的话,NVIDIA的传统构架会因为分离设计而导致效率下降,而采用了更加先进的结构设计的GF100会因为其绝对数量的不足依旧会导致效率提升被冲淡,从而无法取得高于大幅放大规模的竞争对手的表现。
我们要的是事实,这就是事实,谁都抹不去的事实。
走到这里,图形过程从步骤上来讲已经完成接近一半了。我们接下来面对的将是影响图形表现及环境真实度的最重要环节——像素。
● 原子构成物质,像素构成色彩
我们在日常生活中所看到的一切景物都具有自己独特的颜色,将这些颜色反映到计算机程序中并加以定义的最小单元就是像素。颜色不仅能够影响透视感的正确表达,更能将真实的物体信息传达给我们。
如果说多边形影响了物体的精细程度,那像素所影响的就是物体的真实感了。
举个例子,我们在日常生活中自己捏一块橡皮泥、削一块方木,或者折一个纸盒子,这些东西的表面细节不会非常丰富。即便透过多边形操作没有还原他们的全部几何细节,相对来说人们也可以接受。但由于自然界丰富的光线和颜色关系,低颜色细节的自然场景几乎是不存在的,如果表达他们的颜色出现了问题,我们一眼就能发现其中的错误并且产生很自然的不真实感。这就是像素处理的意义,像素处理的作用就在于维护图形颜色的自然和真实。
关于像素表达的方式我们已经在先前的文章《曝你不知道的DX11 解析GPU通用计算妙》中进行了讨论,实际上对像素的表达通常都是由预制烘焙纹理和shader操作共同实现的。使用预制纹理虽然操作简便,但受限于开发者本身的水平界限,这种方式几乎永远不可能表达全部正确的效果。而使用shader对颜色和光线关系进行实时的运算则可以在最大限度上满足高互动性和高自由度的游戏环境下对图形真实性表达的需求,同时大幅降低预制烘焙材质的数量要求。所以,NVIDIA选择了shader而不是纹理作为未来的方向,力求配合业界在最大限度上保证用户能够拥有正确而又真实的游戏体验。
这个选择几乎是致命的。除了最初的成功之外,它险些将NVIDIA的诸代构架都逼上绝路。这个选择又是勇敢的,因为联系后面的真相,我发现它代表了坚定的为用户提供最佳游戏体验的信念,以及阻止图形业界因为任何外来原因出现倒退的信念。
在DirectX 10之前,由于shader格式和结构的严格规范性以及限制性,对shader的处理效率主要依赖于吞吐能力和寄存器的数量。只要寄存器资源达到要求并且不发生溢出,决定体系shader性能的就只有ALU的频率。而当API发展DirectX 10之后,微软通过开放底层ALU和逐步解放shader结构的方式开始了shader效率的提升之路 。在新API的环境下,想要更加灵活和高效的执行shader,硬件必须具备良好的执行能力和优秀的缓冲能力,影响硬件执行性能的因素也变得更多了。
比如说,缓冲存储体系。
● 交通决定物流,缓冲决定效率
构建一个高效的物流网络,其中的分拣站以及路径的设计是极其重要的。合理的分拣站和路径设计可以帮助物流过程最大限度的避开著名的“邮差问题”的困扰,以便将货物有计划的高效送达目的地。
GPU本身实际上就可以被看作是一个物流网络,其中的ALU就是最终用户,指令就是需要被送达的货物,而缓冲存储体系则可以被看做是分拣站以及物流路径。当ALU空闲的时候,指令能够快速的被送达并得以执行,当ALU需要缓冲的时候,路径能够允许ALU方便的调用最近的缓冲资源,当指令执行完毕之后,新的结果成为新的货物被送达其他需要的单元。在寄存器资源充足的前提下,现代ALU的执行效率其实基本上是相当的,所以在不发生寄存器溢出以及发射端资源严重不足的情况下,影响灵活shader指令的执行效率的最重要因素就成了物流,也就是缓冲存储体系。
在G80/GT200时代,NVIDIA构架开始广泛使用shared作为缓冲存储体系,shared虽然不可操作,但本身可以被用来临时释放结果并且允许单个SP单元直接访问。而AMD方的缓冲存储体系设计则要糟糕得多,R600/RV670仅具备最基本的GDS单元,而且只能以VLIW或者说5个ALU团簇为单位进行访问,不论操作方式还是可用资源总量都远低于竞争对手。当构架发展到RV770之后情况有所改善,存储体系中加入了对LDS的支持,但整体操作方式以及可调用存储资源的路径方向性单一的问题依旧没有得到解决,构架访问缓存体系和主存永远只有一条单一方向的不可回头的路径可供选择。
这两张图片精确的反映出了AMD和NVIDIA在各自构架发展过程中缓冲存储体系的变迁。当双方进入到GF100/RV870这个阶段之后,对比差异变得更加明显。GF100除了维持了前面构架的shared体系以及可回溯的路径之外,还引入了具备一致性的可共享多级cache体系,这部分cache面向全部ALU甚至是Tex cache开放。GF100中的ALU可以和过去一样访问shared,可以访问L1 cache,可以透过L1 cache进一步访问L2 cache,甚至还可以透过cache和统一定址直接访问主存,cache也可以在寄存器溢出缓冲、shared、cache甚至是Tex cache之间随意变换。异常灵活的操作路径和充足的空间为GF100的ALU提供了充裕的缓冲资源,这对于实现灵活的图形shader以及通用计算代码都至关重要。反观RV870则只是在多次修补之后总算达到了对手在GT200阶段所能够达成的缓存环境和路径操作方式,ALU所面对的唯一可用临时缓冲 空间依旧是老旧的LDS和GDS,虽然无论LDS和GDS的空间都获得了一定的提升,但这种提升更多的是为了对应暴涨的ALU总规模以及应付微软在DirectX 11中提出的硬性规定而已。
这就是所谓代差的由来。我们说过,秉承CBS之眼的原则,我们只会向您展现真相。
至此,关于图形的处理部分已经基本完成了,剩下的就只有在ROP单元完成像素和纹理的混合以及光栅化的其他步骤比如采样然后输出图像了。ROP单元的设计上双方一直处在此消彼长的状态,比如某方的某代输出效率或者采样手法领先了对手,下一代中对手必定会予以弥补甚至是超越。由于不像其他单元尤其是纹理和像素部分那样具备问题的延伸性和持续性,与本文所要表达的事实又没有很直接的联系,所以我们决定不再专门针对ROP单元进行介绍。
● 流水线游历总结
流水线的过程既然已经结束了,那就让我们来进行一下简单的总结吧。抛开是否符合API要求之类的话题,单纯从构架的绝对能力角度出发来看,多边形生成能力方面和灵活shader领域NVIDIA构架更为占优,而材质以及传统吞吐shader领域则是AMD构架较为拿手。
虽然图形过程纷繁复杂,归根结底还是一个单进单出的过程,处理自多边形生成开始,到完成混合输出为止。既然是单进单出过程,那么短板效应自然就成了影响图形表现的最终因素。应用短板效应我们不难发现,如果游戏代码倾向于采用符合微软意愿的方式,也就是更加灵活的指令模式以及更加倚重shader效果来处理特效的话,NVIDIA的构架将更加高效。而如果程序依靠传统的提倡吞吐的4D shader以及更多的与预制纹理搭配来实现特效的话,AMD构架的劣势不仅不会得到体现,反而可以通过比较传统的设计进一步取得效率方面的优势。
与此同时,如果某游戏的多边形、像素以及纹理要求全部吃重,甚至其压迫力大到足以拖垮任何一方的某个单元的时候,根据短板效应,不论被拖垮的程度如何,双方的最终差距都将被抹平——反正已经卡死了,卡死在多边形也好纹理单元也罢,其他单元再快都没用。
请记住这个并不难理解的结论,因为他对即将到来的事实无比重要。
好了,在经历了漫长的阅读之后,您辛苦了。在过去的篇幅中,我们已经回望了AMD和NVIDIA双方自统一构架出现以来历代桌面GPU的发展历程,对每一款构架的由来、产生环境以及构架重要特点都进行了回顾。这趟数万字的回顾和比较旅程并不轻松,但如果能走到这里,那我就要恭喜您了,您离最终真相已经越来越近了。这个真相不仅困扰了您,更困扰了绝大多数人太长的时间了。
为什么综合比对之下明明是NVIDIA的历代构架更加符合微软API的需求,实际游戏测试下却并没有见到构架设计差异级别的表现差异呢?
因为游戏编程的整体实际环境和现状,已经背离了微软以DirectX 所规划的路线。
沙子-沙漠效应
● 沙子-沙漠效应
为了接近真相,我们要在这里引入一个重要的概念——沙子-沙漠效用。所谓沙子-沙漠效应,其实是几个从量变到质变的过程的集合。
如果您手里有一粒沙子,而你想要一小堆沙子,那好办,只要再找几十几百粒沙子就可以凑成一小堆了。
如果您手头有了一小堆沙子,想要一捧沙子,那也好办,不过是几百万粒沙子而已,弄起来不会很难。
如果您有了一捧沙子,想要一大堆沙子,也不是不能办,几百上千亿粒沙子而已,找个铲子一铲子下去就能有几亿,不几下就搞定了。
如果您有了一大堆沙子,想要一片沙地,这可能就不好办了,沙子有多少是数不过来了,不过也不是不能办,多花点时间就可能做得到。
如果您有一片沙地,想要一个沙漠……
这就是沙子-沙漠效应。他的描述性解释应该是——在最小单位不变的前提下,体系在简单的基础上向复杂方向的每次质变都会比前一次质变更加困难,更加接近不可完成。
这个概念我们将会在今后的文章中遇到两次,作为其中的第一次,就让我们把这个效应应用到shader过程中,去看看究竟发生了什么吧。
最开始,透过几十段shader代码就可以实现单纯的纹理操作无法做到的效果,它可能只是改变了几百个关键像素而已,您的眼前一亮。
接下来,这种效果看多了,您习惯了,于是希望看到更加真实的效果,这种效果的提升导致代码从几十段变成了几千段,而且其中开始出现了各种复杂的比如跳转和条件分支之类的操作,它开始面向所有场景中的像素了,您的眼前又是一亮。
然后,新的效果您又看厌倦了,为了迎合您的需求,代码从几千段变成了几十上百万段,它不仅面向场景中所有的像素,而且其中开始出现了复杂的光线和颜色交互关系的变化,您的眼前勉强的又亮了一下。
现在,很明显新的效果您再一次厌倦了,于是代码开始变成上千万段,它在原有的基础上甚至将直接执行的数学代码都引入进来以便为体系计算更加真实的光线关系提供运算能力,所要处理的像素也经常要根据实际需要再次甚至多次的重新回到流水线中一遍一遍的处理正确的颜色表现。
每一代微软发布的新API所能够产生的特效都是美好的,越来越趋近真实的,但这种真实是建立在日益累积的复杂度之上的,这些特效的基础,也就是每一段代码都是程序员一个字母一个符号的敲打而成的。如果在这么发展下去,当您对现在的特效再一次厌倦了的时候,程序员们该如何去面对可能会达到几十亿段代码级别的编程需求呢。
● 商人为沙子准备了推土机
实际上微软之所以将shader效率提升到了一个极其重要的高度,跟他本身意识到了沙子-沙漠效应有直接关系。API再怎么优秀,最终的使用者都是人。微软如果想要让自己的API有更多的人使用,除了更高效更绚丽之外,逐渐降低的编程难度也是必要条件。否则光有优秀的特效,却动辄需要几万行代码才能实现,这么发展下去早晚有一天全天下的程序员都会被累死。
提倡高效的shader,推广形势更加灵活的代码形式,这从本质上就是在降低编程的难度,因为灵活本身就意味着自由和无拘无束,而自由和无拘无束的编程环境显然有利于程序员甩掉繁文冗节的累赘。同样的使用1D指令就能够达成的效果,在DirectX 10环境下的代码绝对比需要找齐规整4D格式的DirectX 9环境要简单很多。而一样是调用通用计算能力去加速图形处理过程,DirectX 11环境下干脆取消了代码的几何关联性,这使得编程难度和复杂度大幅下降到了足以让绝大多数人都能够接受的程度。
沙子-沙漠效应确实很难办,但微软有能力降低编程者的负担,减缓质变过程的难度提升。虽然微软是商人不是慈善家,但是从使用者就是最好的推广者这个角度出发,在商品中充分考虑使用者的感受应该是没有坏处的,使用者用着高兴,游戏用户看着喜欢,两边都有助于自己API的推广,剩下的事情就是非常简单的收钱和数钱了,这就是微软的如意算盘。
然而现实却往往是残酷的。在进入到DirectX 11时代的今天,我们见到了一个无比奇怪的现象——大量知名的游戏大作在号称支持DirectX 11的同时依旧提供陈旧的DirectX 9版本,而两个版本实际的画面效果和执行效率也未见什么本质的不同,这些大作背后所使用的引擎更是几乎全部由DX9版本添加一部分DirectX 11特性之后简单升级而来。对DirectX 11特性及特效的支持,仅能用浅尝即止来形容。引擎整体效率方面也没有见到具体的提升。更加悲情的DirectX 10甚至给人一种昙花一现的感觉,不仅跟现在的DirectX 11一样得不到支持,几乎所有号称支持DirectX 10的游戏均同时提供了DirectX 9版本,在其全盛期间都未见几部完全支持的大作,在DirectX 11出现之后更是第一时间消失得无影无踪,就好像从来没出现过一样。
现实世界的发展已经很明显的将微软原先的打算踩于脚下。
● 成也Unified Shader,败也Unified Shader
其实从现象上来看,目前这种有API也没人用的局面完全是自己挖坑自己埋这个词的完美注脚。
作为统一构架的开端,DirectX 10为了解决不同shader单元利用率不均的问题而创造了Unified Shader单元,这个单元可以无差别的处理传统的Vertex Shader和Pixel Shader指令。微软的初衷是好的,这种解决方案不仅能够保证Shader指令执行率的最大化,更可以最大限度的保障对旧有指令格式的兼容性。也就是说,即便是一个编程习惯和思路依旧停留在DirectX 9时代的程序员,他所写出的仅符合DirectX 9要求的代码,在DirectX 10硬件中依旧可以被完全无问题的执行。
这也就意味着,开发商并不需要为新的API开发什么新的具备更高自由度啊、使用灵活shader啊、玩命透过像素来表达正确效果啊之类的引擎,程序员也不需要去学习新的编程方式,改变自己的编程习惯。他们只需要为自己的引擎添加最基本的新版本API的特性,扩展好shader以外的诸多更新比如更好的纹理压缩方式等,然后就可以一切照旧继续写游戏了。反正我精力数有限,除了结构上要求严格点复杂点以外,光栅化发展到今天这个地步,指令数一定的前提下1D指令写出来的东西在最终效果上跟4D也没有什么特别本质区别,可能4D指令因为执行效率更低指令更加复杂从而导致了同样条件下效果会稍稍差些,但也不是简陋到完全不能忍受的地步,配合我们无敌的美工精心烘焙的各种精美的纹理,创造沙漠可能困难些,弄个大点的沙地出来您完全可以凑活凑活嘛。
于是,我们面前的游戏继续是4D指令满天飞,依旧是颇具“油画风格”的光照系材质,仍然是各种从03年04年修补到现在依旧坚挺的陈旧引擎。于是,我们看到了虽然更好用但却得不到应用的API,还有符合API设计却得不到施展,只能乏力的继续吞吐旧指令的构架。于是,我们看到了今天这个乱局。
即便升级到支持DirectX 11,UE3依旧无法摆脱过重材质的“油画感”
现在屏幕前的您明白为什么在游戏测试中,我们并没有看到构架设计上所导致的差异了吧。
当然,这仅仅是现象,现象距离本质还有一段不小的距离呢。
● 乱局,以及真正的凶手
自2006年下半年起至今,4年多的时间过去了,不论是否是大作是否卖得出去全部计入,甚至算上透过补丁形式仅在存在感方面支持了新版本API的那些,支持DirectX 10/11的游戏总数,截止到本文发布的今天——2011年3月8日,仅为56个。
寒霜2,CE3,UE3.X,X-ray1.6,这些宣称已经支持到了DirectX11的世界顶级游戏引擎,竟然全部都是从DirectX 9版本的基础上升级而来的。而目前世界上真正意义上的原生DirectX 11引擎的数量,是0。
看到这些数字,我甚至感觉到了一丝恐惧,因为同样的时间里,支持DirectX 9的游戏数量,比这个要多十多倍甚至几十倍。
过少的游戏支持,不完全的使用方式,陈旧低效的引擎,你能玩到的绝大多数游戏都不会使用到图形构架和API进化所带来的好处。这就是今天的现状。
你不是说新版本的API又好看又好用,既高效又进步么?这完全没人搭理的状况又是怎么回事?
问题就出在这里。
在下一期CBS探索与发现节目中,我们将会继续带您接近最后的真实,看看到底是什么拖住了整个图形业界向前进步的脚步。同时分析事情发展到今天所产生的一切,对图形界未来的发展会有怎样的影响。
联系客服