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