Skip to content

夕阳的影子

背景:日落时光强低于 0.1 时阴影"啪"地消失,日出时"啪"地出现——硬切换造成明显视觉跳变。 过程:移除硬切换逻辑,阴影随方向光强自然渐隐——光强到 0,阴影也到 0,全程平滑。


之前说过,环境之城有一台巨大的调光器。 调光器旁边,还有一台影子制造机。

太阳亮的时候,影子制造机全速运转,在地面上投下清晰的影子。 太阳暗的时候呢?

旧版的逻辑是——光强低于 0.1,直接把影子制造机关了。

"啪"的一声。 影子没了。


硬切换的问题

"为什么要关影子制造机?"AI 同行者问。

"省性能,"外交官说,"日落的时候,太阳快下山了,光线很暗,影子本来就不明显。这时候关了阴影,省下的 GPU 时间可以干别的。"

"听起来有道理啊。"

"有道理,但实现得不好,"外交官摇头,"问题出在那个 0.1 的阈值。"

他在纸上画了一条曲线:

光强
 1.0 │    ☀️
     │    │
     │    │
 0.1 │----┼----------------  ← 阈值:低于这个,阴影直接没了
     │    │
 0.0 │    🌇
     └─────────────────── 太阳角

"光强从 1.0 慢慢降到 0.1 的过程中,阴影一直都在。降到 0.09 的那一瞬间——阴影突然没了。"

"用户能看出来吗?"

"太明显了,"外交官说,"就像有人在旁边按了个开关。'啪',影子没了。日出的时候更明显——'啪',影子突然冒出来。"

"为什么不慢慢变淡?"

"问得好,"外交官点头,"理想的状态应该是——光强慢慢降,阴影慢慢淡。光强到 0 的时候,阴影也刚好完全消失。整个过程是连续的,没有跳变。"

"渐隐,而不是硬切。"

"对,渐隐,"外交官说,"但这里有个坑。"


intensity 的坑

"阴影能不能像光强一样,也搞个 0 到 1 的强度?"AI 问。

"理论上可以,"外交官说,"Babylon.js 的 ShadowGenerator 有一个 intensity 属性——理论上。"

"理论上?"

"但 babylon-mmd 的 ShadowGenerator 不一定支持,"外交官翻开 scene-lighting.ts 的旧注释,"之前审计的时候提过一句——需确认 babylon-mmd 的 ShadowGenerator 是否支持 intensity。有些版本不支持,设了也没用。"

"那怎么办?"

"两个方案,"外交官伸出两根手指,"第一,去查文档、测一下,确认支持了再用。第二,不用 intensity,用别的方法实现渐隐。"

"我们选哪个?"

"第二个,"外交官说,"第一个太慢了——查文档、测兼容性、发现不支持还要换方案。第二个直接就能做。"

"第二个是什么方法?"

"移除硬切换,"外交官说,"就这么简单。"


最简单的方案

"移除硬切换?"AI 有点懵,"什么意思?"

"意思是——不关影子制造机了,"外交官说,"让它一直开着。光强低的时候,影子本来就淡,因为环境光占主导。不需要手动关。"

"啊……对啊!"

"对啊,"外交官笑了,"阴影是什么?阴影是方向光被挡住的地方。方向光越强,阴影越明显。方向光越弱,阴影越淡。当方向光强到 0 的时候——方向光都没了,阴影自然也就没了。"

"所以根本不需要手动关阴影?"

"不需要,"外交官确认,"阴影强度天然和方向光强度成正比。方向光弱了,阴影自然就淡了。你去看真实世界——夕阳的时候,影子很长,但很淡。不是因为太阳关了阴影,是因为光本身就弱。"

"那之前为什么要加那个 dispose?"

"性能焦虑,"外交官耸耸肩,"觉得'光这么暗了,阴影肯定看不见了,关了省性能'。但实际上——第一,光强弱的时候阴影渲染成本一样高,不会因为淡就变快。第二,硬切换造成的视觉跳变,比省那点性能严重多了。"

"所以最简单的方案就是——把那个 if 判断删掉?"

