如图,在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