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/UIAndShader/tree/master/UIAndShader/Assets/SimpleRoundedImage

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

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

    android中的ImageView只能显示矩形的图片,这样一来不能满足我们其他的需求,比如要显示圆角矩形的图片,这个时候,我们就需要自定义ImageView了,其原理就是首先获取到图片的Bitmap ...

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

    圆角矩形图片在苹果的产品中很流行,相比于普通的矩形,很多人都喜欢圆角矩形的图片,因为它避开了直角的生硬,带来更好的用户体验,下面是几个设计的例子: 下面在Android中实现将普通的矩形图片绘制成圆角 ...

  3. Android开发之自定义圆角矩形图片ImageView的实现 - Jamy Cai

    android中的ImageView只能显示矩形的图片,这样一来不能满足我们其他的需求,比如要显示圆角矩形的图片,这个时候,我们就需要自定义ImageView了,其原理就是首先获取到图片的Bitmap ...

  4. Android 自定义的圆角矩形ImageView 工具类

    上图看效果 自定义圆角矩形ImageView工具类 package com.wechaotou.utils; import android.content.Context; import androi ...

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

    //创建圆形遮罩,把用户头像变成圆形 /* *CGPointMake(35, 35)  是绘图的中心点,  如果想把控件居中绘圆, 一般用控件的中心点,   radius 是圆半径   startAn ...

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

    UIImage这个对象是swift中的图像类,可以使用UIImageView加载显示到View上. 以下是UIImage的构造函数: init(named name: String!) -> U ...

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

    当网站页面的整体布局设计好后,接下来有很多细节的实现是很让人头疼的.其中之一就是圆角矩形的实现. 在网上看了很多圆角矩形的实现方法,基本有两种,一种是用纯css实现,不需要背景图片:另一种是用背景图像 ...

  8. canvas文字自动换行、圆角矩形画法、生成图片手机长按保存、方形图片变圆形

    canvas的文字自动换行函数封装 // str:要绘制的字符串 // canvas:canvas对象 // initX:绘制字符串起始x坐标 // initY:绘制字符串起始y坐标 // lineH ...

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

    制作圆角矩形好图片水印都是图片合成的操作 composite -gravity southeast mask175.png  src.jpg  dest.jpg -gravity southeast ...

随机推荐

  1. 设计模式 笔记 迭代器模式 Iterator

    //---------------------------15/04/26---------------------------- //Iterator 迭代器模式----对象行为型模式 /* 1:意 ...

  2. 【开源.NET】 轻量级内容管理框架Grissom.CMS(第二篇前后端交互数据结构分析)

    这是 CMS 框架系列文章的第二篇,第一篇开源了该框架的代码和简要介绍了框架的目的.作用和思想,这篇主要解析如何把sql 转成标准 xml 配置文件和把前端post的增删改数据规范成方便后台解析的结构 ...

  3. laravel从5.2到5.5从入门到精通视频教程共16套

    laravel从5.2到5.5从入门到精通视频教程共16套,大部分都是实战项目比如P2P.博客.短网址.知乎门户.app软件开发.微信商城实战等 课程目录: 01.Laravel框架从入门到精通02. ...

  4. 在CentOS上搭建PHP服务器环境(可用)

    原文:https://www.cnblogs.com/zy2009/p/7047828.html 1,先安装apache: yum install httpd 配置ServerName vi /etc ...

  5. [2017BUAA软工助教]个人得分总表(beta阶段)

    一.表 学号 b团队 b团队得分 b贡献分 阅读作业 提问回顾 总分 14011100 hotcode5 228 60 6 7.5 301.5 14061213 PM="PokeMon&qu ...

  6. 基于 Java Web 的毕业设计选题管理平台--系统设计和任务分配

    一.团队作业:http://www.yzhiliao.com/course/70/task/440/show 二.个人作业: 1.项目的代码托管 (1).GitHub 地址:https://githu ...

  7. VS2015 C#的单元测试

    1.安装visual studio 2015过程 visual studio 会对windows系统兼容性有很高的要求,没有达到win7 sp1以上的就不给安装,贴一张官方的系统的要求吧. 很不幸的是 ...

  8. JS基础(三)语句

    一.判断语句(PS:一般情况下判断条件最终应该是一个布尔值.) 1.if语句 1)基本格式 if(判断条件){ 如果判断条件成立则执行的语句 }else{ 如果判断条件不成立则执行的语句 } 2)扩展 ...

  9. 查看django版本的方法

    在cmd输入: python -m django --version django-admin --version

  10. Semantic Versioning Specification & 语义化版本

    Semantic Versioning Specification & 语义化版本 Semantic Versioning Specification http://semver.org 16 ...