从”普通程序员能听懂”到”能改 UE 光照源码”——基于源码逐行考证,六条主线全覆盖:
烘焙流程 / 自动化展开 UV / 压缩 Lightmap / 提高填充率 / 把 UE 当烘焙器 / Unity 场景导入
(材质、灯光、地表对齐)

  • 文档版本:1.0(2026-08-26)
  • 源码快照:本地仓库 d:\Project\GameDevelop\UnrealEngine,UE 5.9 移动优化 fork,HEAD 6cea9bd20f8f
  • 写作原则:文中所有”源码考古”块引用的文件路径、行号、函数名均在本仓库中实际验证过;行号只用于定位,请以 路径:函数名 为准
  • 姊妹文档:本文是《Nanite》《VSM》《UE 网络架构》《空间音频》之后的第五篇——前四篇是纯 GPU/纯实时主题,本篇是第一条离线管线主线(CPU 构建 + GPU 渲染消费 + 跨引擎工作流)
  • 符号说明Lightmap=光照贴图,Bake=烘焙,Texel Density=纹素密度,Atlas=图集,Chart=UV 岛,Padding=边距,Packing=装箱,Tile=瓦片;HQ/LQ/Scale/Add/SH 不翻译

第 0 章 导读:这份文档怎么读

TL;DR|这份文档把 UE Lightmap 拆成六条主线:烘焙流程(GPU Lightmass 怎么把场景
“烤”成光照贴图)、自动化展开 UV(让引擎替你摊橘子皮)、压缩 Lightmap(内存瘦身)、
提高填充率(拼图利用率)、把 UE 当烘焙器(离线光照工厂)、Unity 场景导入
(材质/灯光/地表对齐)。心智模型:Lightmap = 给场景”烤”一层光照贴图——离线预计算,
运行时廉价读取
。三个深度档位:30 秒看懂、半天学会烘焙、1 天读源码。

0.1 目标读者与前置知识

目标读者是普通程序员:会摆场景调灯光、知道 UV 与纹理贴图是什么、见过编辑器里”Build Lighting”转圈。每个术语第一次出现都配一句直觉解释。

前置知识三个:

  1. UV 与纹理:模型表面的”贴图坐标”——光照贴图也是贴图,同样需要 UV;
  2. 场景灯光与移动性:Static/Stationary/Movable 三态(第 1 章细讲);
  3. 烘焙(Bake):离线预计算光照,把结果存成贴图——本篇的一切围绕它展开。

与前三篇的关系:Nanite/VSM 讲实时渲染,空间音频讲听觉,网络讲多机——本篇讲**”光照怎么提前算好”**,与实时 GI(Lumen)是互补关系(第 1 章对比)。

0.2 三条阅读路径

通道 读什么 耗时 读完能做什么
🚀 30 秒通道 每章 TL;DR + 第 2 章概念类比 + 图 F1 10 分钟 跟人讲清 Lightmap 六条主线与”烘焙=拍照片”心智模型
📚 标准通道 TL;DR + 正文 + 第 7~12 章(六专题,占全文 54%) 6~8 小时 会完整烘焙关卡、会开自动 UV、会压内存、会查填充率、能搭离线烘焙流水线、能把 Unity 场景迁进 UE
🔬 源码考古通道 全部 + 源码考古块 + 附录 A 对照 1~2 天 敢打开 GPULightmass/LightMap.cpp 对照着读

0.3 块标记约定

与前四篇一致的五种块:

  • **TL;DR**(每章开头):30 秒读完本章,3~5 句、必须含类比名、不含源码路径;
  • **类比实验室**(正文小节):用普通程序员已懂的东西讲陌生概念;
  • **源码考古**(每章末尾,折叠块):贴真实源码(≤25 行/段),逐行翻译;
  • **避坑**(正文穿插):版本差异、已删除项、公开资料不一致;
  • **动手实验室**(第 7~12 章):配方型内容——步骤|资产/设置|验收标准 表 + 检查清单。

0.4 一张图看懂全局

图 F1 Lightmap 完整生命周期七段(从场景设置到屏幕显示;贯穿全文的主干图)
① 场景设置 灯光 Mobility 三态 Lightmap UV 检查 世界设置烘焙参数 分辨率/纹素密度 (第 7 章) ② GPU Lightmass Build Lighting 触发 tile 路径追踪 辐照度缓存/去噪 逐 tile 渐进收敛 (第 3 章) ③ 编码 RGBA16F → 8bit 量化 HQ=LogLUVW + SH Simple=LogRGB Scale/Add 归一化 (第 4 章) ④ Atlas 装箱 编辑器侧 skyline 装箱 网格侧 FLayoutUV 填充率/边距 CoordinateScale/Bias (第 5/10 章) ⑤ 压缩与存储 bCompressLightmaps BC7 / 移动端 ASTC MapBuildDataRegistry 流送(0.2 因子) (第 6/9 章) ⑥ 运行时采样 BasePass 读 Lightmap GetLightMapColorHQ 解码 Scale/Add + SH 体积光照图插值 (第 6 章) ⑦ 消费端 游戏画面显示 移动端/桌面 烘焙产物导出 (第 11 章)

第 1/7 章
第 3 章
第 4 章
第 5/10 章
第 6/9 章
第 6 章
第 11/12 章

跨引擎工作流(Unity→UE):第 12 章——FBX/USD/Datasmith/GLTF(经 Interchange)导入 + 材质/灯光/地表对齐
UE 当烘焙器:第 11 章——命令行烘焙 / 分层烘焙 / 产物导出

读图方法:场景设置好了 → GPU Lightmass 逐 tile 路径追踪”烤”出光照 → 编码成 8bit 系数 → 装箱进 Atlas → 压缩存储 → 运行时 BasePass 廉价采样 → 显示。①~⑤ 是离线(构建期),⑥⑦ 是运行时——这就是”预计算”的全部含义。

0.5 章节地图

章节 一句话
开篇 第 0~1 章 怎么读 + 静态光照的”预计算”哲学(Lumen vs Lightmap)
概念篇 第 2 章 六大概念速览(每个一个类比)
机制篇 第 3~6 章 烘焙管线 → 编码 → 装箱 → 运行时采样与流送
专题篇 第 7~12 章 六条主线(用户重点,占 54%)
调优篇 第 13~14 章 CVar 速查与常见坑 / 局限与展望
附录 A~D 源码地图、术语表、参考资料、自测题

第 1 章 Lightmap 是什么:静态光照的”预计算”哲学

TL;DR|Lightmap 是”给场景烤一层光照贴图“:把静态光照离线算好存成纹理,
运行时每像素读一下就行——把昂贵的实时光照计算换成廉价的贴图采样。Lumen 是
实时全局光照(贵但动态),Lightmap 是离线预烘焙(便宜但静态),Mobility 三态
(Static/Stationary/Movable)决定了灯光参与哪种方案。

1.1 烘焙 = 拍照片

类比实验室:拍照片
烘焙就像给场景”拍照片”:相机(光照求解器)花很长时间拍(计算),洗出来的照片
(Lightmap 贴图)被贴到模型表面,运行时”看照片”(采样贴图)——不重新拍。
照片是死的:灯移动了照片不会变;但看照片几乎零成本。这就是预计算的全部哲学:
把贵的计算挪到离线,把廉价的结果留给运行时

1.2 为什么需要预计算

一个静态场景(建筑、地形、道具)的光照其实不会变——灯不动、物体不动,光照结果就是确定的。实时重算它是浪费:每帧算全局光照 = 每帧都要昂贵的路径追踪/辐射度。预计算让”确定的结果”只算一次

  • 质量:离线可以跑几百采样/像素(GPU Lightmass 默认 GISamples=512),实时 GI 只能给几十;
  • 成本:运行时每像素一次贴图采样(几十纳秒)vs 实时 GI 每像素几十微秒;
  • 代价:静态(灯动/物体动都需要重新烘焙)、存储(贴图占空间)。

1.3 Lumen vs Lightmap:实时 GI vs 离线烘焙

Lumen(实时 GI) Lightmap(离线烘焙)
计算时机 每帧实时 构建期离线
质量 好(但受帧预算限制) 极高(数百采样/像素)
动态物体 ✅ 支持 ❌ 静态
移动端 重(通常关) 轻(贴图采样)
存储 无额外 贴图占用
适用 动态光照游戏 静态场景/移动端

决策树:移动端/静态场景 → Lightmap;动态光照/高端 PC → Lumen;混合(大多数游戏)→ 静态部分 Lightmap + 动态物体 Lumen/动态光

1.4 Mobility 三态:灯光的三档开关

ENetRole 之外,灯光还有 MobilityEngine/Classes/Engine/EngineTypes.h 的 EComponentMobility):

Mobility 行为 Lightmap 参与
Static 完全静态(烘焙全量) ✅ 全烘焙(阴影/间接光都进 Lightmap)
Stationary 位置固定但亮度可变 ✅ 烘焙间接光 + 动态阴影遮罩(ShadowMapChannel)
Movable 完全动态 ❌ 不烘焙(Lumen/动态阴影)

烘焙的本质:只有 Static/Stationary 灯光参与 Lightmap 烘焙;Movable 灯完全走实时。场景里全是 Movable 灯 = 烘焙了个寂寞(第 7 章检查清单第一条)。

1.5 体积光照图(Volumetric Lightmap)

VolumeLightingMethodWorldSettings.h:123)——除了表面贴图(Lightmap),烘焙还产出体积光照图:稀疏 3D 网格(brick)存 SH 光照,供动态物体(角色/粒子)在静态光照环境中采样——“静态场景里的动态角色也有正确光照”。VolumetricLightmapDetailCellSize=200(:159)控制网格密度。

