Unity图片/文字/特效的渐变遮罩

在做功能时美术有个效果是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。

 

------------------------------------------2020.12.28 更新 示例代码

链接:https://pan.baidu.com/s/1Jrc5wGX0HNznJUfosVpHYQ 
提取码:khc2 

 
posted @ 2020-01-19 10:36  bylle  阅读(5924)  评论(2编辑  收藏  举报