关于spine在cocos2dx使用硬件压缩纹理混合问题总结

问题分析

  • 给axmol增加了ASTC和ETC2纹理支持,默认情况下Image加载这些纹理视为没有alpha预乘,也就是Image的_hasPremultipliedAlpha保持为初始值false, 引擎核心代码中的类基本都会根据该变量正确设置混合模式即{ backend::BlendFactor::SRC_ALPHA, backend::BlendFactor::ONE_MINUS_SRC_ALPHA},所以渲染没什么问题
  • 然而spine运行库的实现中,强制认为纹理已经alpha预乘,因此在使用ETC2或ASTC时,渲染会异常
void SkeletonRenderer::initialize () {
	_clipper = new (__FILE__, __LINE__) SkeletonClipping();

	_blendFunc = BlendFunc::ALPHA_PREMULTIPLIED;
	setOpacityModifyRGB(true);

	setTwoColorTint(false);

	_skeleton->setToSetupPose();
	_skeleton->updateWorldTransform();
}

解决方案

  1. 在初始化的时候判断
   void SkeletonRenderer::setupGLProgramState (bool twoColorTintEnabled) {
   		Texture2D *texture = nullptr;
   		for (int i = 0, n = _skeleton->getSlots().size(); i < n; i++) {
   			Slot* slot = _skeleton->getDrawOrder()[i];
   			Attachment* const attachment = slot->getAttachment();
   			if (!attachment) continue;
   			if (attachment->getRTTI().isExactly(RegionAttachment::rtti)) {
   				RegionAttachment* regionAttachment = static_cast<RegionAttachment*>(attachment);
   				texture = static_cast<AttachmentVertices*>(regionAttachment->getRendererObject())->_texture;
   			} else if (attachment->getRTTI().isExactly(MeshAttachment::rtti)) {
   				MeshAttachment* meshAttachment = static_cast<MeshAttachment*>(attachment);
   				texture = static_cast<AttachmentVertices*>(meshAttachment->getRendererObject())->_texture;
   			} else {
   				continue;
   			}
   	
   			if (texture != nullptr) {
   				break;
   			}
   		}
   	
   		if (texture != nullptr && texture->hasPremultipliedAlpha()) {
   			_blendFunc = BlendFunc::ALPHA_PREMULTIPLIED;
   			setOpacityModifyRGB(true);
   		}
   		else {
   			_blendFunc = BlendFunc::ALPHA_NON_PREMULTIPLIED;
   			setOpacityModifyRGB(false);
   		}
   }

这种方案,可以解决spine多数渲染问题,然而,当包含 BlendMode_Screen 模式的部件时,渲染依然不正常(和png对比),查看代码如下:

BlendFunc makeBlendFunc(BlendMode blendMode, bool premultipliedAlpha) {
		BlendFunc blendFunc;
		switch (blendMode) {
			case BlendMode_Additive:
				blendFunc.src = premultipliedAlpha ? backend::BlendFactor::ONE : backend::BlendFactor::SRC_ALPHA;
				blendFunc.dst = backend::BlendFactor::ONE;
				break;
			case BlendMode_Multiply:
				blendFunc.src = backend::BlendFactor::DST_COLOR;
				blendFunc.dst = backend::BlendFactor::ONE_MINUS_SRC_ALPHA;
				break;
			case BlendMode_Screen:
				blendFunc.src = backend::BlendFactor::ONE;
				blendFunc.dst = backend::BlendFactor::ONE_MINUS_SRC_COLOR;
				break;
			default:
				blendFunc.src = premultipliedAlpha ? backend::BlendFactor::ONE : backend::BlendFactor::SRC_ALPHA;
				blendFunc.dst = backend::BlendFactor::ONE_MINUS_SRC_ALPHA;
		}
		return blendFunc;
	}

怀疑是BlendMode_Screen没有正确设置混合方式造成,待考究

  1. 修改内置shader, 在postionTextureColor.frag中进行alpha预乘
    void main()
    {
        vec4 texColor = texture2D(u_texture, v_texCoord);
        texColor.rgb *= texColor.a; // Premultiply with Alpha channel
        gl_FragColor = v_fragmentColor * texColor;
    }
    
    同时修改Image::PNG_PREMULTIPLIED_ALPHA_ENABLED为false,并在ASTC和ETC2纹理加载成功后,将Image的_hasPremultipliedAlpha设置为true,这样做可以达到使用png,astc,etc2的spine渲染效果一致的目的,但是修改需要覆盖到引擎的其他内置shader

后记:

  • 早期etc1 alpha为什么没问题
    因为etc1的shader etc1.frag中做了预乘:
      const char* etc1_frag = R"(
     	#ifdef GL_ES
     	    precision mediump float;
     	#endif
     	
     	varying vec4 v_fragmentColor;
     	varying vec2 v_texCoord;
     	
     	uniform sampler2D u_texture;
     	uniform sampler2D u_texture1;
     	
     	void main() {
     	    vec4 texColor = vec4(texture2D(u_texture, v_texCoord).rgb, texture2D(u_texture1, v_texCoord).r);
     	
     	    texColor.rgb *= texColor.a; // Premultiply with Alpha channel
     	
     	    gl_FragColor = v_fragmentColor * texColor;
     	}
     )";
    
  • 工具png转etc2或astc时便进行预乘
    目前笔者只发现TexturePacker支持勾选premultiplyAlpha, 但没试过,后续待验证
    更新: 测试发现TexturePacker最新版本,也就是5.4支持ETC2 RGBA预乘alpha导出,且spine不必修改任何代码渲染效果和png一样
    tips: 暂未发现TexturePacker支持astc
    更新:2021.6.15,ARM官方astcenc-2.3+开始支持生成预乘alpha纹理, 转换命令添加参数: -pp-premultiply 即可。

你可能感兴趣的:(cocos2d,spine,adxe)