實時光照計算的開銷很是昂貴。根據翻譯13,延遲渲染容許程序員使用的光源能夠多於Forward渲染,但陰影的開銷仍然對性能有一個限制。若是咱們的場景是動態的,那麼沒有辦法來避免執行這些計算。可是若是光源和幾何物體位置都是不變的,那麼咱們能夠只計算一次光照並重復使用。這使得咱們能夠在場景中放置許多光源,而沒必要在運行的時候再渲染它們。這種方法也可使用那些不能用做實時光源的區域光源(area lighting)。程序員
在本教程中,會將全部內容都放在光照貼圖中,因此根本不會有任何動態光照。算法
爲了嘗試光照貼圖,我建立了一個簡單的測試場景,它具備一個簡單的結構,能夠提供陰影,還有一些放置在其內部的球體。一切物體都使用默認的Unity材質。app
針對光照貼圖的一個測試場景dom
要開始使用光照貼圖,將惟一的光源對象的模式改成「Baked(烘焙)」而不是「Realtime(實時)」。編輯器
使用烘焙模式的主方向光源ide
將主方向光源變成烘培光源後,就不被歸入動態光照計算。從動態對象的角度來看,光源是不存在的。 惟一仍然不變的是環境光照,它仍然是基於主方向光源的。函數
沒有直接光照的效果性能
要實際啓用光照貼圖,請在lighting窗口的「混合光照(Mixed Lighting)」中打開「烘培全局光照(BakedGlobal Illumination)」。 而後將光照模式設置爲「烘培間接光照(BakedIndirect)」。 儘管它的名字說的是烘培間接光照,可是它也包括了直接光照。 它一般用於向場景添加間接光照。另外,確保實時全局光照(Realtime Global Illumination)被禁用,由於咱們尚未支持到這一點。測試
烘培間接光照模式this
場景的對象都應該是固定的:它們位置永遠不會移動。要將這一個信息傳達給Unity,請將這些對象標記爲靜態。你能夠經過啓用檢視器窗口右上角的「靜態」切換鍵來作到這一點。
光源也必須被標記爲靜態嗎?不需。光源只須要設置爲適當的模式。
有各類子系統關心物體是不是靜態的。「靜態(static)」還有一個下拉菜單,你可使用它來微調哪些系統會將這個對象視爲靜態的。如今咱們只關心光照貼圖,但最簡單的作法是使一切都徹底是靜態的。
靜態標籤設定
一個物體對於光照貼圖來講是不是靜態的,也能夠經過其網格渲染器的檢視器來進行查看和編輯。
對於光照貼圖來講是靜態的物體
如今,全部的物體都是靜態的,它們將被包含在光照貼圖的處理過程當中。
使用烘焙光照的場景
必須注意,使用光照貼圖獲得的結果不如使用實時照明獲得的結果亮度那麼高。這是由於缺失了鏡面高光,只剩下了漫反射光照。鏡面高光取決於視角,所以取決於相機的角度。正是因爲相機是移動的,所以它不能包含在光照貼圖中。(使用場景推薦)這種限制意味着光照貼圖能夠用於微弱的光線和暗淡的表面,但不能用於強直射光或有光澤的表面。若是你想要鏡面高光,你將不得不使用實時光源。因此你常常會使用烘烤光源和實時光源的混合。
爲何沒有當即獲得烘焙光源? 爲了確保在須要的時候光照貼圖能夠實際生成和更新,請在光照窗口的底部啓用「自動生成(Auto Generate)」。 不然,你必須手動生成新的光照貼圖。
自動烘焙
光照烘焙窗口包含專門用於光照貼圖設置的部分。在這裏,你能夠在質量、尺寸和烘烤時間之間取得平衡。你還能夠在光照貼圖烘焙算法引擎:Enlighten和Progressive lightmapper之間進行切換。後者會增量地生成光照貼圖,優先考慮場景視圖中可見的內容,這在編輯的時候很方便。本教程中使用的是Enlighten光照貼圖引擎。
默認的光照貼圖設置
在作任何事情以前,請將「DirectionalMode「設置爲」Non-Direction「。 稍後咱們會處理其餘模式。
使用「Non-directional」模式的光照貼圖
烘烤的光照存儲在紋理中。 你能夠經過將光照窗口從「場景(Scene)「切換到」全局地圖(Global Maps)「模式來進行查看。 使用默認設置,個人測試場景很容易與一張1024×1024貼圖相匹配。
獲得光照貼圖
Unity自帶的Objects物體都有用於光照貼圖的UV座標。對於手動導入的模型,能夠本身提供UV座標,也可讓Unity生成。烘烤後能夠在光照貼圖中看到展開的紋理。它們須要多少空間取決於場景中物體的大小和光照貼圖的分辨率設置。 若是質量要求高分辨率太大,一張貼圖漲不下,Unity會建立額外的貼圖存儲,直至完成。
光照貼圖的分辨率的不一樣會帶來很大的差別
對於每一個項目來講,最佳設置都是不一樣。 你必須不斷的調整烘焙參數,直到達成很好的效果及平衡。須要注意的是,視覺質量也很大程度上取決於用於光照貼圖的紋理展開的質量。不存在紋理接縫可能會產生明顯的瑕疵。Unity的默認球體就是一個很好的例子。它不適用於光照貼圖。
烘焙光照會失去鏡面高光,只能得到的是間接光照,它是在到達人眼以前會在多個表面反射的光。烘焙光會在拐角周圍區域反射,那些原本會被遮擋的區域仍然會被照亮。咱們不能實時計算鏡面高光這個信息(本節1.2有說明),可是咱們能夠在烘焙的時候包括反射光。
要清楚地看到實時光照和烘培光照之間的差別:將環境光照的強度設置爲零,去掉天空盒的影響,全部的光都只是來自方向光。比對
沒有環境光照,realtime vs. lightmapped
每次光子反射的時候,它都會失去一些能量,它會被一些須要的材質採樣着色。Unity在烘焙間接光照的時候,物體會根據附近的顏色進行着色。
綠色的地面,realtime vs. lightmapped
自發光表面也會影響烘焙光照。它們會成爲間接光源。
自發光的地面,realtime vs. lightmapped
間接光照的一個特殊設置是AO環境遮擋:這是指在角落和轉折中發生的間接光照形成的陰影。這是一種人爲的提高,能夠加強深度方面的視覺。
使用環境遮擋的效果
環境遮擋效果徹底基於物體表面。它不考慮光線實際來自哪裏。烘焙時並不老是正確,舉個簡單的例子:當與自發光表面組合的時候就會產生一些錯誤的結果。
顯然是錯誤的環境遮擋效果
1.5 透明度-Transparency
光照貼圖在必定程度上能夠處理半透明表面。 光將經過它們,儘管光的顏色不會被它們所過濾。
半透明的屋頂
鏤空材質也能夠在光照貼圖中正常工做。
鏤空的屋頂
但 這僅在使用封閉曲面的時候有效。當使用像是quad這樣的單面幾何,光線將在不存在的一面損壞。當另一面沒有任何東西的時候,這是很好的,可是當使用單面透明表面的時候會致使問題。
四邊形上有一個錯誤
爲了處理這個問題,必須告訴光照貼圖系統將這些表面視爲透明的。 這能夠經過自定義光照貼圖設置完成
一、經過Asset / Create / Lightmap參數來建立這些數據。這些資源容許你自定義每一個對象的光照貼圖計算。在這種狀況下,咱們只想代表咱們正在處理一個透明的對象。因此啓用「它是透明的(Is Transparent)「。 下面它是一個全局做用預計算實時全局光照(Precomputed Realtime GI)部分中的一部分,它會影響全部烘烤光照。
指示這是透明的
二、單獨設置:經過物體的網格渲染器檢視器來選擇它們。你的資源名字將顯示在Lightmap參數的下拉列表中。
爲透明四邊形使用自定義參數
將物體標記爲透明也會改變它對間接光照的貢獻。透明物體讓間接光經過,而不透明物體則會阻擋間接光。
如今咱們知道光照貼圖是如何工做的,咱們能夠爲Shader着色器添加對光照貼圖的支持。第一步是對光照貼圖進行採樣。調整場景中的球體,以便咱們的着色器使用白色材質。
使用咱們的白色材質的球體
當一個着色器被認爲應該使用光照貼圖的時候,Unity會尋找與LIGHTMAP_ON關鍵字關聯的變體。 因此咱們必須爲這個關鍵字添加一個多編譯指令。 當使用forward-render-path的時候,僅在base-pass中採樣光照貼圖。
#pragma multi_compile _ SHADOWS_SCREEN #pragma multi_compile _ VERTEXLIGHT_ON #pragma multi_compile _ LIGHTMAP_ON #pragma multi_compile_fog
當使用光照貼圖的時候,Unity不會包含頂點光源。他們的關鍵字是相互排斥的。因此咱們不須要一個會同時使用VERTEXLIGHT_ON和LIGHTMAP_ON的變體。(互斥)
#pragma multi_compile _ SHADOWS_SCREEN //#pragma multi_compile _ VERTEXLIGHT_ON //#pragma multi_compile _ LIGHTMAP_ON #pragma multi_compile _ LIGHTMAP_ON VERTEXLIGHT_ON #pragma multi_compile_fog
延遲渲染路徑中也支持光照貼圖,所以也能夠將這個關鍵字添加到延遲渲染通道中。
#pragma multi_compile _ UNITY_HDR_ON #pragma multi_compile _ LIGHTMAP_ON
用於採樣光照貼圖的座標存儲在TEXCOORD1。 因此將此通道添加到shader中的VertexData結構體中。Unity給出了uv使用說明表:Shader中是uv0、uv一、uv二、uv3;C#中是UV、UV二、UV三、UV4
struct VertexData { float4 vertex : POSITION; float3 normal : NORMAL; float4 tangent : TANGENT; float2 uv : TEXCOORD0; float2 uv1 : TEXCOORD1; };
光照貼圖座標也必須進行插值。由於它們與頂點光源互斥,因此均可以使用TEXCOORD6。
struct Interpolators { … #if defined(VERTEXLIGHT_ON) float3 vertexLightColor : TEXCOORD6; #endif #if defined(LIGHTMAP_ON) float2 lightmapUV : TEXCOORD6; #endif };
來自模型頂點數據的座標定義了用於光照貼圖的紋理展開(第二套uv)。可是它並無告訴咱們這個展開位置在哪裏,展開尺寸大小。咱們必須縮放和偏移座標才能獲得最終的光照貼圖座標。這種方法相似於常規紋理座標的轉換,除了轉換是特定於對象的,而這裏的方法是特定於材質的。在UnityShaderVariables中將光照貼圖的紋理定義爲unity_Lightmap。
Interpolators MyVertexProgram (VertexData v)
{
…
i.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);
i.uv.zw = TRANSFORM_TEX(v.uv, _DetailTex);
#if defined(LIGHTMAP_ON)
i.lightmapUV = TRANSFORM_TEX(v.uv1, unity_Lightmap);
#endif
…
}
不幸的是,咱們不能使用方便的TRANSFORM_TEX宏,由於它假定光照貼圖的變換被被定義爲unity_Lightmap_ST,而其實是被定義爲unity_LightmapST。因爲這種不一致,咱們必須手動進行這個變換。
i.lightmapUV = v.uv1 * unity_LightmapST.xy + unity_LightmapST.zw;
由於光照貼圖的數據被認爲是間接光照,咱們將在CreateIndirectLight函數中進行採樣。當光照貼圖可用的時候,必須將它們用做間接光而不是球面諧波。
UnityIndirect CreateIndirectLight (Interpolators i, float3 viewDir) { … #if defined(VERTEXLIGHT_ON) indirectLight.diffuse = i.vertexLightColor; #endif #if defined(FORWARD_BASE_PASS) || defined(DEFERRED_PASS) #if defined(LIGHTMAP_ON) indirectLight.diffuse = 0; #else indirectLight.diffuse += max(0, ShadeSH9(float4(i.normal, 1))); #endif float3 reflectionDir = reflect(-viewDir, i.normal); … #endif return indirectLight; }
unity_Lightmap的確切形式取決於目標平臺。 它被定義爲UNITY_DECLARE_TEX2D(unity_Lightmap)。要對它進行採樣,咱們將使用UNITY_SAMPLE_TEX2D宏而不是tex2D。這是根據不一樣平臺決定。
indirectLight.diffuse = UNITY_SAMPLE_TEX2D(unity_Lightmap, i.lightmapUV);
使用原始光照圖數據的效果
咱們如今獲得了烘焙的間接光照,但效果看起來不對。這是由於光照貼圖數據已被編碼。顏色以RGBM格式或是半強度格式進行存儲,以支持高強度的光。UnityCG的DecodeLightmap函數負責爲咱們解碼。
indirectLight.diffuse = DecodeLightmap ( UNITY_SAMPLE_TEX2D(unity_Lightmap, i.lightmapUV) );
使用解碼後光照圖數據的效果
目前,光照貼圖會將場景對象老是視爲不透明和純白色的物體。咱們必須對咱們的着色器進行一些調整,添加一個渲染通道來徹底支持光照貼圖。
從如今開始,對場景中的全部對象使用咱們本身的着色器。也再也不使用默認的材質。
光照貼圖不使用實時渲染管道,所以現有自寫的shader不能支持。 當嘗試使用半透明陰影的時候,這是最明顯的。經過設置屋頂立方體材質的色調alpha份量小於1來賦予屋頂立方體半透明度。
半透明的屋頂,效果不正確
光照貼圖仍然把屋頂當作是實心物體,這是不正確的。它使用材質的渲染類型來肯定如何處理表面,這應該告訴光照貼圖咱們的對象是半透明的。事實上,它確實知道屋頂是半透明的,它只是把它看做是徹底不透明的而已。這是由於它採用Unity的命名約定_Color材質屬性的alpha組件以及主紋理來設置不透明度。
用_Color替換_Tint。
Properties { // _Tint ("Tint", Color) = (1, 1, 1, 1) _Color ("Tint", Color) = (1, 1, 1, 1) … }
而後,爲了保證咱們的着色器的功能,咱們還必須在shader文件、cg文件替換,並且咱們還要調整GUI拓展。
半透明的屋頂,正確的效果
鏤空部分的陰影也有相似的問題。光照貼圖程序指望透明度的閾值存儲在_Cutoff屬性中,可是咱們使用的是_AlphaCutoff。 所以,它使用默認閾值1。
鏤空的屋頂,效果不正確
解決方案是再次採用Unity的命名約定_Cutoff材質屬性。因此替換shader、cg文件、GUI拓展。
鏤空的屋頂,正確的效果
渲染光照貼圖正確的表面反照率和自發光。
綠色的地板,效果不正確
要採樣物體的表面顏色,光照貼圖程序會將它的光照模式設置爲Meta來尋找一個着色器渲染通道。這個渲染通道僅由光照貼圖程序使用,不使用剔除。因此讓咱們在咱們的着色器上添加一個渲染通道。
Pass { Tags { "LightMode" = "Meta" } Cull Off CGPROGRAM #pragma vertex MyLightmappingVertexProgram #pragma fragment MyLightmappingFragmentProgram #include "My Lightmapping.cginc" ENDCG }
如今咱們須要肯定反照率、鏡面高光顏色、平滑度、自發光。只須要頂點的位置和uv座標,以及須要vertexProgram中的光照貼圖座標。不使用法線和切線。
#if !defined(MY_LIGHTMAPPING_INCLUDED) #define MY_LIGHTMAPPING_INCLUDED #include "UnityPBSLighting.cginc" float4 _Color; sampler2D _MainTex, _DetailTex, _DetailMask; float4 _MainTex_ST, _DetailTex_ST; sampler2D _MetallicMap; float _Metallic; float _Smoothness; sampler2D _EmissionMap; float3 _Emission; struct VertexData { float4 vertex : POSITION; float2 uv : TEXCOORD0; float2 uv1 : TEXCOORD1; }; struct Interpolators { float4 pos : SV_POSITION; float4 uv : TEXCOORD0; }; float GetDetailMask (Interpolators i) { … } float3 GetAlbedo (Interpolators i) { … } float GetMetallic (Interpolators i) { … } float GetSmoothness (Interpolators i) { … } float3 GetEmission (Interpolators i) { … } #endif
GetEmission函數去除FORWARD_BASE_PASS和DEFERRED_PASS限制。
float3 GetEmission (Interpolators i) { // #if defined(FORWARD_BASE_PASS) || defined(DEFERRED_PASS) #if defined(_EMISSION_MAP) return tex2D(_EmissionMap, i.uv.xy) * _Emission; #else return _Emission; #endif // #else // return 0; // #endif }
這些函數只有在定義了適當的關鍵字時纔會起做用,所以能夠在渲染通道中爲其添加着色功能。
#pragma vertex MyLightmappingVertexProgram
#pragma fragment MyLightmappingFragmentProgram
#pragma shader_feature _METALLIC_MAP
#pragma shader_feature _ _SMOOTHNESS_ALBEDO _SMOOTHNESS_METALLIC
#pragma shader_feature _EMISSION_MAP
#pragma shader_feature _DETAIL_MASK
#pragma shader_feature _DETAIL_ALBEDO_MAP
#include "My Lightmapping.cginc"
這個pass的vertex 程序很簡單。只是轉換位置、轉換紋理座標。
Interpolators MyLightmappingVertexProgram (VertexData v) {
Interpolators i;
i.pos = UnityObjectToClipPos(v.vertex);
i.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);
i.uv.zw = TRANSFORM_TEX(v.uv, _DetailTex);
return i;
}
計算2.2提到的映射偏移,咱們必須使用光照貼圖uv座標而不是頂點位置,而後進行適當的轉換把紋理uv座標做爲模型頂點的屏幕位置,模型的UV映射必需要正確:紋理上的每一個點必須映射爲模型上的惟一點。
Interpolators i; v.vertex.xy = v.uv1 * unity_LightmapST.xy + unity_LightmapST.zw; v.vertex.z = 0; i.pos = UnityObjectToClipPos(v.vertex);
v.vertex.z = 0,不是全部機器上都能支持,頂點位置的Z座標必須以某種方式使用,即便咱們不使用它也是如此。Unity的着色器爲此使用虛擬值,因此咱們將簡單地作一樣的事情。
Interpolators i; v.vertex.xy = v.uv1 * unity_LightmapST.xy + unity_LightmapST.zw; v.vertex.z = v.vertex.z > 0 ? 0.0001 : 0; i.pos = UnityObjectToClipPos(v.vertex);
在片斷程序中,計算輸出反照率和自發光顏色。光照貼圖程序將經過執行兩次渲染來作到這一點,每次執行有一個輸出。爲了使這個過程更容易,咱們可使用UnityMetaPass.cginc文件中定義的UnityMetaFragment函數。它使用UnityMetaInput結構做爲參數,其中包含反照率和自發光顏色。 該函數將決定要輸出反照率和自發光顏色中的哪個以及如何編碼輸出結果。
UnityMetaInput也包含鏡面高光顏色,即便它不存儲在光照貼圖中。它用於一些編輯器可視化,咱們先忽略它。
#include "UnityPBSLighting.cginc" #include "UnityMetaPass.cginc" … float4 MyLightmappingFragmentProgram (Interpolators i) : SV_TARGET { UnityMetaInput surfaceData; surfaceData.Emission = 0; surfaceData.Albedo = 0; surfaceData.SpecularColor = 0; return UnityMetaFragment(surfaceData); }
UnityMetaFragment是什麼樣子的?
//unity_MetaFragmentControl變量包含一個標記,這個標記會告訴函數是否輸出反照率或是自發光顏色。還有一段有關 //編輯器可視化變體的代碼,可是我把它刪掉了,由於與這裏的內容不相關。 half4 UnityMetaFragment (UnityMetaInput IN) { half4 res = 0; if (unity_MetaFragmentControl.x) { res = half4(IN.Albedo,1); // d3d9 shader compiler doesn't like NaNs and infinity. unity_OneOverOutputBoost = saturate(unity_OneOverOutputBoost); // Apply Albedo Boost from LightmapSettings. res.rgb = clamp( pow(res.rgb, unity_OneOverOutputBoost), 0, unity_MaxOutputValue ); } if (unity_MetaFragmentControl.y) { half3 emission; if (unity_UseLinearSpace) emission = IN.Emission; else emission = GammaToLinearSpace (IN.Emission); res = UnityEncodeRGBM(emission, EMISSIVE_RGBM_SCALE); } return res; }
間接光照設置爲0的效果
要得到自發光顏色,咱們能夠簡單的使用GetEmission函數。要得到反照率,咱們必須再次使用DiffuseAndSpecularFromMetallic函數。 該函數具有鏡面高光顏色和反射率做爲輸出參數,即便咱們如今不使用它們,咱們也必須提供這些參數。咱們可使用surfaceData.SpecularColor來捕獲鏡面高光顏色。
float4 MyLightmappingFragmentProgram (Interpolators i) : SV_TARGET
{
UnityMetaInput surfaceData;
surfaceData.Emission = GetEmission(i);
float oneMinusReflectivity;
surfaceData.Albedo = DiffuseAndSpecularFromMetallic
( GetAlbedo(i), GetMetallic(i), surfaceData.SpecularColor, oneMinusReflectivity ); //surfaceData.SpecularColor = 0; return UnityMetaFragment(surfaceData); }
間接光照着色的效果
但自發光光照可能尚未出如今光照貼圖中。這是由於光照貼圖程序並不老是包含一個自發光光照的渲染通道。材質必須代表它們具備自發光光照屬性,以對烘焙過程作出貢獻。這是經過Material.globalIlluminationFlags屬性完成的。擴展GUI設置:當自發光光照編輯的時候,它應該被烘焙進光照貼圖。
void DoEmission () { … if (EditorGUI.EndChangeCheck()) { if (tex != map.textureValue) { SetKeyword("_EMISSION_MAP", map.textureValue); } foreach (Material m in editor.targets) { m.globalIlluminationFlags = MaterialGlobalIlluminationFlags.BakedEmissive; } } }
咱們的shader如今看起來能夠正常工做了,但它與標準着色器的結果不徹底匹配。 當使用平滑度很是低的有色金屬的時候,物體表面不太明亮。
粗糙的綠色金屬,standard vs. our
標準着色器經過將反射率的一部分加到鏡面高光顏色進行補償(高亮)。它使用UnityStandardBRDF.cginc的SmoothnessToRoughness函數來肯定基於平滑度的粗糙度值,將其縮小一半,並使用它來縮放鏡面高光顏色。
float roughness = SmoothnessToRoughness(GetSmoothness(i)) * 0.5; surfaceData.Albedo += surfaceData.SpecularColor * roughness; return UnityMetaFragment(surfaceData);
SmoothnessToRoughness計算了什麼東西?
//轉換:減去平滑度值,而後平方。 從平滑度到粗糙度的平方映射最終會產生比僅僅作線性轉換更好的結果。 // Smoothness is the user facing name // it should be perceptualSmoothness // but we don't want the user to have to deal with this name half SmoothnessToRoughness(half smoothness) { return(1 - smoothness) * (1 - smoothness); }
調整反照率後的效果
光照貼圖程序只使用物體的頂點數據,不考慮物體的法線貼圖。光照貼圖的分辨率過低,沒法捕獲由典型法線貼圖提供的細節。這意味着靜態光照將是平坦的。當使用具備法線貼圖的材質的時候,這變得很是明顯。
使用了法線貼圖,standard vs. our
當從實時光照切換到烘焙光時,法線貼圖的影響幾乎徹底消失。這是由於它要求環境反射才能看到它們。
經過將「DirectionalMode」改回「Directional」,可讓法線貼圖與烘焙光照一塊兒工做。
再次啓用定向光照貼圖
當使用方向光照貼圖的時候,Unity將建立兩個貼圖。第一張貼圖包含一般的光照信息,稱爲強度圖。 第二張貼圖被稱爲方向圖。 它包含大部分烘烤光來自的方向。
強度圖和方向圖
當方向圖可用的時候,用它來對烘焙光進行簡單的漫反射陰影計算。這使得它可用於法線貼圖之上。注意,只有一個光方向是已知的,因此陰影將是一個近似。至少有一個主方向光照的時候,結果就會很好。
當方向光照貼圖可用的時候,Unity將使用LIGHTMAP_ON和DIRLIGHTMAP_COMBINED關鍵字查找着色器變體。咱們能夠在forward-base-pass通道中使用#pragma multi_compile_fwdbase,而不是爲手動添加多編譯指令。它會負責解決全部的光照貼圖關鍵字,以及VERTEXLIGHT_ON關鍵字。
//#pragma multi_compile _ SHADOWS_SCREEN //#pragma multi_compile _ LIGHTMAP_ON VERTEXLIGHT_ON #pragma multi_compile_fwdbase #pragma multi_compile_fog
咱們能夠爲deferred-pass必須使用#pragma multi_compile_prepassfinal指令。 它解決了光照貼圖和高動態光照渲染的關鍵字。
//#pragma multi_compile _ UNITY_HDR_ON //#pragma multi_compile _ LIGHTMAP_ON #pragma multi_compile_prepassfinal
prepassfinal是什麼東西?
Unity 4使用了一種與之後的版本不一樣的延遲渲染管線。 在Unity 5中,它被稱爲傳統延遲光照。 這種方法有更多的渲染通道。Prepass決定是當時的術語。不須要引入新的指令,#pragma multi_compile_prepassfinal也用於當前的延遲渲染通道。
在CreateIndirectLight函數中,在檢索烘焙光源自己後,須要直接得到烘焙光的方向。方向貼圖能夠經過unity_LightmapInd得到。
#if defined(LIGHTMAP_ON) indirectLight.diffuse = DecodeLightmap(UNITY_SAMPLE_TEX2D(unity_Lightmap, i.lightmapUV)); #if defined(DIRLIGHTMAP_COMBINED) float4 lightmapDirection = UNITY_SAMPLE_TEX2D ( unity_LightmapInd, i.lightmapUV
); #endif #else indirectLight.diffuse += max(0, ShadeSH9(float4(i.normal, 1))); #endif
可是,這將致使編譯錯誤。這是由於一個紋理變量實際上由兩部分組成。 有紋理資源,還有采樣器狀態。採樣器狀態決定紋理的採樣方式,包括濾波器和截取模式。 一般,每一個紋理都定義了這兩個部分,但這並非全部平臺都須要的。 也能夠將這兩個部分分開,這容許咱們爲多個紋理定義單個採樣器狀態。
由於強度和方向貼圖老是以相同的方式進行採樣,因此在可能的狀況下,Unity使用單個採樣器狀態。 這就是爲何咱們在採樣強度貼圖的時候必須使用UNITY_SAMPLE_TEX2D宏。方向貼圖已經定義,沒有采樣器。 要對其進行採樣,咱們必須使用UNITY_SAMPLE_TEX2D_SAMPLER宏來明確地告訴它要使用哪一個採樣器。
float4 lightmapDirection = UNITY_SAMPLE_TEX2D_SAMPLER
(
unity_LightmapInd, unity_Lightmap, i.lightmapUV
);
要使用方向:一、解碼 二、對法向量執行點積,找到漫反射因子並將其應用於顏色。
可是方向貼圖並無包含單位長度的方向,而是比單位長度的方向會大一些。 幸運的是可使用UnityCG的DecodeDirectionLightmap函數來解碼方向數據。
float4 lightmapDirection = UNITY_SAMPLE_TEX2D_SAMPLER
(
unity_LightmapInd, unity_Lightmap, i.lightmapUV
);
indirectLight.diffuse = DecodeDirectionalLightmap
( indirectLight.diffuse, lightmapDirection, i.normal );
使用帶有方向的光照貼圖的效果
DecodeDirectionLightmap內部作了什麼?
DecodeDirectionLightmap實際上並不計算正確的漫射照明因子。 相反,它使用的是半Lambert。 這種方法能夠有效地將光照射在表面周圍,照亮陰影的區域會更多。這麼作是有必要的,這是由於烘烤的光照不是來自於單個方向。
inline half3 DecodeDirectionalLightmap ( half3 color, fixed4 dirTex, half3 normalWorld ) { // In directional (non-specular) mode Enlighten bakes dominant light // direction in a way, that using it for half Lambert and then dividing // by a "rebalancing coefficient" gives a result close to plain diffuse // response lightmaps, but normalmapped. // Note that dir is not unit length on purpose. Its length is // "directionality", like for the directional specular lightmaps. half halfLambert = dot(normalWorld, dirTex.xyz - 0.5) + 0.5; return color * halfLambert / max(1e-4h, dirTex.w); }
代碼的註釋中提到鏡面高光。 這些是支持鏡面高光的光照貼圖,但須要更多的紋理,使用起來也更昂貴,而且在大多數狀況下沒有產生良好的效果。自Unity 5.6起,它們已被刪除了。
5 光照探針-Light Probes
光照貼圖僅適用於靜態對象,而不適用於動態對象。 所以,動態對象不適合帶有烘烤光照的場景。當沒有實時光源的時候,這是很是明顯的。
爲了更好地混合靜態和動態對象,咱們必須以某種方式將烘焙的光照應用於動態對象。爲了解決這個問題,Unity有光照探針。 光照探針是對空間中的一個點包含該位置的光照信息。 它是用球面諧波來存儲這些信息而不是用紋理。 若是可用的話,這些光照探針將用於動態對象,而不是全局環境數據。因此咱們要作的就是建立一些探針,等到烘焙的時候,咱們的着色器就會自動使用它們。
經過GameObject / Light /Light Probe Group將一組光探測器添加到場景中。 這將建立一個新的遊戲對象,在立方體的形狀中共有八個光探測器。 它們將在渲染動態對象的時候當即使用。
一個新的光探測器組
經過檢視器,能夠在啓用「編輯探針」模式後編輯光探測器組。
光照探針組將其包圍的體積分紅四個區域。四個探測器定義了四面體的角。 這些探測器被進行插值以肯定用於動態物體的最終球諧函數,這取決於其在四面體內的位置。這意味着動態對象被視爲一個單一的點,所以這種方法只對至關小的對象有效。在編輯探測器的時候,會自動生成四面體。 你不須要知道他們的配置,但它們的可視化信息能夠幫助你查看探測器的相對位置。放置光照探針須要你去調整他們的位置,直到你獲得一個你能夠接受的結果,就像光照貼圖的設置同樣。首先封裝將要包含動態對象的區域。
封裝區域
而後根據光照條件如何變化來添加更多的探針。你沒必要將它們放置在靜態幾何中。 也不要把它們放在不透明的單面幾何體錯誤的那一面。
放置更多的探測器
繼續添加和移動探測器,直到你在全部區域都有了合理的光照條件,而且在它們之間發生的轉換是能夠接受的。
調整探測器的位置
能夠經過移動動態對象來測試探針。當選擇一個動態對象的時候,也會顯示當前正在發揮做用的探針。探針將顯示其光照,而不只僅是黃色球體。你還能夠看到用於動態對象的內插數據。
移動動態對象
經過不一樣的光照探頭,物體的明暗變化明顯。