您當前的位置:首頁 > 舞蹈

Unity SRP 8.全域性照明

作者:由 HipHopBoy 發表于 舞蹈時間:2019-12-06

翻譯彙總文章:

原文連結:

間接光照

烘焙和取樣光照貼圖

展示非直接光

建立自發光材質

透過探頭和lppv(

Light Probe Proxy Volume

) 取樣燈光

支援預計算實時全域性照明

這是Unity SRP系列的第8篇文章。主要關於怎麼支援靜態和動態全域性光照。

這篇教程使用 Unity 2018。3。0f2。

1 光照貼圖

實時光只處理了直射光。只有直接暴露在光線下的表面才會被照亮,這是因為缺少了從表面反射到另外的表面最終到攝像機的間接光。這也被稱為全域性照明。我們可以把間接光烘焙到光照貼圖來新增它。Rendering 16, Static Lighting 教程講解了再unity中烘焙燈光的基礎概念,但是對於老的渲染管線只能使用Enlighten lightmapper。

1。1 設定場景

當場景中只有一個直射光沒有間接光的時候,可以很容易的看到所有的陰影區域顏色是接近黑色的。

Unity SRP 8.全域性照明

只有實時光的場景

大的陰影更加顯而易見

Unity SRP 8.全域性照明

因為高光環境反射被新增到直射光裡,所以我們還能看到陰影裡面的物體。如果沒有反射探頭我們會看到天空盒的反射,這是明亮的。透過把

Intensity Multiplier

調整到0來排除天空盒的影響。我們就會看到陰影區域是全黑的。

Unity SRP 8.全域性照明

Unity SRP 8.全域性照明

全黑環境

1。2 烘焙光照

在場景燈光設定下的 Mixed Lighting 下勾選

Baked Global Illumination 和

把Lighting Mode調為Baked Indirect 來開啟烘焙間接光。這會讓Unity烘焙燈光,但是我們無法直接看到。

Unity SRP 8.全域性照明

我會使用預設的Lightmapping 設定,在上面做一些小的改變。預設的使用先進的Lightmapper,我會保留它。因為我有一個小的場景,我會把光照貼圖的解析度從10增加到20。我還禁用了壓縮光照貼圖選項,為了得到最好的質量,跳過了貼圖壓縮這一步。改變Directional Model為 Non-Directional ,因為只有使用法線貼圖的場景才用到,這裡我們不需要。

Unity SRP 8.全域性照明

光照貼圖設定

烘焙燈光是靜態的,所以在執行模式不能改變。只有被標記為lightmap-static的遊戲物件才會烘焙它們的間接光貢獻。把所有的幾何圖形都標記為完全靜態是最快的。

Unity SRP 8.全域性照明

靜態遊戲物體

當烘焙時,有uv重疊的話,unity會產生警告。當一個物體的uv展開時在光照貼圖中太小,會引起光照資訊重疊,就會發生這種情況。你可以透過調整Scale in Lightmap的引數來調整物體的比例。另外對一些預設的物體,比如球體,可以透過開啟Stitch Seams 來提升烘焙光照的效果。

Unity SRP 8.全域性照明

最後,為了烘焙最主要貢獻的燈光,設定它的模式為Mixed。這就意味著它被用於實時光照,它的間接光照也會被烘焙。

Unity SRP 8.全域性照明

在烘焙完成之後,你可以透過Lighting window的Baked Lightmaps標籤頁來檢視貼圖。根據貼圖的大小和烘焙所有靜態物體所需空間的多少,你最終會得到多個貼圖。

Unity SRP 8.全域性照明

1。3 取樣光照貼圖

為了取樣光照貼圖,我們需要通知Unity讓我們的貼圖對我們的shader可用並且在頂點資料裡面包含光照貼圖的uv座標。透過

MyPipeline

。Render

裡面的

RendererConfiguration

。PerObjectLightmaps 標誌來完成。就行我們開啟反射探頭一樣。

drawSettings。rendererConfiguration |=

RendererConfiguration。PerObjectReflectionProbes |

RendererConfiguration。PerObjectLightmaps;

當一個物體在光照貼圖中被渲染時,Unity會提供需要的資料並且選一個有LIGHTMAP

ON關鍵字shader變體。所以我們必須新增一個muti

-complie 在我們的shader中。

#pragma multi_compile _ _SHADOWS_SOFT

#pragma multi_compile _ LIGHTMAP_ON

透過在Lit。hlsl中新增 unity_Lightmap和和它配對使用的取樣器宣告來使光照貼圖可用

TEXTURE2D(unity_Lightmap);

SAMPLER(samplerunity_Lightmap);

光照貼圖的座標透過第二個uv通道提供,所以在VertexInput中新增

struct VertexInput {

float4 pos : POSITION;

float3 normal : NORMAL;

float2 uv : TEXCOORD0;

float2 lightmapUV : TEXCOORD1;

UNITY_VERTEX_INPUT_INSTANCE_ID

};

我們也必須將它們新增到VertexOutput中,但這僅在使用燈光貼圖時才需要。

struct VertexOutput {

float4 clipPos : SV_POSITION;

float3 normal : TEXCOORD0;

float3 worldPos : TEXCOORD1;

float3 vertexLighting : TEXCOORD2;

float2 uv : TEXCOORD3;

#if defined(LIGHTMAP_ON)

float2 lightmapUV : TEXCOORD4;

#endif

UNITY_VERTEX_INPUT_INSTANCE_ID

};

