Unity3D 的物理渲染和光照模型

jopen 9年前發布 | 45K 次閱讀 Unity3D 游戲開發

為什么地球在兩極嚴寒,而在赤道火熱?這個問題,仿佛與著色器毫不相干,但卻是理解光照模型怎樣工作的基礎。正如這個教程前面部分所解釋的,表面著色器使用數學模型來預測光照在三角形上怎樣反射。總的來說,Unity 引擎支持兩種著色技術,一個是啞光著色器,一個是鏡面材料著色器。前一種對于不透明表面的支持很完美,而后一種則用來模擬反射對象。這些光照模型背后的數學可能非常復雜,但是如果你想創造屬于你自己的光照效果,你就得理解它們是如何工作的。直到 Unity 版本 4.x,默認的漫射光照模型都基于朗伯反射(Lambertian reflectance)的。

漫反射面:郎伯模型

回到最開始的問題,兩極冷的原因就在于它受照射的陽光比赤道區域少。這是由于它們受到太陽的斜射。下圖顯示了八角形兩極區域受到的光線明顯少于正面區域。

Unity3D 的物理渲染和光照模型

藍線代表了正交法向量單位長度。橙線表示了光線的方向。光通量的衰減取決于光線方向與法向向量的夾角。在郎伯模型( Lambertian model )中,它的值等于垂直的入射光線。

Unity3D 的物理渲染和光照模型

其可以表述為:

     \[I= \left \| L \right \| \, cos \alpha = cos \alpha\]

式中, \left \| L \right \| 為 L(這個量是之前定義過的)的長度 ,然后 \alpha 為 N 和 L 的夾角。這個算子在向量代數中被叫做點積 ,在前邊的文章中也已經簡單介紹了。正式的寫法應該是這個樣子的:

     \[A \cdot B = \left \| A \right \| \, \left \| B \right \| \, cos \alpha\]

在 Cg/HLSL 中都可以使用點操作符。其將返回一個從 -1 到 1 的數,當兩向量正交時將返回 0,并且當它們平行時 \pm 為 1。我們將使用其作為一個乘法系數,代表了從一個光源接收到多少三角光(三角函數光,即正弦余弦的意思,意會!)。

朗伯著色器(Lambertian shader)

我們現在已經有必要來理解一個朗伯模型在著色器中是如何實現的。Cg/HLSL 允許用一個自定義的函數替換標準的朗伯模型。在第8行,在指令 #pragma surface 中使用 SimpleLambert,強制著色器搜索叫做 LightingSimpleLambert 的函數:

