翻譯18 Realtime GI & LPPV & LOD

支持實時全局光照
用動畫控制發光對GI的貢獻
使用光照探針代理體LPPV
LOD組與GI結合
LOD之間的淡入淡出html

從如今開始,這個系列教程將由Unity 2017.1.0f3來完成。它將不適用於Unity的舊版本,由於咱們要使用一個新的着色器函數。緩存

靜態LOD組和實時全局光照混合後的完整預覽app

1 實時全局光照

  烘焙光照在靜態物體上工做的很是好,對於動態幾何體,因爲有光照探針的緣故,烘焙光照這種方法也能工做的很是好。可是,烘焙光照不能處理動態光源。混合模式的光源能夠經過一些實時的調節來消除,但調節的太多使得烘焙出來的間接光照不會改變。因此當你有一個戶外場景的話,使用烘焙光照這種方法太陽的光照就不能有變化。太陽不能像在現實生活中同樣在天空中移動,由於若是須要太陽在天空中移動的話,就須要逐漸變化的全局光照。因此場景必須一直不變。編輯器

  爲了使間接光照可以在移動的太陽這樣的狀況發揮做用,Unity使用Enlighten系統來計算實時全局光照。除了在運行時計算光照和光照探針之外,它還採用烘焙間接光照同樣的方式來工做。ide

  瞭解間接光須要知道光在靜態表面之間如何反射。重點在於哪些表面可能會受到其餘表面的影響,以及程度如何。弄清這些關係須要作不少的工做,不能實時完成。因此這個數據由編輯器處理並存儲在運行時使用。而後 Enlighten系統會使用這個數據來計算實時光照貼圖和探針數據。即便如此,只有低分辨率的光照貼圖才能夠在實時狀況下運行。函數

1.1 啓用全局光照

實時全局光照、烘焙全局光照均可以獨立啓用。你能夠同時啓用兩個,或者啓用其中的一個,或者兩個都不啓用。這兩個選項都是經過「光照」窗口的「實時照光照」部分中的複選框啓用。工具

實時全局光照和烘焙光照同時啓用的狀態測試

要實際查看實時全局光照,請將測試場景中的主光源的模式設置爲實時模式。 因爲咱們沒有其餘光源,即便啓用了烘焙光照也能有效地關閉。優化

主光源設置爲實時模式動畫

確保場景中的全部對象都使用咱們的白色材質。 像上次同樣,球體都是動態的,而其餘的都是靜態幾何體。

只有動態對象能接收實時的全局光照

事實證實,只有動態對象會受益於實時全局光照。靜態物體會變的暗一點。這是由於光照探針自動併入實時全局光照。而靜態對象必須對實時的光照貼圖進行採樣,而這些光照貼圖與烘焙的光照貼圖不一樣。咱們的着色器還不支持。

1.2 烘焙的全局光照

Unity在編輯模式下已經生成了實時的光照貼圖,因此你能夠隨時查看實時的全局光照貼圖。在編輯模式和播放模式之間進行切換的時候,這些貼圖不會被保留,可是它們最終會獲得相同的結果。你能夠經過「光照」窗口的「對象貼圖」選項來選擇一個光照貼圖靜態對象對實時光照貼圖進行檢查。 選擇「實時強度「能夠可視化的查看實時光照貼圖的數據。

實時光照貼圖,屋頂被選中時候的狀態

雖然實時光照貼圖已經被烘焙出來,而且它們還可能顯示正確,但咱們的meta渲染通道實際上使用的是錯誤的座標。實時全局光照具備本身的光照貼圖座標最終可能與靜態光照貼圖的座標不一樣Unity會根據光照貼圖和對象的設置來自動生成這些座標。這些數據存儲在第三套UV中。因此將這些數據添加到My Lightmapping中的VertexData裏面。

struct VertexData {
    float4 vertex : POSITION;
    float2 uv : TEXCOORD0;
    float2 uv1 : TEXCOORD1;
    float2 uv2 : TEXCOORD2;
};

如今,MyLightmappingVertexProgram必須使用第二個或是第三個UV座標,以及靜態或動態光照貼圖的大小和偏移量。 咱們能夠依靠UnityMetaVertexPosition函數來使用正確的數據。

Interpolators MyLightmappingVertexProgram (VertexData v) {
    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);
    i.pos = UnityMetaVertexPosition(
        v.vertex, v.uv1, v.uv2, unity_LightmapST, unity_DynamicLightmapST
    );

    i.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);
    i.uv.zw = TRANSFORM_TEX(v.uv, _DetailTex);
    return i;
}

