原文:《A trip through the Graphics Pipeline 2011》
在上一篇关于纹理采样器以后,咱们如今回到了3D前端。那执行完了顶点着色,如今就能够实际的渲染东西了,对吗?惋惜,还不行。你看,在咱们实际开始光栅化图元以前,仍然还有不少事要作。因此在本篇里咱们不会看到任何光栅化内容——还得等到下次讲。
图元组装
当咱们离开顶点管线时,咱们从shader单元里获得了一块着色过的顶点,这块顶点中包含了一些完整的图元——咱们不会让三角形,直线或片被分割到多个块里。这很重要,由于这意味着咱们能够真正的单独处理每个块,而且不须要缓冲多个shader输出块——虽然能够缓冲,但不必这么作。
下一步是组装单个图元的全部顶点。若是图元碰巧是一个点,就只须要读取确切的顶点并传递它。若是是直线,要读取两个顶点。若是是三角形,要三个顶点。并以此类推大量控制点的片。
简而言之,这里作的工做是收集顶点。咱们既能够经过读取原始的索引缓冲来收集顶点并存一份顶点索引的拷贝——缓存周围的位置映射,或者咱们也能够存储随同着色的顶点彻底展开的图元的索引。这将花费一些空间用于存储输出缓冲,可是在这里咱们就没必要再读取索引了。用哪一种方式均可以。
如今咱们已经展开了组成图元的全部顶点。换言之,咱们如今有完整的三角形了,而不只仅是一堆顶点。那咱们已经能够光栅化它们了吗?还不行。
视口剔除与裁剪
我猜咱们应该先执行这个,对不?这是管线中,你应该最感兴趣的部分。我不打算在这里解释三角形裁剪,你能够在任何一本计算机图形课本上查到,尽管一般都是一大堆内容看上去很可怕。若是你想详细了解,就用Jim Blinn的(本书13章),虽然你可能会想经过传[0,w]的裁剪空间来替代,若是没别的方法就别搞混了。
裁剪简而言之,即:在齐次裁剪空间里,从vertex shader中返回顶点的位置。选用裁剪空间是为了使方程描述视锥体尽量的简单;在D3D中,是

,

,

以及

