今天幫老師判《三維圖形程序設計》的試卷,這門課開卷考,用的教材是段菲翻譯的DX9的龍書。判卷過程中發現有兩道題雖然不難,但是錯的比較多:
1、Direct3D中深度緩沖區中值的范圍? A.0-1 B.可以任意設置 C.取近平面遠平面的值
2、Direct3D支持三類光源為點光源、方向光和聚光燈。
第一題
我仔細看了龍書,也沒找到直接答案(感謝@xiaofeii的提醒,其實有答案),但是如果對投影變換內部原理理解比較深入的話,也是可以很容易得到這個答案的。龍書的繪制流水線如圖1所示,這個比較利於初學者理解,比較專業繪制流水線如圖2所示,可以發現圖1其實是對圖2的Vertex Shader部分進行了細化(圖1中的第一行所有階段相當於圖2的Vertex Shader階段),而對其他部分進行了簡化,個人認為圖1的流水線比較適合初學者理解。這里以圖2進行說明,Triangle Traversal階段遍歷每個三角形,對三個頂點進行插值,計算三角形中對應光柵設備上的像素點,每個像素點的Z值也是在這個階段得到,當然,在Pixel Shader中還可以對其進行修改。之后的階段在這里就不詳述了。
圖1 龍書中的DX9繪制流水線
圖2 DX9繪制流水線
回到題目,深度緩沖區值的范圍?首先必須確定深度緩沖區的Z值來源於哪個階段,因為經過不同階段的坐標變換以后Z值范圍是不一樣的!這里以圖1進行說明,很多人選擇了B,我猜測他們是認為深度緩沖區存儲的是View Space階段頂點的Z值,但是很遺憾,深度緩沖區存儲的是Projection之后的Z值,所以應該選A。
看到這里,有童鞋肯定納悶,為什么投影之后Z的范圍是0-1呢?投影不是往一個平面上投影嗎?最后Z值應該一樣的才對啊!這個就要從投影矩陣說起了,雖然投影到一個平面的投影矩陣很簡單,但是這個過程是不可逆的。值得注意的是,DX中的所有變換都是可逆的,比方說對頂點P使用矩陣M進行變換,變換后只要乘以M-1即可變回P。為此,聰明的開發人員使用可逆的投影矩陣,具體解釋參看MSDN。投影變換后,Z的范圍就變為了[0, 1],0表示近裁剪面,1表示遠裁剪面,在OpenGL中由於投影矩陣不一樣,投影后Z值范圍為[-1,1],但是在視口變換的時候將Z值范圍變為[0, 1](感謝@xiaofeii的提醒)。
順便多嘴提一句,深度緩沖區[0, 1]的范圍並不是用浮點數表示,而是用類似整型的方式表示,這樣既提高了精度,也為之后的Buffer Compression的相關算法買下伏筆。
附:左手坐標系的投影矩陣,具體說明見MSDN
xScale 0 0 0 0 yScale 0 0 0 0 zf/(zf-zn) 1 0 0 -zn*zf/(zf-zn) 0
如果書上有直接答案,明確說明深度緩沖區的范圍是[0, 1],估計大家基本上都會回答對,但是深入思考其中原理的又會有多少人呢?不僅知其然,而且還要知其所以然,這才是學習的態度。
第二題
這一題,在書上有,但只是和題目描述略有不同,有些不太理解的童鞋就寫成了環境光、漫射光和鏡面光。像上一題一樣,如果只是知道表面,確實挺模糊的,同樣是3個空,同樣是和光源相關,憑什么是點光源、方向光和聚光燈而不是環境光、漫射光和鏡面光呢?確切的說,前者是光源類型,后者是光照模型,下面將對其進行詳細描述。
從物理的角度來說,光與材質的交互包括兩個方面:散射(Scattering)和吸收(Absorption)。散射發生在介質不連續的地方,比如不同材質的交界處,或介質密度變化,它只改變光的方向不改變光的能量;吸收發生在材質內部,它將光的能量轉化為其他能量(如熱能),但不改變光的方向。介質表面對光的散射包含兩種方式,分別為折射和反射。對於透明物體,一部分光在表面進行反射,另一部分光發生折射,進入介質繼續傳播;對於不透明物體,一部分光在表面進行反射,另一部分光在介質中傳播、吸收和散射,最后反射回表面(次表面散射),圖3為不透明物體與光作用的示意圖。
圖3 光與材質作用
DirectX光照模型中的漫射光和鏡面光分別對應材質表面的次表面散射和表面反射,這兩個模型共同模擬了光與材質作用時的散射現象,至於環境光,用來描述間接光照,這就是DirectX光照模型的來歷,它們所對應的公式從提出以后一直被不斷修正,趨於完善(直到2008年還有一篇文章對鏡面光公式進行修正)。DirectX的材質描述了材質對於漫射光、鏡面光和環境光的反射,這些對應物理上材質對光的吸收。另外,在材質中還有自發光分量,用於描述自發光的材質。
到此為止,主要的光照現象已經被模擬了,如果需要其他更逼真的渲染,則需要對可編程管線下手了。例如透明玻璃球的折射,如果僅僅使用alpha融合進行渲染,那就太扯淡了,需要利用相關的物理知識,在可編程管線中模擬真實環境,並進行一定的簡化;再比如說人臉的渲染,由於皮膚的次表面散射現象比較明顯,所以使用漫射光來進行渲染就會讓人覺得很假,需要針對次表面散射的物理現象,結合可編程管線進行逼真的渲染,《GPU GEMS 3》封面的人臉就是使用次表面散射相關技術進行渲染,有興趣的童鞋可以看看這本書的第14篇文章。
既然有光,肯定有光源,點光源、聚光燈和方向光正是由現實中的光源抽象而來。對於太陽這種比較大的光源,可以近似認為方向光;對於一些表面積不大的面光源,可以近似用點光源來模擬;至於聚光燈也是根據現實中的聚光燈進行模擬。雖然用這3種光源不能精確的描述現實中的光源,但是可以做到近似。
回到題目,題目問的是光源類型而不是光照模型,如果理解了這些原理,那么自然想到的是點光源、聚光燈和方向光,無論如何也不可能寫成漫射光和鏡面光和環境光。雖然DX提供了相關API,但如果能夠對其原理進行了解,則使用起來將會得心應手,而且,從DX10開始,取消了固定管線,只有可編程管線,所以理解原理顯得更加重要。
寫在最后
本文的目的不是希望大家去背題目,以后看到同樣的題目就不會出錯,而是希望能夠理解內部原理,做到不僅知其然,而且還要知其所以然。社會是浮躁的,在浮躁的環境中需要保持一顆平靜的內心,也許原理性的東西短期內不會帶來什么效益,但是保持這樣的態度,很可能若干年后和別人拉開的差距,比多學幾門熱門技術要大的多。