光照貼圖也有縮放和偏移,但是不應用在整個貼圖中。相反,他們用來指定一個物體展開的uv在光照貼圖上的位置。定義unity_LightmapST 作為UnityPerDraw buffer的一部分。因為它不符合TRANSFORM_TEX所期望的命名約定,如果需要,我們必須自己轉換LitPassVertex中的座標

CBUFFER_START(UnityPerDraw)

float4 unity_LightmapST;

CBUFFER_END

VertexOutput LitPassVertex (VertexInput input) {

output。uv = TRANSFORM_TEX(input。uv, _MainTex);

#if defined(LIGHTMAP_ON)

output。lightmapUV =

input。lightmapUV * unity_LightmapST。xy + unity_LightmapST。zw;

#endif

return output;

}

讓我們建立一個單獨的SampleLightmap函式,在給定一些UV座標的情況下對光照貼圖進行取樣。在裡面我們將呼叫Core EntityLighting檔案裡面的SampleSingleLightmap 函式。我們提供貼圖,取樣器宣告和uv座標,前兩個透過

TEXTURE2D_PARAM 宏傳遞。

float3 SampleLightmap (float2 uv) {

return SampleSingleLightmap(

TEXTURE2D_PARAM(unity_Lightmap, samplerunity_Lightmap), uv

);

}

SampleSingleLightmap需要更多的引數。下一個是UV座標的尺度偏移變換。但是我們已經在頂點程式中做過了,所以這裡我們要提供一個恆等變換。

return SampleSingleLightmap(

TEXTURE2D_PARAM(unity_Lightmap, samplerunity_Lightmap), uv,

float4(1, 1, 0, 0)

);

然後是一個布林值,指示是否需要對光照貼圖中的資料進行解碼。這依賴於目標平臺。如果unity使用HDR 光照貼圖那麼解碼就不是必須的。這是UNITY_LIGHTMAP_FULL_HDR定義時的情況。

return SampleSingleLightmap(

TEXTURE2D_PARAM(unity_Lightmap, samplerunity_Lightmap), uv,

float4(1, 1, 0, 0),

#if defined(UNITY_LIGHTMAP_FULL_HDR)

false

#else

true

#endif

);

最後,我們需要提供解碼指令,使照明在正確的範圍。我們需要使用float4(LIGHTMAP_HDR_MULTIPLIER, LIGHTMAP_HDR_EXPONENT, 0。0, 0。0)。

return SampleSingleLightmap(

TEXTURE2D_PARAM(unity_Lightmap, samplerunity_Lightmap), uv,

float4(1, 1, 0, 0),

#if defined(UNITY_LIGHTMAP_FULL_HDR)

false,

#else

true,

#endif

float4(LIGHTMAP_HDR_MULTIPLIER, LIGHTMAP_HDR_EXPONENT, 0。0, 0。0)

);

我們對光照貼圖進行取樣,因為我們想要新增全域性光照。我們為此建立一個全域性光照函式,在這個函數里面處理細節。給它一個VertexOutput引數,這意味著需要為他定義一個結構體。如果有光照貼圖,取樣它,否則返回0。

struct VertexOutput {

};

float3 GlobalIllumination (VertexOutput input) {

#if defined(LIGHTMAP_ON)

return SampleLightmap(input。lightmapUV);

#endif

return 0;

}

在LitPassFragment的末尾呼叫這個函式,最初替換所有其他的照明,這樣我們就可以單獨看到它了。

float4 LitPassFragment (

VertexOutput input, FRONT_FACE_TYPE isFrontFace : FRONT_FACE_SEMANTIC

) : SV_TARGET {

color += ReflectEnvironment(surface, SampleEnvironment(surface));

color = GlobalIllumination(input);

return float4(color, albedoAlpha。a);

}

Unity SRP 8.全域性照明

只有全域性照明

1。4 透明表面

結果應該看起來很軟,但是不連續的偽影會出現在透明的表面附近,尤其是半透材質。progressive lightmapper使用材質渲染佇列去檢測透明,依賴_Cutoff屬性裁剪材質。所以它可以工作,但是當暴露背面的時候還是有問題。當雙面物體的前後重疊時也會有問題,這就是我們自己生成的雙面圖形的情況。

Unity SRP 8.全域性照明

Transparency artifacts。

問題是光照貼圖值應用於前面。背面不包含資料。渲染背面是可行的,但它們最終會使用來自正面的光照資料。artifacts 出現因為取樣時lightmapper 命中了背面,它產生了無效的光照資訊。你可以減輕這個問題透過自定義物體的

Lightmap Parameters 。

並降低

Backface Tolerance

threshold,以便lightmapper接受更多丟失的資料並將其平滑。

Unity SRP 8.全域性照明

Unity SRP 8.全域性照明

Tolerant lightmapping。

1。5 合併直射光和間接光

現在我們知道全域性照明是有效的,把它加到直射光中。由於間接光只是漫射的,所以把它與表面的漫射特性相乘。

color += GlobalIllumination(input) * surface。diffuse;

Unity SRP 8.全域性照明

直接光照和全域性照明