UnityMetaVertexPosition是什麼樣子的?

它除了經過unity_MetaVertexControl提供的標誌來決定使用哪些座標集和光照貼圖以外,它還作了咱們之前作的工做。

float4 UnityMetaVertexPosition (
    float4 vertex, float2 uv1, float2 uv2,
    float4 lightmapST, float4 dynlightmapST
) {
    if (unity_MetaVertexControl.x) {
        vertex.xy = uv1 * lightmapST.xy + lightmapST.zw;
        // OpenGL right now needs to actually use incoming vertex position,
        // so use it in a very dummy way
        vertex.z = vertex.z > 0 ? 1.0e-4f : 0.0f;
    }
    if (unity_MetaVertexControl.y) {
        vertex.xy = uv2 * dynlightmapST.xy + dynlightmapST.zw;
        // OpenGL right now needs to actually use incoming vertex position,
        // so use it in a very dummy way
        vertex.z = vertex.z > 0 ? 1.0e-4f : 0.0f;
    }
    return UnityObjectToClipPos(vertex);
}

請注意,meta渲染通道既用於烘焙光照貼圖,也用於實時光照貼圖因此當使用實時全局光照的時候,meta渲染通道也將被包含在構建中

1.3 對實時光照貼圖進行採樣

爲了對實時光照貼圖進行採樣,咱們還必須將第三個UV座標添加到My Lightmapping中的VertexData裏面。

struct VertexData {
    float4 vertex : POSITION;
    float3 normal : NORMAL;
    float4 tangent : TANGENT;
    float2 uv : TEXCOORD0;
    float2 uv1 : TEXCOORD1;
    float2 uv2 : TEXCOORD2;
};

當一張實時光照貼圖被使用的時候,咱們必須將這個光照貼圖的座標添加到咱們的插值器中去。標準着色器在單個插值器中將兩個光照貼圖的座標集合組合起來 - 與其餘數據複用 - 可是咱們能夠爲二者準備單獨的插值器。當DYNAMICLIGHTMAP_ON關鍵字被定義的時候,咱們知道有動態光照數據。它是multi_compile_fwdbase編譯器指令的關鍵字列表的一部分。

struct Interpolators 
{
    …
    #if defined(DYNAMICLIGHTMAP_ON)
        float2 dynamicLightmapUV : TEXCOORD7;
    #endif
};

填充座標就像對靜態光照貼圖的座標所作的事情同樣,除了動態光照圖的縮放比例和偏移量的設置之外,這些能夠經過unity_DynamicLightmapST變得可用。

Interpolators MyVertexProgram (VertexData v) {
    …
    #if defined(LIGHTMAP_ON) || ADDITIONAL_MASKED_DIRECTIONAL_SHADOWS
        i.lightmapUV = v.uv1 * unity_LightmapST.xy + unity_LightmapST.zw;
    #endif

    #if defined(DYNAMICLIGHTMAP_ON)
        i.dynamicLightmapUV = v.uv2 * unity_DynamicLightmapST.xy + unity_DynamicLightmapST.zw;
    #endif
    …
}

對實時光照貼圖的採樣是在咱們的CreateIndirectLight函數中完成的。複製 #if defined(LIGHTMAP_ON) 代碼塊並進行一些更改。 首先,新的部分是基於DYNAMICLIGHTMAP_ON關鍵字的。 此外,它應該使用DecodeRealtimeLightmap而不是DecodeLightmap,這是由於實時光照貼圖使用不一樣的顏色格式。並且由於這些數據可能被添加到烘焙光照中,不要當即分配給indirectLight.diffuse,而是使用最後添加的中間變量 最後,當不使用烘焙光照貼圖和實時光照貼圖的時候,咱們只應該對球面諧波進行採樣。

    #if defined(LIGHTMAP_ON)
        indirectLight.diffuse =    DecodeLightmap(UNITY_SAMPLE_TEX2D(unity_Lightmap, i.lightmapUV));
        #if defined(DIRLIGHTMAP_COMBINED)
            float4 lightmapDirection = UNITY_SAMPLE_TEX2D_SAMPLER(
                unity_LightmapInd, unity_Lightmap, i.lightmapUV
            );
            indirectLight.diffuse = DecodeDirectionalLightmap(
                indirectLight.diffuse, lightmapDirection, i.normal
            );
        #endif

        ApplySubtractiveLighting(i, indirectLight);
