现象
2026-08-29,生产任务 878(待机,12 帧)。逐帧量交付产物:
f00 不透明占比 0.101 主体平均亮度 0.0 ← 纯黑剪影
f01 不透明占比 0.101 主体平均亮度 148.0
f02 不透明占比 0.100 主体平均亮度 148.0
…
f11 不透明占比 0.101 主体平均亮度 145.8
第 0 帧是一个没有贴图的纯黑人形,其余 11 帧正常。
为什么没有任何一道闸拦住
出帧循环有两道自检,两道都放它过去:
coverage() 只数 alpha(data[i+3] > 8),不看颜色。纯黑剪影的不透明像素占比与正常帧一模一样——实测 0.101 对 0.101。
grab() 只判空 blob。纯黑帧是一张体积正常的 PNG。
服务端的 min_coverage 同口径,同样拦不住。所以它一路当成正常产物交付。
根因
BakeStage.load() 在 placeCam() 之后直接返回,渲第一帧之前没有任何 warm-up:
const object = await new FBXLoader().loadAsync(url)
loadAsync 在 FBX 解析完就 resolve,而内嵌贴图的解码/上传 GPU 是另一条异步路,没人等它。于是第 0 帧渲出来时着色器与贴图都还没就绪。
(补一条口径更正:此前排查另一批产物时,我把首帧全黑归因为"探针每帧只渲一次、生产渲三次所以不会有"。生产这次证伪了这个说法——三次渲染都发生在同一个 tick 内,贴图仍可能没解码完。)
修法
两道一起:
load() 末尾加 warmUp()——优先 renderer.compileAsync(scene, camera)(three r152+ 起有,本仓 r185),它会把着色器编译与贴图上传都做完;没有该方法时退回同步 compile() + 一次渲染。
- 出帧循环加一道亮度闸:主体平均亮度低于 20 判纯黑并当场失败。覆盖率那道只看 alpha,必须有一道看颜色的补上——否则同类问题(材质丢失、着色器编译失败)照样溜过去。
现象
2026-08-29,生产任务 878(待机,12 帧)。逐帧量交付产物:
第 0 帧是一个没有贴图的纯黑人形,其余 11 帧正常。
为什么没有任何一道闸拦住
出帧循环有两道自检,两道都放它过去:
coverage()只数 alpha(data[i+3] > 8),不看颜色。纯黑剪影的不透明像素占比与正常帧一模一样——实测 0.101 对 0.101。grab()只判空 blob。纯黑帧是一张体积正常的 PNG。服务端的
min_coverage同口径,同样拦不住。所以它一路当成正常产物交付。根因
BakeStage.load()在placeCam()之后直接返回,渲第一帧之前没有任何 warm-up:loadAsync在 FBX 解析完就 resolve,而内嵌贴图的解码/上传 GPU 是另一条异步路,没人等它。于是第 0 帧渲出来时着色器与贴图都还没就绪。(补一条口径更正:此前排查另一批产物时,我把首帧全黑归因为"探针每帧只渲一次、生产渲三次所以不会有"。生产这次证伪了这个说法——三次渲染都发生在同一个 tick 内,贴图仍可能没解码完。)
修法
两道一起:
load()末尾加warmUp()——优先renderer.compileAsync(scene, camera)(three r152+ 起有,本仓 r185),它会把着色器编译与贴图上传都做完;没有该方法时退回同步compile()+ 一次渲染。