3.8.7 使用 RT 作为 SpriteFrame 的 Sprite 节点在 active 变化后会丢失原有材质

起因是我在写一个着色器教程,案例在 386 跑的好好的,结果换到 387 就挂了,经定位,问题和潜在原因如下。

Sprite 节点使用来 RenderTexture 作为 SpriteFrame,当该 Sprite 组件所在节点设置 activefalse,之后再设为 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;
    }

:bulb: 请留意上方从 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 引擎组大佬们能帮忙定位和早日修复,感谢~

我之前在论坛也提过这个问题,但是没人理的。。。我连pr都提了。也没人管

我个人已经觉得…… 3.x 摆烂就算了,但 4.0 别出现了 :cold_sweat:

up 一下