金屬,塑料,傻傻分不清楚

https://zhuanlan.zhihu.com/p/21961722

很多時候可以聽到遊戲玩家說,某某遊戲看起來什麼都是塑料做的。最近還聽一個設計師說,你用Blinn-Phong吧,那東西渲染出來就是濃濃的塑料感的。

金屬和塑料,區別在哪裏,真的是因爲BRDF模型嗎?

Diffuse/Specular

就從Blinn-Phong模型入手。最簡單的基於物理的Blinn-Phong是這樣的:

L_{o}(\mathbf{v})=(\mathbf{c}_{diff} + \frac {\alpha + 2} {8}(\mathbf{n} \cdot \mathbf{h})^{\alpha} F(\mathbf{c}_{spec}, \mathbf{l_c},\mathbf{h})) \otimes \mathbf{c}_{light} (\mathbf{n} \cdot \mathbf{l_c})

其中\mathbf{c_{diff}}\mathbf{c_{spec}}分別是diffuse和specular的顏色,\alpha是表述光滑程度的跟物體材質相關的,也就是這三項了。話句話說,決定一個物體看起來像什麼,在Blinn-Phong模型裏,就是通過調整這三項完成的。

那麼,是光滑度造成的嗎?不是。塑料也有粗糙和光滑,金屬也有粗糙和光滑。並不會因爲是金屬就不那麼粗糙。所以,現在的問題就是,什麼決定了材質的diffuse和specular顏色。

回到基於物理的BRDF這個出發點,看看從物理的角度,diffuse和specular來自哪裏。

上圖來自於Real-time Rendering第三版。箭頭根據顏色可以分成幾種。簡化起見,就看最終出射的情況。深藍色和淺藍色表示diffuse(分別來自於single scattering和multiple scattering,但這不重要),金黃色表示specular。原理上說,diffuse是光線折射入物體後,在內部經過散射,重新穿出表面的光。而specular是光線在物體表面直接反射的結果。並沒有經過轉換,還是原來的光子。

(從這張圖還可以看出,因爲物體的密度和材料不同,diffuse可能分佈到一個很廣的範圍內,不是一個點,而是一個光斑。specular就是一個點。而且,這兩者位置上不重合。不過這已經超出了實時渲染甚至離線渲染的範疇,這裏不討論)

然而,對於金屬(導體)來說,折射入物體的光子會被完全吸收掉,就成了這個樣子:

好了,這下明白了。對於金屬來說,diffuse永遠是0,材質的顏色來自於 specular。對於非金屬來說,材質的顏色一部分來自於diffuse,一部分來自於specular。

顏色分佈

既然如此,也就是說如果我們有能力控制diffuse和specular的顏色,就一定能在渲染上覆現出金屬(導體)和塑料(絕緣體)的區別,甚至介於其間的半導體。在此之前,先找一些現實中的材料,看看它們的diffuse和specular顏色分佈究竟如何。

這裏列舉一下來自Physics and Math of Shading的幾張表。

第一張是金屬的specular顏色。前面說到金屬的diffuse = 0,所以你看到的金屬顏色,就是它的specular。從這張表可以看出,金屬的specular非常強烈,而且各通道都幾乎大於0.5。唯一的例外是金,藍色通道較弱,而紅色通道已經超出了sRGB的表達範圍。(如此獨特的反射曲線,也是金子爲什麼有着獨特的文化和經濟地位的原因之一吧)

再來看看絕緣體陣營

同樣是specular顏色,非金屬則顯得弱了很多。塑料、玻璃、水這些常見的絕緣體,specular都低於0.04。而水晶、鑽石這些絕緣體, 你們知道爲什麼貴了吧。另一個特點是,絕緣體的specular都是單色的。各個通道的光都會被等同地反射,而不像金屬那樣有着不同的吸收偏好。

而半導體呢?如你所想,介於兩者之間。

有了這些信息,我們就可以進一步把它用於渲染了。

GBuffer

這幾年的渲染引擎,幾乎都是用的deferred rendering框架,其中一個兵家必爭之地就是GBuffer。每年都有不同的文章在講如何把更多的信息擠到GBuffer中,正因爲GBuffer空間極其有限,而想放入的信息有越來越多。所以,如何利用信息之間的關係,把數據編碼到有限的空間裏,對deferred rendering來說非常重要。

如果按照前面的結論,需要放diffuse和specular顏色,則需要6個通道,完全超出了GBuffer可以承受的範圍。GBuffer裏留給diffuse和specular顏色的通道只有4個。所以遊戲引擎常見的做法是,保存個diffuse顏色和specular的亮度。就當作specular是單色的來處理。這樣顯然對於金屬不利,也是爲什麼會畫面看着都像塑料的重要原因。

有了上一節的信息,直覺告訴我們一個想法,對於導體和絕緣體,應該分開用不同的保存方法。導體存specular顏色,因爲它的diffuse是0。絕緣體存diffuse,因爲specular非常弱,都用0.04這個常量表達就可以了。那麼半導體呢?就需要一個“金屬性”的係數,在兩者之間插值。

用公式來表達,就是

diffuse = albedo * (1 - metalness)
specular = lerp(0.04, albedo, metalness)

其中,albedo就是3通道的反照率,metalness就是金屬性係數。lerp是線性插值的意思。這樣就用一個方法統一了導體、半導體、絕緣體的顏色分佈。metalness = 0表示絕緣體,diffuse就是albedo,specualr = 0.04。metalness = 1表示導體,diffuse = 0,specular = albedo。這麼一來,只要在GBuffer裏面存albedo(3通道)和metalness(1通道)。

結果和總結

這麼做的渲染結果如何呢?咱們來看看一組對比,用的是前面提到的基於物理的Blinn-Phong,用的同樣的albedo。

粗糙的絕緣體

光滑的絕緣體

粗糙的導體

光滑的導體

從圖上還是很容易分辨那些是絕緣體那些是導體。而且很明顯可以看出,這件事情和光滑度無關,和BRDF模型也無關。

在來對比一下在引入albedo+metalness之前能達到的效果。

對比強烈吧,同樣的參數,原先的做法看起來就是油膩的塑料。

最後,來一組金屬性和粗糙度變化的陣列。

我們用albedo+metalness的方法,在沒有增加存儲空間,和幾乎沒有增加計算開銷的情況下,實現了更高質量的金屬渲染。從此,只要做一些微小的工作,調調材質,就能避免塑料感。

在KlayGE裏面,GBuffer的格式也已經改成這裏說的存儲方式,材質系統也做了相應的修改,以更好地表達這樣的材質分類。

在有些遊戲引擎,比如Dying Light,用了一個有點區別的編碼方式。那裏面只有導體和絕緣體兩種切分,沒有metalness的係數可以做過渡。這樣就可以把絕緣體的specular給編碼進去。又因爲絕緣體specular非常的小,只要佔用很少的位數。空出來的位可以把translucent、皮膚、葉片等信息都編碼進去。但對導體絕緣體的分析和本文相同。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章