//    #else
//        indirectLight.diffuse += max(0, ShadeSH9(float4(i.normal, 1)));
    #endif

    #if defined(DYNAMICLIGHTMAP_ON)
        float3 dynamicLightDiffuse = DecodeRealtimeLightmap(
            UNITY_SAMPLE_TEX2D(unity_DynamicLightmap, i.dynamicLightmapUV)
        );
        #if defined(DIRLIGHTMAP_COMBINED)
            float4 dynamicLightmapDirection = UNITY_SAMPLE_TEX2D_SAMPLER(
                unity_DynamicDirectionality, unity_DynamicLightmap,
                i.dynamicLightmapUV
            );
                   indirectLight.diffuse += DecodeDirectionalLightmap(
                       dynamicLightDiffuse, dynamicLightmapDirection, i.normal
                   );
        #else
            indirectLight.diffuse += dynamicLightDiffuse;
        #endif
    #endif

    #if !defined(LIGHTMAP_ON) && !defined(DYNAMICLIGHTMAP_ON)
        indirectLight.diffuse += max(0, ShadeSH9(float4(i.normal, 1)));
    #endif

image

把實時全局光照應用於一切物體之上

如今咱們的着色器使用的是實時光照貼圖。最初,當使用Distance Shadowmask模式的時候,它的效果可能看起來與使用混合光源的烘焙光照的效果相同。當在播放模式下關閉光源的時候,差別就變得很是明顯。

image

禁用混合光源之後,間接光照仍然被保留

禁用混合光源之後,其間接光照將保持不變。相比之下,實時光照的間接貢獻就會消失,並從新出現 - 這是應該出現的狀況。 不過,新狀況的徹底烘焙好可能須要一段時間。 Enlighten系統會逐步調整光照貼圖和光照探針。 這種狀況發生的速度取決於場景的複雜性和實時全局光照CPU質量設置。

realtime to static

切換實時光與實時GI

全部實時光源都對實時全局光照有貢獻。 然而,它的典型用途是那些僅在主要方向上存在光線的光源,好比能夠表明太陽,由於它在天空中移動。它適用於方向光源。點光源和聚光光源也能工做,但只是沒有陰影。因此當使用帶有陰影的點光源或聚光光源的時候,你可能會遇到不正確的間接光照結果

image image

沒有影響的間接光源和實時的聚光光源

若是要從實時全局光照裏面去掉一個實時光源,能夠經過設置它的Indirect Multiplier將它的光強度設置爲零。

1.4 自發光光源

實時全局光照也能夠用於自發光的靜態物體。這使得能夠匹配實時間接光照來改變物體的自發光變得可能。讓咱們來試試看吧。向場景中添加一個靜態球體,並賦予它一個使用咱們着色器的材質,這個材質具備黑色的反照率和白色的自發光顏色。最初,咱們只能看到經過靜態光照貼圖實現的自發光的間接效果。

image

用自發光球來烘焙全局光照

要將自發光光源烘焙到靜態光照貼圖中,咱們必須在咱們的着色器的GUI中設置材質的全局光照標誌。由於咱們老是將標誌設置爲BakedEmissive,光源最終將以烘焙好的光照貼圖的形式出現。若是自發光光源是恆定的這個效果是很不錯的,但這樣就不容許咱們作動畫控制。

爲了同時對自發光光源支持烘焙和實時光照,咱們必須使其可配置化。咱們能夠經過向MyLightingShaderGUI中添加一個選項來作到這一點,使用的是MaterialEditor.LightmapEmissionProperty方法。這個方法的單個參數是屬性的縮進級別。

    void DoEmission () {
        MaterialProperty map = FindProperty("_EmissionMap");
        Texture tex = map.textureValue;
        EditorGUI.BeginChangeCheck();
        editor.TexturePropertyWithHDRColor(
            MakeLabel(map, "Emission (RGB)"), map, FindProperty("_Emission"),
            emissionConfig, false
        );
        editor.LightmapEmissionProperty(2);
        if (EditorGUI.EndChangeCheck()) {
            if (tex != map.textureValue) {
                SetKeyword("_EMISSION_MAP", map.textureValue);
            }

            foreach (Material m in editor.targets) {
                m.globalIlluminationFlags =
                    MaterialGlobalIlluminationFlags.BakedEmissive;
            }
        }
    }

每次當自發光屬性發生改變的時候,咱們也必須中止覆蓋這個標誌位。其實真正要作的事情比這更復雜一點。其中一個標誌選項是EmissiveIsBlack,這個表示表示的是自發光計算能夠跳過。這個標誌老是會針對新材質進行設置。要讓間接自發光可以工做,咱們必須保證這個標誌不被設置,不管咱們選擇實時光照仍是烘焙。咱們能夠經過老是屏蔽標誌值的EmissiveIsBlack位來作到這一點。

