2017年8月7日 星期一

最佳實踐 - Unity碰撞效能優化

作者:William Armstrong 原文
潤稿:Kelvin Lo

Unity有一個名為Spotlight的團隊,有一群優秀的Unity開發人員一起研究並試著不斷的打破Unity的極限。針對各種複雜圖形效能和設計問題,我們不斷嘗試各種新的解決方案。

這個系列的文章會探討我們在和客戶合作時遇到的一些常見的問題。這些都是我們的合作團隊辛苦得出的經驗和教訓,我們很榮幸能夠和大家分享這些智慧。

這些問題很多只會出現在發佈到主機遊戲、手機遊戲、或者處理大量遊戲內容時才會出現。如果能在開發早期就將這些問題考慮進去,那麼開發過程就會更輕鬆,而遊戲也會做得更好。

較大的問題

有時候我們追蹤某個物理效能問題時,會歸咎到某一個問題的物件資源或設定上。常常查看Profiler並和前次執行結果比對,是發現這些問題的最佳方式。如果能儘早發現效能的減退,就可以透過查看最近所做的變更找到問題所在。

雖然運算單一簡單的物理
節(physics joint)速度很快,但背後的運算卻很複雜。本質上一個關節就是一套由剛體的位置、速度、加速度、旋轉等資訊組成的方程式。如果建立了一個帶有許多不同剛體的物件,而這些剛體又各自包含了許多會互相碰撞的關節。為了不讓這些關節不會互相穿透,要滿足這些關節的碰撞運算代價會非常大。

所以設計這種複雜關節時,要慎重考慮所需關節數量,碰撞類型以及必要的剛體數量。你可以用圖層(Layer)來過濾不必要的碰撞,也要謹慎評估哪些關節要打開"Allow Collision"來計算碰撞。透過限制關節的活動範圍可以減少碰撞檢測的量。或調整關節使它和其他關節不會發生碰撞。透過將關節和剛體作為插值方法的控制點來減少它們的使用數量。

使用Profiler可以看到某個特定時間啟動的剛體總數。觀察Rigidbody count的值
,尤其是當剛體互相接近時,會對效能產生很大的影響。在執行時放置或產生物件時,這個數字很容易會膨脹到超出預期。這就好比一罐汽水罐的碰撞很容易做,但如果罐子做成Prefab疊成超市裡的飲料塔,碰撞計算就很容易出大問題。


採用MeshCollider要非常小心,通常為了方便,開發者很容易會拿模型網格直接作為碰撞網格,但這可能會引起嚴重的效能下降,而且還不易察覺。PhsyX你給他甚麼他就算甚麼,所以如果一個小物體身上有高模或不合身的碰撞體,這時可能還好,但是如果將MeshCollider放大到容易被RayCast偵測運算的環境中,你會發現效能會驟然下降。如果非要用MeshCollider,建議是對於所有可能會有碰撞運算的物體,另外製作一組低模的碰撞網格。如果網格有問題又沒時間製作自訂網格,可以將MeshCollider設定裡的Convex打勾,並調整SkinWidth來取得一個比較適合的低面碰撞網格。

擁有多個重疊碰撞器的影子戰術NPC

較小的問題

通常開發者都會明智的避開不做大或慢的事情。但很多常見的情況是,專案所做的一系列理性決策,每個都一點一滴無形的影響了PhysX的效能。尤其是製作大規模遊戲時很容易出現這種狀況。例如你用5個方塊做關卡AI測試時表現很OK。但相同的AI放到實際的關卡時,大量的Physics.Update運算造成效能不好,而你卻不知道問題在哪? 


"明明測試都很OK的,為甚麼上正式版就壞了?" -- by 工程師


