Unity | Image 自定义顶点数据实现圆角矩形

news2024/11/23 14:56:17

1 圆角方案简介

UGUI 中的 Image 实现圆角效果通常有三种方式,Mask、Shader以及自定义顶点数据,相比于前两者,自定义顶点数据的使用方式更加灵活,同时可以减少 DrawCall,但是会增加顶点及三角形数量。最终实现方案可根据实际情况选择,水不深,自己把握

2 实现方案

1 修改顶点数据

渲染流程这里不再赘述,可以简单回顾下渲染管线的每个阶段:

我们要修改的就是发送给 GPU 的顶点数据, 在 Unity 的 Image 组件中,可以使用 OnPopulateMesh 函数来修改顶点数据

关于 OnPopulateMesh,在之前的 强制新手引导 中也介绍过,这里再重复一遍

API:

protected virtual void OnPopulateMesh(VertexHelper vh);

UI 元素需要生成顶点时的回调函数,通常用于自定义 UI 元素的渲染,可以通过重写该方法来实现自定义的 UI 元素渲染效果

vh 参数是一个 VertexHelper 类型的对象,用于生成网格数据。在该方法中,可以通过调用 VertexHelper 的方法来添加顶点、三角形和颜色等信息,从而生成网格数据

在重写该方法时,需要注意以下几点:

  • 在方法中添加顶点、三角形和颜色等信息时,需要按照一定的顺序添加,以确保生成的网格数据正确无误

  • 在方法中添加顶点、三角形和颜色等信息时,需要注意坐标系的转换,以确保生成的网格数据与 UI 元素的位置和大小一致

  • 在方法中添加顶点、三角形和颜色等信息时,需要注意性能问题,尽量避免生成过多的网格数据,以提高渲染效率

2 填充三角形

将矩形分割成三个矩形(左边、中间、右边)和四个扇形(四个角)

先将所有顶点都放入 VertexHelper 中:

vh.Clear();

// 0 1 2 3
vh.AddVert(new Vector3(left, top), color32, new Vector2(outerUV.x, outerUV.w));
vh.AddVert(new Vector3(left, top - r), color32, new Vector2(outerUV.x, outerUV.w - uvRadiusY));
vh.AddVert(new Vector3(left, bottom + r), color32, new Vector2(outerUV.x, outerUV.y + uvRadiusY));
vh.AddVert(new Vector3(left, bottom), color32, new Vector2(outerUV.x, outerUV.y));

// 4 5 6 7
vh.AddVert(new Vector3(left + r, top), color32, new Vector2(outerUV.x + uvRadiusX, outerUV.w));
vh.AddVert(new Vector3(left + r, top - r), color32,
    new Vector2(outerUV.x + uvRadiusX, outerUV.w - uvRadiusY));
vh.AddVert(new Vector3(left + r, bottom + r), color32,
    new Vector2(outerUV.x + uvRadiusX, outerUV.y + uvRadiusY));
vh.AddVert(new Vector3(left + r, bottom), color32, new Vector2(outerUV.x + uvRadiusX, outerUV.y));

// 8 9 10 11
vh.AddVert(new Vector3(right - r, top), color32, new Vector2(outerUV.z - uvRadiusX, outerUV.w));
vh.AddVert(new Vector3(right - r, top - r), color32,
    new Vector2(outerUV.z - uvRadiusX, outerUV.w - uvRadiusY));
vh.AddVert(new Vector3(right - r, bottom + r), color32,
    new Vector2(outerUV.z - uvRadiusX, outerUV.y + uvRadiusY));
vh.AddVert(new Vector3(right - r, bottom), color32, new Vector2(outerUV.z - uvRadiusX, outerUV.y));

// 12 13 14 15
vh.AddVert(new Vector3(right, top), color32, new Vector2(outerUV.z, outerUV.w));
vh.AddVert(new Vector3(right, top - r), color32, new Vector2(outerUV.z, outerUV.w - uvRadiusY));
vh.AddVert(new Vector3(right, bottom + r), color32, new Vector2(outerUV.z, outerUV.y + uvRadiusY));
vh.AddVert(new Vector3(right, bottom), color32, new Vector2(outerUV.z, outerUV.y));