foreach (Material m in editor.targets) {
    m.globalIlluminationFlags &= ~MaterialGlobalIlluminationFlags.EmissiveIsBlack;
}

image

image

帶有自發光球的實時全局光照效果

烘焙全局光照和實時全局光照之間的視覺差別主要是由於實時光照貼圖一般具備比烘焙全局光照更低的分辨率。因此當自發光不發生不變化的時候,你也可使用烘焙全局光照,確保可以利用其更高的分辨率。

EmissiveIsBlack的目的是什麼?

這是一個優化,使得計算能夠跳過全局光照烘焙過程。然而,只有當自發光顏色確實是黑色的時候,它才依賴於標誌。因爲這個標誌位由着色器的GUI進行設置,這是當材質在檢視器裏面進行編輯的時候肯定的。或者至少,這是Unity的標準着色器的作法。所以,若是自發光顏色稍後被腳本或動畫系統更改,則該標誌位不會作相應的調整。這是許多人不理解爲何對自發光作動畫不會影響到實時全局光照的緣由。結果就是若是你想在運行時更改自發光顏色,那麼就不要將自發光顏色設置爲純黑色。

咱們沒有使用這種方法,咱們使用的是LightmapEmissionProperty,它還提供了對自發光徹底關閉全局光照的選項。 因此這個選擇對於用戶來講是很是明確的,沒有任何隱藏的行爲。若是用戶不要使用自發光? 那麼只要確保它的全局光照被設置爲None就能夠了。

1.5 對自發光進行動畫控制

用於自發光的實時全局光照只能用於靜態對象。雖然物體是靜態的,但其材質的自發光屬性仍是能夠被動畫化,而且將被全局光照系統所捕獲到。讓咱們用一個在自發光顏色爲白色和自發光顏色爲黑色之間振盪的簡單組件來嘗試下這個事情。

using UnityEngine;

public class EmissiveOscillator : MonoBehaviour {
    Material emissiveMaterial;
    void Start () {
        emissiveMaterial = GetComponent<MeshRenderer>().material;
    }

    void Update () {
        Color c = Color.Lerp(
            Color.white, Color.black,
            Mathf.Sin(Time.time * Mathf.PI) * 0.5f + 0.5f
        );
        emissiveMaterial.SetColor("_Emission", c);
    }
}

將這個組件添加到咱們的自發光球體。在播放模式下,自發光將會動畫化,但間接光照不受影響。咱們必須通知實時光照系統,它有工做要作。這能夠經過調用適當網格渲染器的Renderer.UpdateGIMaterials方法來完成。

    MeshRenderer emissiveRenderer;
    Material emissiveMaterial;

    void Start () {
        emissiveRenderer = GetComponent<MeshRenderer>();
        emissiveMaterial = emissiveRenderer.material;
    }

    void Update () {
        …
        emissiveMaterial.SetColor("_Emission", c);
        emissiveRenderer.UpdateGIMaterials();
    }

realtimeGIAnimate

動畫控制實時GI

調用UpdateGIMaterials方法會觸發物體自發光的完整更新,並使用其meta渲染通道進行渲染。當自發光比純色更復雜的時候,這是必要的,舉個簡單的例子來講,好比說咱們使用紋理。若是一個純色就足夠了,那麼咱們能夠經過使用渲染器和自發光顏色調用DynamicGI.SetEmissive方法來獲得一個比較快捷的計算方式。這比使用meta渲染通道來渲染物體更快,因此在可以使用的時候能夠利用這種方法。

//emissiveRenderer.UpdateGIMaterials();
DynamicGI.SetEmissive(emissiveRenderer, c);

2 光照探針

烘焙全局光照和實時全局光照都經過光照探針應用於動態對象。物體的位置用於對光探針數據進行插值,而後將其用於全局光照。這對於至關小的物體來講下效果很好,但對於較大的物體來講就太粗糙了。

舉個簡單的例子來是說,將作了比較大拉伸的立方體添加到測試場景,以便它能夠受到不一樣的光照條件的影響。它應該使用咱們的白色材質。因爲它是一個動態立方體,因此最終使用一個點來肯定它的全局光照貢獻。讓咱們移動這個點的位置,使得這一點最終處於一個被遮蔽的位置,那麼整個立方體就會變黑,這顯然是錯誤的。爲了使這一點很是明顯,讓咱們使用一個烘焙主光源,因此全部光照都來自烘焙全局光照和實時全局光照的數據。