1.6 一点历史

  • UE4:CPU Lightmass(UnrealLightmass,独立进程多线程求解,还有 Swarm 分布式);
  • UE5GPU LightmassEngine/Plugins/Experimental/GPULightmass/)转正为默认——用 GPU 路径追踪逐 tile 收敛,FStaticLightingSystemInterface::GetPreferredImplementation()StaticLightingSystemInterface.cpp:113)硬编码优先 GPULightmass;
  • 本 fork:无 Lightmap/Lightmass 改动(git 验证),移动端 one-pass 管线沿用现有 lightmap 采样路径。

1.7 小结

这一章记住三句话:

  1. Lightmap = 离线预计算的光照贴图(拍照片哲学);
  2. Lumen vs Lightmap = 实时 vs 离线——移动端/静态场景选 Lightmap;
  3. Mobility 三态决定参与度——只有 Static/Stationary 灯进 Lightmap。

下一章,六大概念各配一个类比。

第 2 章 六大核心概念速览(每个概念 = 一个类比)

TL;DR|Lightmap 的六个核心概念,用六个你早就懂的东西类比:纹素密度 = 光照分辨率
UV 展开 = 把橘子皮摊平Atlas 装箱 = 拼图游戏填充率 = 拼图的利用率
编码 = 把亮度塞进 8bit 行李箱Padding = 拼图块之间的缝隙。这一章只立类比不给代码。

2.1 纹素密度(Texel Density)= 光照的”分辨率”

定义:Lightmap 上每个纹素(texel)覆盖多少世界面积。LightMapResolutionStaticMesh.h:1194-1196)是纹理边长(texel 数),实际密度 = 分辨率 ÷ 网格世界尺寸。

类比实验室:分辨率
同样的照片,4K 屏看比 720p 屏看清晰——Lightmap 同理:大模型配小分辨率 =
光照糊成一团(”光斑”);小模型配大分辨率 = 浪费显存。**纹素密度是光照的
“每平方米像素数”**,全场景统一密度(如 4 texel/cm)是烘焙质量的第一要务。

2.2 UV 展开 = 把橘子皮摊平

定义:模型表面(3D)→ UV 平面(2D)的映射。Lightmap 需要第二套 UVLightMapCoordinateIndexStaticMesh.h:1221,默认 UV1)——UV 岛(Chart)不得重叠、必须落在 0-1 空间,因为光照贴图是按 UV 位置写的。

类比实验室:橘子皮
把橘子皮完整摊平(不撕裂、不重叠)就是 UV 展开。Lightmap UV 要求摊得均匀
(每块面积与世界面积成比例——否则纹素密度不均)且留缝(岛与岛之间有
边距,否则采样会串色)。引擎能自动摊(第 8 章),但摊得好的关键参数
(MinLightmapResolution/通道选择)需要人懂。

2.3 Atlas 装箱 = 拼图游戏

定义:多个 Actor 的 Lightmap 打包进共享纹理(Atlas)。编辑器侧:FLightMapPendingTextureskyline 装箱(4px 对齐,LightMap.cpp:457);网格侧:FLayoutUVFindBestPackingLayoutUV.h:67)做 UV 岛装箱。

类比实验室:拼图游戏
所有光照贴图拼进一张大画布(默认 1024×512,PackedLightAndShadowMapTextureSize):
每块光照贴图是拼图片,拼图器(装箱算法)负责把片放进去。片太多/太大 → 换更大的
画布或开新画布
——这是显存与烘焙时间的权衡。

2.4 填充率 = 拼图的利用率

定义:Atlas 中被光照贴图实际占用的面积比例。填充率低 = 拼图有大量空白 = 浪费显存。引擎的 FindBestPackingEfficiencyLayoutUV.h:86)专门统计装箱效率。

类比实验室:拼图利用率
一堆形状怪异的拼图片(细长条、异形)放进画布,空隙多 → 利用率低。
填充率优化 = 让拼图片更”方正”(UV 岛长宽比)、让片大小更匹配
(分辨率统一)、留缝更小(padding 恰到好处)。第 10 章专讲。

2.5 编码 = 把亮度塞进 8bit 行李箱

定义:烘焙结果是高动态范围(RGBA16F),要存进 8bit 贴图——QuantizeLightSamplesLightmapEncoding.cpp:35)用 LogScale=11.5 的对数量化 + 每图 Scale/Add(min/max 归一化)把亮度”塞进行李箱”。

类比实验室:8bit 行李箱
亮度范围从 0 到几百(阳光下的墙 vs 阴影),8bit 只能存 0-255——直接存会爆。
解法:先取对数(LogScale,把大范围压缩进小范围),再整体缩放平移
(Scale/Add,让 0-255 被用满)。**”行李箱装不下的亮度,先对数压缩再装箱”**。

2.6 Padding(边距)= 拼图块之间的缝隙

定义:UV 岛之间/光照贴图之间留的空隙(纹素)。引擎装箱 4px 对齐(LightMap.cpp:491),官方建议约 4 纹素——**缝隙防”串色”**(双线性采样/纹理压缩时相邻岛互相污染)。

类比实验室:拼图缝隙
拼图块之间完全无缝 → 双线性插值会”抄”邻居的颜色(串色/漏光)。
留缝(padding)防串色,但缝隙太宽浪费面积(填充率↓)——缝隙是质量与
效率的平衡
。漏光(Light Bleeding)90% 是 padding 不足。

2.7 概念 → 源码落点预告表

概念 类比 核心类/函数 章节
纹素密度 分辨率 LightMapResolution / GetUVDensity Ch8
UV 展开 橘子皮 FLayoutUV / CreateLightMapUVLayout Ch5/Ch8
Atlas 装箱 拼图 FLightMapPendingTexture / FindBestPacking Ch5/Ch10
填充率 拼图利用率 FindBestPackingEfficiency / FAllocator2D Ch10
编码 8bit 行李箱 QuantizeLightSamples / LogScale=11.5 Ch4/Ch9
Padding 拼图缝隙 FTextureLayout 4px / GLightmassDebugOptions Ch5/Ch10

第 3 章 烘焙管线:GPU Lightmass 全流程

TL;DR|UE5 的默认烘焙器是 GPU LightmassEngine/Plugins/Experimental/GPULightmass/):
Build Lighting 触发 → UGPULightmassSubsystem::LaunchFGPULightmass 注册场景 →
后台每帧推进(BackgroundTick)→ FLightmapRenderer 逐 tile 路径追踪(默认 512 采样/
像素 + 辐照度缓存 + OIDN 去噪)→ 渐进收敛。烘焙不是一次性的,是”洗衣机转桶”——
每转一圈(每帧)处理一批 tile,边转边看效果

3.1 插件结构

1
2
3
4
5
6
7
Engine/Plugins/Experimental/GPULightmass/
├── GPULightmass.uplugin
├── Shaders/Private/ # LightmapPathTracing.usf / LightmapEncoding.ush 等
└── Source/
├── GPULightmass/ # 核心:LightmapRenderer.cpp(4032行) / LightmapEncoding.cpp
│ # / LightmapTilePool.cpp / IrradianceCaching.cpp / Scene/
└── GPULightmassEditor/ # 编辑器入口(工具栏/设置面板/GPULM.* 命令)

避坑:路径是 Experimental/GPULightmass/(实验性插件目录)——不是 Plugins/GPULightmass
老资料讲 UE4 的 Lightmass 是独立进程 CPU 求解器(UnrealLightmass),与 GPU Lightmass
完全不同:GPU 版在引擎进程内、用 GPU 路径追踪、逐 tile 渐进。

3.2 触发链

1
2
3
4
5
6
编辑器 Build Lighting 按钮 / 控制台 GPULM.BuildLighting
→ UGPULightmassSubsystem::Launch(GPULightmassSettings.cpp:195)
→ FGPULightmass(GPULightmass.cpp:15)注册场景
→ FScene::BackgroundTick 每帧推进
→ FLightmapRenderer(IVirtualTextureFinalizer)逐 tile 路径追踪
→ 渐进收敛(每帧可见效果变好)

为什么 GPU Lightmass 是默认FStaticLightingSystemInterface::GetPreferredImplementation()StaticLightingSystemInterface.cpp:113)硬编码优先取 "GPULightmass" 实现——写死优先,无需配置

3.3 关键参数:两张大表

世界设置FLightmassWorldInfoSettingsWorldSettings.h:55-239,挂载 :672):

参数 默认 作用
StaticLightingLevelScale 1 世界缩放补偿(注释”1 UU = 1 cm,大关卡设 2-4 可大幅缩短构建”)
NumIndirectLightingBounces 3 间接光反弹次数
IndirectLightingQuality 1 GI 求解采样数倍率(调高更慢更干净)
IndirectLightingSmoothness 1 间接光平滑(调低细节多噪点多)
bUseAmbientOcclusion false 环境光遮蔽
bCompressLightmaps true 压缩光照贴图(注释”关闭后内存/体积 ×4”)
VolumetricLightmapDetailCellSize 200 体积光照图网格密度
VolumeLightingMethod VLM_VolumetricLightmap 体积光照法

GPU Lightmass 面板UGPULightmassSettingsGPULightmassSettings.h:33):

参数 默认 作用
GISamples 512 每像素 GI 采样数(质量核心旋钮)
StationaryLightShadowSamples 128 Stationary 灯阴影采样
bUseIrradianceCaching true 辐照度缓存(复用相邻点 GI,大幅加速)
IrradianceCacheQuality 128 缓存质量
TilePassesInSlowMode/FullSpeedMode 1/8 慢速/全速模式每 tile 的 pass 数
LightmapTilePoolSize 55 tile 池大小(显存预算)
bCompressLightmaps true 压缩

