一个你可能从没想过的问题先别急着聊技术。我问你一个生活里的问题一件白色的衬衫是什么颜色的你大概会说白色啊。但真的是这样吗白衬衫 正常日光 → 你看到白色 白衬衫 昏暗房间 → 你看到灰色 白衬衫 KTV红灯 → 你看到红色 白衬衫 全黑房间 → 你什么都看不到黑色同一件白衬衫在不同的光下你看到的颜色完全不同。这引出一个反直觉、但极其重要的事实你眼睛看到的颜色从来都不是物体本身的颜色。你看到的 物体本色 和 光 一起决定的结果。理解了这句话你就理解了游戏引擎里 Lightmap 的一切。这篇文章就从这里出发把三个问题讲透Lightmap 到底存的是什么为什么最终画面是本色 × 光而且是乘法Lightmap 里的光又是从哪来的一、Lightmap 存的是光不是颜色先给结论Lightmap 存的是这个点收到了多少光不是物体是什么颜色。这是最容易搞混的地方。一个游戏物体最终显示出来的样子其实是两样东西合成的它们必须分清楚最终画面颜色 物体本色 × 收到的光 └────┬────┘ └───┬───┘ 颜色贴图 Lightmap (Albedo) (光照信息) 美术画的 烘焙算的拿一面白墙举例存的是什么白墙的值颜色贴图Albedo物体本身是什么颜色全白均匀一片Lightmap它每个位置收到多少光靠灯近的亮墙角暗画出来是这样白墙本色颜色贴图 ┌──────────┐ 到处都白没有明暗 │██████████│ └──────────┘ Lightmap光照 ┌──────────┐ 只记录哪亮哪暗 │▓▓▒▒░░▒▒▓▓│ 中间亮、墙角暗 └──────────┘ 两张一乘 你看到的画面 ┌──────────┐ 白墙上有了明暗立体感 │▓▓▒▒░░▒▒▓▓│ └──────────┘注意Lightmap 里根本没有这墙是白的这个信息。它只管哪里亮、哪里暗。墙是什么颜色是颜色贴图的事。两者各管一摊。二、为什么是相乘而不是相加这是全文最关键的一点。为什么最终颜色是本色乘以光而不是加上光答案是物体的本色本质上是一个反射比例。想想物体为什么有颜色——因为它反射一部分光、吸收一部分光白色 1.0 收到的光 100% 反射出去 灰色 0.5 收到的光 只反射 50% 黑色 0.0 收到的光 全部吸收一点不反射所以物体本色其实是个折扣率它作用在光上面你看到的亮度 收到多少光 × 反射出去的比例 光 × 本色这就是为什么是乘法——本色是个比例比例当然是用乘的。用具体数字感受一下光强用 0~1 表示白墙(本色1.0) 被强光(0.9)照 0.9 × 1.0 0.9 → 明亮的白 白墙(本色1.0) 在墙角(0.2) 0.2 × 1.0 0.2 → 暗灰 黑墙(本色0.1) 被强光(0.9)照 0.9 × 0.1 0.09 → 依然很暗看最后一行黑墙就算被强光直射也亮不起来——因为它本色低把光大都吸收了。这种效果只有乘法能表达用加法就完全错了加法会让黑墙被照亮变灰不符合现实。三、彩色的情况为什么白衬衫在红灯下会变红上面是灰度现在加上颜色。颜色是 (红, 绿, 蓝) 三个通道相乘就是三个通道各乘各的。我们来解释开头那个白衬衫在红灯下变红白衬衫 在 红灯 下衬衫本色 (1.0, 1.0, 1.0) ← 白红绿蓝都 100% 反射 红灯 (1.0, 0.0, 0.0) ← 红光只有红没有绿蓝 各通道相乘 红1.0 × 1.0 1.0 ✓ 有红光衬衫也反射红 绿1.0 × 0.0 0.0 ✗ 没有绿光可反射 蓝1.0 × 0.0 0.0 ✗ 没有蓝光可反射 结果 (1.0, 0, 0) 红色 ✓白衬衫在红灯下变红——不是衬衫变了是它老老实实反射了唯一存在的红光。公式自动算对了。再看一个更反直觉的红苹果 在 绿灯 下苹果本色 (1.0, 0.0, 0.0) ← 红只反射红光 绿灯 (0.0, 1.0, 0.0) ← 只有绿光 相乘 红1.0 × 0.0 0 ← 苹果想反射红但环境里根本没红光 绿0.0 × 1.0 0 ← 有绿光但苹果不反射绿吸收掉 蓝0.0 × 0.0 0 结果 (0, 0, 0) 全黑红苹果在纯绿光下会变成黑色——这是真实的物理现象你在实验室里能验证。而这个本色 × 光的公式天然就把它算对了。四、那 Lightmap 里的光是从哪来的既然 Lightmap 存的是光那这些光是哪来的只有三个来源① 直接光——来自光源本身太阳、灯泡直接照过来的光。这个好理解。② 间接光弹射光——来自其他表面光打到墙上墙会把一部分光反射出去照亮旁边的东西。灯 → 照亮地面 → 地面反射 → 照亮墙 → 墙再反射 → 照亮天花板 ...这才是 Lightmap 最有价值的部分。因为间接光要追踪光线反复弹射实时算太贵所以提前烤进 Lightmap 最划算。也正是间接光解释了为什么 Lightmap 图片常常是彩色的——插一句为什么 Lightmap 本身是彩色的你如果见过 Lightmap 贴图会发现它花花绿绿的可能会疑惑不是说它只存光有多强吗怎么有颜色因为——光本身是有颜色的。白灯照过来 → Lightmap 存白光 红霓虹灯照过来 → Lightmap 存红光 光打在红墙上弹过来 → 弹过来的光带红色间接光染色所以一面白墙如果旁边有面红墙白墙靠近红墙的部分在 Lightmap 里会是淡红色——因为它收到了红墙反射来的红光。这里要再次强调那个致命的区别颜色贴图里的红 这个物体表面是红色的 Lightmap 里的红 有红色的光照到了这里两个红完全是两回事。③ 环境光/天空光——来自天空光线没撞到任何物体、飞向天空的部分会收到天空的光比如白天蓝天的淡蓝色光。三者加起来就是 Lightmap 里存的总光照Lightmap 存的光 直接光 间接光 天空光 光源 弹射 天空五、为什么非要分开存合并成一张不好吗你可能会问既然最终是本色 × 光那我直接把它俩乘好存成一张最终图片不就完了何必分两张因为分开存能省下惊人的内存如果合并存本色×光 存成最终图 一张墙的贴图本来能重复用在场景里 100 面墙上 但每面墙的光照不同 → 只能给每面墙各存一张独立的最终图 → 100 张高清图 → 内存爆炸 分开存颜色一张 光一张 颜色贴图100 面墙共用同一张本色都一样 Lightmap只存各自的明暗而且可以用很低的分辨率 → 内存省下一大截 ✓而且还有个巧妙之处光是平滑变化的明暗过渡很缓没有细碎纹理所以 Lightmap 可以用很低的分辨率放大了也不糊。而颜色贴图有细节花纹需要高分辨率。分开存就能各用各合适的精度一点不浪费。总结用一张表收尾全文问题答案Lightmap 存的是颜色吗不是。存的是这个点收到多少光光照信息为什么它是彩色的因为光本身有颜色红灯、红墙反射的红光光从哪来① 直接光光源② 间接光表面弹射③ 天空光物体本身颜色谁管颜色贴图Albedo和 Lightmap 是两码事最终画面怎么算本色 × 收到的光逐像素、逐 RGB 相乘为什么用乘法因为本色是反射比例比例作用在光上就是乘为什么分开存颜色可共用、光可低分辨率省内存如果整篇文章只留一句话那就是你看到的颜色从来不是物体本身的颜色而是物体能反射的颜色和照过来的光相乘的结果。颜色贴图管前者Lightmap 管后者两张图一乘才是你在游戏里看到的画面。全黑房间里再白的墙也是黑的 光0乘出来0 纯绿光下再红的苹果也不红 本色和光对不上乘出来0这就是 Lightmap 的本质。