;注意全部最终方程实际上排除了齐次点(0,0,0,0),这是一种退化状况。
咱们首先须要找出三角形是部分的,仍是彻底的在裁剪平面以外。这能够用 Cohen-Sutherland直线裁剪算法高效执行。为每一个顶点(例如,能够在顶点着色的时候计算,并随位置一块儿存储)计算输出码out-code(或裁剪码clip-code)。而后,对于每一个图元,裁剪码clip-code的按位与运算将会告诉全部的视锥平面全部图元顶点在错误的一侧(意味着图元彻底的在视锥以外,就能够被抛弃了),而且裁剪码clip-code的按位或运算将会告诉视锥平面须要再次裁剪图元。裁剪码只是硬件部分的很简单的东西。
另外,shader还能够生成一组“剔除距离(cull distances)”(若是全部顶点中任何一个剔除距离小于0,该三角形就会被丢弃),和一组“裁剪距离(clip distances)”(定义了额外的裁剪平面)。这些还用来参考图元的rejection/clip testing。
在实际的裁剪过程当中,能够采用两种形式:咱们既可使用多边形裁剪算法(会添加额外的顶点和三角形),也能够添加额外的裁剪边方程到光栅器里(若是听不懂不要紧,等到下个部分讲光栅化,就理解了)。后者方式更好,彻底不须要实际的多边形裁剪器,可是咱们得须要可以规范化32位浮点值来做为有效的顶点坐标;可能会有技巧构建快速的硬件光栅器来这样作,可是彷佛很困难。因此我认为有一个实际的裁剪器,包含了全部相关的东西(生成额外的三角形等)。这很麻烦,还很珍贵(比你想的要珍贵,我立刻就会讲到),因此它不是个大问题。不肯定是否特殊的硬件也是,或者执行实际的裁剪;专用的裁剪单元的大小和须要多少,取决于在这个阶段派发一个新的顶点着色负载是否合适。我不知道这些问题的答案,可是至少在性能方面,它不是很重要:实际上不会频繁地“真的”裁剪。由于咱们会用到保护带裁剪(guard-band clipping)。
保护带裁剪(guard-band clipping)
这个名字不是很恰当;这不是一个神奇的裁剪方法。事实上,偏偏相反:直截了当的不作裁剪:)
底层思路很是简单:在左,右,上和下裁剪面以外部分的大多数图元彻底不须要裁剪。靠GPU来光栅化三角形,实际上的作法是扫描全屏区域(更准确的说,是裁剪区域scissor rect)并询问每一个像素:“这个像素被当前三角形覆盖了吗?”(实际上这有点复杂,而且有更高效的方式,但这是常规思路)。而且这一样适用于三角形彻底在视口内的状况。只要咱们的三角形覆盖测试(coverage test)是可靠的,咱们就彻底不须要裁剪靠近左,右,上和下平面的部分。
这个测试一般都是用固定精度的整数运算。最后,一步步的获得一个三角形顶点,将会整数溢出而且获得错误的结果。我以为由光栅器生成像素而不是在三角形中生成,这点让人感受很不爽很是,这应该是不合理的。硬件其实是违反了规范的。
针对这个问题有两个解决办法:首先是确保绝对不会进行三角形测试。若是真正作到了这点,那么就不用裁剪四个平面了。这就是所谓的“无限保护带”,保护带其实是无限的。解决方案二是最后裁剪三角形,仅当他们在安全区域(光栅器计算不会溢出的区域)以外时。例如,光栅器有足够的内部位来处理整数三角形坐标:

,