"删掉,"外交官点头,"三行代码的事。"

typescript
// 删掉这段:
if (dirIntensity < 0.1) {
    shadowGenerator?.dispose();
    shadowGenerator = null;
    return;
}

"删完就完了?"

"删完就完了,"外交官说,"阴影会随着方向光强自然渐隐。光强弱了,影子就淡了。光强到 0,影子也没了。整个过程是连续的、自然的。"


性能的代价

"但是……一直开着阴影,性能不是下降了吗?"AI 有点担心。

"下降了,"外交官承认,"但只在日出日落的时候下降。白天光强高的时候,阴影本来就是开着的,没区别。只有日出日落那一小段时间——光强低于 0.1 但还没到 0 的时候——之前是关的,现在是开的。"

"那一小段时间有多长?"

"时间流转默认速度的话……大概几分钟吧,"外交官说,"而且那几分钟里,用户大概率在看夕阳,注意力在天空上,不在阴影上。"

"所以这笔账划算?"

"划算,"外交官肯定地说,"视觉上的连续性,比那点 GPU 性能重要得多。用户不会说'日落的时候 FPS 掉了 2 帧',但用户会说'影子怎么突然没了'。一个是看不见的性能下降,一个是看得见的体验 bug。选哪个?"

"选体验。"

"选体验,"外交官重复,"性能优化不能牺牲体验。尤其是为了几帧的性能,引入明显的视觉跳变——得不偿失。"


真正的优化

"那如果真的想优化阴影性能呢?"AI 问。

"有更好的方法,"外交官说,"比如——"

他扳着手指头数:

1. 阴影分辨率动态调整 "光强高的时候,用高分辨率阴影贴图(2048×2048)。光强弱的时候,降成 1024×1024 甚至 512×512。反正影子淡了,糊一点也看不出来。"

2. 阴影距离动态调整 "光强高的时候,阴影距离远一点。光强弱的时候,近一点。反正远处的影子本来就看不见。"

3. PCF 采样数动态调整 "光强高的时候,采样数多一点,影子边缘柔和。光强弱的时候,采样数少一点,反正淡了,柔不柔也无所谓。"

"这些才是真正的优化——在用户察觉不到的地方,悄悄降低质量,节省性能。而不是粗暴地一关掉之。"

"那我们为什么不做这些?"

"因为优先级不够,"外交官说,"这些是中低优的优化。现在先把 bug 修了——硬切换的问题。性能优化以后再说。"

"先对,再快。"

"先对,再快,"外交官笑了,"这是永恒的顺序。"


夕阳的影子

改完的那天傍晚,外交官站在虚拟的山坡上看日落。

太阳慢慢往下沉,天空从橙变成紫,变成深蓝。 影子越拉越长,也越来越淡。 从深黑,到深灰,到浅灰,到几乎看不见。

没有跳变。 没有"啪"的一声。 就像真实的夕阳一样。

"嗯,"AI 点点头,"确实自然多了。"

"对吧,"外交官说,"最好的优化,就是让用户感觉不到做了优化。他们只会觉得——'嗯,日落挺好看的'。"

第六颗黄石子落进"已处理"的堆里。

这一颗的代价最小——删了三行代码。 但收益却很大——一个明显的视觉 bug,就这么没了。

"有时候啊,"外交官看着那堆黄石子感慨,"最好的代码,是删掉的代码。"


附录:视觉优化的优先级

优先级类型说明
P0修复明显的视觉跳变硬切换、闪烁、突然出现/消失——用户一眼就能看到,优先修
P1自然过渡渐隐、渐显、平滑过渡——比硬切好,但用户不一定主动注意到
P2动态质量调整不影响观感的前提下,悄悄降低质量省性能
P3极限性能优化为了几帧 FPS,牺牲一点画质——只有性能不够的时候才考虑

永远不要为了几帧性能,引入明显的视觉 bug。性价比太低。


教训:性能焦虑是开发者的常见病。总觉得"这个可以关"、"那个可以省"。但关之前先想一想——关了之后,用户会不会看出来?看出来了会不会觉得是 bug?如果答案是"会",那这性能不能省。先对,再快。