组装三个矩形,其对应的六个三角形分别是:(2, 5, 1)、(2, 5, 6)、(7, 8, 4)、(7, 8, 11)、(10, 13, 9)、(10, 13, 14)

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

组装四个扇形,分别是:(1,5,4)、(2,6,7)、(8,9,13)、(11,10,14),每个扇形需要用若干个三角形来模拟,三角形数量越多,边缘越平滑,但对应的开销越大

List<Vector2> positionList = new List<Vector2>();
List<Vector2> uvList = new List<Vector2>();
List<int> vertexList = new List<int>();

// 右上角圆心
positionList.Add(new Vector2(right - r, top - r));
uvList.Add(new Vector2(outerUV.z - uvRadiusX, outerUV.w - uvRadiusY));
vertexList.Add(9);

// 左上角的圆心
positionList.Add(new Vector2(left + r, top - r));
uvList.Add(new Vector2(outerUV.x + uvRadiusX, outerUV.w - uvRadiusY));
vertexList.Add(5);

// 左下角圆心
positionList.Add(new Vector2(left + r, bottom + r));
uvList.Add(new Vector2(outerUV.x + uvRadiusX, outerUV.y + uvRadiusY));
vertexList.Add(6);

// 右下角圆心
positionList.Add(new Vector2(right - r, bottom + r));
uvList.Add(new Vector2(outerUV.z - uvRadiusX, outerUV.y + uvRadiusY));
vertexList.Add(10);

// 每个三角形角度
float degreeDelta = Mathf.PI / 2 / this.cornerSegments;

// 当前的角度
float degree = 0;
for (int i = 0; i < vertexList.Count; i++)
{
    int currentVertCount = vh.currentVertCount;
    for (int j = 0; j <= this.cornerSegments; j++)
    {
        float cos = Mathf.Cos(degree);
        float sin = Mathf.Sin(degree);
        Vector3 position = new Vector3(positionList[i].x + cos * r, positionList[i].y + sin * r);
        Vector3 uv0 = new Vector2(uvList[i].x + cos * uvRadiusX,
            uvList[i].y + sin * uvRadiusY);
        vh.AddVert(position, color32, uv0);
        degree += degreeDelta;
    }

    degree -= degreeDelta;
    for (int j = 0; j <= this.cornerSegments - 1; j++)
    {
        vh.AddTriangle(vertexList[i], currentVertCount + j + 1, currentVertCount + j);
    }
}

3 扩展 Inspector

由于脚本是直接继承 Image,脚本中定义的 public 变量不会在 Inspector 面板上显示,所以需要自己扩展面板,方便调节参数:

#if UNITY_EDITOR
[CustomEditor(typeof(BorderRadius), true)]
public class BorderRadiusEditor : ImageEditor
{
    SerializedProperty _sprite;
    SerializedProperty _cornerRadius;
    SerializedProperty _cornerSegments;

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

        this._sprite = this.serializedObject.FindProperty("m_Sprite");
        this._cornerRadius = this.serializedObject.FindProperty("cornerRadius");
        this._cornerSegments = this.serializedObject.FindProperty("cornerSegments");
    }

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

        this.SpriteGUI();
        this.AppearanceControlsGUI();
        this.RaycastControlsGUI();
        bool showNativeSize = this._sprite.objectReferenceValue != null;
        this.m_ShowNativeSize.target = showNativeSize;
        this.MaskableControlsGUI();
        this.NativeSizeButtonGUI();
        EditorGUILayout.PropertyField(this._cornerRadius);
        EditorGUILayout.PropertyField(this._cornerSegments);
        this.serializedObject.ApplyModifiedProperties();
    }
}
#endif

圆角效果:

4 定制圆角

为了视觉体验,多数情况下矩形并非四个角都是圆角,实现该效果只需要在进行圆角的顶点计算时,判断如果是非圆角,则直接填充该扇形对应的矩形:

if (i == 0 && !this.rightTop)
{
    vh.AddTriangle(vertexList[i], 8, 12);
    vh.AddTriangle(vertexList[i], 12, 13);
    continue;
}

if (i == 1 && !this.leftTop)
{
    vh.AddTriangle(vertexList[i], 0, 4);
    vh.AddTriangle(vertexList[i], 0, 1);
    continue;
}