結果是比沒有全域性光照時候更亮,這是預料之中的。然而,現在的情況比以前光明多了。這是因為skybox是全球照明的一部分。讓我們只新增一個方向光的間接光,這樣我們可以更好地檢查它,我們把降低環境光的強度為零。

Unity SRP 8.全域性照明

Unity SRP 8.全域性照明

黑色的環境光

1。6 只烘焙燈光

也可以設定我們的光模式為Baked。這意味著不再需要實時光。相反,它的直接光和間接光都被烘焙到光照貼圖中。在我們的例子中,我們得到一個沒有任何實時光的場景。它也剔除了所有的高光和軟陰影。

Unity SRP 8.全域性照明

Unity SRP 8.全域性照明

Fully baked light。

1。7 Meta Pass (不知道怎麼翻譯)

光對映器必須知道場景中物體的表面屬性才能對光進行烘焙。它透過一個特殊的Meta Pass來獲取它們。我們的shader沒有這個pass,所以unity使用預設的meta pass。但是預設的pass在我們的shader中不是完全匹配的。所以我們要建立我們自己的。新增一個pass 設定mode為Meta,沒有culling,把它的程式碼放到單獨的Meta。hlsl檔案中。

Pass {

Tags {

“LightMode” = “Meta”

}

Cull Off

HLSLPROGRAM

#pragma vertex MetaPassVertex

#pragma fragment MetaPassFragment

#include “。。/ShaderLibrary/Meta。hlsl”

ENDHLSL

}

Meta。hlsl可以用一個精簡版的Lit。hlsl開始。我們只需要unity_MatrixVP矩陣,unity_LightmapST,主要紋理,和非例項化的材質屬性。不需要模型空間到世界空間的轉換,所以直接從物件空間到剪輯空間。開始,讓fragment程式返回零。

#ifndef MYRP_LIT_META_INCLUDED

#define MYRP_LIT_META_INCLUDED

#include

“Packages/com。unity。render-pipelines。core/ShaderLibrary/Common。hlsl”

#include

“Lighting。hlsl”

CBUFFER_START

UnityPerFrame

float4x4

unity_MatrixVP

CBUFFER_END

CBUFFER_START

UnityPerDraw

float4

unity_LightmapST

CBUFFER_END

CBUFFER_START

UnityPerMaterial

float4

_MainTex_ST

float4

_Color

float

_Metallic

float

_Smoothness

CBUFFER_END

TEXTURE2D

_MainTex

);

SAMPLER

sampler_MainTex

);

struct

VertexInput

{

float4

pos

POSITION

float2

uv

TEXCOORD0

float2

lightmapUV

TEXCOORD1

};

struct

VertexOutput

{

float4

clipPos

SV_POSITION

float2

uv

TEXCOORD0

};

VertexOutput

MetaPassVertex

VertexInput

input

{

VertexOutput

output

output

clipPos

=

mul

unity_MatrixVP

float4

input

pos

xyz

1。0

));

output

uv

=

TRANSFORM_TEX

input

uv

_MainTex

);

return

output

}

float4

MetaPassFragment

VertexOutput

input

SV_TARGET

{

float4

meta

=

0

return

meta

}

#endif // MYRP_LIT_META_INCLUDED

就像取樣光照貼圖一樣,在渲染光資料時,unity_LightmapST被用來得到正確的對映區域。在這種情況下,我們必須調整輸入位置XY座標。此外,還使用了一個技巧使OpenGL也能渲染,因為當Z位置沒有調整時,它會失敗。

VertexOutput MetaPassVertex (VertexInput input) {

VertexOutput output;

input。pos。xy =

input。lightmapUV * unity_LightmapST。xy + unity_LightmapST。zw;

input。pos。z = input。pos。z > 0 ? FLT_MIN : 0。0;

output。clipPos = mul(unity_MatrixVP, float4(input。pos。xyz, 1。0));

output。uv = TRANSFORM_TEX(input。uv, _MainTex);

return output;

}

我們需要初始化亮面,但是我們只有顏色、金屬和平滑資訊。新增一個方便的GetLitSurfaceMeta函式在Lighting。hlsl將所有其他值設定為零l。

LitSurface GetLitSurfaceMeta (float3 color, float metallic, float smoothness) {

return GetLitSurface(0, 0, 0, color, metallic, smoothness);

}

在fragment中獲取表面資料

float4 MetaPassFragment (VertexOutput input) : SV_TARGET {

float4 albedoAlpha = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input。uv);

albedoAlpha *= _Color;

LitSurface surface = GetLitSurfaceMeta(

albedoAlpha。rgb, _Metallic, _Smoothness

);

float4 meta = 0;

return meta;

}

我們現在使用合適的表面反照率,我們必須透過RGB通道輸出它,A通道設定為1。然而,他的強度是可以調整的,透過unity_OneOverOutputBoost 提供一個指數,與unity_MaxOutputValue一起定義最大亮度。透過PositivePow 應用他們得到最終的顏色,把它限定在0和最大值中間。

CBUFFER_START(UnityMetaPass)

float unity_OneOverOutputBoost;

float unity_MaxOutputValue;

CBUFFER_END

