问题分析
- 给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();
}
解决方案
- 在初始化的时候判断
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没有正确设置混合方式造成,待考究
- 修改内置shader, 在postionTextureColor.frag中进行alpha预乘
同时修改Image::PNG_PREMULTIPLIED_ALPHA_ENABLED为false,并在ASTC和ETC2纹理加载成功后,将Image的void main() { vec4 texColor = texture2D(u_texture, v_texCoord); texColor.rgb *= texColor.a; // Premultiply with Alpha channel gl_FragColor = v_fragmentColor * texColor; }
_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 即可。