image

對於大型動態物體來講,光照效果很差

爲了使光照探針器適用於這樣的狀況,咱們可使用光照探針代理體,或者簡稱爲LPPV。這能夠經過向着色器發送插值後的探針器數據網格而不是單個插值後的探針器數據來作到。這須要具備線性濾波的浮點數3D紋理,這就將這個方法限制到只能在現代顯卡上使用。此外,還要確保在圖形層設置中啓用LPPV(光照探針代理體)支持。

image

啓用了LPPV(光照探針代理體)支持

2.1 向物體中添加一個光照探針代理體

光照探針代理體能夠以各類方式設置,最直接的方法是在做爲使用光照探針代理體的物體的一個組件。你能夠經過Component / Rendering / Light Probe Proxy Volume來添加它。

image

光照探針代理體組件

LPPV(光照探針代理體)經過在運行時在光照探針之間進行插值來工做,就好像它們是常規動態對象的網格同樣。插值後獲得的結果被緩存,刷新模式(Refresh Mode)控制在什麼時候進行更新。默認值爲「自動(Automatic)」,這意味着當動態全局光照更改和探針器組發生移動的時候會觸發更新。包圍盒模式(Bounding Box Mode)控制着代理體的定位。自動本地化(AutomaticLocal )意味着它會去匹配其附着的對象的包圍盒。這些默認設置適用於咱們的立方體,所以咱們將保留這些設置。

要使咱們的立方體實際使用LPPV(光照探針代理體),咱們必須將其網格渲染器的光照探針(Light Probes)模式設置爲使用光照探針代理體(Use ProxyVolume)。默認行爲是使用對象自己的LPPV(光照探針代理體)組件,但也能夠強制使用另外一個代理體。

image

使用一個光照探針代理體而不是常規的探針器

自動分辨率模式(automaticresolution mode)對於咱們的拉伸立方體不起做用。 所以,將「分辨率模式(Resolution Mode )」設置爲「自定義(Custom )」,並確保立方體的角上有采樣點,並沿着其長邊有多個樣本點。當你選中這個對象的時候,能夠看到這些採樣點。

image

image

自定義探針器分辨率以適應拉伸的立方體

2.2 對光照探針代理體進行採樣

立方體已變黑,由於咱們的着色器如今還不支持LPPV(光照探針代理體)採樣。爲了使其工做,咱們必須在CreateIndirectLight函數內調整球面諧波代碼。當使用LPPV(光照探針代理體)的時候,UNITY_LIGHT_PROBE_PROXY_VOLUME被定義爲1。咱們在這種狀況下什麼都不作,看看會發生什麼。

#if !defined(LIGHTMAP_ON) && !defined(DYNAMICLIGHTMAP_ON)
    #if UNITY_LIGHT_PROBE_PROXY_VOLUME
    //...
    #else
        indirectLight.diffuse += max(0, ShadeSH9(float4(i.normal, 1)));
    #endif
#endif

image

沒有更多球面諧波的效果

獲得的結果是全部的球面諧波被禁用,對於不使用LPPV(光照探針代理體)的動態對象也是如此。這是由於UNITY_LIGHT_PROBE_PROXY_VOLUME在項目範圍內定義,而不是對每一個對象實例進行定義。單個對象是否使用LPPV由UnityShaderVariables中定義的unity_ProbeVolumeParams的X份量指定。若是unity_ProbeVolumeParams的X份量設置爲1,那麼咱們有一個LPPV(光照探針代理體),不然咱們應該使用常規的球面諧波。

#if UNITY_LIGHT_PROBE_PROXY_VOLUME
    if (unity_ProbeVolumeParams.x == 1) {
        //...
    }
    else {
        indirectLight.diffuse += max(0, ShadeSH9(float4(i.normal, 1)));
    }
#else
    indirectLight.diffuse += max(0, ShadeSH9(float4(i.normal, 1)));
#endif

要對光照探針代理體進行採樣,咱們可使用SHEvalLinearL0L1_SampleProbeVolume函數而不是ShadeSH9。這個函數在UnityCG中進行定義,而且須要世界空間中的位置做爲額外的參數。

if (unity_ProbeVolumeParams.x == 1) {
    indirectLight.diffuse = SHEvalLinearL0L1_SampleProbeVolume
    (
        float4(i.normal, 1), i.worldPos
    );
    indirectLight.diffuse = max(0, indirectLight.diffuse);
}

SHEvalLinearL0L1_SampleProbeVolume如何工做?

