UGUI 圆角矩形控件实现

来源:互联网 发布:婚纱照排版台词 知乎 编辑:程序博客网 时间:2024/06/16 11:50

介绍

项目中使用了很多圆角矩形的纯色的按钮,背景之类的图片,如果使用传统的九宫格的拉伸,那么不通的圆角半径必须使用不通的图片,而且拉伸后边缘容易出现狗牙(锯齿)。于是想到了使用shader来实现该功能,利用算法生成圆角矩形。

最终效果

这里写图片描述

shader的实现

Shader "UI/RoundMask"{    Properties    {        [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}        _Color ("Tint", Color) = (1,1,1,1)        _StencilComp ("Stencil Comparison", Float) = 8        _Stencil ("Stencil ID", Float) = 0        _StencilOp ("Stencil Operation", Float) = 0        _StencilWriteMask ("Stencil Write Mask", Float) = 255        _StencilReadMask ("Stencil Read Mask", Float) = 255        _ColorMask ("Color Mask", Float) = 15        [Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0        _RoundRadius("Round Radius", Range(0,0.5)) = 0.25        _Width("Width", Float) = 100        _Height("Height", Float) = 100    }    SubShader    {        Tags        {             "Queue"="Transparent"             "IgnoreProjector"="True"             "RenderType"="Transparent"             "PreviewType"="Plane"            "CanUseSpriteAtlas"="True"        }        Stencil        {            Ref [_Stencil]            Comp [_StencilComp]            Pass [_StencilOp]             ReadMask [_StencilReadMask]            WriteMask [_StencilWriteMask]        }        Cull Off        Lighting Off        ZWrite Off        ZTest [unity_GUIZTestMode]        Blend SrcAlpha OneMinusSrcAlpha        ColorMask [_ColorMask]        Pass        {            Name "Default"        CGPROGRAM            #pragma vertex vert            #pragma fragment frag            #pragma target 2.0            #include "UnityCG.cginc"            #include "UnityUI.cginc"            #pragma multi_compile __ UNITY_UI_ALPHACLIP            struct appdata_t            {                float4 vertex   : POSITION;                float4 color    : COLOR;                float2 texcoord : TEXCOORD0;                UNITY_VERTEX_INPUT_INSTANCE_ID            };            struct v2f            {                float4 vertex   : SV_POSITION;                fixed4 color    : COLOR;                float2 texcoord  : TEXCOORD0;                float4 worldPosition : TEXCOORD1;                UNITY_VERTEX_OUTPUT_STEREO            };            fixed4 _Color;            fixed4 _TextureSampleAdd;            float4 _ClipRect;            float _RoundRadius;            float _Width;            float _Height;            v2f vert(appdata_t IN)            {                v2f OUT;                UNITY_SETUP_INSTANCE_ID(IN);                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);                OUT.worldPosition = IN.vertex;                OUT.vertex = UnityObjectToClipPos(OUT.worldPosition);                OUT.texcoord = IN.texcoord;                OUT.color = IN.color * _Color;                return OUT;            }            sampler2D _MainTex;            fixed4 frag(v2f IN) : SV_Target            {                half4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;                color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect);                #ifdef UNITY_UI_ALPHACLIP                clip (color.a - 0.001);                #endif                float aspect = _Height/_Width;                float2 center = float2(abs(round(IN.texcoord.x) - _RoundRadius*aspect),abs(round(IN.texcoord.y) - _RoundRadius));                float a = color.a*step(distance(fixed2(IN.texcoord.x * _Width,IN.texcoord.y * _Height),fixed2(center.x * _Width,center.y * _Height)),_RoundRadius * _Height);                float oy = max(step(IN.texcoord.y,_RoundRadius),step((1-_RoundRadius),IN.texcoord.y));                float ox = max(step(IN.texcoord.x,_RoundRadius*aspect),step((1-_RoundRadius*aspect),IN.texcoord.x));                color.a = ox * (oy * a + (1-oy) * color.a) + (1-ox) * color.a;;                return color;            }        ENDCG        }    }}

可以看到和UI-Default.shader的差别只是增加了三个参数,并且在片源着色器中加入了一套算法用于修正alpha。

        _RoundRadius("Round Radius", Range(0,0.5)) = 0.25        _Width("Width", Float) = 100        _Height("Height", Float) = 100

三个参数分别对应

  • _RoundRadius 圆角半径
  • _Width 控件宽度
  • _Height 控件高度

这里的圆角半径是UV.y = 1 为单位的。变化范围0~0.5。

有了shader,那么还需要一个脚本来实现MaskableGraphic。当大小改变的时候修改Width和Height两个参数,同时可以直接控制圆角半径。

控制脚本实现

using UnityEngine;using UnityEngine.UI;namespace PTGame.UIExtensions{    [ExecuteInEditMode, RequireComponent(typeof(CanvasRenderer), typeof(RectTransform)), DisallowMultipleComponent]    [AddComponentMenu("PTUI/RoundCorner (Unity UI Canvas)")]    public class PTRoundRectGraphic : MaskableGraphic    {        //Inspector面板上直接拖入          public Shader shader = null;        [Range(0, 0.5f)] public float _cornerArea = 0;        protected override void Start()        {            base.Start();            material = GenerateMaterial(shader);            material.SetFloat("_Width", rectTransform.rect.width);            material.SetFloat("_Height", rectTransform.rect.height);        }        private void Update()        {            material.SetFloat("_RoundRadius", _cornerArea);        }        protected override void OnRectTransformDimensionsChange()        {            base.OnRectTransformDimensionsChange();            material.SetFloat("_Width", rectTransform.rect.width);            material.SetFloat("_Height", rectTransform.rect.height);        }        //根据shader创建用于屏幕特效的材质        protected Material GenerateMaterial(Shader shader)        {            if (shader == null)                return null;            if (shader.isSupported == false)                return null;            Material material = new Material(shader);            material.hideFlags = HideFlags.DontSave;            if (material)                return material;            return null;        }        protected override void OnDestroy()        {            base.OnDestroy();            if (material != null)                Object.DestroyImmediate(material);        }    }}

总结

由于实现了默认的MaskableGraphic,相对Image少了九宫格填充的功能。而且由于每个控件使用单独的材质传入宽高,导致不能动态合并,Drawcall较高,当然还是有改进方法的。可以在修改宽度时重新写入mesh中vertex的数据,将宽,高,半径作为一个textcrood写入,由于UGUI控件只有四个顶点,时间消耗可以忽略不计,并且四个顶点数据相同,那么每个片源就都可以拿到宽高。这样只需要一个材质就可以处理了。

  1. struct appdata_t 中加入float2 texcoord2 : TEXCOORD1;
  2. 修改顶点着色器,增加OUT.properties = IN.texcoord2;
  3. 片源着色器中增加如下代码
    float _RoundRadius = IN.properties.y;     float _Width = IN.properties.x;    float _Height = 1;
  1. C# 中重写代码 对Mesh的UV传入
Vector2 property = new Vector2(rectTransform.rect.width/rectTransform.rect.height, _cornerArea);Vector2[] propertys = new Vector2[workerMesh.vertexCount];for (int i = 0; i < workerMesh.vertexCount; i++){    propertys[i] = property;}workerMesh.uv2 = propertys;

这里写图片描述
经过修改Drawcall成功的降了下来,4个图形共用了一个Drawcall

修改派生关系为继承自Image并重写Inspector之后
这里写图片描述
还可以做遮罩

原创粉丝点击