float4 MetaPassFragment (VertexOutput input) : SV_TARGET {

float4 meta = 0;

meta = float4(surface。diffuse, 1);

meta。rgb = clamp(

PositivePow(meta。rgb, unity_OneOverOutputBoost), 0, unity_MaxOutputValue

);

return meta;

}

我們現在輸出了應用於光照貼圖的反照率,但是meta pass 還要生成其他的資料。這個資料是否需要透過定義一個unity_MetaFragmentControl 布林標誌來知道。如果它的第一個分量為true,那麼我們輸出反照率,否則輸出0

CBUFFER_START(UnityMetaPass)

float unity_OneOverOutputBoost;

float unity_MaxOutputValue;

bool4 unity_MetaFragmentControl;

CBUFFER_END

float4 MetaPassFragment (VertexOutput input) : SV_TARGET {

float4 meta = 0;

if (unity_MetaFragmentControl。x) {

meta = float4(surface。diffuse, 1);

meta。rgb = clamp(

PositivePow(meta。rgb, unity_OneOverOutputBoost),

0,unity_MaxOutputValue

);

}

return meta;

}

到目前為止,我們得到了與預設meta pass相同的結果。然而,預設的meta pass也增加了高光顏色乘以粗糙度的一半加到反照率上。這背後的想法是,高度鏡面和粗糙的材料也透過一些間接的光。預設的陰影是這樣做的,但是它期望平滑值儲存在_Smoothness之外的其他地方。所以我們必須自己做。

meta = float4(surface。diffuse, 1);

meta。rgb += surface。specular * surface。roughness * 0。5;

最好的方法是用一個白色的金屬球,它的平滑度為0,然後只渲染間接光。

Unity SRP 8.全域性照明

without boost

Unity SRP 8.全域性照明

with boost

2 自發光

除了反射或吸收然後再發射光,物體也可以自己發射光。這就是真正的光源的工作原理,但是在渲染時沒有考慮到這一點。要建立自發光材質,只需在計算的照明中新增一種顏色。

2。1 自發光顏色

在shader中新增一個

_EmissionColor 屬性

,把黑色設定為預設值,因為散發的光可能是任意強度的,所以用HDR屬性把顏色標記為高動態範圍。

_Smoothness (“Smoothness”, Range(0, 1)) = 0。5

[HDR] _EmissionColor (“Emission Color”, Color) = (0, 0, 0, 0)

Unity SRP 8.全域性照明

同樣也在

InstancedMaterialProperties

中新增一個自發光顏色欄位,用ColorUsage屬性並把第二個引數置為true標記它為HDR。它的第一個引數表示alpha通道是否顯示,這裡不需要,所以置為false。

static int emissionColorId = Shader。PropertyToID(“_EmissionColor”);

[SerializeField, ColorUsage(false, true)]

Color emissionColor = Color。black;

void OnValidate () {

propertyBlock。SetColor(emissionColorId, emissionColor);

GetComponent()。SetPropertyBlock(propertyBlock);

}

Unity SRP 8.全域性照明

在lit。hlsl中新增自發光作為另外一個例項屬性。然後在片元著色器中,用color值最後加上它的值。

UNITY_INSTANCING_BUFFER_START(PerInstance)

UNITY_DEFINE_INSTANCED_PROP(float4, _EmissionColor)

UNITY_INSTANCING_BUFFER_END(PerInstance)

float4 LitPassFragment (

VertexOutput input, FRONT_FACE_TYPE isFrontFace : FRONT_FACE_SEMANTIC

) : SV_TARGET {

color += GlobalIllumination(input) * surface。diffuse;

color += UNITY_ACCESS_INSTANCED_PROP(PerInstance, _EmissionColor)。rgb;

return float4(color, albedoAlpha。a);

}

Unity SRP 8.全域性照明

2。2 間接自發光

自發光顏色可以使物體的表面更加明亮,但是不會影響到其他物體的表面,因為它不是一盞燈。我們能做的就是再渲染燈光貼圖的時候考慮它,高效的把它轉變成烘焙光。

lightmapper也使用meta通道來收集從表面發出的光線。在這種情況下,

unity_MetaFragmentControl

的第二個分量被設定。輸出自發光顏色,這種情況下alpha值被設定為1。

CBUFFER_START(UnityPerMaterial)

float4 _MainTex_ST;

float4 _Color, _EmissionColor;

float _Metallic;

float _Smoothness;

CBUFFER_END

float4 MetaPassFragment (VertexOutput input) : SV_TARGET {

if (unity_MetaFragmentControl。x) {

}

if (unity_MetaFragmentControl。y) {

meta = float4(_EmissionColor。rgb, 1);

}

return meta;

}

這還不能夠使自發光影響到其他的表面,因為lightmapper預設情況下不會從其他的物體收集自發光,它還需要更多的工作,如果讓每個材質都開啟它,我們要在我們的shader GUI中新增一個全域性照明屬性,透過在

LitShaderGUI

。OnGUI

中呼叫 editor。

LightmapEmissionPropertry

()。 讓我們把它放在陰影投射的按鈕下面。

public override void OnGUI (

MaterialEditor materialEditor, MaterialProperty[] properties

) {

CastShadowsToggle();

editor。LightmapEmissionProperty();

}

Unity SRP 8.全域性照明

把它設定為Baked還是不行,因為Unity 使用了另一個最佳化。如果一個材質的自發光最終是黑色的,它會跳過。這個是透過把材質globalIlluminationFlags 設定為

