SimpleRoundedImage-不使用mask实现圆角矩形图片

1.一张图片是如何显示在屏幕上的

一张图片渲染到unity界面中的大致流程。

2.我们要做什么

我们要做的就是在CPU中将图片的矩形顶点数据修改成圆角矩形的顶点信息,之后Unity会将修改后的顶点数据发到GPU中,并设置对应的shader,GPU就会根据我们发送的顶点数据将图片渲染成我们所要的圆角矩形图片。

3.怎么做

由于Unity已经帮我们做了将数据发送到GPU的工作,我们只需要在代码中去修改要传送顶点数据就可以了。

Unity的Image组件提供了OnPopulateMesh接口。这个接口就是用来更新渲染时用的renderer mesh的顶点信息的的。我们直接重写这个函数,来修改顶点数据。

<1>我们先来看一下一张Simple类型的图片的顶点信息是如何组织的。

/// <summary>
/// Update the UI renderer mesh.
/// </summary>
protected override void OnPopulateMesh(VertexHelper toFill)
{
    if (activeSprite == null)
    {
        base.OnPopulateMesh(toFill);
        return;
    }

    switch (type)
    {
        case Type.Simple:
            GenerateSimpleSprite(toFill, m_PreserveAspect);
            break;
        case Type.Sliced:
            GenerateSlicedSprite(toFill);
            break;
        case Type.Tiled:
            GenerateTiledSprite(toFill);
            break;
        case Type.Filled:
            GenerateFilledSprite(toFill, m_PreserveAspect);
            break;
    }
}
/// <summary>
/// Generate vertices for a simple Image.
/// </summary>
void GenerateSimpleSprite(VertexHelper vh, bool lPreserveAspect)
{
    Vector4 v = GetDrawingDimensions(lPreserveAspect);
    var uv = (activeSprite != null) ? Sprites.DataUtility.GetOuterUV(activeSprite) : Vector4.zero;

    var color32 = color;
    vh.Clear();
    vh.AddVert(new Vector3(v.x, v.y), color32, new Vector2(uv.x, uv.y));
    vh.AddVert(new Vector3(v.x, v.w), color32, new Vector2(uv.x, uv.w));
    vh.AddVert(new Vector3(v.z, v.w), color32, new Vector2(uv.z, uv.w));
    vh.AddVert(new Vector3(v.z, v.y), color32, new Vector2(uv.z, uv.y));

    vh.AddTriangle(0, 1, 2);
    vh.AddTriangle(2, 3, 0);
}

v是顶点坐标信息,uv是贴图坐标信息,vh是用来存储这些信息的变变量。

每个点的位置信息(相对中轴线的位置),默认颜色,uv坐标组成了一个顶点信息放到了vh中,然后再告诉vh如何去画三角行,就可以了。

之后unity会将vh中的信息传到GPU,然后将图片展示在屏幕上。

<2>我们如何将一张图片的顶点信息和三角形信息改成我们要的圆角矩形

首先,我们将一张图分成6个三角形和四个90°的扇形。每个扇形用若干个三角形来模拟。这样我们就将一个圆角矩形,划分成了GPU能认识的三角形了。

我们以扇形的半径,构成扇形的三角形的数量作为变量,就可以算出每个我们需要的顶点的坐标了。具体的实现见代码。

实现代码:

using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.Sprites;
using System.Collections.Generic;

namespace GFramework
{
    public class SimpleRoundedImage : Image
    {

        //每个角最大的三角形数,一般5-8个就有不错的圆角效果,设置Max防止不必要的性能浪费
        const int MaxTriangleNum = 20;
        const int MinTriangleNum = 1;

        public float Radius;
        //使用几个三角形去填充每个角的四分之一圆
        [Range(MinTriangleNum, MaxTriangleNum)]
        public int TriangleNum;