顧名思義,該函數僅包括前兩個球面諧波帶L0和L1。 Unity不使用LPPV(光照探針代理體)的第三個波帶。因此咱們獲得較低質量的光照近似值,可是咱們在多個世界空間中的樣本之間進行插值,而不是使用單個點。下面是這個函數的代碼。

half3 SHEvalLinearL0L1_SampleProbeVolume (half4 normal, float3 worldPos) {
    const float transformToLocal = unity_ProbeVolumeParams.y;
    const float texelSizeX = unity_ProbeVolumeParams.z;

    //The SH coefficients textures and probe occlusion
    // are packed into 1 atlas.
    //-------------------------
    //| ShR | ShG | ShB | Occ |
    //-------------------------

    float3 position = (transformToLocal == 1.0f) ?
        mul(unity_ProbeVolumeWorldToObject, float4(worldPos, 1.0)).xyz :
        worldPos;
    float3 texCoord = (position - unity_ProbeVolumeMin.xyz) *
        unity_ProbeVolumeSizeInv.xyz;
    texCoord.x = texCoord.x * 0.25f;

    // We need to compute proper X coordinate to sample. Clamp the
    // coordinate otherwize we'll have leaking between RGB coefficients
    float texCoordX =
        clamp(texCoord.x, 0.5f * texelSizeX, 0.25f - 0.5f * texelSizeX);

    // sampler state comes from SHr (all SH textures share the same sampler)
    texCoord.x = texCoordX;
    half4 SHAr = UNITY_SAMPLE_TEX3D_SAMPLER(
        unity_ProbeVolumeSH, unity_ProbeVolumeSH, texCoord
    );
    texCoord.x = texCoordX + 0.25f;
    half4 SHAg = UNITY_SAMPLE_TEX3D_SAMPLER(
        unity_ProbeVolumeSH, unity_ProbeVolumeSH, texCoord
    );
    texCoord.x = texCoordX + 0.5f;
    half4 SHAb = UNITY_SAMPLE_TEX3D_SAMPLER(
        unity_ProbeVolumeSH, unity_ProbeVolumeSH, texCoord
    );
    // Linear + constant polynomial terms
    half3 x1;
    x1.r = dot(SHAr, normal);
    x1.g = dot(SHAg, normal);
    x1.b = dot(SHAb, normal);

    return x1;
}
View Code

image

採樣後的LPPV(光照探針代理體)的效果,在伽馬空間中的效果太暗

咱們的着色器如今在須要的時候對LPPV(光照探針代理體)進行採樣,但結果太暗了。至少在伽馬顏色空間中工做就是這樣的結果。這是由於球面諧波數據存儲在線性空間中。所以,可能須要進行顏色的轉換。

if (unity_ProbeVolumeParams.x == 1) {
    indirectLight.diffuse = SHEvalLinearL0L1_SampleProbeVolume(
        float4(i.normal, 1), i.worldPos
    );
    indirectLight.diffuse = max(0, indirectLight.diffuse);
    #if defined(UNITY_COLORSPACE_GAMMA)
               indirectLight.diffuse = LinearToGammaSpace(indirectLight.diffuse);
        #endif
}

image

採樣後的LPPV(光照探針代理體)的效果,帶有正確的顏色

3 LOD Groups

當一個對象最終只覆蓋應用程序窗口的一小部分的時候,你不須要高度詳細的網格來渲染它。你能夠根據對象在視圖中的大小使用不一樣的網格。這被稱爲細節層次,或簡稱LOD。Unity容許咱們經過組件LOD組來實現這樣的功能。

3.1 建立一個LOD層次結構

這個想法是你在各類不一樣的LOD等級使用同一網格的多個版本。最高級 - LOD 0 - 具備最多的頂點、子對象、動畫、複雜的材質等。隨後的級別逐漸變得更簡單,更容易計算。在理想狀況下,相鄰的LOD等級被設計爲使得當Unity從一個LOD等級切換到另外一個LOD等級的時候,你不能輕易地辨別出它們之間的區別。不然忽然有LOD等級變化的時候就會讓人很暈。可是在研究這種技術的時候,咱們會使用明顯的不一樣的網格。

建立一個空的遊戲對象並給它兩個子對象。第一個子對象是標準球體,第二個子對象是標準立方體,其大小設置爲0.75。 預期的結果看起來像是一個重疊的球體和立方體。

image image

球體和立方體做爲一個對象

