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/UIAndShader/tree/master/UIAndShader/Assets/SimpleRoundedImage
SimpleRoundedImage-不使用mask实现圆角矩形图片的更多相关文章
- Android开发之自定义圆角矩形图片ImageView的实现
android中的ImageView只能显示矩形的图片,这样一来不能满足我们其他的需求,比如要显示圆角矩形的图片,这个时候,我们就需要自定义ImageView了,其原理就是首先获取到图片的Bitmap ...
- Android中绘制圆角矩形图片及任意形状图片
圆角矩形图片在苹果的产品中很流行,相比于普通的矩形,很多人都喜欢圆角矩形的图片,因为它避开了直角的生硬,带来更好的用户体验,下面是几个设计的例子: 下面在Android中实现将普通的矩形图片绘制成圆角 ...
- Android开发之自定义圆角矩形图片ImageView的实现 - Jamy Cai
android中的ImageView只能显示矩形的图片,这样一来不能满足我们其他的需求,比如要显示圆角矩形的图片,这个时候,我们就需要自定义ImageView了,其原理就是首先获取到图片的Bitmap ...
- Android 自定义的圆角矩形ImageView 工具类
上图看效果 自定义圆角矩形ImageView工具类 package com.wechaotou.utils; import android.content.Context; import androi ...
- 用贝赛尔曲线把图片, 按钮, label 绘成圆 或圆角矩形
//创建圆形遮罩,把用户头像变成圆形 /* *CGPointMake(35, 35) 是绘图的中心点, 如果想把控件居中绘圆, 一般用控件的中心点, radius 是圆半径 startAn ...
- swift UIImage加载远程图片和圆角矩形
UIImage这个对象是swift中的图像类,可以使用UIImageView加载显示到View上. 以下是UIImage的构造函数: init(named name: String!) -> U ...
- 不用css样式表和背景图片实现圆角矩形,超简洁!
当网站页面的整体布局设计好后,接下来有很多细节的实现是很让人头疼的.其中之一就是圆角矩形的实现. 在网上看了很多圆角矩形的实现方法,基本有两种,一种是用纯css实现,不需要背景图片:另一种是用背景图像 ...
- canvas文字自动换行、圆角矩形画法、生成图片手机长按保存、方形图片变圆形
canvas的文字自动换行函数封装 // str:要绘制的字符串 // canvas:canvas对象 // initX:绘制字符串起始x坐标 // initY:绘制字符串起始y坐标 // lineH ...
- 使用imageMagick 制作圆角矩形和图片加水印
制作圆角矩形好图片水印都是图片合成的操作 composite -gravity southeast mask175.png src.jpg dest.jpg -gravity southeast ...
随机推荐
- Spring+SpringMVC+MyBatis整合基础篇
基础篇 Spring+SpringMVC+MyBatis+easyUI整合基础篇(一)项目简介 Spring+SpringMVC+MyBatis+easyUI整合基础篇(二)牛刀小试 Spring+S ...
- 前端项目模块化的实践1:搭建 NPM 私有仓库管理源码及依赖
以下是关于前端项目模块化的实践,包含以下内容: 搭建 NPM 私有仓库管理源码及依赖: 使用 Webpack 打包基础设施代码: 使用 TypeScript 编写可靠类库 使用 TypeScript ...
- 技术进阶:Kubernetes高级架构与应用状态部署
在了解Kubernetes应用状态部署前,我们先看看Kubernetes的高级架构,方便更好的理解Kubernetes的状态. Kubernetes 的高级架构 包括应用程序部署模型,服务发现和负载均 ...
- 11.5 Daily Scrum
请把现在当成11月5日······ Today's tasks Tomorrow's tasks 丁辛 餐厅列表数据结构设计 餐厅列表UI设计 李承晗 ...
- Daily Scrum NO.3
工作概况 符美潇(PM) 昨日完成的工作 1.Daily Scrum.日常会议及日常工作的分配和查收. 2.整合各DEV所写的代码,在TFS上进行Beta阶段第一次代码签入. 今日工作 1.Daily ...
- 实训一(cocos2d-x相关)
实训内容简介: 大四开始前系里安排的的集中实践环节,根据要求,开发app应用软件. 目标app:Stick_mxj 目的:继续对cocos2d-x的学习,完成实践环节,解决现在对引擎不是很清楚的一些问 ...
- 第三次作业--导入excel表格(完整版)
031302322 031302316 将教师排课表导入系统 使用powerdesigner设计数据库表格 设计概念模型 打开new -> Conceptual Data Model创建概念模型 ...
- windows 64bit 服务器下安装32位oracle database 11g 问题集
1.中文乱码 问题描述: 利用vs2008调试的时候正常,发布到IIS8.5上的时候,当查询语句中包含中文的时候会乱码,比如"select * from tb where name='小s' ...
- Beta版本讨论
目录 组员:胡绪佩 组员:何家伟 组员:黄鸿杰 组员: 翟丹丹 组员:周政演 组员:胡青元 组员:庄卉 组员:刘恺琳 组员:何宇恒 组员:刘一好 组员:葛家灿 组员:胡绪佩 总结 通过这次的Beta版 ...
- 表格-table 样式
.table: 表格基本样式 .table-dark:表格显示为黑色 .thead-light: 表头显示颜色跟亮 .thead-dark:表头显示为黑色 .table-striped:表格以条纹形式 ...