        protected override void OnPopulateMesh(VertexHelper vh)
        {
            Vector4 v = GetDrawingDimensions(false);
            Vector4 uv = overrideSprite != null ? DataUtility.GetOuterUV(overrideSprite) : Vector4.zero;

            var color32 = color;
            vh.Clear();
            //对radius的值做限制,必须在0-较小的边的1/2的范围内
            float radius = Radius;
            if (radius > (v.z - v.x) / 2) radius = (v.z - v.x) / 2;
            if (radius > (v.w - v.y) / 2) radius = (v.w - v.y) / 2;
            if (radius < 0) radius = 0;
            //计算出uv中对应的半径值坐标轴的半径
            float uvRadiusX = radius / (v.z - v.x);
            float uvRadiusY = radius / (v.w - v.y);

            //0,1
            vh.AddVert(new Vector3(v.x, v.w - radius), color32, new Vector2(uv.x, uv.w - uvRadiusY));
            vh.AddVert(new Vector3(v.x, v.y + radius), color32, new Vector2(uv.x, uv.y + uvRadiusY));

            //2,3,4,5
            vh.AddVert(new Vector3(v.x + radius, v.w), color32, new Vector2(uv.x + uvRadiusX, uv.w));
            vh.AddVert(new Vector3(v.x + radius, v.w - radius), color32, new Vector2(uv.x + uvRadiusX, uv.w - uvRadiusY));
            vh.AddVert(new Vector3(v.x + radius, v.y + radius), color32, new Vector2(uv.x + uvRadiusX, uv.y + uvRadiusY));
            vh.AddVert(new Vector3(v.x + radius, v.y), color32, new Vector2(uv.x + uvRadiusX, uv.y));

            //6,7,8,9
            vh.AddVert(new Vector3(v.z - radius, v.w), color32, new Vector2(uv.z - uvRadiusX, uv.w));
            vh.AddVert(new Vector3(v.z - radius, v.w - radius), color32, new Vector2(uv.z - uvRadiusX, uv.w - uvRadiusY));
            vh.AddVert(new Vector3(v.z - radius, v.y + radius), color32, new Vector2(uv.z - uvRadiusX, uv.y + uvRadiusY));
            vh.AddVert(new Vector3(v.z - radius, v.y), color32, new Vector2(uv.z - uvRadiusX, uv.y));

            //10,11
            vh.AddVert(new Vector3(v.z, v.w - radius), color32, new Vector2(uv.z, uv.w - uvRadiusY));
            vh.AddVert(new Vector3(v.z, v.y + radius), color32, new Vector2(uv.z, uv.y + uvRadiusY));

            //左边的矩形
            vh.AddTriangle(1, 0, 3);
            vh.AddTriangle(1, 3, 4);
            //中间的矩形
            vh.AddTriangle(5, 2, 6);
            vh.AddTriangle(5, 6, 9);
            //右边的矩形
            vh.AddTriangle(8, 7, 10);
            vh.AddTriangle(8, 10, 11);

            //开始构造四个角
            List<Vector2> vCenterList = new List<Vector2>();
            List<Vector2> uvCenterList = new List<Vector2>();
            List<int> vCenterVertList = new List<int>();

            //右上角的圆心
            vCenterList.Add(new Vector2(v.z - radius, v.w - radius));
            uvCenterList.Add(new Vector2(uv.z - uvRadiusX, uv.w - uvRadiusY));
            vCenterVertList.Add(7);

            //左上角的圆心
            vCenterList.Add(new Vector2(v.x + radius, v.w - radius));
            uvCenterList.Add(new Vector2(uv.x + uvRadiusX, uv.w - uvRadiusY));
            vCenterVertList.Add(3);

            //左下角的圆心
            vCenterList.Add(new Vector2(v.x + radius, v.y + radius));
            uvCenterList.Add(new Vector2(uv.x + uvRadiusX, uv.y + uvRadiusY));
            vCenterVertList.Add(4);

            //右下角的圆心
            vCenterList.Add(new Vector2(v.z - radius, v.y + radius));
            uvCenterList.Add(new Vector2(uv.z - uvRadiusX, uv.y + uvRadiusY));
            vCenterVertList.Add(8);

            //每个三角形的顶角
            float degreeDelta = (float)(Mathf.PI / 2 / TriangleNum);
            //当前的角度
            float curDegree = 0;

            for (int i = 0; i < vCenterVertList.Count; i++)
            {
                int preVertNum = vh.currentVertCount;
                for (int j = 0; j <= TriangleNum; j++)
                {
                    float cosA = Mathf.Cos(curDegree);
                    float sinA = Mathf.Sin(curDegree);
                    Vector3 vPosition = new Vector3(vCenterList[i].x + cosA * radius, vCenterList[i].y + sinA * radius);
                    Vector3 uvPosition = new Vector2(uvCenterList[i].x + cosA * uvRadiusX, uvCenterList[i].y + sinA * uvRadiusY);
                    vh.AddVert(vPosition, color32, uvPosition);
                    curDegree += degreeDelta;
                }
                curDegree -= degreeDelta;
                for (int j = 0; j <= TriangleNum - 1; j++)
                {
                    vh.AddTriangle(vCenterVertList[i], preVertNum + j + 1, preVertNum + j);
                }
            }
        }