你有做足夠的測試了嗎?
可以從ProjectSettings->Physics裡找到碰撞圖層過濾(Layer Collision Matrix)來檢查是否存在不必要的碰撞規劃。透過打勾/取消打勾那些方塊,可以控制碰撞體之間的碰撞行為。這個功能會在碰撞發生前先進行計算,進而避開不必要的碰撞計算。許多遊戲使用”Is Trigger”為true的大型碰撞器來檢測角色或其他物件,這通常被稱為觸發器。通常這些觸發器會設定和預設圖層或所有物件發生碰撞計算。透過將角色放到特定層,並將這些觸發器放到某個只會與角色層發生碰撞計算的層,可以避免大體積碰撞器與複雜的世界網格或地形網格發生碰撞計算。

觸發器(Trigger)是否是使所有物件慢下來的原因?
當碰撞器屬性的“Is Trigger”設為true,它依然是一個需要進行碰撞計算的碰撞器。因此移動一個觸發器和移動一個碰撞器所產生的消耗都是一樣的,會需要做很多工作來發送碰撞和重疊事件。如果要在每幀都移動觸發器,確保它只對必須的物件進行碰撞計算。盡可能將觸發器做小,並將相似或重疊的觸發器分組。

我們常見到專案在NPC上使用多個大型觸發器用來檢查是否有互動。NPC的每種類型的遊戲物件都會有自己的碰撞器,以及一堆放在OnCollision中的程式,用來找到正確的互動物件。通常將多個觸發器合併一起會比較快,然後在OnCollision函式中用Tag或Layer或距離來進行過濾。很多情況下可以透過完全繞過碰撞計算來獲得更好的效能。而不必每幀都對球體與世界的碰撞進行計算,而是將所有希望存取的物件註冊到一個共用管理器,然後讓NPC對所有已註冊物件做簡單的距離計算。如果有一小堆需要檢測的潛在目標。這種方法要比對世界中所有的碰撞做檢測效能要高的多。

物件在Hierarchy裡的結構會導致PhysX做了額外的工作嗎?
在《ReCore》這個遊戲裡,我們發現場景中圓形旋轉平臺上的每一個平台都擁有自己的剛體。這導致了每個平台都會與其他平台發生碰撞檢測。將所有的平臺歸於同一父物件之下,將剛體賦予父物件,讓父物件進行旋轉,在Physics.Update中節省了大量的時間。
注意:將一個共用剛體下的碰撞器合併會增加Raycast對它或形狀投射檢測的開銷。

從另一個方面來說,如果一個共用的剛體父物件下已經有幾個碰撞器,你需要非常小心,不要讓它們做相對於父物件的移動。因為任何時候剛體改變形狀,會造成中心的質量和慣性張量(Inertial Tensor)
都必須重新計算,這需要大量的時間。當多個剛體連接到動畫角色的四肢時,常常會出現這種問題。可以通過設定自己的質心來關閉它。只要遊戲物件的形狀不要變化太大就沒問題。

這堆小小的系統問題累積起來是很可怕的,所以在開發中要時刻牢記,並在一開始就做好計畫規避這些問題。如果突然發現Physics時間出現一個巨大峰值,那就分析下當下的修改有哪些並思考對應方式。

最後感謝Mimimi Productions和Armature Studio 讓我們用他們的遊戲作為例子。我們還會為大家分享更多最佳實踐系列文章。

2017年7月31日 星期一

Unity 5 Shader系統程式介紹

Unity在Shader(著色器)開發方面提供了很大的靈活性。有些工具需要你編寫一個“合適”的自訂著色器(合適意思就是無法在節點編輯器裡完成,必須要寫程式),相較來說麻煩程度可算是相當輕量。不過,完全基於Deferred渲染器,代表我們無法像在Forward著色器中那樣可以放開手腳蠻幹,必須受限於g-buffer中包含的那些無法進行更改的資訊。

所以說如果你對Unity 5的標準著色器滿意,想拿來加點東西,比如一個額外的著色器屬性或修改某些功能,或重新製作你自己的著色器系統,需要牽涉到陰影、全域光照、光照貼圖、直接光照等等。

最主要的問題是,無法對標準著色器進行編輯。你無法直接修改標準著色器的程式。你得先下載你所安裝的Unity版本的對應著色器原始碼。

如何獲得標準著色器程式碼

