基于矩阵的UGUI引导蒙版方案

UGUI实现引导蒙版有多种方案,可以基于shader,或纯粹靠程序实现,这里分享一种最近在项目中使用的基于shader的蒙版方案。

许多基于shader的引导蒙版方案都是将需要遮罩的区域以vector4的形式传入shader,这种方式实现简单且易于理解,而且效果不错,但使用这种方式容易造成一种无法避免的问题:蒙版区域无法旋转,当然除非额外传入遮罩区域的旋转信息。

今天要介绍的方案是直接计算出蒙版区域的矩阵(一个规范化空间,可以将坐标规范化到0-1内),将矩阵传入shader,在蒙版shader中处理顶点时将顶点转入矩阵,以矩阵中的坐标是否在0-1范围内判断(有点类似投影空间裁剪)。以这种方式实现的蒙版优点是,由于矩阵本身记录了坐标、旋转、缩放等信息,不需要在再shader中额外计算,只需要做一次矩阵左乘操作即可拥有同时具有旋转和遮罩功能的引导蒙版。另外c#脚本可以缓存一个当前矩阵,用来做点击穿透判断。另一个优点是,由于矩阵信息本身来自UI的RectTransform,因此可以避开对UI锚点等屏幕自适应规则的计算,总的来说如果熟悉矩阵运算的话,这种方式可以避开很多头疼的计算。另外由于最终是将蒙版中的像素坐标转到实际遮罩区域,并且遮罩区域规范化到0-1内,可以做出边缘过渡柔和的效果。
效果如下:
旋转支持:

穿透判定:

边缘柔和:

图片蒙版:

自动适应UI的锚点等:

首先是Shader代码:
Pass
{
    CGPROGRAM
    #pragma vertex vert
    #pragma fragment frag
 
    #include "UnityCG.cginc"
    #include "UnityUI.cginc"
 
    #pragma multi_compile __ UNITY_UI_ALPHACLIP
             
    struct appdata_t
    {
        float4 vertex   : POSITION;
        float4 color    : COLOR;
    };
 
    struct v2f
    {
        float4 vertex   : SV_POSITION;
        fixed4 color    : COLOR;
        float4 worldPosition : TEXCOORD0;
        float2 clipPosition : TEXCOORD1;
    };
             
    fixed4 _Color;
    fixed4 _TextureSampleAdd;
    float4 _ClipRect;
 
    float4x4 internalWorldToMaskMatrix;
    half2 internalClipAtten;
 
    sampler2D _MaskTex;
    float4 _Offset;
 
    v2f vert(appdata_t IN)
    {
        v2f OUT;
        OUT.worldPosition = IN.vertex;
        OUT.vertex = UnityObjectToClipPos(OUT.worldPosition);
                 
        #ifdef UNITY_HALF_TEXEL_OFFSET
        OUT.vertex.xy += (_ScreenParams.zw-1.0)*float2(-1,1);
        #endif
                 
        OUT.color = IN.color * _Color;
 
        float4 clipPos = mul(internalWorldToMaskMatrix, IN.vertex);//顶点转入蒙版区域空间(该空间会将坐标规范化,如果xy坐标在蒙版空间内则必然为0-1内)
        OUT.clipPosition = clipPos.xy / clipPos.w;
 
        return OUT;
    }
 
    fixed4 frag(v2f IN) : SV_Target
    {
 
        half4 color = IN.color;
 
        half2 atten = 1-saturate((abs(IN.clipPosition.xy - half2(0.5, 0.5)) - internalClipAtten.x) / (0.5 - internalClipAtten.x));//根据蒙版区域坐标计算边缘柔和
 
        color.a *= 1-saturate(atten.x*atten.y* tex2D(_MaskTex, IN.clipPosition.xy*_Offset.xy+_Offset.zw).a)*internalClipAtten.y;
                 
        color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect);
                 
        #ifdef UNITY_UI_ALPHACLIP
        clip (color.a - 0.001);
        #endif
 
        return color;
    }
ENDCG

然后是C#端,只截取关键部分代码,
以下是矩阵计算:
private void ResetMaterial(RectTransform targetTransform)
{
        if (targetTransform == null)
            return;
        m_MaskAreaMatrix = default(Matrix4x4);
        m_MaskAreaMatrix.m00 = 1 / targetTransform.rect.width;
        m_MaskAreaMatrix.m03 = -targetTransform.rect.x / targetTransform.rect.width;
        m_MaskAreaMatrix.m11 = 1 / targetTransform.rect.height;
        m_MaskAreaMatrix.m13 = -targetTransform.rect.y / targetTransform.rect.height;
        m_MaskAreaMatrix.m33 = 1;
         
        Matrix4x4 ltw = default(Matrix4x4);
        ltw.m00 = targetTransform.right.x;
        ltw.m01 = targetTransform.up.x;
        ltw.m03 = targetTransform.position.x;
 
        ltw.m10 = targetTransform.right.y;
        ltw.m11 = targetTransform.up.y;
        ltw.m13 = targetTransform.position.y;
         
        ltw.m22 = 1;
        ltw.m23 = targetTransform.position.z;
         
        ltw.m33 = 1;
 
        m_MaskAreaMatrix = m_MaskAreaMatrix * ltw.inverse * canvas.transform.localToWorldMatrix;
        if (m_OriginMaskAreaMatrix != m_MaskAreaMatrix)
        {
            material.SetMatrix("internalWorldToMaskMatrix", m_MaskAreaMatrix);
            m_OriginMaskAreaMatrix = m_MaskAreaMatrix;
        }
}

其中ltw矩阵表示被遮罩对象的空间矩阵(其实严格意义上不算,因为将这个空间的x和y轴的z值都设置为0,然后z轴强制设置为和世界空间z轴平行,之所以不直接使用被遮罩对象的worldToLocal,是因为避免被遮罩物体如果发生x和y轴旋转时蒙版会变形,但是如果案例中确定被遮罩物体肯定不会发生x和y轴旋转,则可以直接去掉ltw矩阵,把公式中的ltw.inverse替换成targetTransform.worldToLocal,效果是一样的)。m_MaskAreaMatrix根据被遮罩对象的rect信息计算出一个矩阵,用来把坐标规范化到0-1内。

另外可以注意到m_MaskAreaMatrix不是临时变量,这里将它缓存下来,用来在重写Image类的IsRaycastLocationValid方法时判断是否点击到蒙版区域:
public override bool IsRaycastLocationValid(Vector2 screenPoint, Camera eventCamera)
{
    if (base.IsRaycastLocationValid(screenPoint, eventCamera))
    {
        if (!useRaycastMask)
        {
            return true;
        }
        else
        {
            if (m_IsClear)
                return true;
            Vector3 worldPos = eventCamera == null ? (Vector3)screenPoint : eventCamera.ScreenToWorldPoint(screenPoint);
            worldPos = canvas.transform.worldToLocalMatrix.MultiplyPoint(worldPos);
            worldPos = m_MaskAreaMatrix.MultiplyPoint(worldPos);
            if (worldPos.x >= 0 && worldPos.x <= 1 && worldPos.y >= 0 && worldPos.y <= 1)
            {
                return false;
            }
            return true;
        }
    }
    return false;
}

Demo下载地址:http://www.lsngo.net/2017/10/25/unity_uimask/
更多文章:http://www.lsngo.net

--------------------- 
作者:MrASL 
来源:CSDN 
原文:https://blog.csdn.net/mobilebbki399/article/details/78359468?utm_source=copy 
版权声明:本文为博主原创文章,转载请附上博文链接!

猜你喜欢

转载自blog.csdn.net/qq_14914623/article/details/83115550
今日推荐