if (i == 2 && !this.leftBottom)
{
    vh.AddTriangle(vertexList[i], 3, 2);
    vh.AddTriangle(vertexList[i], 3, 7);
    continue;
}

if (i == 3 && !this.rightBottom)
{
    vh.AddTriangle(vertexList[i], 15, 14);
    vh.AddTriangle(vertexList[i], 15, 11);
    continue;
}

左上和右上非圆角:

左下和右上非圆角:

「完整代码公众号回复:圆角矩形」

更多源码,请扫码获取

更多源码,请扫码获取

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1123772.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

虚拟世界游戏定制开发:创造独一无二的虚拟体验

在游戏开发领域&#xff0c;虚拟世界游戏定制开发是一项引人注目的任务&#xff0c;旨在满足客户独特的需求和愿景&#xff0c;创造一个完全个性化的虚拟世界游戏。这种类型的游戏开发需要专业的技能、深刻的游戏开发知识和密切的与客户合作&#xff0c;以确保游戏满足客户的期…

【23真题】师范强者,均分135,复试难!

哈喽大家好&#xff0c;现在这个时间节点&#xff0c;有很多同学开始刷真题了&#xff01;所以23真题系列正式启动&#xff01;小马哥将全面发布23真题及详细解析&#xff01; 今天分享的是23年华中师范大学838的信号与系统试题及解析。 本套试难度分析&#xff1a;23年平均分…

JVM 类的加载子系统

文章目录 类的加载过程加载阶段链接阶段初始化 类的加载器测试代码中获取对应的加载器获取加载器加载的路径不同类对应的加载器自定义加载器自定义加载器的方式 获取类的加载器的方式双亲委派机制双亲委派机制的好处 Java 的 SPI 机制1. 接口定义2. 具体实现3. 配置 META-INF/s…

Android Termux安装MySQL,通过内网穿透实现公网远程访问

&#x1f525;博客主页&#xff1a; 小羊失眠啦. &#x1f516;系列专栏&#xff1a; C语言、Linux、Cpolar ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 文章目录 前言1.安装MariaDB2.安装cpolar内网穿透工具3. 创建安全隧道映射mysql4. 公网远程连接5. 固定远程连接地址 前…

探索Java中最常用的框架:Spring、Spring MVC、Spring Boot、MyBatis和Netty

文章目录 Spring框架Spring MVC框架Spring Boot框架MyBatis框架Netty框架总结 &#x1f389;欢迎来到Java面试技巧专栏~探索Java中最常用的框架&#xff1a;Spring、Spring MVC、Spring Boot、MyBatis和Netty ☆* o(≧▽≦)o *☆嗨~我是IT陈寒&#x1f379;✨博客主页&#xff…

改变了数组内的值,但是页面没有重新渲染

新增的时候可以展示&#xff0c;但是编辑在点新增就没有效果 原因&#xff1a;改变了数组内的值&#xff0c;但是页面没有重新渲染 <el-form-item label"信息:" required><div style"display: flex; align-items: flex-end"><div><e…

Go 代码包与引入:如何有效组织您的项目

一、引言 在软件开发中&#xff0c;代码的组织和管理是成功项目实施的基础之一。特别是在构建大型、可扩展和可维护的应用程序时&#xff0c;这一点尤为重要。Go语言为这一需求提供了一个强大而灵活的工具&#xff1a;代码包&#xff08;Packages&#xff09;。代码包不仅允许…

Java 基础 面试 多线程

1.多线程 1.1 线程&#xff08;Thread&#xff09; 线程时一个程序内部的一条执行流程&#xff0c;java的main方法就是由一条默认的主线程执行 1.2 多线程 多线程是指从软硬件上实现的多条执行流程的技术&#xff08;多条线程由CPU负责调度执行&#xff09; 许多平台都离不开多…

看微功耗遥测终端机如何轻松应对野外环境挑战?

在野外&#xff0c;数据的实时监测和传输是至关重要的。无论是环境温度、湿度&#xff0c;还是水位、流量&#xff0c;都需要精准把控。然而&#xff0c;传统的监测方法往往受限于电源供应问题&#xff0c;而无法充分发挥其功能。这时候&#xff0c;一款微功耗遥测终端机&#…