3.4 与 CPU Lightmass 的差异

CPU Lightmass(UE4) GPU Lightmass(UE5)
计算设备 CPU 多线程(可 Swarm 分布式) GPU(DX12 + 硬件光追/DXR)
方式 一次性求解 渐进式(边烘边看,可中断续烘)
采样 静态配置 GISamples 可实时调
去噪 无(靠采样数) Intel OIDN 内置
分布式 ✅(多机)

3.5 源码考古:Launch 触发链

源码考古:想看真东西的人进。

GPULightmassSettings.cpp:195(要点):

1
2
3
4
5
6
7
8
9
void UGPULightmassSubsystem::Launch()
{
// 创建烘焙系统实例
GPULightmass = FGPULightmassModule::CreateGPULightmassForWorld(World.Get());
// 注册为当前世界的静态光照系统(GetPreferredImplementation 优先 GPULightmass)
FStaticLightingSystemInterface::SetPreferredImplementation(FName("GPULightmass"));
...
// 标记场景需要烘焙(编辑器勾选"Build Lighting"后进入后台推进)
}

这段代码在干什么:Launch 是烘焙的”点火”——创建 FGPULightmass 并显式把 GPULightmass
设为首选实现
SetPreferredImplementation)。之后烘焙不再是一次性阻塞调用,而是
FScene::BackgroundTick 每帧推进的渐进过程——这就是”边烘边看、可中断”的来源。


第 4 章 编码与数据结构:FLightMap2D 的行李箱

TL;DR|烘焙结果是高动态范围(RGBA16F),存进 8bit 贴图要靠量化 + Scale/Add
QuantizeLightSamples 用 LogScale=11.5 对数量化(HQ=LogLUVW 亮度+方向、Simple=LogRGB),
每张图按 min/max 算 Scale/Add 归一化。运行时 FLightMap2D 持有纹理数组与解码参数,
shader 用 LightMapScale/LightMapAdd 还原亮度。类比:8bit 行李箱——对数压缩 +
缩放平移,把大范围亮度装进小行李箱

4.1 从 RGBA16F 到 8bit:量化

GPU Lightmass 的 tile 渲染目标是 FLinearColor(RGBA16F),最终 QuantizeLightSamplesLightmapEncoding.cpp:35)量化成 8bit 系数:

1
2
3
// LightmapEncoding.cpp:44-47
const float LogScale = 11.5f;
const float LogBlackPoint = FMath::Pow(2.0f, -0.5f * LogScale);

为什么取对数:亮度范围从 0.01(阴影)到 500+(阳光),线性量化会丢失暗部细节——对数压缩让暗部与亮部都有足够的 8bit 档位

4.2 两种编码:HQ 与 LQ

编码 内容 用途
HQ(复杂) LogLUVW(亮度+色度 UVW)+ SH 方向(Dx/Dy/Dz+残差) 高质量(默认)
LQ(简单) LogRGB(三分量对数) 低质量/移动端

HQ 的方向性:不只是”这个像素多亮”,还有”光从哪来”(SH 球谐)——材质法线不同,同一 Lightmap 像素的间接光方向不同(法线贴图配合真实感的关键)。解码时 Directionality = max(0, dot(SH, WorldNormal))LightmapCommon.ush:166)。

4.3 Scale/Add:整图归一化

量化后还要按全图 min/maxScaleVectors/AddVectorsLightMap.h:338-341):最终亮度 = 量化值 * Scale + Add——让 8bit 的 0-255 被全图用满(不然整张图只用了一半档位,暗部更糊)。

4.4 FLightMap2D:纹理布局

FLightMap2DLightMap.h:198):

1
2
3
4
5
6
7
8
9
10
// The textures containing the light-map data.
TObjectPtr<ULightMapTexture2D> Textures[2]; // [0]=系数(LogLUVW) [1]=方向(SH)
TObjectPtr<ULightMapTexture2D> SkyOcclusionTexture;
TObjectPtr<ULightMapTexture2D> AOMaterialMaskTexture;
TObjectPtr<UShadowMapTexture2D> ShadowMapTexture;
...
FVector4f ScaleVectors[NUM_STORED_LIGHTMAP_COEF]; // 解码 scale
FVector4f AddVectors[NUM_STORED_LIGHTMAP_COEF]; // 解码 bias
FVector2D CoordinateScale; // atlas 内 UV 缩放(PostEncode 计算)
FVector2D CoordinateBias;

运行时纹理ULightMapTexture2DLightMapTexture2D.h:24LODGroup=TEXTUREGROUP_Lightmap)——源数据 FColor(8bit),压缩经 SetModernSettingsForNewOrChangedTextureLightMap.cpp:673)决定格式。

4.5 运行时解码

FPrecomputedLightingUniformParametersLightmapUniformShaderParameters.h:16-25)携带 LightMapCoordinateScaleBias/LightMapScale[2]/LightMapAdd[2];shader 解码(LightmapCommon.ush:91-159):

1
2
3
LogL = Lightmap0.w
UVW = Lightmap0.rgb^2 * LightMapScale[0].rgb + LightMapAdd[0].rgb
L = exp2(LogL) - LogBlackPoint // 还原真实亮度

4.6 源码考古:PostEncode 的 atlas 收尾

源码考古:想看真东西的人进。

LightMap.cpp:685-723PostEncode,要点):

1
2
3
4
5
6
7
// 计算光照贴图在 atlas 内的 UV 缩放/偏移:
// 每个实例的光照贴图被装箱进共享纹理后,它的 UV 要乘以 Scale 加上 Bias
// 才能指向 atlas 中自己的位置
FLightMap2D::CoordinateScale = PaddedSize / GetSizeX();
FLightMap2D::CoordinateBias = Base / GetSizeX();
...
// 写入 ScaleVectors/AddVectors(解码用)

这段代码在干什么装箱的”坐标翻译”——光照贴图在 atlas 里的位置(Base/Size)被
换算成 UV 的 Scale/Bias,运行时每个实例的 lightmap UV 先乘 Scale 加 Bias 再采样,
就精确指向 atlas 中自己的区域。
这是第 10 章”填充率”的收尾动作
:装箱完成后,
每个实例都知道”我的光照贴图在 atlas 的哪个角落”。


第 5 章 打包装箱:Atlas 拼图游戏

TL;DR|装箱发生在两个层面:网格侧FLayoutUV:UV 岛 FindCharts → FindBestPacking
→ CommitPackedUVs,把岛装进网格的 lightmap UV 空间)与编辑器侧FLightMapPendingTexture
的 skyline 装箱:把多个 Actor 的光照贴图装进共享 1024×512 atlas)。填充率 = 装箱的
利用率,第 10 章专讲优化。类比:拼图游戏——岛装进网格,网格装进图集

5.1 一条链两套实现

1
2
3
4
5
6
7
8
9
10
11
12
网格侧(每网格):
FLayoutUV(LayoutUV.h:40-87)
FindCharts(:66) 三角形聚成 UV 岛(Chart)
FindBestPacking(:67) 岛装箱进 lightmap UV 空间
CommitPackedUVs(:68) 写回 UV
→ 调用链:StaticMeshOperations.cpp:1866-1973(CreateLightMapUVLayout)

编辑器侧(每关卡):
FLightMapAllocationGroup(LightMap.cpp:426)按 Outer/距离分组(GMaxLightmapRadius=5000)
FLightMapPendingTexture(:457)FTextureLayout 4px 对齐 skyline 装箱(:491)
PackedLightAndShadowMapTextureSize=1024(2:1 长宽比,:2478-2479)
降序装箱(:2525)→ PostEncode 算 CoordinateScale/Bias

为什么两套:网格侧解决”这个网格的 UV 岛怎么摆“(每网格的 lightmap UV 空间),编辑器侧解决”每个实例的光照贴图怎么拼“(共享 atlas)——两层拼图。

5.2 FAllocator2D:段式 best-fit

FAllocator2DAllocator2D.h:19-80):段式空闲空间分配器,FindWithSegments(:74)按”最合适矩形”准则(best-fit)找空位——装箱质量的核心算法FAllocator2DFlipFix 是 ELightmapUVVersion 的一个修复版本(翻转修复)。

5.3 ELightmapUVVersion:装箱算法的演进

MeshUtilitiesCommon.h:7-21BitByBit=0 → ... → ConsiderLightmapPadding=6 → ForceLightmapPadding=7 → Segments2D=8 → OptimalSurfaceArea=9 → ScaleByEdgesLength=10(=Latest)——每个版本改进了装箱质量(padding 处理、段分配、按边长缩放岛)。

避坑LightmapUVVersion(StaticMesh.h:772)是”UV 打包算法版本”——改了引擎的装箱
算法后,旧资产的 UV 布局不会自动重打(除非重新生成)。改装箱质量 = 旧资产可能要
重新”Apply Changes”。

5.4 GPU Lightmass 侧:tile 池

GPU Lightmass 不共享 atlas——每实例独立 FLightmapLightmapStorage.h:66),渲染目标是 tile 池FLightmapTilePoolLightmapTilePool.h:28-72LightmapTilePoolSize=55 个 tile,FreeHeap 线性分配);每实例 lightmap 尺寸向上取整到 128 tile(GetPaddedSizeInTiles,LightmapStorage.h:73-79)。

ISM 实例打包InstancedStaticMesh.cpp:108-119):TotalLightmapRes = 宽 × ceil(sqrt(实例数))——所有实例排进一个正方形大图。

5.5 源码考古:FindBestPacking 装箱循环

源码考古:想看真东西的人进。

LayoutUV.h:67-86(要点):

