Light Probe 的工作原理与应用
https://docs.unity3d.com/2022.3/Documentation/Manual/class-LightProbeGroup.html
建议先把官方文档浏览一遍
Light Probe 是三维场景中的一种光照采样技术。它由一组分布在空间中的采样点组成,每个点记录了烘焙时的光照信息。当动态物体在这些点之间穿梭移动时,通过插值计算物体表面的光照和阴影,既能减少实时计算的开销,又能保持较好的视觉效果。
创建与排布技巧
可以关闭平行光,直接如下图在场景中创建,放置 Light Probe 时,通常会在光照变化明显的区域(如阴影边缘、拐角、物体交界处)增加采样点密度,确保光照过渡自然。其他平坦或光照均匀的区域可以减少采样点数量。记得点光源要设置成backed烘焙时候才会把光照信息记录到lightprobe里。

烘焙流程
(确定单个烘焙的场景,无关场景关掉)
光源设置 :确保场景中的点光源等设置为 Baked 模式,仅计算间接光照。
(我是把平行光关掉不烘焙的)
静态物体标记:将接受光照的静态物体设置为 Static,使其参与烘焙计算。
执行烘焙 :在光照面板启动烘焙,生成 Light Probe 数据。
烘焙完成
替换动态物体:烘焙完成后,可以移除原先的静态物体和光源,并用动态物体代替,此时 Light Probe 会提供近似的光照效果。



如何验证烘焙结果有效?
放置任意sphere物体到烘焙完成的点之间移动,如果线跟随物体进行移动则说明正在计算动态物体表面光照。

场景编辑可以把这些标记点隐藏。