經過Component /Rendering / LOD Group將一個LOD組組件添加到父對象。你會獲得一個具備默認設置的LOD組,它有三個LOD等級。 百分比是指由對象的包圍盒覆蓋的窗口的垂直部分。所以,當垂直尺寸降低到窗口高度的60%的時候,默認設置爲切換到LOD 1,當垂直尺寸降低到窗口高度的30%的時候,默認設置爲切換到LOD 2。當垂直尺寸降低到窗口高度的10%的時候,它根本不渲染。 你能夠經過拖動LOD框的邊來更改這些閾值。

image

組件LOD組

這些閾值由LOD偏移(LOD Bias)進行修改,LOD偏移(LOD Bias)能夠在組件檢視器裏面查看並修改。目前使用的是質量設置爲2的默認值,這意味着閾值被減半。也能夠設置爲最大LOD等級,這將致使跳過最高級別。

爲了使其工做,你必須告訴組件每一個LOD等級都會使用哪些對象。這是經過選擇一個LOD塊並將對象添加到其「渲染器」列表中完成的。你能夠在場景中添加任何對象,但必定要確保添加其子對象到LOD塊的「渲染器」列表。讓LOD 0的「渲染器」使用球體,讓LOD 1的「渲染器」使用立方體。咱們將LOD 2的「渲染器」留空,因此咱們只有兩個LOD等級。若是須要的話,你能夠經過右鍵單擊上下文菜單刪除並插入LOD等級。

image

讓球這個子物體使用LOD 0等級

一旦配置了LOD等級,你能夠經過移動相機來查看它們的效果。若是物體足夠大的話,它將使用球體,不然的話它將使用立方體,或根本不會渲染。

LOD等級切換的效果如這個連接所示:https://gfycat.com/gifs/detail/ShyAffectionateFairyfly。

3.2 烘焙全局光照和LOD組

由於LOD組是如何渲染的取決於它的視圖大小,因此它們天然是動態的。可是,你仍然可使其成爲靜態。對整個對象層次結構執行此操做,所以也包括了根節點和它的兩個子節點。而後設置主光源爲烘焙光源,看看會發生什麼。

image

使用烘焙光源獲得的效果

看起來在烘焙靜態光照貼圖的時候使用的是LOD 0。 咱們最終老是可以看到球體的陰影和間接光照的貢獻,即便LOD組切換到一個立方體或是對自身作了剔除。但請注意,立方體也是使用了靜態光照貼圖。 因此它不使用光照探針,對吧? 轉動光照探針組就能發現這一點。

image

沒有光照探針時候的烘焙光照

禁用光探針組會使得立方體變得更暗。這意味着他們再也不接受間接光照。 這是由於在烘焙過程當中肯定間接光照的時候使用的是LOD 0。爲了找到其餘LOD等級下的間接光照, Unity能夠作到的最好程度是依靠烘焙光照探針。 所以,即便在運行時咱們不須要光照探針,咱們也須要光照探針來爲咱們的立方體計算間接光照。

3.3 實時全局光照和LOD組

當只使用實時全局光照的時候,方法是相似的,除了咱們的立方體如今在運行時使用的是光照探針。你能夠經過選擇球體或立方體來驗證這一點。選擇立方體後,你能夠看到小工具顯示了哪些光照探針被使用。 球體不顯示它們,由於它使用的是動態光照貼圖。

image

LOD 1使用光照探針來計算實時全局光照

當烘焙全局光照和實時全局光照同時使用的時候,它會變得更加複雜。 在這種狀況下,立方體應該對烘焙全局光照使用光照貼圖,對實時全局光照使用光照探針。不幸的是,這是不可能的,這是由於光照貼圖和和球面諧波不能同時使用。這是一個非此即彼的問題。由於光照貼圖數據對於立方體來講是可用的,因此Unity最終會使用它。所以,立方體不受實時全局光照的影響。

image

僅對LOD 1等級使用烘焙光照,使用的是低強度的主光源

一個重要的細節是,烘焙的LOD等級和渲染的LOD等級是徹底獨立的。 他們不須要使用相同的設置。若是實時全局光照最終比烘焙全局光照更重要,你能夠強制立方體使用光照探針,確保它對於光照貼圖來講不是靜態的,同時保持球體靜止。

image

LOD 1強制使用光照探針

3.4 在不一樣的LOD等級切換的時候支持淡入淡出功能

LOD組這種方法的缺點是,當LOD等級發生變化的時候,它能夠在視覺上很明顯的表現出來。幾何體會在視圖中忽然彈出、消失或改變形狀。 這能夠經過相鄰LOD等級之間的淡入淡出來緩解,這經過將LOD組的漸變模式設置爲淡入淡出來完成。還有另外一種漸變模式,由Unity用於SpeedTree對象,咱們不會使用這種模式。