MaterialGlobalIlluminationFlags

。EmissiveIsBlack 來體現的。然而這個標記不是自動設定的,所以我們要自己設定。當全域性照明屬性改變時,我們將簡單地移除標記,這意味著自發光會被烘焙到所有使用這個材質的物體上。所以我們只在我們需要的時候使用這個材質。

EditorGUI。BeginChangeCheck();

editor。LightmapEmissionProperty();

if (EditorGUI。EndChangeCheck()) {

foreach (Material m in editor。targets) {

m。globalIlluminationFlags &=

~MaterialGlobalIlluminationFlags。EmissiveIsBlack;

}

}

Unity SRP 8.全域性照明

3 光照探頭

光照貼圖之影響合併後的靜態幾何體。不能被用於動態物件。也不能很好的適用於小物體。但是,將lightmapping和非lightmapping物件組合在一起並不好,因為他們的不同是很明顯的。為了模擬這個現象,我標記所有不自動發光白色的球是動態的。(I‘ve made all white spheres that aren’t emissive dynamic。)

Unity SRP 8.全域性照明

非自發光白球是動態的

當設定燈光為全烘焙時,不同變得更加明顯。這時候,動態物體沒有接受任何燈光,全部是黑色。

Unity SRP 8.全域性照明

當光照貼圖不能用時,我們只有依賴光照探頭。光照探頭是在一個特定點上的簡單燈光,編碼為球諧函式。Rendering 5, Multiple Lights 中解釋了球諧函式是怎麼工作的。

3。1 取樣光照探頭

光照探頭資訊已經傳遞給shader,像光照貼圖資料一樣。在這裡我們必須開啟

RendererConfiguration

。PerObjectLightProbe

標誌。

drawSettings。rendererConfiguration |=

RendererConfiguration。PerObjectReflectionProbes |

RendererConfiguration。PerObjectLightmaps |

RendererConfiguration。PerObjectLightProbe;

球諧函式在shader中透過七個float4 向量表示,在UnityPerDraw 緩衝區。建立

SampleLightProbes

函式用法向量作為引數。 將係數放入陣列和法向量一起傳遞給

SampleSH9

。也定義在

EntityLighting中。

保證這個函式返回值是正值。

CBUFFER_START(UnityPerDraw)

float4 unity_SHAr, unity_SHAg, unity_SHAb;

float4 unity_SHBr, unity_SHBg, unity_SHBb;

float4 unity_SHC;

CBUFFER_END

float3 SampleLightProbes (LitSurface s) {

float4 coefficients[7];

coefficients[0] = unity_SHAr;

coefficients[1] = unity_SHAg;

coefficients[2] = unity_SHAb;

coefficients[3] = unity_SHBr;

coefficients[4] = unity_SHBg;

coefficients[5] = unity_SHBb;

coefficients[6] = unity_SHC;

return max(0。0, SampleSH9(coefficients, s。normal));

}

GlobalIllumination

新增一個surface引數,並且在光照貼圖不使用的時候用

SampleLightProbes

代替0作為返回值返回。

float3 GlobalIllumination (VertexOutput input, LitSurface surface) {

#if defined(LIGHTMAP_ON)

return SampleLightmap(input。lightmapUV);

#else

return SampleLightProbes(surface);

#endif

//return 0;

}

然後在LitPassFaragment中新增需要的引數。

color += GlobalIllumination(input, surface) * surface。diffuse;

3。2 放置光照探頭

動態物體現在使用光照探頭,但目前只有環境照明儲存在其中,我們將其設定為黑色。為了使烘焙光透過光探頭可用,我們必須在場景中新增光探頭組,透過GameObject / Light /

Light Probe Group

。這建立一個包含8個探頭的組, 你自己在場景中編輯它,在Rendering 16, Static Lighting。 中解釋了原因。

Unity SRP 8.全域性照明

Unity SRP 8.全域性照明

在光照探頭組新增之後,動態物體就會接收間接光。Unity內插了附近的光探頭,使其在每個物體的區域性原點處都有一個探頭值。這意味著動態物體不能例項化當他們在光照探頭組內部時。可以覆蓋每個物件用於插值的位置,這樣您就可以讓附近的物件使用相同的探測資料,這仍然允許它們被例項化。

3。3 Light Probe Proxy Volumes

因為光照探頭資料是基於物件的區域性原點,所以它只能影響相對小的物體。為了說明它,我在場景中放了一個瘦長的動態立方體。它會受到不同程度的烘焙光,但最終被均勻地照亮。

Unity SRP 8.全域性照明

對於這樣的物體,如果我們取樣多個探頭,我們就可以得到合理的結果。我們可以透過使用LPPV來完成它。透過

Component / Rendering / Light Probe Proxy Volume

新增,我們在Rendering 18, Realtime GI, Probe Volumes, LOD Groups。 中解釋過它。

Unity SRP 8.全域性照明

Unity SRP 8.全域性照明

要開啟LPPV,物體上的Light Probes模式要設定為 Use Proxy Volume。

Unity SRP 8.全域性照明

我們還需要告訴Unity傳送必要的資料導GPU,這裡我們使用

RendererConfiguration

。PerObjectLightProbeProxyVolume

標誌。