前端react入门day01-了解react和JSX基础

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 React介绍 React是什么 React的优势 React的市场情况 开发环境搭建 使用create-react-app快速搭建…

windows系统mysql服务无法启动

### 原因 电脑重启navicat连接mysql失败&#xff0c;在电脑-管理-服务中没有mysql服务 ### 解决方案 1. 找到mysql的安装目录进入bin目录 2. 执行mysqld --install 进行重新安装 提示服务安装成功 3. net start mysql mysql 启动成功

【工作中用到的功能-windows篇】自用备忘

文章目录 利用性能监视器查看内存使用情况历史记录 利用性能监视器查看内存使用情况历史记录 直接通过任务管理器CtrlshiftESC或者资源监视器winr resmon只能看到实时情况&#xff0c;没法查看历史使用情况。 perfmon添加监控方法&#xff1a; winr perfmon打开性能监视器 新…

同为科技(TOWE)工业级多位USB快充桌面PDU插座

如今&#xff0c;许多便捷式、轻薄化电子设备越来越多&#xff0c;很多设备上预留的端口越来越少&#xff0c;甚至很多款笔记本电脑只预留了一个单一的Type-C接口。这样做虽然在体验感、美观度和轻薄尺寸的优势显而易见&#xff0c;然而也存在接口不足的明显弊端。USB快充插排产…

浙江大学利用 SVM 优化触觉传感器,盲文识别率达 96.12%

生物传感是人类与机器、人类与环境、机器与环境交互的重要媒介。其中&#xff0c;触觉能够实现精准的环境感知&#xff0c;帮助使用者与复杂环境交互。 为模仿人类的触觉&#xff0c;科研人员开发了各种传感器&#xff0c;以模拟皮肤对环境的感知。然而&#xff0c;触觉传感的要…

【Mongo】数据删了磁盘空间但没有减少

Author:skate Time:2023/10/22 一、问题描述 产线用户反馈&#xff0c;一个华为云的mongo实例磁盘空间告警&#xff0c;使用率超过90%&#xff08;使用状况 1630.9/1800GB&#xff09;&#xff0c;让其通过数据库运维平台找到占用大空间的表&#xff0c;然后清理历史数据&…

zabbix安装部署笔记

记一次zabbix安装部署过程&#xff0c;由于各版本配置的文件系统不同&#xff0c;以及出现许多意外的出错。 一、安装&#xff1a; 安装很简单&#xff0c;直接apt-get安装。 #服务器 apt-get install zabbix-server-mysql #web apt-get install zabbix-frontend-php #客户…

RHCE8 资料整理(三)

RHCE8 资料整理 第三篇 网络相关配置第11章 网络配置11.1 网络基础知识11.2 查看网络信息11.3 图形化界面修改11.4 通过配置文件修改11.5 命令行管理11.6 主机名的设置 第12章 ssh12.1 ssh基本用法12.2 打开远程图形化界面12.3 ssh无密码登录12.4 ssh安全设置12.5 ssh限制用户1…

【python】--python环境安装及配置

目录 一、python开发环境部署1、下载安装Miniconda2、python环境3、进入或退出python环境4、对应python环境安装工具/库5、进入pyhton环境&#xff0c;查看已安装的工具/库6、安装pycharm专业版7、pycharm创建项目并关联python版本环境 一、python开发环境部署 要安装一个pyth…

Amazonlinux2023(AL2023)获取metadata

今年AWS发布了新的Amazonlinux2023版本&#xff0c;其中获取metadata元数据方式发生了一点改变。 早些时候&#xff0c;在 Amazon Linux 2 中&#xff0c;使用以下命令获取实例元数据 http://169.254.169.254/latest/meta-data/ 具体可以获取的元数据类别可以查阅如下aws官方…

win7录屏软件哪个好用?盘点3款实用软件

在当今科技迅猛发展的时代&#xff0c;录屏已经成为了教育、演示和内容创作的重要工具。对于使用windows 7操作系统的用户来说&#xff0c;选择合适的录屏软件至关重要。可是win7录屏软件哪个好用呢&#xff1f;在本文中&#xff0c;我们将介绍3款常用的win7录屏软件。通过比较…