        private Vector4 GetDrawingDimensions(bool shouldPreserveAspect)
        {
            var padding = overrideSprite == null ? Vector4.zero : DataUtility.GetPadding(overrideSprite);
            Rect r = GetPixelAdjustedRect();
            var size = overrideSprite == null ? new Vector2(r.width, r.height) : new Vector2(overrideSprite.rect.width, overrideSprite.rect.height);
            //Debug.Log(string.Format("r:{2}, size:{0}, padding:{1}", size, padding, r));

            int spriteW = Mathf.RoundToInt(size.x);
            int spriteH = Mathf.RoundToInt(size.y);

            if (shouldPreserveAspect && size.sqrMagnitude > 0.0f)
            {
                var spriteRatio = size.x / size.y;
                var rectRatio = r.width / r.height;

                if (spriteRatio > rectRatio)
                {
                    var oldHeight = r.height;
                    r.height = r.width * (1.0f / spriteRatio);
                    r.y += (oldHeight - r.height) * rectTransform.pivot.y;
                }
                else
                {
                    var oldWidth = r.width;
                    r.width = r.height * spriteRatio;
                    r.x += (oldWidth - r.width) * rectTransform.pivot.x;
                }
            }

            var v = new Vector4(
                    padding.x / spriteW,
                    padding.y / spriteH,
                    (spriteW - padding.z) / spriteW,
                    (spriteH - padding.w) / spriteH);

            v = new Vector4(
                    r.x + r.width * v.x,
                    r.y + r.height * v.y,
                    r.x + r.width * v.z,
                    r.y + r.height * v.w
                    );

            return v;
        }
    }
}

Editor代码:

using System.Linq;
using UnityEngine;
using UnityEditor.AnimatedValues;
using UnityEngine.UI;
using UnityEditor;
using UnityEditor.UI;

namespace GFramework
{
    [CustomEditor(typeof(SimpleRoundedImage), true)]
    //[CanEditMultipleObjects]
    public class SimpleRoundedImageEditor : ImageEditor
    {

        SerializedProperty m_Radius;
        SerializedProperty m_TriangleNum;
        SerializedProperty m_Sprite;

        protected override void OnEnable()
        {
            base.OnEnable();

            m_Sprite = serializedObject.FindProperty("m_Sprite");
            m_Radius = serializedObject.FindProperty("Radius");
            m_TriangleNum = serializedObject.FindProperty("TriangleNum");

        }
        public override void OnInspectorGUI()
        {
            serializedObject.Update();

            SpriteGUI();
            AppearanceControlsGUI();
            RaycastControlsGUI();
            bool showNativeSize = m_Sprite.objectReferenceValue != null;
            m_ShowNativeSize.target = showNativeSize;
            NativeSizeButtonGUI();
            EditorGUILayout.PropertyField(m_Radius);
            EditorGUILayout.PropertyField(m_TriangleNum);
            this.serializedObject.ApplyModifiedProperties();
        }
    }
}

需要注意的点:

①UV坐标是[0-1]的,不随image的宽和高变换的,所以在做uv映射的时候要将uv坐标做等比例的处理,不然会出现断层的情况。

②在计算顶点信息的时候,要注意Pivot对顶点坐标的影响(直接照搬Image的处理就可以了)

③注意没有贴图的时候的处理,要让这张图片显示默认颜色。

Vector4 uv = overrideSprite != null ? DataUtility.GetOuterUV(overrideSprite) : Vector4.zero;

④因为直接继承Image类的类在Inspector面板上不会显示新定义的public变量,所以我们还要写一个SimpleRoundedImageEditor.cs来将新定义的圆角矩形半径和构成一个90°扇形的三角型的展示在面板上,顺便隐藏一下图片的类型,因为只实现了simple类型图片的圆角矩形。

4.效果

5.关于效率

Mask SimpleRoundedImage
DrawCall 3 1
顶点数 4 30个左右(一般每个扇形由6个三角型组成就可以达到较好的效果),顶点数量可以接受。

总结:如果在相同mask且之间没有相互遮挡的情况下,unity会对drawCall进行动态批处理,所以Mask数量的增加对drawCall的影响很小,只有在有多个不同mask或mask相互遮挡的情况下,每个mask会额外增加2次DrawCall。对DrawCall数量有较大的影响,但这种情况较少。

所以SimpleRoundedImage在大多数情况下对效率的提升并不明显。但通过修改顶点的方式实现圆角的方式会比使用遮罩实现圆角更加灵活方便。

代码链接:https://github.com/blueberryzzz/SimpleRoundedImage

原文地址:https://www.cnblogs.com/blueberryzzz/p/9521233.html

时间: 2024-10-04 15:38:12

SimpleRoundedImage-不使用mask实现圆角矩形图片的相关文章