drawSettings。rendererConfiguration |=

RendererConfiguration。PerObjectReflectionProbes |

RendererConfiguration。PerObjectLightmaps |

RendererConfiguration。PerObjectLightProbe |

RendererConfiguration。PerObjectLightProbeProxyVolume;

LPPV配置要放入

UnityProbeVolume

緩衝區。包含一個變換矩陣,和尺寸資料以及一些引數。探頭體積資料被儲存在一個 浮點3D貼圖中,我們用

TEXTURE3D_FLOAT

(unity_ProbeVolumeSH)定義,和取樣器一起使用。

CBUFFER_START(UnityProbeVolume)

float4 unity_ProbeVolumeParams;

float4x4 unity_ProbeVolumeWorldToObject;

float3 unity_ProbeVolumeSizeInv;

float3 unity_ProbeVolumeMin;

CBUFFER_END

TEXTURE3D_FLOAT(unity_ProbeVolumeSH);

SAMPLER(samplerunity_ProbeVolumeSH);

在SampleLightProbes 函式中,檢查

unity_ProbeVolumeParams

的第一個分量是否被設定,如果設定了,我們必須取樣LPPV代替 普通的光照探頭。我們從

EntityLighting 中呼叫

SampleProbeVolumeSH4

函式,使用紋理、表面位置和法線、轉換矩陣、第二個和第三個引數值以及尺寸配置作為引數。

float3 SampleLightProbes (LitSurface s) {

if (unity_ProbeVolumeParams。x) {

return SampleProbeVolumeSH4(

TEXTURE3D_PARAM(unity_ProbeVolumeSH, samplerunity_ProbeVolumeSH),

s。position, s。normal, unity_ProbeVolumeWorldToObject,

unity_ProbeVolumeParams。y, unity_ProbeVolumeParams。z,

unity_ProbeVolumeMin, unity_ProbeVolumeSizeInv

);

}

else {

}

}

Unity SRP 8.全域性照明

4 實時全域性照明

烘焙光照的缺點是不能執行時改變。正如 Rendering 18, Realtime GI, Probe Volumes, LOD Groups, 中解釋的一樣,Unity 使用預先計算全域性照明關係使執行時改變變成可能,而光的方向和強度也可以在執行時改變。這透過開啟Lighting 視窗下的

Realtime Global Illumination

under

Realtime Lighting 來完成。

讓我們來完成它,同時也禁用烘焙照明,將燈光模式設定為實時模式。

Unity SRP 8.全域性照明

Unity將使用Enlighten引擎預先計算傳播間接光所需的所有資料,然後儲存這些資訊,以便以後完成烘焙過程。這使得更新全域性光照成為可能。最初,只有光探頭獲取實時全域性照明。靜態物件使用動態光照貼圖。

Unity SRP 8.全域性照明

透過光照探頭實現的實時全域性照明

4。1 渲染實時全域性照明

為實時光照貼圖渲染表面資訊也是透過meta pass來完成的。但是實時光照貼圖的解析度將會很低,而且UV展開也是不同的。所以我們需要不同的uv座標和變換資訊,透過第三個頂點UV通道和unity_DynamicLightmapST 來實現

CBUFFER_START(UnityPerDraw)

float4 unity_LightmapST, unity_DynamicLightmapST;

CBUFFER_END

struct VertexInput {

float4 pos : POSITION;

float2 uv : TEXCOORD0;

float2 lightmapUV : TEXCOORD1;

float2 dynamicLightmapUV : TEXCOORD2;

};

烘焙和實時光照貼圖需要相同的輸出,唯一不同的是我們必須使用哪個座標。這是透過unity_MetaVertexControl顯示的,它的第一個標誌設定為烘焙,第二個為實時。

CBUFFER_START(UnityMetaPass)

float unity_OneOverOutputBoost;

float unity_MaxOutputValue;

bool4 unity_MetaVertexControl, unity_MetaFragmentControl;

CBUFFER_END

VertexOutput MetaPassVertex (VertexInput input) {

VertexOutput output;

if (unity_MetaVertexControl。x) {

input。pos。xy =

input。lightmapUV * unity_LightmapST。xy + unity_LightmapST。zw;

}

if (unity_MetaVertexControl。y) {

input。pos。xy =

input。dynamicLightmapUV * unity_DynamicLightmapST。xy +

unity_DynamicLightmapST。zw;

}

input。pos。z = input。pos。z > 0 ? FLT_MIN : 0。0;

}

4。2 取樣動態光照貼圖

現在我們在Lit。hlsl中取樣動態光照貼圖,和烘焙光照貼圖一樣,但是透過

unity_DynamicLightmap

貼圖以及它的取樣器 。複製

SampleLightmap

建立

SampleDynamicLightmap

函式,除了使用其他的貼圖和不對它進行編碼,其他的都一樣。

TEXTURE2D(unity_DynamicLightmap);

SAMPLER(samplerunity_DynamicLightmap);

float3 SampleDynamicLightmap (float2 uv) {

return SampleSingleLightmap(

TEXTURE2D_PARAM(unity_DynamicLightmap, samplerunity_DynamicLightmap), uv,

float4(1, 1, 0, 0), false,

float4(LIGHTMAP_HDR_MULTIPLIER, LIGHTMAP_HDR_EXPONENT, 0。0, 0。0)

);

}

