现象
#918 加的亮度闸开始拦真实任务。线上连续 4 单失败(886 / 888 / 890 / 891,均为 2026-08-28 21:28–21:30):
浏览器出帧失败:第 0 帧主体是纯黑(平均亮度 0.0 < 20),贴图可能还没就绪
用户从「悄悄拿到一张黑帧」变成「整单做不出来」。闸拦对了,但代价太大。
根因:预热不够
#918 的 warmUp() 走 renderer.compileAsync(scene, camera),它保证的是着色器编译与已解码贴图的上传——不等图片本身解码完。FBXLoader.loadAsync() resolve 之后,内嵌贴图的 decode 仍在异步进行。
所以 compileAsync 解决不了这一档。
为什么用重试而不是继续加预热
这是个纯等待问题:再渲一次就好了。要精确等到"贴图一定就绪",得逐个 texture 拿 image 去 await decode,而 three 的贴图来源有 ImageBitmap / HTMLImageElement / CanvasTexture 好几种,覆盖不全就仍会漏。
判黑后重渲、直到不黑为止,对所有成因都有效(贴图未解码、着色器未编译、材质异步替换),且不需要预测任何时序。
修法
判黑后每 300ms 重渲一次、最多 10 次;3 秒内没好才失败。贴图解码是几百毫秒的事,3 秒是十倍余量。
compileAsync 那道预热保留——它仍然消掉一部分情况,只是不够。
一条口径更正
#918 的结论「渲第一帧前先 compileAsync 就能解决」被线上证伪。当时我只在自己的探针里验过(探针成功,是因为它的贴图更小、解码更快),没有在真实 BakeStage 里验。
现象
#918 加的亮度闸开始拦真实任务。线上连续 4 单失败(886 / 888 / 890 / 891,均为 2026-08-28 21:28–21:30):
用户从「悄悄拿到一张黑帧」变成「整单做不出来」。闸拦对了,但代价太大。
根因:预热不够
#918 的
warmUp()走renderer.compileAsync(scene, camera),它保证的是着色器编译与已解码贴图的上传——不等图片本身解码完。FBXLoader.loadAsync()resolve 之后,内嵌贴图的 decode 仍在异步进行。所以 compileAsync 解决不了这一档。
为什么用重试而不是继续加预热
这是个纯等待问题:再渲一次就好了。要精确等到"贴图一定就绪",得逐个 texture 拿 image 去 await decode,而 three 的贴图来源有 ImageBitmap / HTMLImageElement / CanvasTexture 好几种,覆盖不全就仍会漏。
判黑后重渲、直到不黑为止,对所有成因都有效(贴图未解码、着色器未编译、材质异步替换),且不需要预测任何时序。
修法
判黑后每 300ms 重渲一次、最多 10 次;3 秒内没好才失败。贴图解码是几百毫秒的事,3 秒是十倍余量。
compileAsync那道预热保留——它仍然消掉一部分情况,只是不够。一条口径更正
#918 的结论「渲第一帧前先 compileAsync 就能解决」被线上证伪。当时我只在自己的探针里验过(探针成功,是因为它的贴图更小、解码更快),没有在真实 BakeStage 里验。