Shader "Example/SimpleLambert" {
Properties {
_MainTex ("Texture", 2D) = "white" {}
}
SubShader {
Tags { "RenderType" = "Opaque" }
CGPROGRAM

pragma surface surf SimpleLambert

struct Input { float2 uv_MainTex; };

sampler2D _MainTex; void surf (Input IN, inout SurfaceOutput o) { o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb; }

half4 LightingSimpleLambert (SurfaceOutput s, half3 lightDir, half atten) { half NdotL = dot (s.Normal, lightDir); half4 c; c.rgb = s.Albedo  _LightColor0.rgb  (NdotL  atten  2); c.a = s.Alpha; return c; }

ENDCG }  Fallback "Diffuse" } Shader "Example/SimpleLambert" {Properties {_MainTex ("Texture", 2D) = "white" {}}SubShader {Tags { "RenderType" = "Opaque" }CGPROGRAM#pragma surface surf SimpleLambertstruct Input {float2 uv_MainTex;};sampler2D _MainTex;void surf (Input IN, inout SurfaceOutput o) {o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;}half4 LightingSimpleLambert (SurfaceOutput s, half3 lightDir, half atten) {half NdotL = dot (s.Normal, lightDir);half4 c;c.rgb = s.Albedo  _LightColor0.rgb  (NdotL  atten  2);c.a = s.Alpha;return c;}ENDCG}Fallback "Diffuse"}</pre>




從19到25行 展示了朗伯模型如何簡單地在一個表面著色器中再實現。NdotL 為光顏色的強度系數(相乘)。參數衰減器用于調制光強。其需要被兩個量乘是最初 Unity3D 用于仿真某些特效的技巧。這在 Aras Pranckevi?ius 中有解釋,在Unity4中留存了下來為了向后兼容。最終在 Unity5 進行了修復,所以如果你在 Unity5 上再實現一個朗伯模型的時候,僅僅只要乘上一個量就行啦。

理解標準發光模型的原理是改變其不可或缺的步驟。許多可選的著色技術事實上仍然使用朗伯模型作為其第一步。

Toon shading

最近在游戲中常用的風格之一即是Toon shading(又稱 cel shading).這是一種非逼真渲染風格,通過改變了光在一個模型上反射實際情況來給人以手繪的感覺。為了達到這樣的效果,我們需要用一個自定義模型來替換至今使用的標準光照模型。最常見用于達到這種效果的方法就是使用加性紋理,在下面的著色器中叫做_RampTex。

Shader "Example/Toon Shading" {
Properties {
_MainTex ("Texture", 2D) = "white" {}
_RampTex ("Ramp", 2D) = "white" {}
}
SubShader {
Tags { "RenderType" = "Opaque" }
CGPROGRAM

pragma surface surf Toon

struct Input { float2 uv_MainTex; }; sampler2D _MainTex; void surf (Input IN, inout SurfaceOutput o) { o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb; }

sampler2D _RampTex; fixed4 LightingToon (SurfaceOutput s, fixed3 lightDir, fixed atten) { half NdotL = dot(s.Normal, lightDir);  NdotL = tex2D(_RampTex, fixed2(NdotL, 0.5));

fixed4 c; c.rgb = s.Albedo  _LightColor0.rgb  NdotL  atten  2; c.a = s.Alpha;

return c; }

ENDCG }  Fallback "Diffuse" } Shader "Example/Toon Shading" {Properties {_MainTex ("Texture", 2D) = "white" {}_RampTex ("Ramp", 2D) = "white" {}}SubShader {Tags { "RenderType" = "Opaque" }CGPROGRAM#pragma surface surf Toonstruct Input {float2 uv_MainTex;};sampler2D _MainTex;void surf (Input IN, inout SurfaceOutput o) {o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;}sampler2D _RampTex;fixed4 LightingToon (SurfaceOutput s, fixed3 lightDir, fixed atten){half NdotL = dot(s.Normal, lightDir);NdotL = tex2D(_RampTex, fixed2(NdotL, 0.5));fixed4 c;c.rgb = s.Albedo  _LightColor0.rgb  NdotL  atten  2;c.a = s.Alpha;return c;}ENDCG}Fallback "Diffuse"}</pre>


Unity3D 的物理渲染和光照模型

LightingToon模型計算了光強的朗伯系數NdotL并使用ramp紋理以將其離散化。該例子中僅映射了四級光強階。不同的ramp紋理將細化地獲得不同的toon shading變形。

Unity3D 的物理渲染和光照模型

鏡面: Blinn-Phong模型

朗伯模型并不能仿真鏡面反射材料。這種情況下就需要另一種技術; Unity4.x 采用了 Blinn-Phong 模型. 不需要計算法向量N 和光矢量 L的點積, 而是通過L和視角V的角平分矢量 H 來計算 :

BlinnPhong

     \[\mathrm{Lambertian\,model:} \,\,\,\ I = N \cdot L\]

     \[\mathrm{Blinn-Phong\,model:} \,\,\,\ I = \left ( N \cdot H \right )^{specular} \cdot gloss\]

     \[H = \frac{L+V}{\left| L +V\right|}\]

數量 N \cdot H 通過 speculargloss 設置進一步計算。如果你想深入了解Unity中光照模型的計算, 可以下載其內置著色器的源碼. 朗伯和Blinn-Phong表面函數都是在Lighting.cginc文件中計算的。而在 Unity5 中需要在遺產著色器(Legacy shaders)中尋找。

在Unity5中物理渲染

就像本文在前面提出的那樣, Uniy4.x使用朗伯光照模型作為其默認著色器。Unity5中發生了改變, 其引入了 物理渲染 (PBR). 這個名字聽起來相當有趣啊, 但是與其它光照模型沒有什么不同。相比于朗伯反射, PBR提供了一個更加逼真的光線物體作用模型。術語physically 來源于,PBR考慮了材料的物理屬性, 比如能量守恒以及光的散射。Unity5為藝術家和開發者提供了兩種不同的方法,用于創建他們的PBR 材料: 金屬工作流 和 鏡面工作流。在前者中,一個材料對光的反射取決于其是什么樣的金屬(或者說,含有多少金屬的量)。

簡單來說就是,光是電磁波啊,其行為因接觸到的是導體還是絕緣體而不同(電磁波唱由電場和磁場組成,但電場屬性實際上比磁場屬性更重要)。在鏡面工作流中, 所提供的是鏡面映射。盡管被當做兩個不同的東西呈現出來, 金屬材料和鏡面材料實際上以不同的方式初始化同一個著色器;Marmoset 上有一個很好的例程展現了同樣的材料如何通過金屬和鏡面工作流分別創建。這也是為什么當第一次接觸 Unity5 著色器時會因為源代碼中出現了同一事物卻有兩個工作流現象時會產生誤解。 Joe Wilson 創建了一個相當清晰的例程來知道我們的藝術家:如果你想學習怎么通過 PBR 創建非常逼真的材質,這將是非常好的開始喲。如果需要更詳細的技術信息,在 Unity5 博客里關于 PBR 的地方猛戳a very well done primer

metallic

Unity5 中新光照模型名字既簡單又標準。取這個名字是因為現在 PBR 是每一個 Unity3D 中新建對象的初始材料。而且,每一個新建的著色器文件會被自動配置為一個 PBR 表面著色器:

Shader "Custom/NewShader" {
Properties {
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200

CGPROGRAM // Physically based Standard lighting model, and enable shadows on all light types

pragma surface surf Standard fullforwardshadows

// Use shader model 3.0 target, to get nicer looking lighting

pragma target 3.0

sampler2D _MainTex;

struct Input { float2 uv_MainTex; };

half _Glossiness; half _Metallic; fixed4 _Color;

void surf (Input IN, inout SurfaceOutputStandard o) { // Albedo comes from a texture tinted by color fixed4 c = tex2D (_MainTex, IN.uv_MainTex)  _Color; o.Albedo = c.rgb; // Metallic and smoothness come from slider variables o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = c.a; } ENDCG }  FallBack "Diffuse" } Shader "Custom/NewShader" {Properties {_Color ("Color", Color) = (1,1,1,1)_MainTex ("Albedo (RGB)", 2D) = "white" {}_Glossiness ("Smoothness", Range(0,1)) = 0.5_Metallic ("Metallic", Range(0,1)) = 0.0}SubShader {Tags { "RenderType"="Opaque" }LOD 200CGPROGRAM// Physically based Standard lighting model, and enable shadows on all light types#pragma surface surf Standard fullforwardshadows // Use shader model 3.0 target, to get nicer looking lighting#pragma target 3.0sampler2D _MainTex;struct Input {float2 uv_MainTex;};half _Glossiness;half _Metallic;fixed4 _Color;void surf (Input IN, inout SurfaceOutputStandard o) {// Albedo comes from a texture tinted by colorfixed4 c = tex2D (_MainTex, IN.uv_MainTex)  _Color;o.Albedo = c.rgb;// Metallic and smoothness come from slider variableso.Metallic = _Metallic;o.Smoothness = _Glossiness;o.Alpha = c.a;}ENDCG}FallBack "Diffuse"}</pre>


soldier standard

14行將告知 Unity3D 該表面著色器將使用 PBR 光照模型。17行 意味著該著色器將使用高級特性,因而其將不同在落后的硬件上使用。同樣的, SurfaceOutput 也不能同 PBR 一起使用;而是必須使用  SurfaceOutputStandard

PBR 表面輸出

SurfaceOutputStandard 中不光是 Albedo,Normal,Emission 和 Alpha 這些屬性,還有三個新的:

sesese色