起因是我在写一个着色器教程,案例在 386 跑的好好的,结果换到 387 就挂了,经定位,问题和潜在原因如下。
若 Sprite 节点使用来 RenderTexture 作为 SpriteFrame,当该 Sprite 组件所在节点设置 active 为 false,之后再设为 true 时,原本在编辑器里绑定的自定义着色器材质会失效。
具体原因估计:
SpriteNode.active 变化后会触发 updateMaterial 方法:
public updateMaterial (): void {
if (this._customMaterial) {
if (this.getSharedMaterial(0) !== this._customMaterial) {
this.setSharedMaterial(this._customMaterial, 0);
}
return;
}
const mat = this._updateBuiltinMaterial();
this.setSharedMaterial(mat, 0);
if (this.stencilStage === Stage.ENTER_LEVEL || this.stencilStage === Stage.ENTER_LEVEL_INVERTED) {
this.getMaterialInstance(0)!.recompileShaders({ USE_ALPHA_TEST: true });
}
this._updateBlendFunc();
}
然而 Cocos 好像不使用 _customMaterial 属性来存储我们在编辑器中拖拽的材质,导致逻辑走到下方的 _updateBuiltinMaterial 逻辑:
protected _updateBuiltinMaterial (): Material {
let mat = super._updateBuiltinMaterial();
if (this.spriteFrame && this.spriteFrame.texture instanceof RenderTexture) {
const rtMatName = `rt-${mat.name}`;
let rtMat = builtinResMgr.get(rtMatName) as Material | null;
if (!rtMat) {
rtMat = new Material(rtMatName);
rtMat.copy(mat, { defines: { SAMPLE_FROM_RT: true } });
builtinResMgr.addAsset(rtMatName, rtMat);
}
mat = rtMat;
}
return mat;
}
请留意上方从 const rtMatName = rt-${mat.name} 到 if (!rtMat) 代码块结束的位置,是被 3.8.7 重写了的,这大概率是导致问题的地方。
在我的案例里 Sprite 的 SpriteFrame 刚好使用的是 RenderTexture,导致触发了这段 3.8.7 重构代码的逻辑 —— 材质被强行替换为了内置的材质,它完全丢失了我在自定义的各种 Uniform 属性,导致我的材质直接失效和报错(在使用 .setProperty 给材质设置属性时)。
目前我自己项目中的 hack 如下(仅供参考):
// Sprite 节点切换后针对 3.8.7 进行 hack
// 3.8.7 版本的 Sprite 状态机存在缺陷,容易丢失材质,需要暴力恢复
if (this._isVer387) {
this._currentSampleCount = count;
const horiSprite = this.horiSprite;
const vertSprite = this.vertSprite;
// 检查并恢复材质
if (horiSprite.sharedMaterial !== this._originHoriMat) {
// _originHoriMat 是在 start 生命周期内就保存的原始材质
horiSprite.setSharedMaterial(this._originHoriMat, 0);
this.BGHoriMat = horiSprite.material;
}
// 强制刷新材质状态
(horiSprite as any)._instanceMaterialType = -1;
(horiSprite as any).updateMaterial();
this.BGHoriMat = horiSprite.material; // updateMaterial 后可能会产生新的 material 实例,务必重新获取
}
这是一个很糟糕的 hack,希望 Cocos 引擎组大佬们能帮忙定位和早日修复,感谢~
