在做功能時美術有個效果是UGUI的漸變透明,實現上可以用遮罩,但是Unity提供的遮罩RectMask2D是硬裁剪,即超出遮罩范圍就alpha直接設為0,沒有過渡,所以我就要給這個圖片設一個自定義shader讓它能支持alpha漸變。
其實就是修改下Unity的UI Default shader,但注意Unity在下載時每個版本都提供了內置着色器,所以你還要選擇對應的Unity版本的內置着色器代碼。
將UI Default shader的UnityGet2DClipping(IN.worldPosition.xy, _ClipRect);替換為自定義函數,UI Default代碼不放了,要從對應版本的Unity(官網)下載,我一開始從網上找了個UI Default shader可能因為接口對不上,連遮罩都不成功。
從Unity的安裝目錄下找到CGIncludes/UnityUI.cginc
inline float UnityGet2DClipping (in float2 position, in float4 clipRect) { float2 inside = step(clipRect.xy, position.xy) * step(position.xy, clipRect.zw); return inside.x * inside.y; }
clipReect傳入的是float4,clipRect.xy是左上遮罩的裁剪位置,clipRect.zw是右下遮罩的裁剪位置.
step()用法:https://developer.download.nvidia.cn/cg/step.html
step - implement a step function returning either zero or one
step(clipRect.xy, position.xy) * step(position.xy, clipRect.zw)
即position.xy >= clipRect.xy返回1,同樣position.xy < clipRect.zw返回1,雖然參數是float3,但傳fix/fix2/fix3/fix4, half~half4, float~float4都可以cg內部幫我們做了轉換。
step的操作減少了gpu做比較運算的消耗,gpu不適合做<這類運算,那是cpu擅長的。
通過這一步,可以判斷這個世界坐標在clipRect內否,不滿足在內部,則直接設置alpha=0,剛好step() * step()計算后在clipRect外的返回值為0.
SoftUnityGet2DClipping軟裁剪
使用的是雨松momo的裁剪,https://www.xuanyusong.com/archives/4650,momo還用UGUI的方式裁剪了模型,我做的功能倒不需要裁剪模型。
float _ClipSoftX; float _ClipSoftY; inline float SoftUnityGet2DClipping(in float2 position, in float4 clipRect) { float2 xy = (position.xy - clipRect.xy) / float2(_ClipSoftX, _ClipSoftY)*step(clipRect.xy, position.xy); float2 zw = (clipRect.zw - position.xy) / float2(_ClipSoftX, _ClipSoftY)*step(position.xy, clipRect.zw); float2 factor = clamp(0, zw, xy); return saturate(min(factor.x, factor.y)); }
做裁剪區域判定時,添加了類似下面的代碼,將裁剪內的坐標alpha添加一個除法,除以我需要軟化的邊緣像素值,比如
(position.xy - clipRect.xy) / float2(_ClipSoftX, _ClipSoftY)
_ClipSoftX=15,position距離clipRect 15坐標單位的點,其alpha為距離/15。
clamp - returns smallest integer not less than a scalar or each vector component.
factor是在距離左上和右下邊距里,取個離邊距最近的alpha值。
saturate - returns smallest integer not less than a scalar or each vector component.
Returns x saturated to the range [0,1] as follows:
https://developer.download.nvidia.cn/cg/saturate.html
最后將輸出值規范化為0~1區間內的值。
效果:
TMP直接提供了漸變參數,在Debug Settings-Softness X,放在RectMask2D里會軟化左右X方向的裁剪。
------------------------------------------2020.5.12 更新 特效遮罩
最近遇到了特效在UI上列表滑動是需要遮罩顯示,想起雨松大神的[UGUI軟裁剪](https://www.xuanyusong.com/archives/4650),文章里提到用UGUI的方式處理3D模型,按道理特效也可以使用這種用方式,其中遇到一個‘坑’:完全照搬代碼不看Unity shader版本你看着代碼很完美單就是顯示不對,一定要用當前Unity版本的shader!!!
新建ParticleMask.cs繼承UIBehavIour, IClippable, ICanvasElement,UGUI在處理響應RectMask2D區域時會調用IClippable相應接口,主要還是SetClipRect()
ParticleMask沒有掛RectMask2D,GiftScrollView上有RectMask2D,RectMask2D掛在ScrollView上比較符合我們項目的開發方式,ParticleMask的位置和大小都是0,由於ScrollView的運行方式,ParticleMask不好設置Scale(100, 100, 100)。
特效遮罩需要的ClipRect參數是通過運行時的方式設給特效對應Material,注意要設置aharedMaterial,否則Unity會創建一個Material Instance(多一個Material實例對象)。
public void SetClipRect(Rect value, bool validRect) { //在這里就可以將正確的Rect2DMask傳入shader了 if (m_RendererList != null && m_RendererList.Count > 0) { Vector4 clipRect = new Vector4(value.xMin, value.yMin, value.xMax, value.yMax); for (int i = 0; i < m_RendererList.Count; i++) { //縮放是屏幕高度的一半 float scale = m_Canvas.sizeDelta.y / 2f; //將RectMask2D的區域傳入 m_RendererList[i].sharedMaterial.SetVector("_ClipRect", clipRect); //將Scale傳入 m_RendererList[i].sharedMaterial.SetFloat("_Scale", scale); } } }
在shader內的設置和UI-Default.shader的設置差不多,inline UnityGet2DClipping取自己本地Unity安裝目錄內的CGINCLUDE文件夾內的UnityUI.cginc函數,注意worldPosition的傳值,Particle默認是沒有worldPosition的。
struct v2f { float4 vertex : SV_POSITION; fixed4 color : COLOR; float2 texcoord : TEXCOORD0; float4 worldPosition : TEXCOORD1; };
float _Scale;
v2f vert(appdata_t v)
{
v2f o;
...
// 這里的_Scale / 10主要是因為我的ParticleMask並沒有設置縮放,所以具體場景具體分析~
o.worldPosition = mul(unity_ObjectToWorld, v.vertex * _Scale / 10);
return o;
}
不知道是哪個Unity版本用的clamp(0, zw, xy)裁剪,看來以后要養成習慣一開始就從使用的Unity版本中拷貝inline cg函數或者官方unity shader實現,少繞少糾結,一開始clamp(0, zw, xy)看着代碼很正確但結果就是錯的,我用的2017.4.11在UnityGet2DClipping最后判定在裁剪區域內用的inside.x * inside.y。