光照探针 Light Probe
目錄
1. 概念
1.1 什么是光照探針
1.2 光照探針作用
2. 使用 光照探針組 Light Probe Group
2.1 概念
2.2 使用方式
2.3 屬性
2.4 光探頭放置故障排除
3. 用代碼添加光照探針
1. 概念
首先,我們來回顧全局光照(Global Illumination )的設置,就是為了獲取反射光影效果,增強場景真實性
| realtime | Realtime Global Illumination | not static | 有效果 | 無烘焙貼圖,但會有實時貼圖 | 有 |
| realtime | Mixed Global Illumination | static | 無效果 | 有烘焙貼圖 | 有 |
| baked | Mixed Global Illumination | not static | 無效果 | 無烘焙貼圖 | 無 |
| baked | Mixed Global Illumination | static | 有效果 | 有烘焙貼圖 | 有 |
| Mixed | Mixed Global Illumination | not static | 無效果 | 無烘焙貼圖 | 有 |
| Mixed | Mixed Global Illumination | static | 有效果 | 有烘焙貼圖 | 有 |
圖1:靜態、非靜態物體,都沒有配置好全局光照(間接光照)的效果
圖2:靜態物體有全局光照(間接光照)、非靜態物體無間接光照的效果
圖3:使用光照探針配合光照貼圖,靜態、非靜態物體,都有間接光照效果
注意:
- 實時光照,如果想要有間接光影效果,必須將全局光照配置為 Realtime Global Illumination
- 實時光照,如果想要有間接光影效果,還需要將物體設置為 Contribute Global Illumination?
- 但實際游戲中,是不會使用實時全局光照 Realtime Global Illumination(耗費大,而且效果差并非完全實時),所以要用 Mixed 光照配合 Mixed Global Illumination
- 為了彌補 非靜態物體的間接光照效果,需要使用光照探針
間接光影效果最佳方案總結:
1.1 什么是光照探針
- 實時 Realtime 光照太浪費資源,一般不會大范圍使用在實際游戲場景中;
- 烘焙 Baked 光照又不能作用在動態物體上,這就有了光照探針,來彌補光照貼圖的不足
- 對于 Mixed 光照,雖然可以將光影效果投射到移動物體上,但移動物體不會從靜態環境中接收到反射光,所以也需要使用光照探針
未添加 光照探針:
?
正在添加 光照探針:?
?
添加光照探針后:?
光照探針大致原理:在某一光照探針的所在位置點上對光照信息進行采樣,然后從該光照探針相鄰的其他光照探針的位置上對光照信息進行采樣,把這些采樣得到的光照信息進行插值運算,便可算出這些光照探針之間某個位置的光照信息。
1.2 光照探針作用
光探測器(光照探針 Light Probe)存儲有關場景中照明的“烘焙”信息。
光照貼圖存儲有關光線照射場景中表面的光照信息,但光照探測器存儲有關光線穿過場景中空白空間的信息。
說白了,光照貼圖只存儲 mesh 表面的光影信息;而光照探針,存儲空白空間的光影信息,包括直接光和間接光(反射光),是對光照貼圖的補充
2. 使用 光照探針組 Light Probe Group
2.1 概念
光照探針組就是一組光照探針,默認是一個立方體,4*2一組,共八個光照探針組成。
可以通過編輯,增加或刪除光照探針,也可以隨意移動光照探針位置
2.2 使用方式
可以從菜單中添加 Light Probe Group 到當前場景中:Component > Rendering > Light Probe Group
光照探針組初始形態:
2.3 屬性
光照探針組組件在 Inspector 窗口如下:
- Edit Light Probes: 編輯光照探針,點擊后,可以在場景中編輯光照探針組中的光照探針,進行增加、刪除,移動位置等操作
- Show Wireframe :顯示光照探針之間的連線組成的線框,如果禁用,則只顯示探針所在點,而不顯示之間的連線;
- Remove Ringing :去除振鈴。啟用此屬性后,Unity 會自動從場景中移除 Light Probe 振鈴。
在某些情況下,Light Probe 會表現出一種稱為“振鈴”的不良行為。當 Light Probe 周圍的光線存在顯著差異時,通常會發生這種情況。例如,如果光探頭的一側有亮光,而另一側沒有光,則光強度可能會在背面“過沖”。這種過沖會在背面產生一個光點。
-
啟用Remove Ringing。Unity 會自動移除不希望出現的光點。但是,這通常會降低 Light Probes 的準確度,并降低光線對比度,因此您必須檢查視覺結果 - Selected Probe Position : 選定光照探針所在位置
2.4 光探頭放置故障排除
對 Light Probe 位置的選擇必須考慮到光照是在 Light Probe 組之間插值的。如果您的 Light Probe 沒有充分覆蓋整個場景中的光照變化,則可能會出現問題。
下面的示例顯示了一個夜間場景,兩側有兩個明亮的路燈,中間有一個黑暗區域。如果 Light Probe 僅放置在路燈附近,而在黑暗區域沒有放置,則來自燈的照明直接穿過黑暗的縫隙,照射到移動的物體上。這是因為照明從一個亮點插入到另一個亮點,中間沒有關于暗區的信息。
如果您使用的是實時或混合光,則此問題可能不太明顯,因為只有間接光會穿過間隙。如果您完全使用烘焙光,問題會更加明顯 ,因為在這種情況下,移動物體上的直射光也是由 Light Probes 插入的。在此示例場景中,兩個燈被烘焙,因此移動對象從 Light Probes 獲得直接光。在這里您可以看到結果 - 移動的物體(救護車)在穿過黑暗區域時保持明亮,這不是預期的效果。黃色線框四面體顯示插值發生在街道明亮的一端與另一端之間。
這是一個不希望的效果 - 救護車在通過黑暗區域時仍然保持明亮,因為沒有在黑暗區域放置光探頭。
為了解決這個問題,你應該在黑暗區域放置更多的Light Probes,如下圖:
現在場景在黑暗區域也有光探針。因此,移動的救護車在從場景的一側行駛到另一側時會呈現較暗的燈光。
3. 用代碼添加光照探針
在比較大的場景中,如果需要光照探針時,還是使用手動添加,不僅位置不會很精確,而且會非常耗時。
這種情況下,就可以使用代碼來自動防止光照探針:
下面是核心功能代碼:
//獲取光照探針組件LightProbeGroup lightProbeGroup = GetComponent<LightProbeGroup>();//建立要插入探針位置的列表List<Vector3> positions = new List<Vector3>();// 下面就是向 positions 中添加合理的位置點..............//向光照探針組中,添加探針lightProbeGroup.probePositions = positions.ToArray();?完整實例:下面腳本可以將 Light Probes 放置在一個圓圈或一個環中
using System.Collections.Generic; using UnityEngine;[RequireComponent(typeof(LightProbeGroup))] public class LightProbesTetrahedralGrid : MonoBehaviour {// Common[Tooltip("邊長 : 同層探針間連線長度")]public float m_Side = 1.0f;[Tooltip("半徑 : 圓形探針組大圓半徑")]public float m_Radius = 5.0f;[Tooltip("內半徑 : 圓形探針組內部小圓半徑")]public float m_InnerRadius = 0.1f;[Tooltip("高度 : 圓形探針組高度")]public float m_Height = 2.0f;[Tooltip("層數 : 圓形探針組層數")]public uint m_Levels = 3;//最小半徑const float kMinSide = 0.05f;//最低高度const float kMinHeight = 0.05f;//最小內圓半徑const float kMinInnerRadius = 0.1f;//最小迭代數const uint kMinIterations = 4;// 游戲運行時,先調用 Generate 方法,添加光照探針到場景中private void FixedUpdate(){Generate();}// OnValidate可以用來驗證一些數據,腳本加載或Inspector中的任何值被修改時會調用// 可以作為數據的保護,避免一些字段(或屬性)被設置為不可用(不合理)的數據值public void OnValidate(){//保證 m_Side 不小于 kMinSidem_Side = Mathf.Max(kMinSide, m_Side);//保證 m_Height 不小于 kMinHeightm_Height = Mathf.Max(kMinHeight, m_Height);// 對 m_Radius 和 m_InnerRadius 進行限制if (m_InnerRadius < kMinInnerRadius){TriangleProps props = new TriangleProps(m_Side);m_Radius = Mathf.Max(props.circumscribedCircleRadius + 0.01f, m_Radius);}else{m_Radius = Mathf.Max(0.1f, m_Radius);m_InnerRadius = Mathf.Min(m_Radius, m_InnerRadius);}}// 結構體 三角形探針struct TriangleProps{public TriangleProps(float triangleSide){side = triangleSide;halfSide = side / 2.0f;height = Mathf.Sqrt(3.0f) * side / 2.0f;//內切圓半徑inscribedCircleRadius = Mathf.Sqrt(3.0f) * side / 6.0f;//外切圓半徑circumscribedCircleRadius = 2.0f * height / 3.0f;}public float side;public float halfSide;public float height;public float inscribedCircleRadius;public float circumscribedCircleRadius;};//三角形探針位,用來輔助生成探針的位置private TriangleProps m_TriangleProps;//生成探針主方法public void Generate(){//獲取光照探針組件LightProbeGroup lightProbeGroup = GetComponent<LightProbeGroup>();//建立要插入探針位置的列表List<Vector3> positions = new List<Vector3>();//三角形探針位,用來輔助生成探針的位置m_TriangleProps = new TriangleProps(m_Side);if (m_InnerRadius < kMinInnerRadius)//生成圓柱GenerateCylinder(m_TriangleProps, m_Radius, m_Height, m_Levels, positions);else//生成環GenerateRing(m_TriangleProps, m_Radius, m_InnerRadius, m_Height, m_Levels, positions);//向光照探針組中,添加探針lightProbeGroup.probePositions = positions.ToArray();}//嘗試添加static void AttemptAdding(Vector3 position, Vector3 center, float distanceCutoffSquared, List<Vector3> outPositions){if ((position - center).sqrMagnitude < distanceCutoffSquared)outPositions.Add(position);}//計算圓柱體迭代uint CalculateCylinderIterations(TriangleProps props, float radius){int iterations = Mathf.CeilToInt((radius + props.height - props.inscribedCircleRadius) / props.height);if (iterations > 0)return (uint)iterations;return 0;}//生成圓柱體void GenerateCylinder(TriangleProps props, float radius, float height, uint levels, List<Vector3> outPositions){uint iterations = CalculateCylinderIterations(props, radius);float distanceCutoff = radius;float distanceCutoffSquared = distanceCutoff * distanceCutoff;Vector3 up = new Vector3(props.circumscribedCircleRadius, 0.0f, 0.0f);Vector3 leftDown = new Vector3(-props.inscribedCircleRadius, 0.0f, -props.halfSide);Vector3 rightDown = new Vector3(-props.inscribedCircleRadius, 0.0f, props.halfSide);for (uint l = 0; l < levels; l++){float tLevel = levels == 1 ? 0 : (float)l / (float)(levels - 1);Vector3 center = new Vector3(0.0f, tLevel * height, 0.0f);if (l % 2 == 0){for (uint i = 0; i < iterations; i++){Vector3 upCorner = center + up + (float)i * up * 2.0f * 3.0f / 2.0f;Vector3 leftDownCorner = center + leftDown + (float)i * leftDown * 2.0f * 3.0f / 2.0f;Vector3 rightDownCorner = center + rightDown + (float)i * rightDown * 2.0f * 3.0f / 2.0f;AttemptAdding(upCorner, center, distanceCutoffSquared, outPositions);AttemptAdding(leftDownCorner, center, distanceCutoffSquared, outPositions);AttemptAdding(rightDownCorner, center, distanceCutoffSquared, outPositions);Vector3 leftDownUp = upCorner - leftDownCorner;Vector3 upRightDown = rightDownCorner - upCorner;Vector3 rightDownLeftDown = leftDownCorner - rightDownCorner;uint subdiv = 3 * i + 1;for (uint s = 1; s < subdiv; s++){Vector3 leftDownUpSubdiv = leftDownCorner + leftDownUp * (float)s / (float)subdiv;AttemptAdding(leftDownUpSubdiv, center, distanceCutoffSquared, outPositions);Vector3 upRightDownSubdiv = upCorner + upRightDown * (float)s / (float)subdiv;AttemptAdding(upRightDownSubdiv, center, distanceCutoffSquared, outPositions);Vector3 rightDownLeftDownSubdiv = rightDownCorner + rightDownLeftDown * (float)s / (float)subdiv;AttemptAdding(rightDownLeftDownSubdiv, center, distanceCutoffSquared, outPositions);}}}else{for (uint i = 0; i < iterations; i++){Vector3 upCorner = center + (float)i * (2.0f * up * 3.0f / 2.0f);Vector3 leftDownCorner = center + (float)i * (2.0f * leftDown * 3.0f / 2.0f);Vector3 rightDownCorner = center + (float)i * (2.0f * rightDown * 3.0f / 2.0f);AttemptAdding(upCorner, center, distanceCutoffSquared, outPositions);AttemptAdding(leftDownCorner, center, distanceCutoffSquared, outPositions);AttemptAdding(rightDownCorner, center, distanceCutoffSquared, outPositions);Vector3 leftDownUp = upCorner - leftDownCorner;Vector3 upRightDown = rightDownCorner - upCorner;Vector3 rightDownLeftDown = leftDownCorner - rightDownCorner;uint subdiv = 3 * i;for (uint s = 1; s < subdiv; s++){Vector3 leftDownUpSubdiv = leftDownCorner + leftDownUp * (float)s / (float)subdiv;AttemptAdding(leftDownUpSubdiv, center, distanceCutoffSquared, outPositions);Vector3 upRightDownSubdiv = upCorner + upRightDown * (float)s / (float)subdiv;AttemptAdding(upRightDownSubdiv, center, distanceCutoffSquared, outPositions);Vector3 rightDownLeftDownSubdiv = rightDownCorner + rightDownLeftDown * (float)s / (float)subdiv;AttemptAdding(rightDownLeftDownSubdiv, center, distanceCutoffSquared, outPositions);}}}}}//生成環void GenerateRing(TriangleProps props, float radius, float innerRadius, float height, uint levels, List<Vector3> outPositions){float chordLength = props.side;float angle = Mathf.Clamp(2.0f * Mathf.Asin(chordLength / (2.0f * radius)), 0.01f, 2.0f * Mathf.PI);uint slicesAtRadius = (uint)Mathf.FloorToInt(2.0f * Mathf.PI / angle);uint layers = (uint)Mathf.Max(Mathf.Ceil((radius - innerRadius) / props.height), 0.0f);for (uint level = 0; level < levels; level++){float tLevel = levels == 1 ? 0 : (float)level / (float)(levels - 1);float y = height * tLevel;float iterationOffset0 = level % 2 == 0 ? 0.0f : 0.5f;for (uint layer = 0; layer < layers; layer++){float tLayer = layers == 1 ? 1.0f : (float)layer / (float)(layers - 1);float tIterations = (tLayer * (radius - innerRadius) + innerRadius - kMinInnerRadius) / (radius - kMinInnerRadius);uint slices = (uint)Mathf.CeilToInt(Mathf.Lerp(kMinIterations, slicesAtRadius, tIterations));float x = innerRadius + (radius - innerRadius) * tLayer;Vector3 position = new Vector3(x, y, 0.0f);float layerSliceOffset = layer % 2 == 0 ? 0.0f : 0.5f;for (uint slice = 0; slice < slices; slice++){Quaternion rotation = Quaternion.Euler(0.0f, (slice + iterationOffset0 + layerSliceOffset) * 360.0f / (float)slices, 0.0f);outPositions.Add(rotation * position);}}}}}要求一個組件,如果游戲物體身上沒有就會自動添加
?Tooltip? 在unity窗口中鼠標懸停在名字上時的提示
參考資料:
- 光照探針文檔 - Unity 官方
- Brackeys - youtube - HIGH QUALITY LIGHTING using Light Probes
總結
以上是生活随笔為你收集整理的光照探针 Light Probe的全部內容,希望文章能夠幫你解決所遇到的問題。