具體做法是確認你所安裝的Unity 5版本,然後訪問Unity Download Archive網頁,在下拉清單中選擇平台(Win/Mac)的“Built in Shaders”,就可以下載zip檔。

概述

Zip檔中包含了標準著色器的完整原始碼,包括特殊的視圖UI以及它包含的所有內容共四個資料夾:
  • CGIncludes
  • DefaultResources
  • DefaultResourcesExtra
  • Editor

Editor僅包含實現標準著色器檢視視圖UI的.cs文件。
CGIncludes裡面包含了所有其他著色器所需要的函數。我們會仔細研究它們,因為我們將會用到那些函數。
DefaultResources和DefaultResourcesExtra 包含了許多適用於不同情況的著色器。

下面來學習如何解讀標準著色器,然後依次查看各個子系統,直接光照、陰影、全域光照等等。本文以Unity 5.4版本為例,建議大家採用Unity 5.3或以上版本,因為新版Unity將光照模型(BRDF)從Phong改為了GGX。Phong簡單快速,各向同性,但是表現能力有限;GGX較為複雜,支援各向異性,並且有接近現實世界的高光效果,表現能力強。這是一個很大的改進,使我們可以製作更多有趣的範例。

追蹤pragma

回到著色器程式碼。從一個簡單的標準著色器開始:DefaultResourcesExtra\Standard.shader。

打開這個檔案後會發現,它是一個Surface著色器,包含一個Properties部分以及不同的著色器pass。它針對
DeferredForward渲染器還有不同的pass。

讓我們分析
Forward渲染器(Forward渲染器有兩個Pass,Base Pass針對第一個光源,另一個Add Pass針對所有其他光源)的Base Pass:


