五光十色的新世界
前言:在之前的GPU大百科全书中,我们按照硬件流水线的顺序完成了几何部分以及光栅化部分的介绍,尽管上一期的大百科话题有些沉重,但整体上来讲,我们已经远离了光栅化这个凝固生命的过程。接下来,我们将跟随光栅化之后崭新诞生的图元生命,开始一段有关像素的异彩纷呈的新旅程。
与几何部分和光栅化部分若干年都没什么变化的情况不同,GPU内部发展最快的部分就是像素处理部分。如果你是一个像素,你一定会为几年来自己周围环境的巨变感到兴奋,伴随着像素处理部分的演进,像素不仅被处理得越来越快,而且颜色也日趋准确和符合效果的需求。究竟这几年间像素处理部分发生了怎样的变化,像素的处理过程又有着哪些特点呢?接下来就让我们翻开GPU大百科全书新的章节来一探究竟吧。
● 五光十色的新世界
颜色和明暗,是让我们这个世界变得美丽的一大原因,无论晨曦中朝露的璀璨,雨后彩虹的柔美,还是钢筋水泥森林的质感,无不来自于颜色及明暗所传达的强烈信息。想要在计算机世界中传递这些信息,我们需要一个最小的信息单位来最为载体,这个最小的信息单位,就是像素。
我们面前的屏幕能够反映的最小的颜色点就是像素,大量像素按照正确的规律排列所形成的集合就成了图像。由此可见,如要想要让我们看到美丽真实,或者说正确的图像,像素的颜色必须也是正确和自然的。
反应正确的颜色?这不是很容易么?每种颜色都有一个RGB值,只要让每一个像素都具有对应位置上正确的RGB值,颜色不就没问题了?对于固定的反映式输出,比如说照片来说确实如此,人们只需要通过感光元件采集自然颜色的RGB数值,然后将它们直接转化成像素就行了。但对于电脑,或者说3D虚拟世界来说,这显然是不可能的——我们所做的事情,是在没有对象的基础上凭空创造出一个数字的世界,这世界的每一点细节,每一滴颜色,全部都要由我们来决定,而我们所有的,只有脑海中无尽的想象,以及由现实中抽离出来的描述这些想象的数学关系。
用数学关系创造出来的世界,是什么样子的呢?
顾杰所写过的技术分析类文章索引(持续更新) | |
2、 忠实微软是否有用 A/N统一构架细节分析 | |
3、揪出幕后罪魁祸首 是谁拖累了统一架构 | |
5、融聚的未来在哪里 APU构架方向发展分析 | |
7、NV也玩融合 探GTX700核心CPU+GPU构架 | |
11、iPad2也成无尽陷阱?移动GPU构架全揭秘 | |
13、GPU大百科全书第一章:美女 方程与几何 | 14、重归正途迎未来 AMD新GPU构架深度解析 |
15、GPU大百科全书第二章 凝固生命的光栅化 |
|
像素的精彩
● 像素的精彩
对于大多数人来说,第一次明白“3D游戏”这个概念,大概是从DOOM开始的,而第一次明白什么是贴近真实的视觉效果,则是在Pixel Shader出现以后。下面这些,就是伴随我们成长的那些像素们。
3Dmark2001的nature场景
3Dmark的测试场景精确的反映了这些年像素特效进化的过程。我们创造出来的像素世界,已经从最初的“美轮美奂”向着“栩栩如生”的方向坚定地前进了。
这日趋真实,甚至已经可以以假乱真的图像显然不是GPU出现第一天就被呈现出来的。那GPU究竟是怎样具备的对象素的自由处理能力?这个过程又经历了怎样的曲折过程呢?
凡事总有第一次嘛
● 凡事总有第一次嘛
在可编程shader出现之前,人们对像素的操作实际上仅仅能够称之为染色。当时的我们可不像现在这么幸福,那时候对特效的处理只能通过固定的单元来直接实现,每一代API下所能够实现的特定的特效,都需要通过预先将其固化成固定指令的形式出现在硬件中,而对于像素的处理,也仅能局限于固化指令所能够允许的范围内,一旦像素进入管线,程序员就失去了对他的控制。因此,可编程shader尤其是Pixel Shader的出现,在当时是一件轰动的大事,Pixel Shader的出现,标志着程序员在像素级层面上第一次具备了可以精确而且随心所欲的控制自己想要实现特效的能力。
DirectX 7的不可编程像素流水线处理的画面
其实说起来,想要在像素层面上精确控制并不是一件非常复杂的事情。颜色的表现来自构成它的三原色的混合度以及透明度,而三原色以及透明度在计算机的世界里都是可以通过数字来精确度量和表达的。所以对于像素来说,只要能够随心所欲的处理构成它颜色以及透明度的RGBA这四组数字,就可以精确控制一个像素的颜色表现。这件事听上去似乎很简单,做起来就是另一回事了,尽管只要将固定管线替换成可以任意处理方程的可编程运算单元就能实现上述目的,但毕竟大家在固化单元里生活里很多年了,想迈出突破性的第一步是很艰难的。
这世界上其实并不存在什么无法克服的困难,关键在于人们有没有动力去克服它。能够控制像素这件事,不仅对程序员来说诱惑极大,对最终用户也有着非常重要的意义,更好的图形表现是行业发展下去的根本动力,而行业的蓬勃发展就意味着滚滚钞票的到来,所以大商人微软再次体现了它的无处不在,2001年微软发布了全新的图形API——DirectX 8,正式将可编程shader program引入到了桌面图形界,并宣称透过shader,人们可以实现电影工业中CG一般的真实特效。微软这一推动的作用是极其明显的,就算固化单元里的生活再怎么安逸宁静,大商人放话要求可编程单元来执行新的指令,不出来帮衬一下显然太不给面子了,于是,第一代Pixel shader单元也就应运而生了。
第一代完全符合DirectX 8要求的图形构架均采用可编程单元来替代固定指令渲染管线,算术单元的引进成了这些构架共同的特征,它们都通过支持INT16及FX12数据格式的combine单元来执行对像素RGBA数据的运算处理。使用可编程的具备直接执行能力的算术运算单元来代替固定指令处理像素,这让程序员得以实现许多过去根本无法想象的特殊视觉效果,通过直接使用数学关系来对应图形,真实的表面光照效果、界面半反射、散射以及折射等等视觉效果让人们彻底摆脱了单纯alpha贴图的乏味。可编程shader program,尤其是Pixel Shader的出现,标志着人类正式进入了游戏应用向电影特效进军的时代。
跑!阿甘!快跑!
● run Forrest!run!
Pixel Shader的出现是一件让所有人都欢欣鼓舞的事情,程序员们非常高兴,他们就像摘掉了有色眼镜的画家一样,终于拥有了可以正确控制所有像素颜色的可能,一般用户也非常高兴,他们如愿以偿的看到了光,看到了波光淋漓的水面,看到了各种各种各样以前不可能见到的新奇的特效。有了令人“叹为观止”的特效,消费者就会愿意为新游戏买单,于是整个业界也变一片欣欣向荣了。
当你第一次看到3Dmark01的nature场景时,你是否为能够生活在这个技术发达的美好世界而感到高兴呢?是的,能够在计算机的世界里看到如此美妙且连贯流畅的画面,而且还有人说今后一切特效都可以得到实现,自己掏的cash没有白费,这是一件多么美好的事情啊。包括笔者自己在内,大多数人在那个时代都有着这种幸福甚至是满足的感觉。
当然,不是所有人看到新的shader效果之后都会那么的幸福和满足,比如刚刚还很高兴的程序员就开始犯愁了——奇怪,这图像怎么跟自己想象的不太一样啊?
第一代Pixel Shader确实为程序员打开了控制所有像素的大门,但它本身还存在诸多问题,其中最大的问题便来自精度。第一代shader所采用的combine仅能处理整型数据,对于浮点数据则无能为力,这极大地限制了数据处理的精度,进而影响到了数据背后的像素颜色的表现。数据的精度决定颜色的精度,当特效对像素的要求达到一定高度之后,对颜色的正确性也会变得更加敏感,整型数据的运算精度显然不能满足最终效果的要求,连数字都没算对,程序员当然看不到自己想象中的结果了。
由于精度不足,shader modle1.0的水面永远都是“波澜不惊”的
Pixel Shader1.0确实为人们带来了比过去更高的对像素控制的自由度,但它距离真正的自由还有极大的距离,与其说它可以实现一切特效,不如说它只能实现“错误的特效”。这种现状显然不可能令一直都处在高速发展状态下的图形界满足,当时图形界刚刚看到了自由的曙光,就好像甩掉助行器的阿甘,才品尝到奔跑带来的快感,显然不可能就此停下。所以大家一起通力合作,大商人制定路线计划和规则,众厂商奋力攻关,终于将Pixel Shader2.0的硬件带到了人们的面前。
提高像素处理精度之后的效果
Pixel Shader2.0相对于前代最大的不同来自精度,在Pixel Shader2.0中微软第一次引入了FP24/32浮点数据作为颜色处理的基本精度,而执行这些数据的硬件也从combine转变成了功能更加强大的mini ALU。更加精确的浮点型数据让RGBA数值具备了充足的准确性,这对于最终效果的准确表达起到了决定性的作用。另外,Pixel Shader2.0还提供了更大的指令数,让程序有了比第一代Pixel Shader更好的执行效率。在Pixel Shader2.0的帮助下,程序员就好像治好了近视一样,第一次达到了正确的控制像素以及控制正确的像素的程度,3D图形终于完成了从“美妙”到“栩栩如生”的转变。
打开真实世界的大门
● 打开真实世界的大门
Pixel Shader1.0让图形界从固定的条条框框中跳了出来,但它存在精度不足的问题。Pixel Shader2.0弥补了精度方面的缺失,那它就完美了么?显然不。尽管Pixel Shader2.0能够提供足够让画面达到人眼分辨上限的精度,但它却存在不比精度不足好到哪里去的问题——它太过笨拙了。
3D图形不同于静态画面,连续动态画面的流畅度和画面的准确表现是同等重要的,如果硬件仅仅能输出准确的效果,却无法在输出速度上予以保证,用户获得的最终体现效果一样会大打折扣。随着Pixel Shader的逐步发展,其最大指令长度慢慢的从出现之初的16条发展到了2.0b的512条,这使得指令的执行效率成了摆在人们面前的现实问题。非常遗憾的是,Pixel Shader发展到2.0b为止都没有引入真正有效的提升指令执行效率的手段,跳转、分支、流控制等今天看来很平常的东西在当时全都是不存在或仅仅存在于“支持”这种程度的,这让Pixel Shader的执行效率出现了极大的问题。
3Dmark03的Pixel Shader2.0测试场景
与此同时,由于最大指令数本身的限制,程序员对每个像素所能够进行的改变实际上仍然会受到限制。如果想要添加更多更真实的效果,程序员往往需要让一个像素多次反复进入Pixel Shader单元中进行处理,这让本来就已经存在的效率问题变得更加雪上加霜。
好不容易能够表达正确的结果了,效率却跟不上,程序员们都觉得很不舒服。程序员不舒服,大商人的收入就会受到影响。于是注意到问题所在的微软推出了号称史上最完美的DirectX版本——DirectX 9.0C。
在DirectX 9.0C中,MS显然想将之前版本的DirectX中所遗留的问题一次性彻底搞定,Shader Modle3.0的最大指令数提升至65535,增加了寄存器数量,引入了动态程序流控制,将分支和跳转能力彻底开放给了程序员,同时通过多目标渲染(MRT)和延迟渲染(Deferred Shading)等创造性的技术保证了光栅化过程中整个流水线的整体效率,可以说DirectX 9.0C几乎解决了ALU利用率和效率之外的一切问题,它成了历史上第一个真正能够“实现一切特效”的图形API。
得益于微软英明的指挥和领导,支持Shader Modle3.0的图形构架拥有了远比之前构架更多的寄存器资源、接近无限长度的指令执行能力以及灵活的控制操作方式,因此也就具备了远比之前构架更高的执行效率。程序员们获得了比先前所有版本都多得多的自由度,他们终于可以在支持Shader Modle3.0的硬件中近乎于不受约束的尽情堆砌指令,来实现越来越接近于现实的色彩和效果了。
从像素向计算的跨越
● 从像素向计算的跨越
如果要评价这世界上最悲情的职业,我觉得程序员一定会以高票数当选的。程序员永远都得不到他们想要的理想环境,当他们遇到某些问题并最终等到了这些问题的解决时,往往会发现这些问题的解决其实只不过是引出了新的问题而已,像素的世界就是如此。Shader Modle3.0确实解决了先前版本对于指令长度的限制以及执行效率方面的缺陷问题,但急剧放大的指令长度以及其本身的出发点却将另一个问题非常明显的凸现了出来,那就是执行单元的总体效率问题。
彩虹六号:维加斯的执行效率已经受到了Shader Modle3.0的制约
传统的Shader Modle中,Vertex Shader和Pixel Shader是完全分立的两组programs,他们拥有不同的寄存器要求,不同的指令格式以及不同的运算器要求。因此传统硬件只有使用专门的比例一定的Vertex Shader和Pixel Shader单元,将它们分开进行处理。这种举措本身从最开始就注定了很多不可调和的矛盾——固定单元比例的硬件凭什么就能达到程序员对Vertex Shader和Pixel Shader数量及分布的要求?Shader Modle3.0号称是破除一切限制的史上最自由的Shader Modle,结果到头来还是要程序员严格按照硬件构架的大致比例和节奏来分配自己的Vertex Shader programs和Pixel Shader programs,只要稍有出格,硬件马上会以直线下降的执行效率来回报你。
除此之外,这种固定比例还导致了非常严重的单元利用率低落的问题。假定一段shader programs中仅包含10%的Vertex Shader指令,剩下的90%都是Pixel Shader指令,那么当重载的Pixel shader单元全力动作的时候Vertex shader单元实际上是处在欠载状态的。这种情况反之亦然。一段实际的shader program是不可能完全做到50:50的指令平衡设计的,再加上指令的串行吞吐特性,程序员无论如何都不可能做到指令密度的平均化。因此我们不难发现,实际应用中根据传统的API设计出来的硬件经常会出现大面积的负载不平衡的现象。
正当程序员为自己一次又一次的看到自由的曙光,却又一次又一次的被打回到各种束缚中而黯然落泪时,一只手轻轻地递过来一张纸巾,程序员用纸巾擦拭着眼泪,却发现纸巾下面盖着一个金光灿灿的新API——DirectX 10。程序员惊讶的半张着嘴巴,正准备说些什么的时候,微软微笑着用它特有的浑厚嗓音说:“不用问了,我叫雷锋!”
使用Shader Modle4.0全新GI效果的《狂野西部》
DirectX 10以及其所带来的Shader Modle4.0可以说为图形界翻开了全新的篇章,它创造性的引入了Unified Shader的概念,将传统的Vertex Shader和Pixel Shader从软件和硬件层面上予以统一,shader programs内部不再需要严格按照格式来区分Vertex/Geometry/Pixel。软件变了,硬件也要跟着改,所以对于支持Shader Modle4.0的硬件来说,其执行shader programs的单元也从过去的分立式固定功能变成了更加强大完整且完全统一的通用ALU。因为ALU可以对全部shader进行无差别吞吐,整个硬件的执行效率第一次在理论上达到了100%。
Shader Modle3.0与Shader Modle4.0效果对比
Shader Modle4.0对像素来说有着极其重要的意义,它不仅通过统一ALU吞吐提升了硬件的执行效率,更让程序员们可以更加随心所欲的使用效率更高的指令。传统的DirectX 9硬件中的shader格式是非常固定的,Vertex Shader指令天生就是4D(X,Y,Z,A),而Pixel Shader指令因为硬件单元设计通常都是对应RGBA的3D+1D 结构的缘故,一般情况下也会写成4D。这导致了DirectX 9环境下的Pixel Shader指令无论属于何种应用,哪怕仅包含一条Z-buffer或者一条texture load,也要在指令结构上找齐成4D格式。这种格式的刻板要求极大的限制了程序员对shader尤其是Pixel Shader的发挥。在Shader Modle4.0环境下,统一且无限制的指令格式要求以及直接面向底层ALU的特点让程序员可以大胆的直接使用更加灵活的1D、2D指令以及各种算数函数,而不用担心任何来自硬件方面的限制,这让Shader Modle4.0的效率和灵活度提升到了一个新的高度。
与此同时,由于对最底层运算单元的直接开放,人们忽然发现原来GPU里竟然蕴藏着如此丰富的运算资源。常规GPU用来处理shader的大规模并行ALU,本身可以轻松的拥有数倍甚至数十倍于CPU的吞吐能力,通过Shader Modle4.0面向ALU的开放,现在程序员们可以通过编程手段将这些原本用于处理像素效果的运算单元拿出来进行数学计算。像素处理,终于开始了自己从图形向计算的跨越。
从计算到图形的回归
●
从计算到图形的回归Shader Modle4.0是Shader Modle历史上的一大飞跃,它不仅让像素处理变得更加有效率,而且还让过去单纯处理像素的单元腾出手来处理图形之外的计算工作,使得整个GPU的用途和应用范围变得更加宽广,从任何角度来说这个版本都应该是史上最完美的了。如此完美的版本,难道程序员们终于可以摆脱自己悲催的命运了么?
苦中作乐的程序员
雷锋同志的表现是极其稳定的,它为程序员们带来的DirectX 10以及Shader Modle4.0一如既往的从根本上解决了前一个版本所暴露出的一堆严重的问题,然后将另一堆同样严重的问题暴露给了程序员们,对程序员们来说,命运依旧是照旧轮回的,这次暴露给他们的东西包括了并行度和几何关联性问题。
在我们对像素进行处理时,每一个像素都会对应一个线程。随着指令的日趋灵活和复杂,以及每代DirectX所带来的效率提升等效化之后的更多视觉效果,分支、跳转以及常规算术函数等等的日益增多,都让线程队列的执行效率问题变得越发重要起来,这种情况在Shader Modle4.0取消了对指令格式的机械限制之后变得更加明显了。大量的线程聚集成CTA,进而成为一个kernel,若干kernel形成队列,这些kernel由于待处理像素的不同以及内部线程的指令灵活度不同而变得不再同样“丰满”,如果kernel队列以顺序的方式一次一个的送入到GPU中,势必无法做到让整个GPU的所有ALU都满负荷工作。换句话说,Shader Modle4.0对指令的解放,就如同Vertex Shader和Pixel Shader出现时一样,在为我们解决问题的同时非常传统的再次为我们带来了一个新的麻烦,因为很不幸的,Shader Modle4.0对kernel的吞吐,刚好就是依顺序方式一个一个来的……如何让线程能够尽可能的充盈所有的ALU,减少ALU的等待周期并提高他们的重复负载率,是近两年GPU构架改进最重要的目的和任务。
除了kernel之外,Shader Modle4.0还存在计算性能难以释放的问题。对,我没说错,你也没有看错,Shader Modle4.0虽然是第一个允许程序员直接使用ALU的运算能力做像素处理之外的事情的Shader Modle版本,但他也确实存在计算性能难以使用的问题。
想要使用Shader Modle4.0的通用计算能力,你可能还要去学学fortran
假如你是一个物理学家,想要用Shader Modle4.0带来的运算能力来为你解决某个刚体碰撞问题,硬件会告诉你没问题,你只需先学习图形相关的GLSL或者HLSL,了解图形处理过程以及整个图形流水线的特点,甚至还可能要跑去学点fortran什么的,然后将你所要运算的目标转化成图形线程,将之按照严格的几何关联性一一对应到图形过程,比如Vertex、Texture或者Z-buffer中去,最后再把弄好的你也不知道到底应该算是图形程序代码还是科学运算代码的东西打好包发给硬件。这样算是方便释放运算性能么?起码我不觉得是。
有矛盾就要被解决,有困难就要找雷锋。于是在程序员们(这次还包括了大量非图形界的程序员)凄楚的目光注视下,微软再次拿出了解决上一个版本问题的全新版本——DirectX 11以及Shader Modle5.0。在Shader Modle5.0中,微软引入了两个重要的概念:并行kernel及Compute Shader,前者通过引入kernel并行执行的形式来解决ALU利用率的问题,而后者则通过打破几何关联性的方式将ALU的运算能力真正释放了出来。
并行kernel的作用和意义非常直白,就如同字面意思一样,并行kernel通过把不同的kernel以并行队列的方式发送给GPU,达到提升其处理效率的目的。相对于并行kernel来说,Shader Modle5.0引入的Compute Shader要重要得多。取消了几何关联的Compute Shader是史上第一个完全开放的数学指令型shader,Compute Shader可以透过并行管理方便的实现数据共享,可以透过树结构和延迟操作快速执行任意过程,虽然丧失了几何关联所带来的各种自动功能让Compute Shader看上去与大多数图形过程绝缘了,但事实却恰恰相反。Compute Shader的出现,不仅没有进一步的将通用计算和图形计算割裂开,反倒直接打破了传统的界限和束缚,将图形和通用计算彻底联系在了一起。有了Compute Shader,显卡的通用计算能力不仅可以以最直接的数学形态被释放出来以帮助需要计算量的领域,而且还能很方便的直接被用于特效的处理,使其成为图形计算能力,ALU在经历了Shader Modle4.0短暂的运算能力分离之后,终于在Shader Modle5.0完成了回归和升华。
像素的沉重灵魂
也许你会问,像素不就是颜色么,处理像素就是刷个油漆而已,屏幕上几个需要刷刷漆的点,何德何能让包括微软在内的整个图形界围着它们折腾了10年之久而且还要继续折腾下去呢?
实际像素操作可不是刷刷漆那么简单
如果一个像素被摆放在静止的空间内,周围的环境完全没有任何的变化,这个像素自然也就不会有任何变化。对于这样的像素我们甚至不用处理,直接以烘焙材质+纹理贴图的形式就可以完成表现了。但是,现实中的像素点肯定不会是这样的,如果你想要表达真实自然的颜色效果,这些像素就必然的会与光和其他像素发生关系,并在发生关系之后表现出正确的符合物理规律的颜色。而与光以及其他像素发生关系,就势必会导致复杂的处理过程,于是,也就有了这段长达10年而且可能会永远没完没了的纠葛。
以光照为例,在实际的应用中,对光照的操作有很多种方式,传统的方式大多是将光照信息直接对应到Pixel Shader指令的执行过程,比如multi-pass render及multi-light single pass render等。在Pixel Shader2.0中,这些方式被用于处理不同场景的光源对物体的影响。如multi-pass render pipeline会为每个光源创建一个单独的过程用来执行,多用于室内环境处理,而multi-light single pass render能够在一个过程中同时处理3至4个光源,可被用于室外大范围表现的环境。
不论采用哪种方式,在处理过程中都会对光线与像素的数学关系,也就是光线对像素颜色的影响以及透明度的影响,最简单的点光照改变像素颜色的公式可以写成Color = Ambient + Shadow * Att * (N.L * DiffColor * DiffIntensity * LightColor + R.V^n * SpecColor * SpecIntensity * LightColor),这个最简单的公式中最少有四个分量,即N.L、LightColor、R.V^n以及Attenuation需要处理,对每一个分量的处理都会导致最少一个单独的指令,同时还要为这些处理过程搭配对应的buffer以便能够缓冲和临时存放中间结果,另外,由于颜色是三原色组成,因此LightColor还要被拆分成LightColor.r、LightColor.g、LightColor.b三个分量分开处理,最终再将它们合并在一起。
折腾完这么一大堆的方程、变量和指令之后,你终于完成了一个像素在一个点光源照射之下的颜色变化。如果是multi-pass render pipeline,这个像素也许就算是弄完了,要是赶上multi-light single pass render,后面还有长长的其他光源以及多光源符合效应等一大堆过程在等着呢。
一个像素尚且如此,我们的屏幕中存在着2304000个像素(1920X1200),即便假设其中只有1/4需要处理,那也有576000个像素,即便所有这些像素都只被一个点光源照耀着,而且全部没有其他的交互作用关系,我们也要把上面那些步骤整体重复576000次才算完,这其中的运算量到底有多大,诸位可以想象一下。而如果对效果的要求很高,比如说将漫散反射定义成新光源,那么每个像素所要处理的运算量都将因为光源的激增而急剧加大,即便加入光照探针之类的手段,这巨大的运算量依旧会给现有的常规硬件带来很大的压力。而实际的游戏应用中,我们面对的效果显然不止漫散反射光源这么点而已,大量的像素间的交互作用一样要被处理,这种种处理需求加在一起,就构成了像素沉重的灵魂。
现在,你应该明白为什么微软带着大家忙活了十来年,也没搞定刷漆这么个简单的活了吧。
像素到效果的桥梁
● 像素到效果的桥梁
尽管恩怨纠葛持续了很久,硬件的结构也越来越复杂,但像素本身其实还是非常单纯的。我们说过,对像素的处理,归根结底是对构成颜色的三原色的处理,而对三原色的RGB值以及透明度Alpha的量化处理,实际上是非常单纯的数学运算。这个单纯的计算过程,为什么会衍生出了如此纷繁复扰的处理过程的呢?像素到效果之间,究竟跨越了怎样的阶段呢?
Grid到Thread所对应的硬件
在前一页中我们已经知道,如果我想将材质中某个原本黄色的像素点揉到光线效果中并令其因此改变颜色,需要将像素原本颜色的数值以及它与光线和其他通过数学的手段处理成一个或一组方程。这就是像素向效果迈进的第一步,从像素变成方程。不同的像素改变对应了不同的方程或者方程组,要处理这些方程/方程组,就必须将它们转化成ALU能够接受的形式,因此方程/方程组就变成了指令。
接下来,硬件流水线如果要执行这些指令,就必须让其拥有符合流水线特征的身份,于是以1个像素为单位,这些指令被转换成了Thread(线程)。要实现某种特效,显然不太可能只改变一个像素,于是一堆像素的改变就变成了若干Thread的集团,为了方便硬件进行吞吐,若干Thread会合并成为CTA(线程块),它对应硬件能够执行的最小粒度,也就是NVIDIA的warp或者AMD的wavefront。若干CTA又可以组合成一个Block,这是方便程序员进行发放管理而设定的最小Thread单位。一个最小GPU执行单元比如SM可以面对若干个Block,当一个Block里的所有CTA都被执行完之后,SM就会寻找其他未被执行的Block,被划分在一起可以由同一个SM完成吞吐的Block,被称作Grid。
最后,很多特效都是基于全屏基础的,换句话说,屏幕内会出现大量需要更改的像素,这些像素更改所对应的全部过程就是一个kernel,这个kernel,就是由所有Grid组合成的集合。
你觉得有些头晕?没关系,我换个可以帮助理解的方式——因为我们要理解的是像素到画面之间的递进关系,所以我们只看像素即可。要实现特效,所要改变的最基本单位首先是像素,一个改变的最终结果对应一个像素任务(Thread);出于提高GPU的像素吞吐效率的考量,若干个像素会被组合成一个整体集团被ALU团簇执行(CTA);既然方便了硬件,程序员也要得到便利,所以我们又设定了一个程序员方便管理的最小单元,里面包含了若干被组合在一起的像素集团(Block);一个ALU团簇一次能够面对若干个这样的像素集团,他们也可以被称作一个更高级的集合体(Grid);最后,当屏幕内所有的这种最高级待改变像素集合体被执行完毕之后(kernel),特效就呈现在我们眼前了。
表面的把戏
我们知道,在GPU的图形流水线处理过程中,像素处理是排在光栅化过程后面的,当几何模型完成3D到2D的坐标变换之后,留在流水线中的就只剩下原几何模型中可以被摄像机镜头看到的那部分表面的投影了。因此,对这部分范围内像素效果的处理,也可以被理解成是对表面颜色的操作。
光栅化之后的三角形
我们在前面对shader modle的回顾中提到过,在DirectX 10以前,这部分运算操作由专门的含有combine或者mini ALU的Pixel Shader单元来完成,而DirectX 10之后,则可以简单的理解成由ALU直接完成。接下来,就让我们轻松一下,来看看对表面的处理流程究竟是怎样的吧。
当经过光栅化的模型投影,或者说图元出现在流水线之后,材质单元会根据程序的要求对图元进行区域划分定位,然后从材质库中寻找对应表面区域的材质,将其拾取出来贴到已经2D化的模型平面对应的区域内。与此同时,ALU则要根据程序的要求,对不同的像素区域中的纹理进行对应的操作,比如光照探针侦测、光线关系判断等等。需要注意的是这里的所谓光线关系并非仅仅是明暗之类光照度以及光照角度那么简单,这其中还包括半漫反散射以及折射之类光线传递关系的效果。在完成上述关系的判断之后,ALU会按照结果执行程序包含的描述这些关系的对应方程,并最终经过对方程的运算得到某个像素正确的颜色数值。最后,处理完的结果将被传送至ROP单元,它会将处理好的像素与已经存在的基本材质进行混合,然后就可以输出我们能够看到的最终效果了。
不难发现,对物体表面效果的处理并非shader/ALU一己之力完成,它实际上是与纹理单元共同完成的。尽管shader/ALU完成了其中最重要最复杂,同时也是最为考验运算能力的环节,最终的表现结果都是由ALU决定的,但整个过程无法离开纹理单元以及材质的参与,这是为什么呢?
像素离不开纹理
既然像素处理过程就是处理像素,那我们为什么还要现在物体表面蒙上一层预先烘焙好的材质作为基础呢?反正这些材质上大部分的颜色都不是正确的,到头来还是需要ALU对其进行运算并完成修改,那为何不直接让像素处理单元直接在正确的位置上生成正确的像素呢?这样既可以避免改错这么一个看上去似乎没有必要的步骤,又可以用原本进行材质操作的单元的晶体管来进一步强化ALU部分,让其拥有更强大的功能,何乐而不为呢。
shader与Texture是一对“好碰友”
答案很简单——因为现在的ALU根本没有那个本事面对直接生成像素所带来的运算量。
我们在本文中反复强调过,像素的处理过程从本质上来说并不复杂,其巨大的执行难度并不来自步骤的繁琐,而是来源于对大量像素进行数学关系运算所导致的运算量,这10年来针对shader反复的折腾其实也只是因为人们对执行单元能够更加高效的处理数学关系的渴求。在以前的文章中我们曾经面对过类似的问题,当方程的数量达到一定级别之后,对于运算单元的压迫将让任何本来看上去很和谐优雅的方程式变得丑陋无比。而随着程序员和用户对效果要求的不断提升,对于像素处理的方程总量在未来将呈现出明显的只增不减的态势。更加复杂真实的光照模型,更加多变且逼近现实的光线传递效果,甚至包括更多像素透明度遮蔽所带来的混合,这些都让目前的ALU单元承受着巨大的压力。在这种情况下,要让ALU去独立生成全新的像素,为ALU添加一个近乎于100%增幅、甚至比这个还要大得多的像素处理压力,显然是不现实的。
为了减轻这种压力,人们历经10年,不断地压榨着半导体工艺的极限,以期能够在GPU内部塞下更多的运算单元;不断地改进着运算单元集合的逻辑关系,以期能够让它们尽可能高效率甚至全功率的运作起来;不断地开发着如Compute Shader之类能够尽可能多的以灵活的运作手段和更加贴近纯数学的应用方式来解决问题的方法,以此来进一步提升ALU在处理过程中的效率,并进一步压榨ALU的价值。对于材质的操作,也是这些努力中的一部分。预先烘焙好的材质可以带来大量已经具备基本效果关系的像素,这些操作上相对廉价的像素中会有相当一部分不需要被处理,直接使用材质可以大大减轻ALU单元的负担,让已经不堪重负的ALU得到喘息。
所以,在我们可见的未来中,对于shader的处理依旧只能跟材质操作紧密的联系起来,Direct Pixel这种东西,相对来说还是一个遥远的梦想。
下一章是……番外篇?
根据惯例,每一章GPU大百科全书的结尾,我们都会对本章的内容进行小结,总览一下该章所介绍的单元,总结和评价它在图形渲染流水线中的作用和地位,并且承前启后的引出流水线上的下一个单元。但是今天,我们要打破这个惯例。因为关于Pixel Shader以及相关单元的故事,其实还远未结束呢。
像素处理单元是一个有故事的地方
对像素的处理,本质上就是对色彩相关的方程的处理,这种处理的背后又附庸着大规模的数学运算和大量的指令执行,所以像素对于硬件的逻辑结构设计,存在着极高的要求。不同的逻辑结构对于庞大数学运算和指令的吞吐以及处理能力,显然是存在差距的。每一家硬件厂商对于数学以及效率的理解都不相同,设计出来的硬件也存在着极大的差异。
另外,我们的活雷锋大商人微软同学非常顽皮,虽然他带领着大家不断的改进着像素的世界,但爱耍小性子的他有时候会跟这位小同学走得近一些,有时候又会和那位小朋友玩的好一点,这就导致了微软在新API规则制定时或多或少的倾向性,以及大家对每一代API意义及精髓理解方面的差异。
好了,有差异,就会有分歧,有分歧,就会有争执,当争执最终达到了商业竞争的你死我活以后,战争也就爆发了。
自shader出现至今,围绕着Pixel Shader以及相关单元设计方面的激烈竞争,可以说是人类IC史上最为灿烂的一场大碰撞,这10年间ATI/AMD与NVIDIA的此消彼长,不仅带来了甚至已经事实上打破摩尔定律的技术进步,更我们上演了一幕又一幕惊心动魄又异彩纷呈的战争大戏。宣传战、精度战、谍报战、工艺战、芯片策略战、产能战……相信经历过的人都会觉得,这10年间发生在Pixel Shader以及相关单元身上的种种奇遇,绝对可以写成一部相当跌宕起伏的小说或者剧本了。
在下一章的GPU大百科全书中,我们将继续关于像素的未讲完的故事。这个特别准备的番外篇最终会让你明白,究竟是怎样巨大的差异,能够让一个处理像素的单元的设计工作迸发出如此耀眼的火花,这火花又是多么的夺目璀璨。敬请期待吧。
- 相关阅读:
- ·AMD下一代高端GPU终极曝光:和三星共享
//vga.zol.com.cn/559/5591184.html - ·曝华为正在研发自主GPU和手机操作系统
//vga.zol.com.cn/558/5588228.html - ·三星野心勃勃研发GPU 然而技术尚不成熟
//vga.zol.com.cn/557/5577259.html - ·12核心 三星Exynos8890芯片GPU跑分曝光
//vga.zol.com.cn/557/5573716.html - ·有了CPU还不够,传苹果正秘密开发GPU
//vga.zol.com.cn/557/5573714.html