【Unity】Light Probe 替代点光源给环境动态物体加光照

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);
                }
            }
        }
    }
}

(我在场景中偷懒于是生成并放了一堆点)

相关推荐
@LYZY3 小时前
Unity 中隐藏文件规则
unity·游戏引擎·游戏程序·vr
霜绛5 小时前
C#知识补充(二)——命名空间、泛型、委托和事件
开发语言·学习·unity·c#
Sator17 小时前
使用Unity ASE插件设置数值不会生效的问题
unity·游戏引擎
AA陈超8 小时前
虚幻引擎5 GAS开发俯视角RPG游戏 P07-08 点击移动
c++·游戏·ue5·游戏引擎·虚幻
程序猿追9 小时前
轻量级云原生体验:在OpenEuler 25.09上快速部署单节点K3s
人工智能·科技·机器学习·unity·游戏引擎
B0URNE10 小时前
【Unity基础详解】(7)Unity核心:动画系统
unity·游戏引擎
我的golang之路果然有问题10 小时前
mac M系列芯片 unity 安装会遇到的错误以及解决
经验分享·学习·macos·unity·游戏引擎
Hody912 天前
【XR开发系列】2025 年 XR 开发入门,我该选择 Unity 还是 Unreal Engine?
unity·xr·虚幻
DvLee10242 天前
UnityGLTF 材质创建与赋值流程
unity·材质