第一回 开篇 D3D渲染流程简介
开发这个3D engine已经两年半了,从06年8月刚开始统计的4万多行,到今年7月份的21万多行,有一些感慨,感觉有那么点成就感,不过更多的是惴惴之 心:这些代码可以很好的在一起工作吗,足够快吗?bug肯定不少,因为测试的强度毕竟不高,这还不是最重要,架构上会不会有致命的疏忽的地方?会不会在未 知的需求面前不堪一击?前些日子看了看两年前写的代码,觉得不满意的地方很多,代码写出来的一瞬间就开始贬值,两年多时间,也的确该贬得差不多了,有心要 重写,似乎又没多少时间,接下来还有很多事情要做,而自由宽松的开发环境不是能永远维持下去的.还有一些担心:我是否真的尽力了呢?完成这些功能,是否真 的需要这么长时间呢?它们是否真的有价值呢?不仔细回忆,都有点想不起来一年前都干了些什么.虚度光阴?有这个可能--往消极的一面想,还真能想出不少东 西.不过要积极点呢,也有不少值得欣慰的地方,两年来至少学到了不少东西,对3D图形方面的经验有了些积累,不像两年前那么生疏了,脚本也接触了一些,编 辑器写得更熟练了,外观也更漂亮了(这个很关键,写程序不就是为了写点好看的东西吗?),模板也用得比以前熟练多了,有些问题的解决方法自己也挺满 意...二十多万行垃圾代码里,居然也有那么些闪光点,若隐若现,叫人安慰.
所以我打算从这个炎热的夏天开始,关于这个engine写点什么,一来可以留作纪念,二来可以整理下思路,并做将来的备忘,三来可以把自己的一些想法公布 出来,用以交流,不敢说高台教化,起码是劝人向善,教人学好(呵呵,怎么和郭德纲一个口风),如果能有人看到这些东西,受到些启发,也是很好的事.
先从最基础的写起吧,关于Device的渲染流程.
D3D9的Device就是D3D给我们提供的一个绘制3D图形的工具,它的绘制流程大致是这样的:
D3D9的Device就是D3D给我们提供的一个绘制3D图形的工具,它的绘制流程大致是这样的:
*.首先Device的使用者要准备好顶点数据,也就是一个顶点的数组,称为A
*.然后这个数组A被传入device的渲染管线
*.device内部依次对每个顶点进行处理,有两种模式,固定管线和shader模式,所谓固定管线就是device内部实现的一个固定的程 序,用户只能通过设定各种参数(一些RenderState)来控制它,当然这不够灵活,所以有了shader模式,也就是说,用户需要写一个程序片段 (所谓vertex shader),传给device,然后device使用这个片段对每个顶点进行处理.这个程序片段是在显卡上执行的.
*.传入的顶点数组A的每一个元素被转换后,存储到另一个数组B中.数组B中的每个元素必须至少包含一个透视空间的位置,用来做裁剪.
*.数组B被传入到device的下一个计算阶段,在这个阶段里,数组B中的(被转换过的)顶点被组织成一个个三角形,然后对这些三角形进行裁 剪(利用顶点数据里包含的那一个透视空间的位置),背面剔除(注意背面剔除和顶点的法线是没关系的),最后剩下的三角形被保存到一个数组C中.(注意在这 个阶段里顶点数组变成了三角形数组)
*.数组C被传入到下一个计算阶段,光栅化,对于数组C中每一个三角形,首先把它们从透视空间映射到屏幕空间,然后找出它们在屏幕上覆盖的像素 (一个三角形覆盖的像素的数量有可能是很多的),对于每一个像素,根据它在三角形中的位置,通过三角形的顶点进行线性插值,计算出一个像素数据(注意像素 数据是通过三角形的顶点数据插值而来,所以它们的数据类型是一致的),所有三角形算出来的像素数据最后被存储到一个数组D中.(在这个阶段里,三角形的数 组变成了像素数据数组)
*.数组D被传入到下一个计算阶段,在这个阶段里,device会对这些像素做一些初步的过滤,主要是进行stencil test(根据stencil
buffer上的值)和z-test(根据这个像素的Z值和z-buffer上的值进行比较),根据测试结果会对stencil buffer进行一些修改(使用一组render state来控制这个过程),通过这些test的像素被存储到数组E.
*.数组E被传入到下一个计算阶段,在这个阶段里,device对每个像素数据进行处理,这个阶段也有两种模式,固定管线和shader模式, 与顶点处理阶段类似,用户也可以写一个程序片段,来对每一个像素数据进行处理,称为pixel shader.像素数据可能包含各种类型的数据,但经过这一阶段的处理后,输出是很简单的,一般就是一个颜色值和一个alpha值(透明度),也可以输出 一个Z值,不过好像不常用.在pixel shader里还可以使用专门的指令来放弃某一个像素的后续处理.所有的像素数据被处理后,结果存在一个数组F中.
*.数组F进入下一个阶段,在这一个阶段里,进行alpha test(根据像素的alpha 值),alpha test是最后一个test了,通过了alpha test的像素可以保证绘制到屏幕上去,通过test的像素会把它们的z值更新到z-buffer中去(具体由一组render state控制),通过test的像素被存入数组G
*.数组G进入下一个阶段,在这个阶段里,主要是把数组G里的像素和屏幕上已有的像素进行混合,具体混合的方式有多种多样,由一系列render state进行控制.混合以后的像素就被"画"到屏幕上了
基本上的流程就是这样了,环节很多,每个环节也有很多技巧,很多需要注意的地方,太多了,就不铺开来写了,我觉得这个流程很重要,虽然理解起来 并不困难,但真的能够非常熟练的记住它,并且运用其中的各个环节来解决实际的问题就不是那么容易的事了.所以不厌其烦的又把它粗粗的写了一遍,希望自己也 能进一步加深印象.
最近正在写关于多线程渲染的代码,下一回可能写写这方面的东西.
谢谢楼下的评论纠正了我的一个错误,stencil test和depth test的确是在pixel shader后才进行的,虽然这看上去没什么道理, 不过标准的渲染应该是这样的流程,但好像某些显卡会在pixelshader前做一些早期的test(early-z和early-stencil)来提高性能,我希望这样的显卡越多越好.看来我要去重新check一下引擎中使用stencil buffer提高性能的地方了,不过好像当时的测试的确是提高了一些性能的.