1
2
3
4
5
6
/** 找到把全部 Chart 装进指定分辨率纹理的最佳排列 */
bool FindBestPacking(uint32 InTextureResolution);
/** 提交装箱结果(把 PackingScale/Bias 写回 UV) */
void CommitPackedUVs();
/** 装箱效率统计(填充率!)——每帧统计,LogStats() 输出 */
uint32 FindBestPackingEfficiency() const;

这段代码在干什么FindBestPacking 尝试把全部 UV 岛装进给定分辨率的纹理——
装不下就返回失败(调用方提高分辨率重试);FindBestPackingEfficiency 量化
“装得满不满”(填充率)。这就是”自动生成 lightmap UV 时分辨率怎么定”的机制
从 MinLightmapResolution 开始试,装不下就翻倍,直到装下或达上限。


第 6 章 运行时渲染与流送:烘焙结果怎么被画出来

TL;DR|运行时 BasePass 读 Lightmap:FUniformLightMapPolicy 绑定 uniform(Scale/Add/
CoordinateScaleBias)→ shader 里 GetLightMapColorHQ 解码 → 与直接光合并。UV 从
顶点流的 LightMapCoordinate 通道(默认 UV1)来,两个系数纹理上下打包进一张
float2(1,0.5) 切分)。流送:GAllowStreamingLightmaps + LightmapStreamingFactor=0.2
按距离流送 mip。类比:运行时是”看照片”——读贴图,不重算

6.1 BasePass 采样链

1
2
3
4
5
BasePass 像素着色器(BasePassPixelShader.usf:717-731):
#elif HQ_TEXTURE_LIGHTMAP
GetLightMapCoordinates(Interpolants, LightmapUV0, LightmapUV1, LightmapDataIndex);
GetLightMapColorHQ(...); // 解码 + SH 方向性
OutDiffuseLighting *= View.PrecomputedIndirectLightingColorScale;

LightMapPolicyImpl::ModifyCompilationEnvironmentLightMapRendering.cpp:42-49)设置 HQ_TEXTURE_LIGHTMAP/LQ_TEXTURE_LIGHTMAP 宏;FUniformLightMapPolicy::GetPixelShaderBindings(:562-577)绑定 PrecomputedLightingBuffer(含 Scale/Add 等)。

6.2 HQ/LQ 策略选择

触发 用途
HQ_TEXTURE_LIGHTMAP r.HighQualityLightMaps=1UnrealEngine.cpp:18478 高质量(默认)
LQ_TEXTURE_LIGHTMAP r.SupportLowQualityLightmaps=1BasePassRendering.cpp:139 低质量/移动端

HQ 需要两个系数纹理(方向 SH),LQ 只需一个(LogRGB)。

6.3 顶点流双纹理打包

LocalVertexFactoryCommon.ush:118-128LocalVertexFactoryCommon.ush):

1
2
3
// lightmap UV 来自顶点流的 LightMapCoordinate 通道(默认 UV1,LightMapCoordinateIndex)
LightmapUV0 = Interpolants.LightMapCoordinate.xy * float2(1, 0.5); // 上半区:系数纹理
LightmapUV1 = LightmapUV0 + float2(0, 0.5); // 下半区:方向纹理

关键技巧:两个系数纹理上下打包进一张贴图(UV.y 乘 0.5 切分上下半区)——省一次纹理绑定与采样。

6.4 VT Lightmap 与流送

  • VT Lightmapr.VirtualTexturedLightmaps=1LightMap.cpp:95)时生成虚拟纹理光照贴图(每 tile 64×64 + 2px 边界)——按可视性流送,大世界省显存;
  • 传统流送GAllowStreamingLightmapsLightMap.cpp:53,ini [TextureStreaming] AllowStreamingLightmaps)+ LightmapStreamingFactor=0.2BaseEngine.ini:2040)——lightmap mip 按距离流送;
  • 体积光照图流送FVolumetricLightmapStreamingManagerPrecomputedVolumetricLightmapStreaming.cpp)。

避坑:老资料(UE4)的 FLightmapStreamingManager / r.LightmapStreaming CVar 在
本 fork 已不存在(UE4 遗留已删)——流送只走上面的三条路径。

6.5 源码考古:GetPixelShaderBindings

源码考古:想看真东西的人进。

LightMapRendering.cpp:562-577(要点):

1
2
3
4
5
6
7
8
void FUniformLightMapPolicy::GetPixelShaderBindings(...)
{
// 绑定预计算光照 uniform(Scale/Add/CoordinateScaleBias/纹理引用)
FPrecomputedLightingUniformParameters LCI;
SetupLCIUniformBuffers(FeatureLevel, Mesh, ...);
PixelShader->SetUniformBufferParameter(..., PrecomputedLightingBuffer);
...
}

这段代码在干什么:把烘焙结果(纹理 + 解码参数)打包进 uniform 绑给像素着色器——
运行时”看照片”的全部准备。烘焙阶段算好的 Scale/Add/CoordinateScaleBias 在这里
变成 shader 可读的参数,GetLightMapColorHQ 用它们把 8bit 系数还原成亮度。

第 7 章 专题一:烘焙流程实操——从世界设置到贴图落地

TL;DR|烘焙实操四步:检查(Mobility 归位/UV 检查/漏光预防)→ 设参(世界设置
Lightmass + GPU Lightmass 面板)→ 触发(Build Lighting / GPULM.BuildLighting,渐进
烘烤边看边等)→ 验证(视口/Lightmap Density 视图/对比)。类比:**相机的快门与
ISO——参数决定”拍多久、多干净”**。

7.1 烘焙前检查清单

检查项 为什么 怎么查
灯光 Mobility 只有 Static/Stationary 灯进 Lightmap(Movable 白烘) 选中灯光看 Mobility 面板
Lightmap UV 没有合法第二 UV 的网格会静默降级(错位/发黑) 视口 → Lightmap Density 视图
漏光预防 UV padding 不足 → 串色 烘焙后检查墙角/岛边缘
网格 LightMapResolution 密度统一(全场景同 texel 密度) 静态网格资产面板
世界规模 大关卡设 StaticLightingLevelScale 2-4 缩短构建 世界设置

7.2 世界设置参数(逐项)

第 3.3 章的两张表是参考——实操要点:

  • **StaticLightingLevelScale**:大关卡(数 km)设 2-4——注释原话”可大幅缩短构建时间”(尺度相关参数全部缩放);
  • **NumIndirectLightingBounces**:室内 3 足够;洞穴/密闭空间可 4-5;室外 2 足够;
  • IndirectLightingQuality:正式烘焙 1;预览烘焙 0.5(快 4 倍,验证布局用);
  • **bUseAmbientOcclusion**:美术风格需要接触阴影时开;
  • **bCompressLightmaps**:默认开(第 9 章详述,调试质量问题时可临时关)。

7.3 GPU Lightmass 面板(编辑器)

UGPULightmassSettingsGPULightmassSettings.h:33):

  • GISamples:512 是质量/速度平衡;预览 64、最终 1024+;
  • bUseIrradianceCaching:开(默认)——相邻点复用 GI,大幅加速;
  • TilePassesInSlowMode/FullSpeedMode:慢速模式(1 pass/tile)用于”烘一会儿看效果”,全速模式(8 pass)用于定稿;
  • LightmapTilePoolSize:显存预算(默认 55 tile,爆显存就调小,会慢)。

7.4 触发与监控

  • 触发:工具栏 Build → Build Lighting;或控制台 GPULM.BuildLightingGPULightmassEditorModule.cpp:44);
  • 监控:烘焙是渐进的——视口里光照逐帧变好(这是 GPU Lightmass 的标志性体验);输出日志有进度;
  • 中断:可以随时停止(渐进式 = 已烘的部分保留)。

7.5 验证三件套

  1. 视口目检:漏光(墙角发亮)、光斑(纹素密度不足)、噪点(采样不够);
  2. Lightmap Density 视图(编辑器视图模式):红=密度过高、蓝=过低——全场景尽量同一色
  3. 构建对比:改参数前先记录(截图/时长),每次只动一个旋钮。

7.6 避坑清单

  1. 漏光(Light Bleeding):90% 是 UV padding 不足——装箱 4px 对齐是默认,官方建议约 4 纹素边距;
  2. Stationary 灯阴影噪点StationaryLightShadowSamples=128 不够就调高;
  3. 大关卡烘焙慢:先 StaticLightingLevelScale,再调 IndirectLightingQuality 预览;
  4. 烘焙结果与实机不一致:检查压缩(bCompressLightmaps)与分辨率差异。

7.7 动手实验室:从零烘焙一个室内关卡

步骤 操作 验收标准
① 场景准备 建室内关卡(四面墙+地板),放 1 Static 定向光 + 1 Static 点光;所有网格确认有 Lightmap UV(自动生成已开) 视口 Lightmap Density 视图无红色/蓝色块
② 参数 世界设置:Bounces=3、Quality=1;GPU Lightmass:GISamples=128(预览) 参数面板正确
③ 烘焙 Build Lighting → 观察渐进收敛 视口光照逐步变好,日志无 error
④ 验证 目检 + Density 视图 + 检查墙角漏光 无漏光/无光斑/无噪点
⑤ 迭代 有问题 → 单旋钮调整重烘 每次只改一个参数,记录对比

检查清单:Mobility 全对 ✓、UV 全绿 ✓、无漏光 ✓、无噪点 ✓、烘焙时长记录 ✓。


第 8 章 专题二:自动化展开 UV——让引擎替你摊开橘子皮

TL;DR|引擎能自动生成 Lightmap UV:FMeshBuildSettings 的 bGenerateLightmapUVs=true
默认开启——构建时 CreateLightMapUVLayout(或旧路径 FLayoutUV Packer)自动把 UV 岛
装箱进第二通道(DstLightmapIndex=1)。缺第二 UV 不会报错,而是静默降级钳制
FBX 导入还会自动识别名为 LightMapUV 的 UV 集。类比:橘子皮自动摊平机