[C#]

// ------------------------------------------------------------------
        //  Base forward pass (directional light, emission, lightmaps, ...)
        Pass
        {
            Name "FORWARD"
            Tags { "LightMode" = "ForwardBase" }
            Blend [_SrcBlend] [_DstBlend]
            ZWrite [_ZWrite]
            CGPROGRAM
            #pragma target 3.0
            // -------------------------------------
            #pragma shader_feature _NORMALMAP
            #pragma shader_feature _ _ALPHATEST_ON _ALPHABLEND_ON _ALPHAPREMULTIPLY_ON
            #pragma shader_feature _EMISSION
            #pragma shader_feature _METALLICGLOSSMAP
            #pragma shader_feature ___ _DETAIL_MULX2
            #pragma shader_feature _ _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
            #pragma shader_feature _ _SPECULARHIGHLIGHTS_OFF
            #pragma shader_feature _ _GLOSSYREFLECTIONS_OFF
            #pragma shader_feature _PARALLAXMAP
            #pragma multi_compile_fwdbase
            #pragma multi_compile_fog
            #pragma vertex vertBase
            #pragma fragment fragBase
            #include "UnityStandardCoreForward.cginc"
            ENDCG
        }



如你所見,它基本上由一堆pragma和定義組成。在ubershader樣式中,那些pragma啟動了在不同包含檔中的程式碼片段。因此要瞭解這個pass中實際發生的事情,必須打開CGIncludes\UnityStandardCoreForward.cginc,並逐一查看每個程式碼片段中的每個pragma。這個過程太長,需要太多解釋很多,所以現在還是讓我們專注於尋找基本函數,即主要的光照計算過程發生的地方。

CGIncludes\UnityStandardCoreForward.cginc的作用僅僅是將在其他cginclude中包含的東西連在一起。在這裡,它負責根據定義UNITY_STANDARD_SIMPLE,設定好要使用的頂點與片段函數。

下面看看更簡單的那個CGIncludes\UnityStandardCoreForwardSimple.cginc。它很“簡單”,因為它不支持PARALLAXMAP、DIRLIGHTMAPCOMBINED、DIRLIGHTMAP_SEPARATE,因此解讀起來也相對簡單。

基礎函數與結構體

最後,這個檔裡還有一些函數與結構體,基礎的有以下這些:
  • struct VertexOutputBaseSimple,這個資料結構用於保存從頂點著色器向片段著色器傳送的資料。
  • vertForwardBaseSimple 是在每個頂點上都會執行的函數,填充 VertexOutputBaseSimple結構體。
  • fragForwardBaseSimpleInternal,正向渲染器中,接受頂點輸出結構體,並計算第一個光源的函數。

片段函數

它回傳一個向量,其中包含四個half精度浮點數(一個顏色和透明度),它接受一個VertexOutputBaseSimple結構體:

[C#]

half4 fragForwardBaseSimpleInternal (VertexOutputBaseSimple i) 
{
    FragmentCommonData s = FragmentSetupSimple(i);
    UnityLight mainLight = MainLightSimple(i, s);  
    half atten = SHADOW_ATTENUATION(i);
    half occlusion = Occlusion(i.tex.xy);
    half rl = dot(REFLECTVEC_FOR_SPECULAR(i, s), LightDirForSpecular(i, mainLight));
    UnityGI gi = FragmentGI (s, occlusion, i.ambientOrLightmapUV, atten, mainLight);
    half3 attenuatedLightColor = gi.light.color * mainLight.ndotl;
    half3 c = BRDF3_Indirect(s.diffColor, s.specColor, gi.indirect, PerVertexGrazingTerm(i, s), PerVertexFresnelTerm(i));
    c += BRDF3DirectSimple(s.diffColor, s.specColor, s.oneMinusRoughness, rl) * attenuatedLightColor;
    c += UNITY_BRDF_GI (s.diffColor, s.specColor, s.oneMinusReflectivity, s.oneMinusRoughness, s.normalWorld, -s.eyeVec, occlusion, gi);
    c += Emission(i.tex.xy);
    UNITY_APPLY_FOG(i.fogCoord, c);
    return OutputForward (half4(c, 1), s.alpha);
}


從程式碼中可以看到,它收集了所需的資訊,並對直接與間接光的貢獻、霧、衰減、自發光和遮蔽進行了計算。

這些過程已被封裝,因此我們需要依次查看這些函數,才能瞭解它們的實際用途,以及程式碼的具體作用。

追蹤更多的函數

對光源進行實際計算的函數並不在此檔中,部分在CGIncludes/UnityStandardCore.cginc中:
  • MainLight (實際上用於 UnityStandardCoreForward.cginc中的MainLightSimple)

[C#]

UnityLight MainLightSimple(VertexOutputBaseSimple i, FragmentCommonData s) 
{
    UnityLight mainLight = MainLight(s.normalWorld);
    #if defined(LIGHTMAP_OFF) && defined(_NORMALMAP)
        mainLight.ndotl = LambertTerm(s.tangentSpaceNormal, i.tangentSpaceLightDir);
    #endif
    return mainLight;
}



我們能看到那裡對LambertTerm進行了計算,但僅在光照貼圖關閉且法線貼圖打開時會這樣。

CGIncludes/UnityStandardBRDF.cginc:

  • BRDF3DirectSimple (使用BRDF3Direct)


[C#]

half3 BRDF3_Direct(half3 diffColor, half3 specColor, half rlPow4, half oneMinusRoughness) 
{
    half LUT_RANGE = 16.0; // must match range in NHxRoughness() function in GeneratedTextures.cpp
    // Lookup texture to save instructions
    half specular = tex2D(unity_NHxRoughness, half2(rlPow4, 1-oneMinusRoughness)).UNITY_ATTEN_CHANNEL * LUT_RANGE;
#if defined(_SPECULARHIGHLIGHTS_OFF)
    specular = 0.0;
#endif


它看起來在使用查表法計算鏡面反射的貢獻。

  • LambertTerm, 導向到DotClamped


[C#]

inline half DotClamped (half3 a, half3 b) 
{
    #if (SHADER_TARGET < 30 || defined(SHADER_API_PS3))
        return saturate(dot(a, b));
    #else
        return max(0.0h, dot(a, b));
    #endif
}



從MainLightSimple我們得知,傳入的參數是N和L。所以片段函數首先設定好片段,計算主光源ndotl、衰減、遮蔽、全域光照以及燈光顏色。然後計算最終光線的所有貢獻,並將直接、間接、全域光照加總後再計算霧。

如你所見,著色器在光照計算方面相當少,只使用了一個Lambert演算法,並查了一下表。它不像基於物理的著色器使用的那麼多,這很可能是最精簡的標準著色器版本了。



重新回到Standard.shader,這次在UnityStandardCoreForward.shader中,我們選擇另一個“不簡單”的那個分支。它將我們引向UnityStandardCore.shader,而我們感興趣的是fragForwardBaseInternal函數。


[C#]

half4 fragForwardBaseInternal (VertexOutputForwardBase i) 
{
    FRAGMENT_SETUP(s)
#if UNITY_OPTIMIZE_TEXCUBELOD
    s.reflUVW       = i.reflUVW;
#endif
 
    UnityLight mainLight = MainLight (s.normalWorld);
    half atten = SHADOW_ATTENUATION(i);
 
 
    half occlusion = Occlusion(i.tex.xy);
    UnityGI gi = FragmentGI (s, occlusion, i.ambientOrLightmapUV, atten, mainLight);
 
    half4 c = UNITY_BRDF_PBS (s.diffColor, s.specColor, s.oneMinusReflectivity, s.oneMinusRoughness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect);
    c.rgb += UNITY_BRDF_GI (s.diffColor, s.specColor, s.oneMinusReflectivity, s.oneMinusRoughness, s.normalWorld, -s.eyeVec, occlusion, gi);
    c.rgb += Emission(i.tex.xy);
 
    UNITY_APPLY_FOG(i.fogCoord, c.rgb);
    return OutputForward (c, s.alpha);
}


拿來做參考的簡單版本:


[C#]

half3 c = BRDF3_Indirect(s.diffColor, s.specColor, gi.indirect, PerVertexGrazingTerm(i, s), PerVertexFresnelTerm(i)); 
    c += BRDF3DirectSimple(s.diffColor, s.specColor, s.oneMinusRoughness, rl) * attenuatedLightColor;
    c += UNITY_BRDF_GI (s.diffColor, s.specColor, s.oneMinusReflectivity, s.oneMinusRoughness, s.normalWorld, -s.eyeVec, occlusion, gi);


與上一節中的版本不同,最終的顏色由對UNITY_BRDF_PBS、UNITY_BRDF_GI和Emission的呼叫結果相加得出。

Emission與簡單版本中的相同。UNITY_BRDF_PBS和UNITY_BRDF_GI是包含檔中定義的函式別名。在下面這些包含檔中進行查找:


[C#]

#include "UnityCG.cginc"
#include "UnityShaderVariables.cginc"
#include "UnityInstancing.cginc"
#include "UnityStandardConfig.cginc"
#include "UnityStandardInput.cginc"
#include "UnityPBSLighting.cginc"
#include "UnityStandardUtils.cginc"
#include "UnityStandardBRDF.cginc"
 
#include "AutoLight.cginc"


UnityStandardBRDF和UnityPBSLighting看起來最像,所以先查看它們。它們就在UnityPBSLighting.cginc中,不同的著色器目標會選擇不同的函式。

選擇BRDF1_Unity_PBS,它就在UnityStandardBRDF.cginc中,它看起來是最逼真的可用BRDF,而BRDF3_Unity_PBS則是消耗最低的版本。

如你所見,這是個大函式,因此跳過一些與優化相關的細節,依次逐塊的進行講解,首先從這個非常有用的注釋開始:



[C#]

// Main Physically Based BRDF
// Derived from Disney work and based on Torrance-Sparrow micro-facet model
//
//   BRDF = kD / pi + kS * (D * V * F) / 4
//   I = BRDF * NdotL
//
// * NDF (depending on UNITY_BRDF_GGX):
//  a) Normalized BlinnPhong
//  b) GGX
// * Smith for Visiblity term
// * Schlick approximation for Fresnel


注解給出了使用的公式,以及引用與作用。NDF(法線分佈函數)有多個選擇,但這裡僅介紹GGX,因為我覺得它更好。

下面對注解中的公式進行簡單的介紹:

  • kD: 漫反射率
  • pi: π常量
  • kS: 鏡面反射率
  • D: 法線分佈
  • V: 幾何可見度係數
  • F: 菲涅爾反射率

自訂光照函數:


[C#]

half4 BRDF1_Unity_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half oneMinusRoughness, 
    half3 normal, half3 viewDir,
    UnityLight light, UnityIndirect gi)
{
    half roughness = 1-oneMinusRoughness;


將光滑度轉換為粗糙度。


[C#]

half3 halfDir = Unity_SafeNormalize (light.dir + viewDir);


half向量。

正確處理NdotV(查看檔中的注解):


[C#]

half nl = DotClamped(normal, light.dir);
 
half nh = BlinnTerm (normal, halfDir);
half nv = DotClamped(normal, viewDir);
 
half lv = DotClamped (light.dir, viewDir);
half lh = DotClamped (light.dir, halfDir);


計算 V 和 D:

[C#]

half V = SmithJointGGXVisibilityTerm (nl, nv, roughness);
   half D = GGXTerm (nh, roughness);


根據Disney BRDF,計算漫反射項,以及鏡面反射係數:


[C#]

half disneyDiffuse = (1 + (Fd90-1) * nlPow5) * (1 + (Fd90-1) * nvPow5);
   half specularTerm = (V * D) * (UNITY_PI/4); // Torrance-Sparrow model, Fresnel is applied later (for optimization reasons)
   //HACK (see file for more comments)
   specularTerm = max(0, specularTerm * nl);
   half diffuseTerm = disneyDiffuse * nl;
 
   // surfaceReduction = Int D(NdotH) * NdotH * Id(NdotL>0) dH = 1/(realRoughness^2+1)
   half realRoughness = roughness*roughness;       // need to square perceptual roughness
   half surfaceReduction = 1.0 / (realRoughness*realRoughness + 1.0);          // fade \in [0.5;1]
 
   half grazingTerm = saturate(oneMinusRoughness + (1-oneMinusReflectivity));


將所有的加總,包括全域光照貢獻:

[C#]

 half3 color =    diffColor * (gi.diffuse + light.color * diffuseTerm)
                    + specularTerm * light.color * FresnelTerm (specColor, lh)
                    + surfaceReduction * gi.specular * FresnelLerp (specColor, grazingTerm, nv);
 
    return half4(color, 1);
}


以上就是光照函數的全部。下面來深入介紹全域光照對最終結果的貢獻。

本節我們將介紹全域光照貢獻的計算方式。過程有些麻煩,因為進行關鍵計算的程式碼隱匿在品質選擇層的層層定義之後。

所以讓我們查看下所有與全域光照有關的函數和結構體,它們就位於我們前面三節提及的代碼中。

在UnityStandardCore.cginc中, fragForwardBaseInternal:



[C#] 


UnityGI gi = FragmentGI (s, occlusion, i.ambientOrLightmapUV, atten, mainLight); 
half4 c = UNITY_BRDF_PBS (s.diffColor, s.specColor, s.oneMinusReflectivity, s.oneMinusRoughness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect); 

    c.rgb += UNITY_BRDF_GI (s.diffColor, s.specColor, s.oneMinusReflectivity, s.oneMinusRoughness, s.normalWorld, -s.eyeVec, occlusion, gi);

在我們的片段Forward基本函數中,FragmentGI被用於計算全域光照資料:“gi”,它被傳遞給UNITY_BRDF_PBS 和UNITY_BRDF_GI (它們的定義分別對應著不同的品質級別)。

在UnityStandardBRDF.cginc中, BRDF1_Unity_PBS:



[C#]

half4 BRDF1_Unity_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half oneMinusRoughness, 
    half3 normal, half3 viewDir,
    UnityLight light, UnityIndirect gi)
{
[...]
    half3 color =    diffColor * (gi.diffuse + light.color * diffuseTerm)
                    + specularTerm * light.color * FresnelTerm (specColor, lh)
                    + surfaceReduction * gi.specular * FresnelLerp (specColor, grazingTerm, nv);
    return half4(color, 1);
}


這是UNITY_BRDF_PBS部分,它接受gi,用它來計算著色圖元的顏色。

以下兩個定義至少需要定義一個:

  • LIGHTMAP_ON
  • DYNAMICLIGHTMAP_ON

還有一堆額外的定義,用來控制程式的跳轉,或決定函數的選擇:
  • DIRLIGHTMAP_SEPARATE
  • DIRLIGHTMAP_COMBINED
  • UNITY_BRDF_PBS_LIGHTMAP_INDIRECT
  • UNITY_BRDF_GI
  • UNITY_SHOULD_SAMPLE_SH
  • UNITY_SPECCUBE_BLENDING
  • UNITY_SPECCUBE_BOX_PROJECTION
  • _GLOSSYREFLECTIONS_OFF
  • UNITY_SPECCUBE_BOX_PROJECTION

全域光照資料的運作基本是這樣的,從基本結構體流向與定義相關的函數:

  • 結構體UnityGI (在UnityLightingCommon.cginc中) 保存著多個UnityLight,取決於光照貼圖的類型
  • 結構體UnityGIInput (在UnityLightingCommon.cginc中) 保存著計算GI所需的其他不同資訊,被用於許多函式中
  • 函式UNITY_BRDF_GI (在UnityPBSLighting.cginc中) 在fragForwardBaseInternal 中用於計算對BRDF的間接貢獻(通過呼叫BRDF_Unity_Indirect)
  • 函數BRDF_Unity_Indirect(在UnityPBSLighting.cginc中)將UNITY_BRDF_PBS_LIGHTMAP_INDIRECT 的結果與傳入的colour相加
  • 函數UNITY_BRDF_PBS_LIGHTMAP_INDIRECT (在UnityPBSLighting.cginc中) 被定義為BRDF2_Unity_PBS (但一條注釋說也可以使用BRDF1_Unity_PBS ,以獲得更佳品質)
  • 函式BRDF2_Unity_PBS 或BRDF1_Unity_PBS,我們在前面一節中見過。這裡用於計算間接貢獻
  • 函數FragmentGI (在UnityStandardCore.cginc中) 填充必要的資料,包括來自反射探針的資料,然後傳遞給UnityGlobalIllumination
  • 函式UnityGlobalIllumination:(4個版本,不同的簽名)傳遞資料給UnityGI_Base 和UnityGI_IndirectSpecular
  • 函式UnityGI_Base(在UnityGlobalIllumination.cginc中)對光照貼圖進行採樣和解碼,混合即時衰減和應用遮蔽
  • 函數UnityGI_IndirectSpecular(在UnityGlobalIllumination.cginc中),計算反射,對盒型投影進行矯正(如果已啟動),處理遮擋

對於瞭解全貌同樣有用的東西:
  • 結構體UnityIndirect(在UnityLightingCommon.cginc中) 僅包含一個漫反射和一個鏡面反射顏色
  • 結構體UnityLight(在UnityLightingCommon.cginc中)保存光源的顏色、方向和NdotL
  • 貼圖立方體unity_SpecCube0 和unity_SpecCube1: 反射探針
  • 結構體Unity_GlossyEnvironmentData: 保存粗糙度和反射UV
  • 函數ResetUnityGI: 清空一個UnityGI結構體
  • 函數ResetUnityLight: 清空一個UnityLight 結構體
  • 函數ShadeSHPerPixel: 對每個圖元進行Spherical Harmonics採樣
這些知識應該已足以讓你在修改標準著色器時,不會意外的將全域光照玩壞XD。

關於我自己

我的相片
Unity台灣官方部落格 請上Facebook搜尋Unity Taiwan取得Unity中文的最新資訊