如图,在2D游戏中常使用到spine动画,比如人物动态立绘

为了与UI场景结合,使用SkeletonGraphic模式

而在部分情形下,需要spine动画(与界面一起)淡入或淡出

此时spine表现效果不佳,会出现透视效果

如图所示

 

 

可以明显看出,各骨骼蒙皮出现了透视叠加的视觉效果

原本应被腿部遮挡的服饰也透视了出来

那么如何解决这个问题呢?

 

目前看来效果最好的方式是使用RenderTexture把100%alpha的spine渲染到一张纹理上

再对纹理设置alpha实现半透效果

 

此外我也尝试了通过shader实现类似效果

现记录如下

 

1.双通道使用深度写入

在冯乐乐的书中有一个shader可实现3D半透明物体的渲染Chapter8-AlphaBlendZWrite.shader

然而在我们这里并不适用

因为spine贴图中的各个顶点深度都是一致的

 

 

 

 

这里ZSpacing默认是0

若手动修改其值可以改变各顶点的Z值

进而可使用深度写入/深度测试

但spine的表现会受到较大影响,不可取

 

 

 

2.双通道使用模板测试

这个方式相对更接近我们的目标(虽然比起RenderTexture效果逊色)

思路如下

spine渲染各个骨骼按从底部到顶部的顺序,最高层级的骨骼最后渲染

因此若只渲染最高层级的像素,即不会存在透视

 

首先第一个pass输出一个空颜色rgba0,0,0,0

注:若输出的颜色不为空,该颜色很可能会显示出来,双通道先后输出的颜色受混合模式决策后进入帧缓冲区

并对模板值操作,使通过alpha测试的部分模板值+1

Stencil
{
    Ref 0 // 参考值0(并未使用)
    Comp Always // 比较函数,总是通过
    Pass IncrSat // 比较通过后的模板操作,模板值加一
    ReadMask [_StencilReadMask]
    WriteMask [_StencilWriteMask]
}

 

然后第二个pass对spine图像正常采样

并使用一套不同的模板逻辑

Stencil
{
    Ref 1 // 参考值1
    Comp Equal // 比较函数,仅当参考值等于模板值时通过
    Pass Keep // 比较通过后,不改变模板值
    Fail DecrSat // 比较失败时,模板值减一
    ReadMask [_StencilReadMask]
    WriteMask [_StencilWriteMask]
}

 

注:双通道时可以指定不同的Stencil写在Pass内

 

这里详细说明一下核心逻辑

比如当一个像素点包含了衣服和手臂两个重复的骨骼时

且在正常叠加顺序时是衣服被手臂遮挡

当渲染时,首先屏幕空间的模板值被清零(对应每个像素点模板值均清零)

通道1

按顺序先后渲染衣服/手臂(注:衣服手臂均在同一张spine图集内,所以是一批的,顺序是spine导出时就决定确定的)

该像素点进行两次模板检测,均通过(Comp Always),模板值变为2(Pass IncrSat)

通道2

仍是按顺序先后渲染衣服/手臂

而此次模板参数决定了,前n-1次模板测试都会失败,且失败后模板值-1

直到最后一次,也就渲染最上层手臂时,模板值已经减到了1,与Ref参考值相等,通过模板测试

所以只会渲染手臂对应的部分

 

效果如下

该效果与我们期望的差距很大

到底发生了什么呢?

通过模板的确只渲染了最上层的骨骼蒙皮

但问题是最上层的骨骼蒙皮往往是方形区域伴随较大块透明区域

而我们在模板测试中,并未区别区里透明区域与非透明区域

理想情况应该是,对于一个像素点,仅当骨骼蒙皮与其有重叠且重叠部分非透明时才修改其模板值

于是我们对通道1做以下修改

 

fixed4 frag1 (VertexOutput IN) : SV_Target

{

// 对spine贴图采样得到rgba颜色 部分代码省略

// 注意clip丢弃掉的部分(接近透明的像素)不会写入(修改)模板值

#ifdef UNITY_UI_ALPHACLIP

clip (color.a - 0.01);

#endif

return half4(0.0, 0.0, 0.0, 0.0);

}

 

修改后表现如下

可以看出在人物右腿后的披风完全没有透视了

但此时还存在两个问题

 

1.骨骼蒙皮之间的边缘处出现透视,漏出了底色

边缘判定时若只渲染最上层骨骼,很可能其边缘处alpha很小

本应透入下方骨骼,但却因为现有的机制,直接透出了底色

 

2.模板计算问题

当叠加层数较多时,还会存在模板计算的问题

假设某点处叠有5层骨骼

自下而上

第五层 非透明

第四层 全透明

第三层 非透明

第二层 全透明

第一层 非透明

经过pass1后模板值变成3

显然我们希望的是只渲染第一层

但在pass2处理时,第五/第四两层模板检测失败后,模板值已经变成了1

而由于Pass Keep的设定,因此后面的第三/第二/第一层均可通过模板检测

若仅通过第二/第一层尚无影响,因为第二层是全透明的

但第三层通过的话必然会继续引发透视的问题

 

另外,肯定还会想到混合模式

如果混合模式使用Blend One Zero

一定程度上可以丢弃掉底层像素只保留最上层

与我们模板测试所实现的方案非常相似

区别在于,混合模式下完全抛弃了背景

spine半透后只能透出黑色而模板的方式可以透出背景图

其次就是部分区域不能正常展示

比如眼睛这里,因为眼球基础上还有一层类似妆的接近透明的层,于是眼球被完全丢弃了

 

 

参考文献:枚举值对照表

Comp [_StencilComp] 为什么是浮点数? - 知乎

https://docs.unity.cn/cn/current/Manual/SL-Stencil.html