8.1 两条生成路径

1
2
3
4
5
新 MeshDescription 路径(默认):
MeshDescriptionHelper.cpp:101-129 → FStaticMeshOperations::CreateLightMapUVLayout
(校验 Src/Dst 索引 → 自动补通道 → 装箱)
旧 RawMesh 路径(legacy):
MeshUtilities.cpp:2612-2640 → FLayoutUV Packer(FindCharts → FindBestPacking → CommitPackedUVs)

默认设置FMeshBuildSettings,EngineTypes.h:2947):bGenerateLightmapUVs=trueMinLightmapResolution=64SrcLightmapIndex=0DstLightmapIndex=1(:2988-3007,默认值 :3051-3056)——默认就从 UV0 复制并重新装箱到 UV1

8.2 缺第二 UV 的”静默降级”

UStaticMesh::EnforceLightmapRestrictionsStaticMesh.cpp:9857-9941):计算实际 UV 通道数后把 LightMapCoordinateIndex 钳制到合法范围——缺第二 UV 不报错,静默用 UV0 采样(lightmap 会错位/发黑)。CheckLightMapUVs(:9952)区分 Missing/Bad/Valid 供审计。

避坑:**”没报错 = 没问题”是错的**。烘焙后某个物体光照错位/全黑,第一件事就是
查它的 Lightmap UV(视口 Lightmap Density 视图 + 资产 CheckLightMapUVs 工具)。

8.3 分辨率与密度

  • **LightMapResolutionStaticMesh.h:1194-1196):纹理边长 texel 数(ClampMax=4096)——不是”每 texel 世界单位”**,实际密度 = 分辨率 ÷ 网格尺寸;
  • **LightmapUVDensity**(:1176):按 Section 加权平均的密度(流送用),GetUVDensityStaticMesh.cpp:5194-5216);
  • GPU Lightmass 逐 LOD 减半StaticMesh.cpp:34-82):LODIndex > 0 ? max(基础分辨率 / 2^(LOD-1), 32) : 基础——LOD 越高光照越粗(合理)。

实践:全场景统一纹素密度(如 4 texel/cm)——大模型(建筑)分辨率 512-1024,小道具 64-128,按世界尺寸而不是感觉调

8.4 FBX 导入的自动处理

FbxStaticMeshImport.cpp

  • :400-403:FBX 里有名为 LightMapUV 的 UV 集 → 自动设为 LightMapCoordinateIndex(DCC 导出时命名为 LightMapUV 就会被识别);
  • :1865-1867:默认 SetLightMapResolution(64)SetLightMapCoordinateIndex(1)
  • :2065-2069bGenerateLightmapUVsDstLightmapIndex = FirstOpenUVChannel(自动找空通道)。

8.5 Datasmith 自动展 UV

Datasmith 自带展平工具(UVTools/):UUVGenerationFlattenMapping::GenerateUVsUVGenerationFlattenMapping.cpp:1218,角度阈值 + 面积权重展平);SetupGeneratedLightmapUVResolutionUVGenerationUtils.cpp:48:由 chart 数算最小分辨率)——Datasmith 导入无 UV 的模型会自动摊

8.6 引擎级自动 UV 接口

  • **IGeometryProcessing_MeshAutoUV**(MeshAutoUV.h:17-72):EAutoUVMethod{PatchBuilder/UVAtlas/XAtlas} 三种算法——程序化批量摊 UV 的官方接口(建模工具/程序化生成用);
  • UVEditor 插件UVEditorLayoutTool(自动排版)/ UVEditorRecomputeUVsTool(重算)/ UVEditorTexelDensityTool(纹素密度可视化);
  • 蓝图UStaticMeshEditorSubsystem::SetGenerateLightmapUVsStaticMeshEditorSubsystem.cpp:1970)。

8.7 动手实验室:自动生成 + 检查配方

步骤 操作 验收标准
① 开启 静态网格资产 → Build Settings → Generate Lightmap UVs 勾选(默认已勾) 复选框状态正确
② 通道 SrcLightmapIndex=0、DstLightmapIndex=1、MinLightmapResolution=64 参数默认
③ 重建 资产上右键 Reimport 或 Apply Changes 构建日志无 UV 错误
④ 检查 视口 Lightmap Density 视图 + CheckLightMapUVs(资产工具) 无 Missing/Bad UV
⑤ 密度微调 LightMapResolution 按世界尺寸调(大模型 512+,小道具 64) Density 视图全场景同色

检查清单:UV 集 Valid ✓、通道=UV1 ✓、密度统一 ✓、无静默降级(烘焙后无错位物体)✓。


第 9 章 专题三:压缩 Lightmap——内存瘦身

TL;DR|Lightmap 压缩三把刀:bCompressLightmaps(默认开,关掉内存×4——调试质量
才关)、纹理压缩格式(BC7 桌面/ASTC 移动端)、流送(LightmapStreamingFactor=0.2
按距离流送 mip)。移动端还有编码级优化(社区实测:跳 SkyOcclusion、AO 写 A 通道,
包体减 150MB——非本仓库实测)。类比:行李箱抽真空——质量与体积的权衡

9.1 压缩链全貌

1
2
烘焙(RGBA16F tile)→ 量化 8bit(FColor)→ SetModernSettingsForNewOrChangedTexture
(LightMap.cpp:673)→ 纹理压缩(BC7 等)→ 存储/流送

bCompressLightmapsWorldSettings.h:151):默认 true——注释原话”关闭后内存/体积 ×4”。

9.2 什么时候不该关压缩

场景 建议
正常发布 开(默认)
调试漏光/串色 临时关(压缩是串色的来源之一)
移动端 开(内存是移动端生命线)

9.3 移动端格式与编码级优化(公开资料,非本仓库实测)

社区对移动端 Lightmap 的编码优化(知乎,写作时未能访问核实):

  • 编码格式:HQ=RGBLogL 8:8:8:8、LQ=移动端专属 24bit RGB8、SimpleLogScale=16.0 对数量化、cook 后重映射 ASTC;
  • 自定义优化(源码级改 LightMap.cpp):跳过 SkyOcclusionTexture 分配、把全局 AO 写进系数纹理 A 通道、半尺寸 LQ Lightmap——实测包体减约 150MB

避坑:这些文章基于 UE4——编码常量与本 fork 的 GPU Lightmass(LogScale=11.5)
不同;思路可借鉴(AO 合并、跳纹理、半尺寸),数值别照抄

9.4 VT 有损压缩

r.VT.EnableLossyCompressLightmaps=1LightMap.cpp:111,默认 0):VT Lightmap 路径下有损压缩——体积更小,质量略降(阴影过渡变糊)。

9.5 流送与内存账本

  • GAllowStreamingLightmapsLightMap.cpp:53,ini [TextureStreaming] AllowStreamingLightmaps);
  • LightmapStreamingFactor=0.2BaseEngine.ini:2040):lightmap 流送权重(0.2 = 比其他纹理更早降 mip——光照糊一点可接受);
  • 内存估算:单张 1024×512 RGBA8 = 2MB(未压缩)→ BC7 约 0.5MB。

9.6 动手实验室:内存测量与压缩对照

步骤 操作 验收标准
① 基线 烘焙完成,stat memory 记录 Lightmap 内存 记录基线数字
② 对照 世界设置关 bCompressLightmaps → 重烘 → 对比内存 内存约 ×4(验证注释)
③ 恢复 开回压缩,对比画质差异(墙角阴影过渡) 无可见差异 = 压缩无损观感
④ 流送 确认 ini AllowStreamingLightmaps=true 远距离光照糊、靠近变清晰
⑤ 定案 记录最终内存与画质 达标

检查清单:压缩开 ✓、流送生效 ✓、画质无可见损失 ✓、内存记录 ✓。


第 10 章 专题四:提高填充率——让拼图每一块都被用到

TL;DR|填充率 = Atlas 中光照贴图的实际占用率。四把刀:统一纹素密度(片大小匹配)、
岛形状方正(ELightmapUVVersion 的 ScaleByEdgesLength 减小细长岛)、合理 padding
(缝隙够用就行)、分组装箱(编辑器按距离分组,PackedLightAndShadowMapTextureSize)。
测量:FindBestPackingEfficiency(引擎统计)+ Lightmap Density 视图。类比:拼图
利用率——片越方正、缝隙越小、利用率越高

10.1 填充率为什么重要

填充率低 = Atlas 里有大片空白 = 显存浪费 + 烘焙时间浪费(空白区域也占 tile)。高填充率 = 同样的显存装更多光照信息。

10.2 网格侧装箱:岛的”方正化”

ELightmapUVVersion::ScaleByEdgesLength=10MeshUtilitiesCommon.h:7-21,=Latest):按边长缩放岛——细长岛(长宽比极端)装箱时浪费大,按边长缩放让岛更”方正”,填充率提升。FAllocator2D 的 best-fit 段分配(Allocator2D.h:19-80)负责找空位。

10.3 编辑器侧分组装箱

LightMap.cpp

  • 分组FLightMapAllocationGroup :426):按 Outer(package)、LightmapFlags、空间距离分组——GMaxLightmapRadius=5000(:56)限制同图最大包围球半径;
  • 装箱FLightMapPendingTexture :457):FTextureLayout 4px 对齐 skyline 装箱(:491);
  • atlas 尺寸PackedLightAndShadowMapTextureSize=1024WorldSettings.cpp:137),2:1 长宽比(:2478-2479);
  • 降序装箱(:2525):大的先放(大件先占位,小件填空——经典贪心)。

10.4 影响填充率的五因素

