找回密码
 立即注册
查看: 281|回复: 0

Unity实用小技巧(矩形图片的圆切角)

[复制链接]
发表于 2022-8-19 16:44 | 显示全部楼层 |阅读模式
图片的圆形切角是所有游戏UI都经常用到的功能。一般都是在图片上直接画出遮罩。或者在shader上多一层Pass来处理圆型的切角。但遇到高精度需求就无法满足需要。想要制作一个漂亮的圆形切角还是是需要一定的数学计算和逻辑思维的。
解题逻辑:(正方形)



为了画出四圆滑的边角因此需依靠UV坐标进行四等划分。求得四个圆的坐标中心然后根据坐标绘制一个正圆我们。第一步得先确定这四个圆的坐标点的位置。因为是2维所以比较好求得。


以右上角的圆为例。我们先需要明确能获得的值和需要求得的值。半径R和UV坐标可以明确获得。再由UV-半径可以获得圆心所在的的坐标。得到此值。后续就可以套取公式。
定义圆形绘制用函数:
float DrawCircle(float x, float y, float r, float sfx1,float sfy1,v2f i)
{
    float x_r = x - i.uv.x;
    float y_r = y - i.uv.y;
    float dis = sqrt((x_r)*(x_r)/sfx1 + (y_r)*(y_r)/sfy1);
    if(dis <= r)
    {
        if(r - dis < 0.001)
        {
            return (r - dis)/0.001;
        }
        else
        {
            return 1;
        }
    }
    else
    {
        return 0;
    }   
}

绘制圆:(简略代码)
half2 TRC = half2(1, 1);
half2 center = half2(TRC.x - _Radius*sfx0,TRC.y - _Radius*sfy0);

float c1 = DrawCircle(center.x,center.y,_Radius,sfx1,sfy1,i);
if(c1 == 1)
{
    col.rgb *= float3(1,0,0);
}首先需要定义一下UV的最大坐标例如1,1。而后利用其减去UV而获得该顶点的位置。而后套取公式sqrt(pow(x,2)+pow(y,2))。便可以绘制出一个正圆。sfx1,sfy1,sfx0,sfy0是为了做偏移补得补值。后面会做讨论。
分别写出四次坐标分别定义坐标为: BRC=half2(1,0); BLC = half2(0,0); BTC = half2(0,1); 替换掉center中的相应变量可以分别绘制出四个正圆。


绘制裁剪:


我们需要裁剪的是画黑线的区域因此依旧是利用我们之前获得的绘制圆的公式。但要新增一个判断。
                    if(dist > _Radius)
                    {
                        col.a = 0;
                        //discard;
                    }凡是dist大约半径的距离都判断为0;这样就可以剔除掉这一部分的像素。还是挺简单的;对比一下绘制正圆的判断:
                if(dis <= r)
                {
                    if(r - dis < 0.001)
                    {
                        return (r - dis)/0.001;
                    }
                    else
                    {
                        return 1;
                    }
                }
                else
                {
                    return 0;
                }其实就很明确。都是根据半径的距离来判断哪些显示哪些剔除。这样一个正方形的圆角就切好了。但是现实制作中不仅仅会用到正方形。还会有矩形因此就需要在进行新的判断。
裁剪矩形(长方形):



将UI的宽度和高度进行放大。其实我们很容易发现。只要保证四个圆的圆形依旧是在正确的位置其实就可以保证我们获得的裁剪不会由于长宽不对称而产生形变。因此我们需要的就是新增判断获得长宽的对比值并输入给与绘制计算。


当我们的长宽不成正比时会发现绘制的圆形成为了椭圆。但裁剪的形状依旧可以保持圆润。



这两张图的长宽是不对等的

但最后获得圆形裁剪角度却可以对起来,因此图片的长宽比对裁剪的影响微乎其微。