分享一下官方的生成点的代码
csharp
#region
using System.Collections.Generic;
using Sirenix.OdinInspector;
using UnityEngine;
#endregion
[RequireComponent(typeof(LightProbeGroup))]
public class LightProbesTetrahedralGrid : MonoBehaviour
{
// 公用
public float m_Side = 1.0f;
public float m_Radius = 5.0f;
public float m_InnerRadius = 0.1f;
public float m_Height = 2.0f;
public uint m_Levels = 3;
private const float kMinSide = 0.05f;
private const float kMinHeight = 0.05f;
private const float kMinInnerRadius = 0.1f;
private const uint kMinIterations = 4;
public void OnValidate()
{
m_Side = Mathf.Max(kMinSide, m_Side);
m_Height = Mathf.Max(kMinHeight, m_Height);
if (m_InnerRadius < kMinInnerRadius)
{
var 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);
}
}
private 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;
[Button]
public void Generate()
{
var lightProbeGroup = GetComponent<LightProbeGroup>();
var 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();
}
private static void AttemptAdding(Vector3 position, Vector3 center, float distanceCutoffSquared,
List<Vector3> outPositions)
{
if ((position - center).sqrMagnitude < distanceCutoffSquared)
outPositions.Add(position);
}
private uint CalculateCylinderIterations(TriangleProps props, float radius)
{
var iterations = Mathf.CeilToInt((radius + props.height - props.inscribedCircleRadius) / props.height);
if (iterations > 0)
return (uint)iterations;
return 0;
}
private void GenerateCylinder(TriangleProps props, float radius, float height, uint levels,
List<Vector3> outPositions)
{
var iterations = CalculateCylinderIterations(props, radius);
var distanceCutoff = radius;
var distanceCutoffSquared = distanceCutoff * distanceCutoff;
var up = new Vector3(props.circumscribedCircleRadius, 0.0f, 0.0f);
var leftDown = new Vector3(-props.inscribedCircleRadius, 0.0f, -props.halfSide);
var rightDown = new Vector3(-props.inscribedCircleRadius, 0.0f, props.halfSide);
for (uint l = 0; l < levels; l++)
{
var tLevel = levels == 1 ? 0 : l / (float)(levels - 1);
var center = new Vector3(0.0f, tLevel * height, 0.0f);
if (l % 2 == 0)
{
for (uint i = 0; i < iterations; i++)
{
var upCorner = center + up + i * up * 2.0f * 3.0f / 2.0f;
var leftDownCorner = center + leftDown + i * leftDown * 2.0f * 3.0f / 2.0f;
var rightDownCorner = center + rightDown + i * rightDown * 2.0f * 3.0f / 2.0f;
AttemptAdding(upCorner, center, distanceCutoffSquared, outPositions);
AttemptAdding(leftDownCorner, center, distanceCutoffSquared, outPositions);
AttemptAdding(rightDownCorner, center, distanceCutoffSquared, outPositions);
var leftDownUp = upCorner - leftDownCorner;
var upRightDown = rightDownCorner - upCorner;
var rightDownLeftDown = leftDownCorner - rightDownCorner;
var subdiv = 3 * i + 1;
for (uint s = 1; s < subdiv; s++)
{
var leftDownUpSubdiv = leftDownCorner + leftDownUp * s / subdiv;
AttemptAdding(leftDownUpSubdiv, center, distanceCutoffSquared, outPositions);
var upRightDownSubdiv = upCorner + upRightDown * s / subdiv;
AttemptAdding(upRightDownSubdiv, center, distanceCutoffSquared, outPositions);
var rightDownLeftDownSubdiv = rightDownCorner + rightDownLeftDown * s / subdiv;
AttemptAdding(rightDownLeftDownSubdiv, center, distanceCutoffSquared, outPositions);
}
}
}
else
{
for (uint i = 0; i < iterations; i++)
{
var upCorner = center + i * (2.0f * up * 3.0f / 2.0f);
var leftDownCorner = center + i * (2.0f * leftDown * 3.0f / 2.0f);
var rightDownCorner = center + i * (2.0f * rightDown * 3.0f / 2.0f);
AttemptAdding(upCorner, center, distanceCutoffSquared, outPositions);
AttemptAdding(leftDownCorner, center, distanceCutoffSquared, outPositions);
AttemptAdding(rightDownCorner, center, distanceCutoffSquared, outPositions);
var leftDownUp = upCorner - leftDownCorner;
var upRightDown = rightDownCorner - upCorner;
var rightDownLeftDown = leftDownCorner - rightDownCorner;
var subdiv = 3 * i;
for (uint s = 1; s < subdiv; s++)
{
var leftDownUpSubdiv = leftDownCorner + leftDownUp * s / subdiv;
AttemptAdding(leftDownUpSubdiv, center, distanceCutoffSquared, outPositions);
var upRightDownSubdiv = upCorner + upRightDown * s / subdiv;
AttemptAdding(upRightDownSubdiv, center, distanceCutoffSquared, outPositions);
var rightDownLeftDownSubdiv = rightDownCorner + rightDownLeftDown * s / subdiv;
AttemptAdding(rightDownLeftDownSubdiv, center, distanceCutoffSquared, outPositions);
}
}
}
}
}
private void GenerateRing(TriangleProps props, float radius, float innerRadius, float height, uint levels,
List<Vector3> outPositions)
{
var chordLength = props.side;
var angle = Mathf.Clamp(2.0f * Mathf.Asin(chordLength / (2.0f * radius)), 0.01f, 2.0f * Mathf.PI);
var slicesAtRadius = (uint)Mathf.FloorToInt(2.0f * Mathf.PI / angle);
var layers = (uint)Mathf.Max(Mathf.Ceil((radius - innerRadius) / props.height), 0.0f);
for (uint level = 0; level < levels; level++)
{
var tLevel = levels == 1 ? 0 : level / (float)(levels - 1);
var y = height * tLevel;
var iterationOffset0 = level % 2 == 0 ? 0.0f : 0.5f;
for (uint layer = 0; layer < layers; layer++)
{
var tLayer = layers == 1 ? 1.0f : layer / (float)(layers - 1);
var tIterations = (tLayer * (radius - innerRadius) + innerRadius - kMinInnerRadius) /
(radius - kMinInnerRadius);
var slices = (uint)Mathf.CeilToInt(Mathf.Lerp(kMinIterations, slicesAtRadius, tIterations));
var x = innerRadius + (radius - innerRadius) * tLayer;
var position = new Vector3(x, y, 0.0f);
var layerSliceOffset = layer % 2 == 0 ? 0.0f : 0.5f;
for (uint slice = 0; slice < slices; slice++)
{
var rotation = Quaternion.Euler(0.0f,
(slice + iterationOffset0 + layerSliceOffset) * 360.0f / slices, 0.0f);
outPositions.Add(rotation * position);
}
}
}
}
}
(我在场景中偷懒于是生成并放了一堆点)