Android开发之自定义圆角矩形图片ImageView的实现

android中的ImageView只能显示矩形的图片,这样一来不能满足我们其他的需求,比如要显示圆角矩形的图片,这个时候,我们就需要自定义ImageView了,其原理就是首先获取到图片的Bitmap,然后进行裁剪对应的圆角矩形的bitmap,然后在onDraw()进行绘制圆角矩形图片输出. 效果图如下: 自定义的圆形的ImageView类的实现代码如下: package com.xc.xcskin.view; import android.content.Context; import and

Android中绘制圆角矩形图片及任意形状图片

圆角矩形图片在苹果的产品中很流行,相比于普通的矩形,很多人都喜欢圆角矩形的图片,因为它避开了直角的生硬,带来更好的用户体验,下面是几个设计的例子: 下面在Android中实现将普通的矩形图片绘制成圆角矩形.首先看最终效果: 代码清单: package com.example.phototest; import android.os.Bundle; import android.app.Activity; import android.graphics.Bitmap; import android

【Android学习笔记】圆角矩形ImageView自定义控件的实现与使用

在做安卓项目的过程中,我们总会遇到需要以圆角矩形控件来显示图标.图片或者按钮的需求,解决办法有两种,一种是在drawable下创建shape布局xml文件,另一种是自定义一个继承于ImageView的自定义控件类来实现,下面是具体的实现办法. 首先我们命名一个XCRoundRectImageView类,并继承于ImageView.代码如下: 1 import android.content.Context; 2 import android.graphics.Bitmap; 3 import a

用贝赛尔曲线把图片, 按钮, label 绘成圆 或圆角矩形

//创建圆形遮罩,把用户头像变成圆形 /* *CGPointMake(35, 35)  是绘图的中心点,  如果想把控件居中绘圆, 一般用控件的中心点,   radius 是圆半径   startAngle是圆周 圆的一周就是2*m_pi */ //    UIBezierPath* path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(35, 35) radius:35 startAngle:0 endAngle:2 * M_PI c

使用imageMagick 制作圆角矩形和图片加水印

制作圆角矩形好图片水印都是图片合成的操作 composite -gravity southeast mask175.png  src.jpg  dest.jpg -gravity southeast 指叠加位置为右下角 如果要求在正中间,参数为center 如果要求在垂直据顶部.水平居中(正北方向),参数为north imagemagick官网:http://www.imagemagick.org/script/composite.php 使用imageMagick 制作圆角矩形和图片加水印

swift UIImage加载远程图片和圆角矩形

UIImage这个对象是swift中的图像类,可以使用UIImageView加载显示到View上. 以下是UIImage的构造函数: init(named name: String!) -> UIImage // load from main bundle init(named name: String!, inBundle bundle: NSBundle!, compatibleWithTraitCollection traitCollection: UITraitCollection!)

iOS 图片设置为圆角矩形,圆形等

有的时候需要将图片现实为圆形 比如头像等 以下面的图片为例 我们按照正常的方式添加后效果如下 UIImageView *userIconImageV=[[UIImageView alloc]initWithFrame:CGRectMake(30, 120, 188, 188)]; [self.view addSubview:userIconImageV]; userIconImageV.image=[UIImage imageNamed:@"icon_girl.jpg"]; 此时需要用

不用css样式表和背景图片实现圆角矩形,超简洁!

当网站页面的整体布局设计好后,接下来有很多细节的实现是很让人头疼的.其中之一就是圆角矩形的实现. 在网上看了很多圆角矩形的实现方法,基本有两种,一种是用纯css实现,不需要背景图片:另一种是用背景图像实现.但是,不管是哪一种,都有一个共同的缺点:需要使用很多代码来嵌套,而这些代码对搜索引擎来说毫无意义. 在<css cookbook>一书中介绍了一种实现圆角矩形十分简洁的方法,那就是用Nifty Corners Cube 先看一个简单的例子:http://www.sz137.com/sz137

Xfermode实现圆角矩形或者圆角图片

最近一段时间学了很多关于圆角矩形或者圆角图片制作的文章,写的都很好,但是每次一学完都会做,但是过段时间又不记得该怎么写代码了,反思了一下,是自己只是在看,并没有真正的消化,所以还不算自己的东西,于是今天又把大神的代码又看了看,自己总结,自己再写一遍,算是明白了吧,也想写个文章记录一下,方便下次寻找,也可以分享出来. Xfermode是android画笔Paint可以设置的一种画笔属性,具体就是可以把两张图片进行组合,根据设置的形式,可以组合多种形式,具体大家看图: 这里用的就是SrcIn模式,大