當一個動態光照貼圖需要取樣Unity將選擇一個著色器變數與DYNAMICLIGHTMAP_ON關鍵字集,所以為它新增一個多編譯指令。

#pragma multi_compile _ LIGHTMAP_ON

#pragma multi_compile _ DYNAMICLIGHTMAP_ON

新增所需的UV座標和變換,就像烘焙光照貼圖一樣。

CBUFFER_START(UnityPerDraw)

float4 unity_LightmapST, unity_DynamicLightmapST;

CBUFFER_END

struct VertexInput {

float2 lightmapUV : TEXCOORD1;

float2 dynamicLightmapUV : TEXCOORD2;

UNITY_VERTEX_INPUT_INSTANCE_ID

};

struct VertexOutput {

#if defined(LIGHTMAP_ON)

float2 lightmapUV : TEXCOORD4;

#endif

#if defined(DYNAMICLIGHTMAP_ON)

float2 dynamicLightmapUV : TEXCOORD5;

#endif

UNITY_VERTEX_INPUT_INSTANCE_ID

};

VertexOutput LitPassVertex (VertexInput input) {

#if defined(DYNAMICLIGHTMAP_ON)

output。dynamicLightmapUV =

input。dynamicLightmapUV * unity_DynamicLightmapST。xy +

unity_DynamicLightmapST。zw;

#endif

return output;

}

在GlobalIllumination中,如果有動態光照貼圖,對其進行取樣。烘焙光照貼圖和實時光照貼圖可以同時使用,所以在這種情況下新增它們。

float3 GlobalIllumination (VertexOutput input, LitSurface surface) {

#if defined(LIGHTMAP_ON)

float3 gi = SampleLightmap(input。lightmapUV);

#if defined(DYNAMICLIGHTMAP_ON)

gi += SampleDynamicLightmap(input。dynamicLightmapUV);

#endif

return gi;

#elif defined(DYNAMICLIGHTMAP_ON)

return SampleDynamicLightmap(input。dynamicLightmapUV);

#else

return SampleLightProbes(surface);

#endif

}

靜態物件現在也接受實時全域性照明

Unity SRP 8.全域性照明

4。3 實時間接發光

我們的靜態自發光物體也會對實時全域性照明產生髮射光貢獻,代替被烘焙。

Unity SRP 8.全域性照明

Unity SRP 8.全域性照明

這樣做的好處是它可以調整發射顏色,並在播放模式下影響間接光,就像調整主光源的顏色和方向一樣。為了演示這一點,在InstancedMaterialProperties中新增一個脈衝發射頻率配置選項。在播放模式時,如果它大於零,使用餘弦振盪發射顏色之間的原始值和黑色。

[SerializeField]

float pulseEmissionFreqency;

void Awake () {

OnValidate();

if (pulseEmissionFreqency <= 0f) {

enabled = false;

}

}

void Update () {

Color originalEmissionColor = emissionColor;

emissionColor *= 0。5f +

0。5f * Mathf。Cos(2f * Mathf。PI * pulseEmissionFreqency * Time。time);

OnValidate();

emissionColor = originalEmissionColor;

}

Unity SRP 8.全域性照明

Unity SRP 8.全域性照明

僅僅改變發射顏色並不會自動更新全域性照明。我們必須告訴Unity照明情況已經改變,這可以透過呼叫改變物件的MeshRenderer元件上的UpdateGIMaterials來實現。

OnValidate();

GetComponent()。UpdateGIMaterials();

emissionColor = originalEmissionColor;

這將觸發一個meta pass渲染我們的物件,當只改變一個統一的顏色這是多餘的。在這種情況下,我們可以透過呼叫DynamicGI。SetEmissive直接設定統一的顏色,使用渲染器和顏色作為引數,這樣計算速度更快。

//GetComponent()。UpdateGIMaterials();

DynamicGI。SetEmissive(GetComponent(), emissionColor);

emissionColor = originalEmissionColor;

Unity SRP 8.全域性照明

Updating global illumination。

https://www。zhihu。com/video/1186289539114577920

4。4 透明

Unity 使用Enlighten 生成實時全域性照明,它預設是不支援透明表面的。

Unity SRP 8.全域性照明

透明的地方很黑

這種情況下,我們必須明確指出哪些物件是透明的,透過給他自定義 lightmapping 引數和開啟 Is Transparent選項。

Unity SRP 8.全域性照明

Unity SRP 8.全域性照明

透明表面不阻塞燈光,但它們仍然對間接光積累有充分的貢獻。結果是全域性光照在透明表面附近變得太強烈。這在使物件完全透明時變得非常明顯。

Unity SRP 8.全域性照明

我們可以透過在meta pass中乘上反照率的透明度和發射色來彌補這一點。

float4 albedoAlpha = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input。uv);

albedoAlpha *= _Color;

albedoAlpha。rgb *= albedoAlpha。a;

if (unity_MetaFragmentControl。y) {

meta = float4(_EmissionColor。rgb * albedoAlpha。a, 1);

}

因為動態光照貼圖的解析度很低,紋理不會對最終的結果有很大的影響,但是均勻的不透明度值會。

Unity SRP 8.全域性照明