因素 影响 对策
岛大小分布 大小悬殊 → 空隙多 统一分辨率/拆分超大网格
岛长宽比 细长岛浪费 ScaleByEdgesLength(默认已开)
Padding 缝隙宽 = 浪费 保持默认 4px,别乱加
分辨率匹配 大网格小分辨率 = 岛小 按世界尺寸定分辨率
LOD 差异 LOD 间分辨率不一致 GPU Lightmass 已按 LOD 减半(合理)

10.5 测量手段

  • 引擎统计FindBestPackingEfficiencyLayoutUV.h:86)——装箱效率原子统计,LogStats() 输出;
  • Lightmap Density 视图:红=过密(浪费)、蓝=过疏(糊)——全场景同色 = 填充率健康
  • AverageTexelDensity(UnrealLightmass,TextureMapping.cpp:1892-1927):lightmap 面积 ÷ 世界面积的平均密度。

10.6 动手实验室:填充率测量与优化

步骤 操作 验收标准
① 统计 烘焙后看 Density 视图 + 引擎装箱效率日志 记录填充率基线
② 归因 找出红色/蓝色区域对应的网格 定位密度不均来源
③ 修复 统一分辨率(大网格 512、小道具 64)+ 拆超大网格 密度图趋于同色
④ 复测 重烘 → 对比填充率与显存 填充率提升、显存下降
⑤ 定案 记录最终数据 达标

检查清单:Density 视图同色 ✓、无细长岛(ScaleByEdgesLength 已开)✓、padding 默认 ✓、显存记录 ✓。


第 11 章 专题五:把 UE 当烘焙器——离线光照工厂

TL;DR|把 UE 当纯烘焙器 = 让烘焙与关卡制作解耦:命令行/脚本触发烘焙(无人值守)、
分层烘焙(直接光/间接光分离)、产物导出(给其他引擎/流程用)。三个配方:① 命令行
烘焙 ② 分层烘焙 ③ 产物导出。类比:离线光照工厂——输入场景,输出贴图

11.1 定位:UE 作为烘焙器的价值

  • 质量:GPU Lightmass 的 512 采样/像素 + OIDN 去噪 = 顶级离线光照质量;
  • 可脚本化GPULM.BuildLighting 控制台命令可进批处理(无人值守烘焙);
  • 产物可导出:GLTFExporter 等把带 lightmap 的网格导出(烘焙结果”带走”)。

11.2 管线架构

1
2
资产规范(分辨率/UV 规范)→ 自动 UV(第 8 章)→ GPU Lightmass(第 3 章)
→ 产物管理(MapBuildDataRegistry)→ 导出(GLTFExporter 等)

11.3 动手实验室配方 ×3

配方 A:命令行/批处理烘焙

步骤 操作 验收标准
① 打开 UnrealEditor.exe 项目 -game -run=...(或编辑器内执行 GPULM.BuildLighting) 命令可执行
② 触发 控制台 GPULM.BuildLightingGPULightmassEditorModule.cpp:44 日志出现烘焙进度
③ 等待 渐进收敛完成(观察视口/日志) 无 error
④ 保存 保存关卡(烘焙数据进 MapBuildDataRegistry) 重新打开光照仍在

配方 B:分层烘焙(引用公开资料《命运扳机》经验,非本仓库实测)

步骤 操作 验收标准
① 分层 直接光/间接光分离(不同光照场景) 两套烘焙可独立出
② 地形 RVT 地形需先 Scene Capture 预渲染(公开资料) 地形光照正确
③ 材质 特殊材质需重写 Lightmass 材质导出模块(公开资料:顶点 Position 换 Lightmap UV 的 trick) 材质光照正确

配方 C:产物导出(把烘焙结果带出 UE)

步骤 操作 验收标准
① 导出 启用 GLTFExporter(GLTFExporter)导出网格 导出成功
② 检查 确认 Lightmap 纹理随网格导出 其他引擎可读
③ 复用 回导 Unity 等(第 12 章迁移流程) 光照贴图可显示

11.4 避坑

  1. 烘焙与实机差异:分辨率/去噪/压缩在实机上观感不同——导出前用实机设置验收;
  2. 多场景烘焙:增量烘焙(只烘改动的场景)省时间;
  3. 版本管理:烘焙产物(MapBuildDataRegistry)进版本库——团队协作不重烘。

第 12 章 专题六:Unity 场景导入 UE——材质、灯光、地表对齐

TL;DR|Unity→UE 迁移四关:坐标单位(100 倍缩放:UE 1UU=1cm vs Unity 1 单位=1m;
Y-up→Z-up 换手)、材质对齐(FBX 导入器自动映射 sDiffuse→BaseColor 等,多数需手动
修 Metallic/Roughness)、灯光对齐(单位换算:UE 厘米制 vs Unity 米制,物理公式
GetUnitsConversionFactor 注释里)、地表对齐(Heightmap 导入 Landscape)。
导入通道:FBX(官方主线)/ USD / Datasmith(清单无 Unity)/ GLTF(本 fork 经
Interchange 可用)。类比:度量衡转换 + 坐标系换手 + 材质翻译

12.1 导入器现状对比

通道 本 fork 状态 Unity 支持 适用
FBX ✅ 经典 UnrealEd 管线 ✅ 官方推荐(FBX Exporter 包) 迁移主线
USD ✅ Importers/USDImporter/ ✅ Unity 官方 USD 包 复杂场景/协同
Datasmith ✅ Enterprise 插件 ❌ 官方清单无 Unity 3dsMax/Revit 等
GLTF 经 Interchange 可用(实测 InterchangeGltfTranslator.cpp 存在,InterchangeImportModule.cpp:111 注册) ✅ Unity 可导出 开源中间格式

避坑:此前资料说”本 fork 无 GLTF 导入器”——实测有(Interchange 的 GLTFCore/
InterchangeImport 模块 + InterchangeGltfTranslator)。GLTF 可用,但 FBX 仍是 Unity
迁移官方主线(材质映射最成熟)。

12.2 坐标与单位:100 倍缩放 + 换手

  • 100 倍:UE 1 UU = 1 cm,Unity 1 单位 = 1 m——WorldToMetersWorldSettings.h:595,本 fork 用旧名;注释 “1uu = 1cm would be 100.0”)。FBX 导入勾 Convert Scene 自动处理;
  • 换手:Unity Y-up → UE Z-up——FBX Import Options 勾 Force Front X Axis(官方迁移文档);
  • 佐证:UE 默认碰撞盒 32 单位 = 32cm(BoxComponent.cpp 构造 BoxExtent=32)。

12.3 材质对齐:自动映射 + 手动修复

FBX 导入器自动映射(FbxMaterialImport.cpp:817-834):

FBX 属性 UE 属性
sDiffuse BaseColor
sEmissive EmissiveColor
sSpecular Specular
sSpecularFactor Roughness
sShininess Metallic
sNormalMap / sBump Normal
sTransparentColor Opacity(+BlendMode=Translucent)

手动修复清单(自动映射的经典坑):

  1. Metallic/Roughness 反了:Unity 的 Smoothness = 1 - Roughness——FBX 导出器一般已处理,但检查”金属质感全无”;
  2. 纹理颜色空间:Unity 的 sRGB 标记可能丢失——检查 BaseColor 的 sRGB 开关;
  3. 透明度:Unity Transparent → UE 要手动设 BlendMode(自动映射设了但通常要微调)。

12.4 灯光对齐:单位换算

UE 灯光单位(LocalLightComponent.hIntensityUnits 注释):点光/聚光默认 Candelas(cd)或 Lumens(lm);方向光用 lux(ELightUnitsLightComponent.h)。

换算公式GetUnitsConversionFactor 注释原文):

1
2
3
4
5
Flux = Lm = cd·sr          (光通量 = 坎德拉 × 立体角)
Intensity = Cd (峰值光强 = 坎德拉)
Illuminance = Cd·sr/m² = Lux (照度 = 流明/平方米)
UE unit is in centimeters (CM), while SI unit version are in meter (M),
hence the conversion unit (100*100)

与 Unity 的换算:Unity 用流明(Lumen)或 cd(Candela,米制)——迁入 UE 时:

Unity 值 UE 值(单位换算后)
点光 100 lm UE Lumens 单位直接填 100(同单位)
点光 100 cd UE Candelas 直接填 100
方向光(lux) 直接填(同 lux 单位)

关键:单位一致时数值直接搬;但 UE 的 100 倍缩放让”物理正确”的光强在厘米制下显得暗——光源面积/距离都 ×100,GetUnitsConversionFactor100*100 就是物理换算;实战中更多靠目测微调(方向光 10 lux 量级、点光按半径试)。

项目设置DefaultLightUnitsRendererSettings.h,ConsoleVariable r.DefaultFeature.LightUnits)——新建灯光的默认单位。

12.5 地表对齐:Heightmap 导入 Landscape

LandscapeImportHelperLandscapeImportHelper.cpp):GetHeightmapImportDescriptor/GetHeightmapImportData/TransformHeightmapImportData——支持 MyHeightmap_x0_y0.png 分块命名。Unity Terrain 导出 Heightmap(Raw/PNG)→ UE Landscape 导入 → 材质重新指定(地形材质是 UE 的 Landscape 材质体系,Unity Terrain 材质不能直接搬)。

12.6 动手实验室:Unity 场景端到端迁移

