Lightmap 存的到底是什么?从“白衣服在红灯下变红“说起
一个你可能从没想过的问题
先别急着聊技术。我问你一个生活里的问题:
一件白色的衬衫,是什么颜色的?
你大概会说"白色啊"。但真的是这样吗?
白衬衫 + 正常日光 → 你看到白色 白衬衫 + 昏暗房间 → 你看到灰色 白衬衫 + 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 的本质。