(注意我这里都用大写的X和Y来表示屏幕空间的位置)。仍然用常规的视平面作视口裁剪测试,但实际上在投影和视口变换以后只是裁剪了指定的保护带裁剪平面,结果的坐标都在安全区域里。如图所示:
中间的小块蓝边白色矩形表示咱们的视口,而大块的橙色区域就是保护带(guard band)。图中的视口看起来貌似很小,但其实我还画大了呢,好让你能够看到全部东西!在保护带裁剪范围-32768~32768里,视口大约是5500个像素宽度,这里能够容纳下一些很大的三角形。这些三角形表示了某些重要状况。黄色三角形是最多见的——延伸到视口以外但没出保护带。这种能够经过测试,不必进一步处理。绿色三角形在保护带之内视口区域之外,因此它会被视口裁剪掉不会经过测试。蓝色三角形延伸到了保护带裁剪区域以外,须要被裁剪,但它彻底在视口区域以外,会被视口裁剪拒绝。最后的紫色三角形既延伸到了视口区域以内又延伸到了保护带以外,就须要被裁剪了。
如你所见,这几类三角形须要被四个侧面裁剪都是比较极端的状况。正如所说的,不要担忧,这都是很罕见的状况。
题外话:正确的裁剪
若是你熟悉算法,这块就不是很难。但其中细节老是很神奇。三角形裁剪器实际上不得不遵照一些潜规则。若是破坏了这些规则,共用一个边的邻接三角形就会产生裂缝。这是不容许的。
- 视锥内的顶点位置必须被裁剪器保存为比特率(bit-exact)。
- 裁剪一个平面中的边AB必须与裁剪边BA(方向相反)产生相同的结果(这能够保证数学上彻底对称,或者能够保证老是裁剪相同方向上的边)。
- 对多个平面裁剪的图元必须按相同的顺序对平面裁剪(或者一次对全部平面裁剪)。
- 若是用到了保护带,必须对保护带平面裁剪。若是真的须要剔除,就不能用保护带了,得对原始的视口平面裁剪。不这么作的话会产生裂缝。
讨厌的远近平面
好吧,虽然对于4侧平面有很好解决方案,可是对于近和远平面呢?尤为近平面是很麻烦的,由于全部。那咱们该怎么作呢?用z保护带吗?可是要怎么工做呢——咱们实际上并无按z轴来光栅化!事实上只是在三角形上作插值!
另外,这只是三角形的插值。实际上插值Z的话,z-near test(Z<0)是很简单的——只是 符号位。而z-far(Z>1)要额外比较(这里我使用Z,而不是z,表示屏幕坐标或投影以后的坐标)。可是咱们还要进行逐像素的Z比较(Z test),因此这不是很大的开销。视状况而定,但这样执行z裁剪是一个可选项。若是你想要支持像NVidias的“depth clamp”OpenGL扩展的话,就须要跳过z-near/z-far裁剪。实际上,这个扩展很好的暗示了他们是这样作的,至少用过一段时间。
对于w>0的裁剪。也能摆脱它吗?答案是固然,好比齐次坐标的光栅化算法(
http://www.cs.unc.edu/~olano/papers/2dh-tri/)。 我不肯定硬件是是否这样用的。这个方法不错,不过很难符合D3D11的光栅化规则。也可能用一些我不了解的技巧。以上就是裁剪相关内容。
投影和视口变换
投影只须要将x,y和z坐标除以w(除非你使用了齐次的光栅器,不然其实是并不投影,下面将忽略这种可能性),就获得了在-1到1之间的NDC
(规范化的设备坐标Normalized device coordinates)。而后用视口变换将投影的x和y映射到像素坐标(将称为X和Y)以及投影的z映射到[0,1](将称为Z),这样在z-near平面Z=0而且在z-far平面Z=1。
咱们还要对齐像素到子像素格上的小数坐标。从D3D11开始,硬件须要精确的8位三角形坐标的子像素精度。这个对齐会把一些很是窄的碎片(这些碎片会致使问题)变成退化三角形(不须要被渲染)。
背面和其它三角形剔除
当咱们拥有了全部顶点的X和Y,咱们就能够叉乘边向量来计算标记的三角形面积。若是面积是负值,三角形就是逆时针的(在这里负面积对应逆时针,由于咱们正处于像素坐标空间,在D3D的像素空间中y向下增长而不是向上增长,因此符号是相反的)。若是面积是正值,就是顺时针。若是是0,就是退化三角形,不覆盖任何像素,那么它就能够被安全的剔除了。咱们知道了三角形朝向就能够进行背面裁剪了(开启的状况下)。
咱们如今快准备好光栅化了。实际上咱们还得先设置好三角形。但这块还须要光栅化如何执行的知识,因此我会把放到下一篇再讲。
结束语
我跳过并简化了一部份内容,实际状况要更复杂:好比,我假设你只是使用常规的齐次裁剪算法。一般是这样——但你能够用一些vertex shader属性标记做为使用屏幕空间线性插值来替代透视矫正插值。目前,常规齐次裁剪都是透视矫正插值;在使用屏幕空间线性属性的时,你实际上须要执行一些额外的工做来不进行透视矫正:)
有不少光栅化算法(好比我提过的Olanos 2DH方法)可让你跳过几乎全部的裁剪,但如前所述,D3D11对于三角形光栅器需求很严格,全部没有不少硬件实现的余地;我不肯定那些方法是否符合规范(有不少细节下次会介绍)。我用的方法不是很先进,在光栅器中逐像素处理上用到少许的数学运算。若是你知道更好的解决方案,请在评论中告之。
最后,三角形剔除我这里描述的是最基本状况;例如,一类三角形在光栅化时会生成零个像素远大于零面积的三角形,若是你能够足够快的查找到它,你就能够当即丢弃掉这个三角形而且不须要通过三角形设置。最后说一点,在三角形设置以前以最低限度的光栅化进行剔除——找到其它方法来早期拒绝(early-reject)三角形是至关值得的。