全局光照假是实时渲染中最容易被玩家察觉的画面问题之一。简单来说,全局光照模拟的是光线在场景中多次弹射的效果,比如阳光照进房间后,地板会把光反射到天花板上,墙面又会把光反弹给附近的家具。但实时引擎出于性能考虑,往往只计算直接光照,间接光部分要么被简化、要么被跳过,于是出现了角色走进阴影就变成黑块、静态场景和动态物体亮度对不上、明暗交界处出现色斑等典型的光照假现象。本文从问题成因讲起,重点介绍光照探针与光照烘焙这两套互补的解决方案。

光照假是怎么产生的:从渲染方程说起
要理解光照假,得先知道完整的光照计算有多贵。渲染方程描述了某一点接收到的全部光能,包括光源直接照射的部分和从其他表面反弹过来的间接部分。一个中等复杂度的场景,光线可能要弹射四到六次才能达到视觉上自然的效果,而每次弹射都意味着对场景中大量三角形的求交运算。电影级的离线渲染可以用几分钟甚至几小时渲染一帧,但游戏需要每秒跑满六十帧,每帧只有十六毫秒左右的时间预算,根本来不及实时计算多次弹射。
于是实时引擎普遍采取的策略是:直接光照实时算,间接光照想办法近似。近似手段一旦不够精细,画面就会露馅。最常见的几种表现包括:角色站在背光面时全身发黑,因为角色是动态物体,无法享受场景的烘焙光照;明暗过渡区域出现硬边或色斑,因为烘焙分辨率不足;静态物体和动态物体亮度不一致,一个亮一个暗,看起来像贴上去的。这些问题的根源都是间接光照的缺失或不连续。
解决思路大体分两条线:对于不会动的场景几何,把光照计算结果提前算好存进贴图或体素里,这就是烘焙;对于会动的物体,用空间中分布的光照采样点来插值出近似环境光,这就是光照探针。两者配合使用,才能覆盖完整的场景需求。
光照烘焙:把间接光提前算好存起来
烘焙的核心思想是空间换时间。引擎在编辑阶段用渐进式光照模拟算法,从光源出发追踪大量光线,让它们在场景中弹射多次,把最终落到每个表面上的光照结果记录下来。以Unity为例,使用Progressive Lightmapper时,需要先把场景中的光源和静态物体标记为Lighting Static,然后在Lighting窗口中开启Baked Global Illumination,设置好间接光弹射次数后点击Generate Lighting即可。
// Unity中通过脚本触发烘焙
using UnityEngine;
using UnityEditor;
public class BakeTool
{
[MenuItem("Tools/开始烘焙")]
static void StartBake()
{
// 开启渐进式光照贴图
Lightmapping.bakedGI = true;
Lightmapping.giWorkflowMode = Lightmapping.GIWorkflowMode.OnDemand;
// 开始生成光照数据
Lightmapping.Bake();
Debug.Log("烘焙已提交,等待完成");
}
}烘焙的质量主要取决于两个参数:光照贴图分辨率和间接光弹射次数。分辨率决定了单位面积上存储多少光照信息,一般给到每平方米10到20个纹素就能应付多数场景,关键区域可以单独调高。弹射次数控制光线反弹几层,室内场景建议至少3次,否则暗部会显得死黑;次数过高则烘焙时间成倍增长,收益却递减明显。此外要注意UV的处理,物体必须有正确的Lightmap UV,重叠或利用率低的UV会导致烘焙结果出现漏光和接缝。
烘焙的优点是运行时几乎零开销,静态场景的间接光质量可以做到非常接近离线渲染。缺点也很突出:只适用于不动的物体,门开了、墙塌了,光照不会跟着变。烘焙数据还会显著增大包体,一个大型场景的光照贴图动辄几十上百兆,需要配合压缩和流式加载来控制内存。
光照探针:让动态物体也吃上间接光
角色、载具、掉落物这些会移动的物体没法烘焙,但让它们只受直接光照又太假。光照探针就是为了填补这个空档而生的。原理并不复杂:在场景空间中布置一批采样点,烘焙时每个点记录下来自四面八方的间接光照信息,通常用三阶球谐函数压缩存储。运行时,动态物体根据自身位置找到包围它的最近的几个探针,对探针数据做三线性插值,得到当前位置的近似环境光,再作用到物体的材质上。
布置探针是门手艺活。原则是用最少的点覆盖最大的空间,重点区域加密,空旷区域放松。比如角色主要活动的走廊和房间,探针间距两到三米比较合适;天花板上方、墙外、地下这些角色到不了的地方就不用放。Unity中用Light Probe Group组件,可以在场景里摆放节点形成体素网格;虚幻引擎则提供了GPULightmass配合探针体积的方案。布置完后一定要进光照复杂的区域实测,观察明暗过渡是否平滑。
// Unity中获取探针插值结果的自定义用法示例
using UnityEngine;
public class ProbeSampler : MonoBehaviour
{
void Update()
{
// 探针插值通常由引擎自动完成
// 这里演示如何用API手动查询球谐数据用于自定义着色
SphericalHarmonicsL2 sh;
LightProbes.GetInterpolatedProbe(transform.position, null, out sh);
// 将球谐系数传给自定义shader做环境光计算
Debug.Log("已获取当前位置的探针光照");
}
}插值带来的一个副作用是光照变化永远是平滑的,这在少数情况下反而失真,比如角色穿过一堵薄墙从一个亮房间走进暗房间,插值会让亮度渐变而不是突变。缓解办法是在墙体两侧多放几层探针,让过渡发生在墙体内。另外探针只提供低频环境光,无法还原清晰的反射和高光细节,这部分需要反射探针来补充,两者名字相近但职责不同,别混淆。
方案选型:烘焙、探针与实时方案如何取舍
烘焙加探针的组合不是唯一选择,现代引擎还提供了实时光线追踪和屏幕空间全局光照等方案。它们各有边界:烘焙适合静态为主的场景,比如建筑可视化、固定关卡的游戏;探针补足了其中的动态物体;实时方案则适合光照剧烈变化的场景,比如可破坏地形、昼夜循环频繁的游戏,但对硬件要求高,主机和移动端往往扛不住。
实际项目中通常是混合使用。一个常见的做法是:静态几何用烘焙,中低配设备上的动态物体用探针,高配设备再叠加DDGI或光追反射提升细节。切换光照模式时要保证不同方案之间的亮度标定一致,否则玩家调整画质选项时会发现画面忽明忽暗,体验很差。
最后提醒两个常见的翻车点。一是烘焙后改动了几何体却忘记重新烘焙,结果阴影和物体对不上,建议把烘焙步骤纳入自动化构建流程;二是探针网格太稀疏导致角色光照突变,宁可多花一点内存也尽量在光照复杂的区域加密。把这两套工具的原理吃透,光照假问题基本可以在不牺牲帧率的前提下得到解决。