裁剪的公式:
                // 修正圆心X坐标位置
                half sfx0 = 1;
                // 修正UV点与圆心距离
                half sfx1 = 1;
                // 修正圆心Y轴坐标位置
                half sfy0 = 1;
                // 修正UV点与圆心距离
                half sfy1 = 1;


                // 当矩形的width > height 时计算圆心、UV修正系数
                if (_scale.x > _scale.y)
                {
                    _scale.x = _scale.x/_scale.y;
                    _scale.y = 1;
                        // 计算UV坐标空间下,圆心X坐标对应缩放系数
                    sfx0  = _scale.y/_scale.x;
                    sfx1  = (_scale.y/ pow(_scale.x,2));
                }
                else
                {
                    _scale.y = _scale.y/_scale.x;
                    _scale.x = 1;
                        // 计算UV坐标空间下,圆心Y坐标对应缩放系数
                    sfy0  = _scale.x/_scale.y;
                    sfy1  = (_scale.x/ pow(_scale.y,2));
                }将获得的比例值套入之前的正圆绘制中即可。由此我们便得到了一个可以根据图片的长宽来判断切圆角的shader。
完整代码:
Shader "Unlit/RoundRectDyn"
{
    Properties
    {
         _Color("Color", Color) = (1,1,1,1)
         _MainTex ("Texture", 2D) = "white" {}
         _Radius ("_Radius", Range(0, 0.5)) = 0.25
         _scale ("scale", vector) = (1,1,0,1)

            [MaterialToggle] _TR ("_TopRightCorner", Float) = 1
            [MaterialToggle] _BR ("_BottomRightCorner", Float) = 1
            [MaterialToggle] _BL ("_BottomLeftCorner", Float) = 1
            [MaterialToggle] _TL ("_TopLeftCorner", FLoat) = 1
        [Header(DrawCircle)]
        [MaterialToggle] _DrawCircle("DrawCircle", Float) = 1

    }
    SubShader
    {
        Tags {
               "RenderType"="Transparent"
               "Queue"="Transparent"
               "IgnoreProjector"="True"
               "PreviewType"="Plane"
               "CanUseSpriteAtlas"="True"
              }
        Stencil
        {
            Ref [_Stencil]
            Comp [_StencilComp]
            Pass [_StencilOp]
            ReadMask [_StencilReadMask]
            WriteMask [_StencilWriteMask]
        }

        Pass
        {
            Blend SrcAlpha OneMinusSrcAlpha
            ZWrite Off
            ZTest LEqual
            Cull Off
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            #pragma multi_compile __ UNITY_UI_ALPHACLIP
            #include "UnityCG.cginc"
            #include "UnityUI.cginc"




            struct appdata
            {
                float4 vertex : POSITION;
                float4 color : COLOR;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;

                float4 vertex : SV_POSITION;
                float3 worldPos : TEXCOORD1;
                fixed4 color : COLOR;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _TextureSampleAdd;
            float _Radius;
            float _TR, _BR, _BL, _TL, _DrawCircle;
            float4 _scale;
            float4 _Color;

            //====================正圆绘制========================
            float DrawCircle(float x, float y, float r, float sfx1,float sfy1,v2f i)
            {
                float x_r = x - i.uv.x;
                float y_r = y - i.uv.y;
                float dis = sqrt((x_r)*(x_r)/sfx1 + (y_r)*(y_r)/sfy1);
                if(dis <= r)
                {
                    if(r - dis < 0.001)
                    {
                        return (r - dis)/0.001;
                    }
                    else
                    {
                        return 1;
                    }
                }
                else
                {
                    return 0;
                }
               

            }


            v2f vert (appdata v)
            {
                v2f o;
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);

                o.color = v.color *_Color;

                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {


                // UV采样数据
                    fixed4 col = (tex2D(_MainTex, i.uv)+ _TextureSampleAdd) * i.color;

                    // 用于保存像素与圆心距离
                    half dist = 0;
                    // 默认右上角圆心坐标
                    half2 TRC = half2(1, 1);
                    // 默认右下角圆心坐标
                    half2 BRC = half2(1, 0);
                    // 默认左下角圆心坐标
                    half2 BLC = half2(0, 0);
                    // 默认左上角圆心坐标
                    half2 TLC = half2(0, 1);
                // 修正圆心X坐标位置
                half sfx0 = 1;
                // 修正UV点与圆心距离
                half sfx1 = 1;
                // 修正圆心Y轴坐标位置
                half sfy0 = 1;
                // 修正UV点与圆心距离
                half sfy1 = 1;


                // 当矩形的width > height 时计算圆心、UV修正系数
                if (_scale.x > _scale.y)
                {
                    _scale.x = _scale.x/_scale.y;
                    _scale.y = 1;
                        // 计算UV坐标空间下,圆心X坐标对应缩放系数
                    sfx0  = _scale.y/_scale.x;
                    sfx1  = (_scale.y/ pow(_scale.x,2));
                }
                else
                {
                    _scale.y = _scale.y/_scale.x;
                    _scale.x = 1;
                        // 计算UV坐标空间下,圆心Y坐标对应缩放系数
                    sfy0  = _scale.x/_scale.y;
                    sfy1  = (_scale.x/ pow(_scale.y,2));
                }

                half2 center = half2(TRC.x - _Radius*sfx0,TRC.y - _Radius*sfy0);
                //==============右上角================
                if(_TR == 1)
                {

                    if(center.x < i.uv.x && center.y < i.uv.y)
                    {
                        dist = sqrt(pow(center.x -i.uv.x, 2)/sfx1 + pow(center.y - i.uv.y , 2)/sfy1);
                        //col.rgb *= float3(1,0,0);                        

                    }

                    if(_DrawCircle == 1)
                    {
                       float c1 = DrawCircle(center.x,center.y,_Radius,sfx1,sfy1,i);
                       if(c1 == 1)
                       {
                           col.rgb *= float3(1,0,0);
                       }

                    }

                    if(dist > _Radius)
                    {
                        col.a = 0;
                        //discard;
                    }
                }
                //==============右下角================
                half2 center02 = half2(BRC.x-_Radius*sfx0, BRC.y+_Radius*sfy0);

                if(_BR == 1)
                {

                    if(center02.x < i.uv.x && center02.y > i.uv.y)
                    {
                        dist = sqrt(pow(center02.x -i.uv.x, 2)/sfx1 + pow(center02.y - i.uv.y , 2)/sfy1);


                    }
                    if(_DrawCircle == 1)
                    {
                        float c2 = DrawCircle(center02.x,center02.y,_Radius,sfx1,sfy1,i);
                        if(c2 == 1)
                        {
                            col.rgb *= float3(0,1,0);
                        }

                    }


                    if(dist > _Radius)
                    {
                        col.a = 0;
                    }
                }
                //===============左下角================
                half2 center03 = half2(saturate(BLC.x + _Radius*sfx0), saturate(BLC.y + _Radius*sfy0));

                if(_BL == 1)
                {

                    if(center03.x > i.uv.x && center03.y > i.uv.y)
                    {
                        dist = sqrt(pow( center03.x-i.uv.x, 2)/sfx1 + pow(center03.y-i.uv.y ,2)/sfy1);

                    }
                    if(_DrawCircle == 1)
                    {
                        float c3 = DrawCircle(center03.x,center03.y,_Radius,sfx1,sfy1,i);
                        if(c3 == 1)
                        {
                            col.rgb *= float3(0,0,1);
                        }
                        
                    }

                    if(dist > _Radius)
                    {
                        col.a = 0;
                    }
                }
                //==============左上角=================
                half2 center04 = half2(TLC.x+_Radius*sfx0, TLC.y-_Radius*sfy0);
                if(_TL == 1)
                {

                    if(center04.x > i.uv.x && center04.y <i.uv.y)
                    {
                        dist = sqrt(pow(center04.x -i.uv.x, 2)/sfx1 + pow(center04.y - i.uv.y , 2)/sfy1);
                    }
                    if(_DrawCircle == 1)
                    {
                        float c4 = DrawCircle(center04.x,center04.y,_Radius,sfx1,sfy1,i);
                        if(c4 == 1)
                        {
                            col.rgb *= float3(1,1,0);
                        }
                        
                    }


                    if(dist > _Radius)
                    {
                        col.a = 0;
                    }
                }               
                //================核心判断============================
               
                    return col;
            }
            ENDCG
        }
    }
}
RoundedRect.unitypackage
2M
· 百度网盘



记录时间:
                                                                                                                                2022年8月16日

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-24 13:52 , Processed in 0.065910 second(s), 23 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表