步骤 操作 验收标准
① Unity 导出 装 FBX Exporter 包,导出场景 FBX(勾 Embed Textures) FBX 含材质贴图
② 坐标核对 UE 侧 FBX Import Options:Convert Scene ✓、Force Front X Axis ✓ 模型方向/大小正确(人物 180cm 左右)
③ 材质修复 导入后检查 Metallic/Roughness/透明度(12.3 清单) 材质观感接近 Unity
④ 灯光换算 灯光按 12.4 表换算(单位一致直接搬 + 目测微调) 光照氛围接近
⑤ 地表导入 Terrain Heightmap → Landscape 导入 → 指定地形材质 地形轮廓正确
⑥ 烘焙 第 7 章流程烘焙(Lightmap 替代 Unity 的烘焙) 光照质量达标

检查清单:缩放正确 ✓、轴向正确 ✓、材质观感一致 ✓、灯光氛围接近 ✓、地形轮廓正确 ✓、烘焙完成 ✓。

第 13 章 平台支持与性能调优

TL;DR|Lightmap 调优三板斧:内存(压缩/分辨率/流送)→ 烘焙时间(采样数/
LevelScale/辐照度缓存)→ 质量(漏光/光斑/噪点)。CVar 十一项速查 + 症状定位表。

13.1 CVar 速查表

CVar/参数 默认 作用 位置
r.HighQualityLightMaps 1 HQ 编码开关(ReadOnly) UnrealEngine.cpp:18478
r.SupportLowQualityLightmaps 1 LQ 编码编译开关 BasePassRendering.cpp:139
r.VirtualTexturedLightmaps 0 VT Lightmap LightMap.cpp:95
r.IncludeNonVirtualTexturedLightmaps 0 非 VT 光照贴图也保留 LightMap.cpp:103
r.VT.EnableLossyCompressLightmaps 0 VT 有损压缩 LightMap.cpp:111
r.MultithreadedLightmapEncode 1 多线程编码 StaticLightingSystem.cpp:157
GPULM.GISamples 512 GI 采样数(编辑器面板参数) GPULightmassSettings
GPULM.LightmapTilePoolSize 55 tile 池(显存预算) GPULightmassSettings
GPULM.TilePassesInSlowMode 1 慢速模式 pass 数 GPULightmassSettings
GPULM.TilePassesInFullSpeedMode 8 全速模式 pass 数 GPULightmassSettings
LightmapStreamingFactor(ini) 0.2 流送权重 BaseEngine.ini:2040

13.2 调优工作流:三象限决策

1
2
3
4
5
6
7
8
9
内存问题(显存涨/包体大):
压缩已开?→ 分辨率降档(统一密度)→ 流送生效?→ VT Lightmap
烘焙时间问题(等太久):
StaticLightingLevelScale(大关卡)→ IndirectLightingQuality=0.5 预览
→ GISamples 降 → 辐照度缓存确认开启
质量问题(漏光/光斑/噪点):
漏光 → padding/压缩(临时关压缩排查)
光斑 → 分辨率不够(密度视图定位)
噪点 → GISamples 升 / IndirectLightingQuality 升

13.3 症状 → 定位表

症状 大概率原因 检查/对策
烘焙慢 采样太高/关卡太大/辐照度缓存关 LevelScale → Quality 预览 → GISamples
显存涨 压缩关/分辨率过高/流送关 压缩开 → 统一密度 → 流送确认
漏光(Light Bleeding) UV padding 不足/压缩串色 padding 检查 → 临时关压缩排查
光斑(糊) 纹素密度不足 Lightmap Density 视图定位 → 分辨率
噪点 采样不足 GISamples/IndirectLightingQuality
物体光照错位/发黑 Lightmap UV 缺失(静默降级) CheckLightMapUVs → 重新生成 UV
烘焙结果与实机不同 压缩/分辨率差异 实机设置验收

第 14 章 局限、替代方案与展望

TL;DR|Lightmap 的局限:静态(灯动/物体动要重烘)、存储占用迭代慢
替代:Lumen(实时)、CPU Lightmass(分布式)、第三方烘焙器。方向:GPU Lightmass
成熟化、VT Lightmap 大世界标配。

14.1 局限与对策

局限 表现 对策
静态 动态物体/灯光无光照 体积光照图给动态物体 + Lumen 补动态
存储 贴图占用显存/包体 压缩 + 流送 + VT(第 9 章)
迭代慢 改灯重烘 预览质量烘焙 + 单旋钮调参(第 7 章)
移动端纹理格式 不同平台格式差异 ASTC(Android)/BC7(桌面)

14.2 替代路径

方案 定位
Lumen 实时 GI(动态场景)
CPU Lightmass(UnrealLightmass) 分布式/无 GPU 环境
第三方烘焙器 特殊需求(更高采样/特殊 GI)

14.3 演进时间线与阅读进阶

时间 里程碑
UE4 CPU Lightmass(Swarm 分布式)
UE5.0~5.2 GPU Lightmass 实验性 → 默认
5.3~5.9 VT Lightmap、OIDN 去噪、辐照度缓存成熟
未来 GPU Lightmass 转正式、大世界 VT Lightmap 标配

进阶路线:官方文档(Lighting the Environment → GPU Lightmass → Understanding Lightmapping → Generating Lightmap UVs → Migrating assets from Unity to Unreal)→ 中文深挖(附录 C)→ 本仓库源码(附录 A 指路)。


附录 A 源码地图

全部路径基于本仓库快照(2026-08-26,HEAD 6cea9bd20f8f)。行号只用于定位;代码演进后以函数名为准。
阅读顺序建议:先 GPULightmassSettings.cpp(烘焙入口)→ LightMap.cpp(装箱/编码)→ LightmapCommon.ush(解码)→ 按专题深入。

A.1 GPU Lightmass 插件(Plugins/Experimental/GPULightmass/)

文件 关键内容
Source/GPULightmass/Private/GPULightmassSettings.cpp Launch :195(烘焙触发链入口)、UGPULightmassSubsystem
Source/GPULightmass/Public/GPULightmassSettings.h UGPULightmassSettings :33(GISamples=512/StationaryLightShadowSamples=128/bUseIrradianceCaching=true/IrradianceCacheQuality=128/TilePassesInSlowMode=1/FullSpeedMode=8/LightmapTilePoolSize=55/bCompressLightmaps)
Source/GPULightmass/Private/GPULightmass.cpp FGPULightmass :15(场景注册)、GetPrimitiveMeshMapBuildData :278
Source/GPULightmass/Private/LightmapRenderer.cpp FLightmapRenderer(4032 行,逐 tile 路径追踪、IrradianceCaching 组合 :230/:1287-1305)
Source/GPULightmass/Private/LightmapEncoding.cpp QuantizeLightSamples :35、LogScale=11.5/LogBlackPoint :44-47
Source/GPULightmass/Private/LightmapTilePool.cpp FLightmapTilePoolGPU :111(FreeHeap 线性分配)
Source/GPULightmass/Private/LightmapStorage.h FLightmap :66、GetPaddedSizeInTiles :73-79
Source/GPULightmass/Private/IrradianceCaching.h/.cpp FIrradianceCache(辐照度缓存,上限 4194304 条)
Source/GPULightmass/Private/Scene/StaticMesh.cpp AllocateLightmaps :34-82(逐 LOD 减半下限 32)
Source/GPULightmass/Private/Scene/InstancedStaticMesh.cpp ISM 打包 :108-119(宽×ceil(sqrt(N)))
Source/GPULightmass/Private/Scene/Landscape.cpp Landscape 烘焙(复用 GetTerrainExpandPatchCount)
Source/GPULightmassEditor/Private/GPULightmassEditorModule.cpp GPULM.BuildLighting 命令 :44、r.RayTracing 判定 :110

A.2 引擎核心(Runtime/Engine)

