实现 creator spine 根据灰度渐变着色

项目背景

想要将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 最重要的意义是使得带透明度图片纹理可以正常的进行线性插值

相关推荐
excel39 分钟前
Three.js 材质(Material)详解 —— 区别、原理、场景与示例
前端
掘金安东尼1 小时前
抛弃自定义模态框:原生Dialog的实力
前端·javascript·github
hj5914_前端新手5 小时前
javascript基础- 函数中 this 指向、call、apply、bind
前端·javascript
薛定谔的算法5 小时前
低代码编辑器项目设计与实现:以JSON为核心的数据驱动架构
前端·react.js·前端框架
Hilaku5 小时前
都2025年了,我们还有必要为了兼容性,去写那么多polyfill吗?
前端·javascript·css
yangcode5 小时前
iOS 苹果内购 Storekit 2
前端
LuckySusu5 小时前
【js篇】JavaScript 原型修改 vs 重写:深入理解 constructor的指向问题
前端·javascript
LuckySusu5 小时前
【js篇】如何准确获取对象自身的属性?hasOwnProperty深度解析
前端·javascript
LuckySusu5 小时前
【js篇】深入理解 JavaScript 作用域与作用域链
前端·javascript
LuckySusu5 小时前
【js篇】call() 与 apply()深度对比
前端·javascript