請注意,即使在重新建立預先計算的資料時,Unity也不會總是獲取這些更改。它可能會在編輯模式下快取舊的照明結果,如果它認為它們仍然有效。我發現切換播放模式來顯示差異是最可靠的方法。

5 點光源和聚光燈

到目前為止我們只在方向光下生效,我們來檢查下這一切是否也在點光源和聚光燈下工作。

Unity SRP 8.全域性照明

5。1 實時全域性照明

當啟用實時全域性照明時,兩個燈都按預期進行烘烤,但結果不正確。在計算陰影的間接照明時,沒有考慮陰影。它們也太亮了。

Unity SRP 8.全域性照明

這證明了陰影實時間接照明不支援這些燈,Unity在inspector面板也提示了這一點。動態全域性照明主要是為了支援太陽的間接光,允許一個晝夜迴圈與全域性照明相結合。這隻需要完全支援定向光。

Unity SRP 8.全域性照明

你仍然可以使用這些燈,但如果你想要它們有助於動態全域性照明,你必須繞過這個限制。或者,設定

Indirect Multiplier 為0

,防止他們影響全域性照明。

5。2 烘焙全域性照明

混合和全烘焙點光源和聚光燈不會有上面的問題,只是還是太亮了。

Unity SRP 8.全域性照明

Baked only, too bright。

光的貢獻太多了,因為Unity假設使用預設管道的舊的光照衰減,而我們使用物理上正確的平方衰減。這對方向光來說沒有問題,因為它們沒有衰減。為了修正這個問題,在lightmapping的時候

MyPipeline 必須

告訴Unity使用拿一個衰減函式。 我們要使用Unity。Collections和UnityEngine。Experimental。GlobalIllumination的型別。但是,使用後者會導致LightType的型別衝突,所以應該將其顯式地使用為UnityEngine。LightType。

using Unity。Collections;

using UnityEngine;

using UnityEngine。Rendering;

using UnityEngine。Experimental。Rendering;

using UnityEngine。Experimental。GlobalIllumination;

using LightType = UnityEngine。LightType;

using Conditional = System。Diagnostics。ConditionalAttribute;

我們必須重寫lightmapper如何設定它的光線資料。這是透過向一個方法提供一個委託來完成的,該方法將資料從一個輸入光陣列傳輸到一個輸出

LightDataGI

陣列。委託型別是

Lightmapping

RequestLightsDelegate

我們用一個lamada表示式來定義它。

static Lightmapping。RequestLightsDelegate lightmappingLightsDelegate =

(Light[] inputLights, NativeArray outputLights) => {};

這個委託只在編輯器下能用,所以我們要加上編譯條件。

#if UNITY_EDITOR

static Lightmapping。RequestLightsDelegate lightmappingLightsDelegate =

(Light[] inputLights, NativeArray outputLights) => {};

#endif

我們必須遍歷所有的光,配置合適的

LightDataGI 結構體

,設定falloff型別為

FalloffType

。InverseSquared

,並且把它複製給輸出陣列。

static Lightmapping。RequestLightsDelegate lightmappingLightsDelegate =

(Light[] inputLights, NativeArray outputLights) => {

LightDataGI lightData = new LightDataGI();

for (int i = 0; i < inputLights。Length; i++) {

Light light = inputLights[i];

lightData。falloff = FalloffType。InverseSquared;

outputLights[i] = lightData;

}

};

每一個燈都必須明確配置,即使我們不修改預設行為的任何行為除了衰減。我們可以使用

LightmapperUtils

。Extract

方法將適當的值放在特定於光的結構中,然後複製這些給光資料透過它的Init方法。如果我們最終得到一個未知的light型別,我們將呼叫InitNoBake函式,使用該light的例項識別符號當做引數。

Light light = inputLights[i];

switch (light。type) {

case LightType。Directional:

var directionalLight = new DirectionalLight();

LightmapperUtils。Extract(light, ref directionalLight);

lightData。Init(ref directionalLight);

break;

case LightType。Point:

var pointLight = new PointLight();

LightmapperUtils。Extract(light, ref pointLight);

lightData。Init(ref pointLight);

break;

case LightType。Spot:

var spotLight = new SpotLight();

LightmapperUtils。Extract(light, ref spotLight);

lightData。Init(ref spotLight);

break;

case LightType。Area:

var rectangleLight = new RectangleLight();

LightmapperUtils。Extract(light, ref rectangleLight);

lightData。Init(ref rectangleLight);

break;

default:

lightData。InitNoBake(light。GetInstanceID());

break;

}

lightData。falloff = FalloffType。InverseSquared;

outputLights[i] = lightData;

在委託中重寫預設的行為,透過把委託傳入

Lightmapping

。SetDelegate

方法,在我們構造方法的結尾呼叫。 我們還必須在管道物件被釋放時恢復到預設行為,我們透過覆蓋它的Dispose方法來呼叫基本實現,然後呼叫Lightmapping。ResetDelegate。

public MyPipeline (

) {

#if UNITY_EDITOR

Lightmapping。SetDelegate(lightmappingLightsDelegate);

#endif

}

#if UNITY_EDITOR

public override void Dispose () {

base。Dispose();

Lightmapping。ResetDelegate();

}

#endif

Unity SRP 8.全域性照明

下一篇是烘焙陰影。

標簽: Unity  貼圖  光照  float4  input