文件 关键内容
Public/LightMap.h FLightMap2D :198(Textures[2] :326/SkyOcclusionTexture :328/ScaleVectors :338/AddVectors :341/CoordinateScale :344/CoordinateBias :347)
Private/LightMap.cpp FLightMapAllocationGroup :426FLightMapPendingTexture :457(FTextureLayout 4px :491)、GMaxLightmapRadius :56、GAllowStreamingLightmaps :53、r.VirtualTexturedLightmaps :95、r.VT.EnableLossyCompressLightmaps :111、PostEncode :685-723、PackedLightAndShadowMapTextureSize 装箱 :2469-2585、SetModernSettingsForNewOrChangedTexture :673
Classes/Engine/LightMapTexture2D.h ULightMapTexture2D :24(LODGroup=TEXTUREGROUP_Lightmap)
Public/LightmapUniformShaderParameters.h FPrecomputedLightingUniformParameters :16-25
Renderer/Private/LightMapRendering.cpp LightMapPolicyImpl :42-49、FUniformLightMapPolicy::GetPixelShaderBindings :562-577、GEmptyPrecomputedLightingUniformBuffer :723
Shaders/Private/LightmapCommon.ush GetLightMapColorLQ :78、解码 :91-159、GetLightMapColorHQ :132、SH 方向性 :166
Shaders/Private/LocalVertexFactoryCommon.ush GetLightMapCoordinates :118-128(UV0=Coord*float2(1,0.5)/UV1=UV0+float2(0,0.5))
Shaders/Private/BasePassPixelShader.usf HQ_TEXTURE_LIGHTMAP 采样 :717-731
Classes/GameFramework/WorldSettings.h FLightmassWorldInfoSettings :55-239(StaticLightingLevelScale :67/NumIndirectLightingBounces :76/IndirectLightingQuality :92/bCompressLightmaps :151/VolumetricLightmapDetailCellSize :159)、WorldToMeters :595
Classes/Engine/StaticMesh.h LightMapCoordinateIndex :1221-1224、LightMapResolution :1194-1196、LightmapUVDensity :1176、LightmapUVVersion :772
Private/StaticMesh.cpp GetUVDensity :5194-5216、EnforceLightmapRestrictions :9857-9941CheckLightMapUVs :9952
Classes/Engine/EngineTypes.h FMeshBuildSettings :2947(bGenerateLightmapUVs :2988/MinLightmapResolution :3001/SrcLightmapIndex :3004/DstLightmapIndex :3007)
Classes/Engine/MapBuildDataRegistry.h UMapBuildDataRegistry :294(MeshBuildData :426/LightmapResourceClusters :434)、FMeshMapBuildData :55
Private/PrecomputedVolumetricLightmapStreaming.cpp FVolumetricLightmapStreamingManager
MeshUtilitiesCommon/Public/LayoutUV.h FLayoutUV :40-87(FindCharts :66/FindBestPacking :67/CommitPackedUVs :68/FindBestPackingEfficiency :86
MeshUtilitiesCommon/Public/Allocator2D.h FAllocator2D :19-80(FindWithSegments :74)
MeshUtilitiesCommon/Public/MeshUtilitiesCommon.h ELightmapUVVersion :7-21(ScaleByEdgesLength=10=Latest)
StaticMeshOperations.cpp CreateLightMapUVLayout :1866-1973
MeshBuilder/Private/MeshDescriptionHelper.cpp 自动 UV 生成 :101-129
UnrealLightmass/Private/Lighting/TextureMapping.cpp AverageTexelDensity :1892-1927

A.3 导入器与编辑器

文件 关键内容
Editor/UnrealEd/Private/Fbx/FbxMaterialImport.cpp 材质属性映射 :817-834(sDiffuse→BaseColor 等)
Editor/UnrealEd/Private/Fbx/FbxStaticMeshImport.cpp LightMapUV 命名识别 :400-403、默认 64/Index=1 :1865-1867、DstLightmapIndex=FirstOpenUVChannel :2065-2069、EnforceLightmapRestrictions :2395
Plugins/Interchange/Runtime/Source/Import/Private/Gltf/ InterchangeGltfTranslator.cpp(GLTF 导入,本 fork 存在)
Plugins/Interchange/Runtime/Source/Import/Private/InterchangeImportModule.cpp UInterchangeGLTFTranslator 注册 :111
Plugins/Enterprise/DatasmithImporter/Private/UVTools/ UUVGenerationFlattenMapping::GenerateUVs :1218、SetupGeneratedLightmapUVResolution :48-69
Plugins/Enterprise/DatasmithImporter/Private/DatasmithStaticMeshImporter.cpp GetLightmapSize :58-70
Plugins/Enterprise/DatasmithImporter/Private/DatasmithLightImporter.cpp 灯光单位映射(Candelas/Lumens/EV/Unitless)
Editor/LandscapeEditor/Private/LandscapeImportHelper.cpp Heightmap 导入(GetHeightmapImportDescriptor 等)
Classes/Components/LocalLightComponent.h IntensityUnits(cd/流明注释)
Private/Components/LocalLightComponent.cpp GetUnitsConversionFactor(”UE unit is in centimeters…100*100”)
Classes/Engine/RendererSettings.h DefaultLightUnits(r.DefaultFeature.LightUnits)
GeometryProcessingInterfaces/Public/MeshAutoUV.h IGeometryProcessing_MeshAutoUV :17-72(PatchBuilder/UVAtlas/XAtlas)
StaticMeshEditorSubsystem.cpp SetGenerateLightmapUVs :1970

附录 B 术语表(中英对照)

英文 中文(本文用词) 一句话解释 首次出现
Lightmap 光照贴图 离线烘焙的光照纹理 Ch1
Bake 烘焙 离线预计算光照 Ch1
Texel Density 纹素密度 每纹素覆盖的世界面积 Ch2
Atlas 图集 多张光照贴图拼成的共享纹理 Ch2
Chart UV 岛 UV 展开后的连通区域 Ch2
Padding 边距 UV 岛/贴图之间的缝隙 Ch2
Packing 装箱 把片装进画布 Ch2
Tile 瓦片 GPU Lightmass 的处理单元(128×128) Ch3
HQ / LQ (不译) 高质量(LogLUVW+SH)/低质量(LogRGB)编码 Ch4
LogScale (不译) 对数量化尺度(11.5) Ch4
Scale / Add (不译) 每图归一化的缩放/平移 Ch4
CoordinateScale / Bias (不译) atlas 内 UV 的缩放/偏移 Ch4
SH (不译) 球谐(方向性光照编码) Ch4
Mobility 移动性 Static/Stationary/Movable 三态 Ch1
Stationary (不译) 固定位置可变亮度的灯 Ch1
Bounce 反弹 间接光反弹次数 Ch7
Irradiance Caching 辐照度缓存 复用相邻点 GI(加速) Ch3
OIDN (不译) Intel 开源降噪器 Ch3
MinLightmapResolution (不译) 自动 UV 的最小分辨率 Ch8
LightMapCoordinateIndex (不译) Lightmap UV 通道号(默认 1) Ch2
ELightmapUVVersion (不译) UV 打包算法版本 Ch5
FAllocator2D (不译) 段式 best-fit 装箱分配器 Ch5
PackedLightAndShadowMapTextureSize (不译) Atlas 最大尺寸(默认 1024) Ch5
LightmapTilePoolSize (不译) GPU Lightmass tile 池大小 Ch3
ISM (不译) 实例化静态网格 Ch5
VT (不译) 虚拟纹理 Ch6
LightmapStreamingFactor (不译) 流送权重(0.2) Ch6
ELightUnits (不译) 灯光单位(Candela/Lumen/Lux/EV) Ch12
WorldToMeters (不译) 世界单位换算(1UU=1cm → 100) Ch12
Convert Scene (不译) FBX 导入的单位/轴向转换 Ch12
Force Front X Axis (不译) FBX 导入的轴向修正 Ch12
Heightmap 高度图 地形高度数据(导入用) Ch12
Interchange (不译) UE 新导入框架(GLTF 经此) Ch12
Datasmith (不译) 场景导入插件(无 Unity) Ch12
Light Bleeding 漏光 UV 边距不足导致的串色 Ch7
Lightmap Density 视图 (不译) 编辑器密度可视化(红/蓝) Ch7

附录 C 参考资料

Epic 官方(dev.epicgames.com)

  1. Lighting the Environment in Unreal Engine(5.0+)—— 光照总览(光源 Mobility/Lumen/VSM 导航);
  2. GPU Lightmass Global Illumination(含官方中文版)—— GPU 烘焙文档:DX12+DXR、Full Bake/Bake What You See 两模式、面板参数、已知限制;
  3. Understanding Lightmapping(5.2+)—— Lightmap UV 硬性要求:UV 岛不重叠、0-1 空间、约 4 纹素 padding;
  4. Generating Lightmap UVs(5.3)—— Build Settings 自动生成(Generate Lightmap UVs/Min Resolution/Source→Destination);
  5. Datasmith Supported Software and File Types(5.6)—— 官方清单无 Unity
  6. Importing glTF Files Into Unreal Engine(经 Interchange)—— 本 fork 已实测可用
  7. Migrating assets from Unity to Unreal Engine(官方迁移文档)—— FBX Exporter 包、Convert Scene + Force Front X Axis、100 倍缩放、Y-up→Z-up。

中文社区(标注:以下基于 UE4 或写作时未能访问核实,非本仓库实测):

  1. 知乎《UE5 光照烘焙—CPU Lightmass》(p/660381167)—— 烘焙全流程(关 Lumen/VSM、Lightmass 参数、Lightmap Density 视图);
  2. 知乎《UE4 LightMap 编码格式(移动端)》(p/539598416)—— HQ=RGBLogL 8:8:8:8、LQ=24bit RGB8、SimpleLogScale=16、cook 后 ASTC;
  3. 知乎《UNREAL 移动端 LightMap 自定义优化修改(六)》(p/261297862)—— 跳 SkyOcclusion、AO 写 A 通道、包体减 150MB;
  4. ldpk《Unity 光照贴图 UV 优化实战》—— UV 空间浪费与 packing 优化(跨引擎对照);
  5. masterTOB《From Unity to Unreal》(Epic 论坛)—— 一键导出整个 Unity 场景为 FBX;
  6. 知乎《全局光照引擎—Lightmap 烘焙器构件》(p/384470030)—— 自研烘焙器视角的 Lightmap 管线拆解;
  7. 知乎《命运扳机》的光与影(UFSH2025)—— UE 当烘焙器的坑(RVT 地形预渲染、Lightmass 材质导出模块重写)。

附录 D 自测练习

入门级

  1. 用三句话 + 一个类比向同事解释”烘焙”(Lightmap)是什么。
  2. Lumen 和 Lightmap 的区别?什么场景选哪个?
  3. LightMapResolution 是”每 texel 多少世界单位”吗?它实际是什么?

进阶级

  1. 画出一张光照贴图从烘焙到显示的七段流程(图 F1 复述)。
  2. 缺第二 UV 时引擎会怎样?为什么”没报错 = 没问题”是错的?
  3. Unity 场景导入 UE 的 100 倍缩放是哪来的?灯光单位怎么换算?
  4. 漏光(Light Bleeding)的成因与排查顺序是什么?

源码级

  1. LightmapEncoding.cpp:35(QuantizeLightSamples)找到 LogScale 常量,解释为什么要取对数。
  2. LightMap.cpp:685(PostEncode)找到 CoordinateScale/Bias 的计算,说明它们解决什么问题。
  3. LocalVertexFactoryCommon.ush:118 找到双纹理打包的 UV 变换,推导 UV1 为什么是 UV0 + float2(0,0.5)
  4. FbxMaterialImport.cpp:817 找到材质属性映射表,说出 Unity Smoothness 应该映射到 UE 的什么。
  5. 实验:建一个室内关卡烘焙,用 Lightmap Density 视图找密度不均的物体,调整分辨率后对比显存占用。