项目背景
想要将spine整体进行变色,大概效果如下图
效果分析
简单说就是将整体效果灰度化
后,然后进行目标颜色的着色,具体着色算法为根据灰度值进行插值,也就是从mixColor
->white
渐变。
实现过程
像素灰度
算法想好了,接下来就开始实现, 先置灰
ini
float gray = dot(vec3(finalColor.r, finalColor.g, finalColor.b), vec3(0.2126, 0.7152, 0.0722));
gray = max(min(1.0,gray),0.0);
finalColor.r = finalColor.g = finalColor.b = gray;
gl_FragColor = finalColor;
效果和预期的一致,变成了灰度图
着色
接下来就是着色
ini
float gray = dot(vec3(finalColor.r, finalColor.g, finalColor.b), vec3(0.2126, 0.7152, 0.0722));
gray = max(min(1.0,gray),0.0);
finalColor.r = mix(mixColor.r, 1.0, gray);
finalColor.g = mix(mixColor.g, 1.0, gray);
finalColor.b = mix(mixColor.b, 1.0, gray);
gl_FragColor = finalColor;
发现透明区域出现了mixColor
这个很好理解,因为透明区域计算出来的gray为0,mix之后就是mixColor
discard处理透明区域
解决办法:可以将透明度为0的丢弃掉,这种办法会导致出现很多毛边
if ( finalColor.a <= 0.0 ) discard;
预乘出现黑边
所以废弃discard
这种方案,可以采用alpha预乘
ini
finalColor.r = mix(mixColor.r, 1.0, gray) * texColor.a;
finalColor.g = mix(mixColor.g, 1.0, gray) * texColor.a;
finalColor.b = mix(mixColor.b, 1.0, gray) * texColor.a;
可以看到效果有明显的提升,但是发现嘴巴附近有黑边,这个黑边主要就是来自边缘的过渡色
ALPHA_TEST
ALPHA_TEST
可以缓解这个问题,但是会带来锯齿问题
因为ALPHA_TEST
本质也是discard
c
#if USE_ALPHA_TEST
uniform float alphaThreshold;
#endif
void ALPHA_TEST (in vec4 color)
{
#if USE_ALPHA_TEST
if (color.a < alphaThreshold) discard;
#endif
}
void ALPHA_TEST (in float alpha)
{
#if USE_ALPHA_TEST
if (alpha < alphaThreshold) discard;
#endif
}
基于源纹理处理
如果再顺着这个思路解决下去,好像到了死胡同,卡了我好几天,最后还是热心网友大华
的帮助,顺利解决了这个问题,感谢大华
哥
以上的所有处理都是基于最终计算(finalColor
)的颜色处理的,再次理清需求,发现我们要基于纹理的颜色处理。
ini
float gray = dot(vec3(texColor.r,texColor.g, texColor.b), vec3(0.2126, 0.7152, 0.0722));
gray = max(min(1.0,gray),0.0);
float r = mix(mixColor.r, 1.0, gray);
float g = mix(mixColor.g, 1.0, gray);
float b = mix(mixColor.b, 1.0, gray);
vec3 grayColor = vec3(r,g,b) * texColor.a; // 预乘
finalColor = vec4(grayColor, texColor.a);
发现嘴巴部分还是有黑边
spine 纹理alpha预乘
检查完毕逻辑,发现的确算法是符合预期的,那么问题只可能出在纹理上,在spine中有这个选项,可以将输出的纹理预乘。
尝试还原下纹理颜色,基于纹理真实的颜色进行灰度
ini
vec3 color = texColor.rgb / texColor.a;
float gray = dot(color, vec3(0.2126, 0.7152, 0.0722));
至此,黑边也消失了,效果也就符合我们的预期了
v_light 处理
最后,我们还需要还原spine原有的颜色处理逻辑,也就是要增加灯光的处理。
下图是v_light
的不同效果,可以看到阴影处的不同,没有按照预期着色
解决办法也简单,对原始颜色处理下v_light就可以了
其他地方的预乘
spine组件的预乘
查看引擎源码,你会发现这个预乘是作用于blend的
js
let srcBlendFactor = this.premultipliedAlpha
? cc.gfx.BLEND_ONE
: cc.gfx.BLEND_SRC_ALPHA;
let dstBlendFactor = cc.gfx.BLEND_ONE_MINUS_SRC_ALPHA;
baseMaterial.setBlend(
true,
cc.gfx.BLEND_FUNC_ADD,
srcBlendFactor, srcBlendFactor,
cc.gfx.BLEND_FUNC_ADD,
dstBlendFactor, dstBlendFactor
);
图片资源的预乘属性
和spine的预乘一个道理,都是对纹理图片的修改
预乘的意义
Premultiplied Alpha 最重要的意义是使得带透明度图片纹理可以正常的进行线性插值