當啓用淡入淡出的時候,每一個LOD等級都會顯示一個淡入變換寬度(Fade Transition Width )字段,用於控制其塊的哪一個部分用於衰落。舉個簡單的例子來講,當設置爲0.5的時候,一半LOD範圍將用於淡出到下一級。或者,淡入淡出過程能夠是有動畫的,在這種狀況下,在LOD等級之間的切換須要大約半秒鐘。

image

帶有0.5變換寬度的淡入淡出

當啓用淡入淡出的時候,在LOD組之間進行轉換的時候會同時渲染兩個LOD等級。

3.5 支持淡入淡出

Unity的標準着色器在默認狀況下是不支持淡入淡出的。若是想要支持支持淡入淡出的話,你必須複製標準着色器併爲LOD_FADE_CROSSFADE關鍵字添加一個多編譯指令。添加這條指令還有一個緣由是爲了在My First Lighting着色器裏面支持淡入淡出功能。讓咱們將這條指令添加到除了meta渲染通道之外的全部渲染通道。

#pragma multi_compile _ LOD_FADE_CROSSFADE

咱們將使用抖動來在LOD等級之間進行轉換。這種方法適用於前向渲染和延遲渲染,也適用於有陰影的狀況。

在建立半透明陰影的時候,咱們已經使用了抖動這種方法。它須要片斷的屏幕空間座標,這迫使咱們爲頂點程序和片斷程序使用不一樣的插值器結構。因此讓咱們複製My Lighting 中的Interpolators結構,將其重命名爲InterpolatorsVertex。

struct InterpolatorsVertex {
    …
};
struct Interpolators {
    …
};

…
InterpolatorsVertex MyVertexProgram (VertexData v) {
    InterpolatorsVertex i;
    …
}

當咱們必須進行淡入淡出處理的時候,片斷程序的插值器裏面必須包含vpos,不然咱們可使用一樣的位置信息。

struct Interpolators {
    #if defined(LOD_FADE_CROSSFADE)
        UNITY_VPOS_TYPE vpos : VPOS;
    #else
        float4 pos : SV_POSITION;
    #endif
    …
};

咱們能夠在咱們片斷程序中開始的位置使用UnityApplyDitherCrossFade函數來執行淡入淡出操做。

FragmentOutput MyFragmentProgram (Interpolators i) {
    #if defined(LOD_FADE_CROSSFADE)
        UnityApplyDitherCrossFade(i.vpos);
    #endif
    …
}

UnityApplyDitherCrossFade是如何工做的?

這個函數在UnityCG中進行定義。它的方法相似於咱們在《渲染12:半透明陰影》中使用的抖動方法,區別只是整個對象的抖動級別是均勻的。 所以,不須要混合抖動級別。 它使用存儲在4×64大小的二維紋理中的16個抖動級別,而不是4×4×16大小的三維紋理。

FragmentOutput MyFragmentProgram (Interpolators i) {
    #if defined(LOD_FADE_CROSSFADE)
        UnityApplyDitherCrossFade(i.vpos);
    #endif

    …
}

unity_LODFade變量在UnityShaderVariables中進行定義。它的Y份量包含的是對象的漸變量,共有十六步。

image

經過抖動方法獲得的淡入淡出幾何體

淡入淡出如今能夠在幾何體上正常工做了。爲了使其適用於陰影,咱們必須調整My Shadows着色器。 首先,當咱們進行淡入淡出處理的時候,必須使用vpos。其次,咱們還必須在片斷程序開始的位置使用UnityApplyDitherCrossFade函數。

struct Interpolators {
    #if SHADOWS_SEMITRANSPARENT || defined(LOD_FADE_CROSSFADE)
        UNITY_VPOS_TYPE vpos : VPOS;
    #else
        float4 positions : SV_POSITION;
    #endif
    …
};
…
float4 MyShadowFragmentProgram (Interpolators i) : SV_TARGET {
    #if defined(LOD_FADE_CROSSFADE)
        UnityApplyDitherCrossFade(i.vpos);
    #endif
    …
}

image lod fade

對幾何體和陰影都作了淡入淡出處理

由於立方體和球體相互交叉,因此咱們在對它們作淡入淡出處理的時候,獲得一些奇怪的自陰影效果。這對於看到淡入淡出處理能在陰影上起做用是很方便的,可是當你爲實際遊戲建立LOD幾何體的時候,須要注意這些瑕疵。

相關文章
相關標籤/搜索