Unity中Mesh重叠顶点合并参考及其应用
在Unity中,如果将一个模型文件(比如从max里面导出一个fbx文件)导入到编辑器中之后,Unity会把所有在原来在面列表中公用的顶点复制一份,保证每个三角形使用的顶点都是单独的,不与其它三角形共用顶点,Unityh这么做应该有他的道理,但我尚未想明白Unity这么做的原因是什么。
但是考虑到在有些应用中使用共用顶点能够更好的处理一些问题,这里写了一个将Unity中Mesh重叠顶点合并成公用顶点的代码,目的是将位置重叠的顶点去掉,同时也将面列表的内容更新,由一个原来有重叠顶点的Mesh获得一个新的使用公用顶点的Mesh,参考如下:
Mesh GetWeldMesh(Mesh mesh)
{Vector3[] vs = mesh.vertices;int[] ts = mesh.triangles;Debug.Log(ts.Length);Dictionary<Vector3, int> dicVert = new();for (int i = 0; i < ts.Length; i++){bool hasSamePos = false;Vector3 vert = vs[ts[i]];foreach (var kv in dicVert){Vector3 key = kv.Key;if (vert == key){if (kv.Value < ts[i]){ts[i] = kv.Value;}else{dicVert[kv.Key] = ts[i];}hasSamePos = true;break;}}if (!hasSamePos){dicVert.Add(vert, ts[i]);}}List<Vector3> listVertice = new();foreach (var kv in dicVert){listVertice.Add(kv.Key);}Mesh meshNew = new(){vertices = vs,triangles = ts};meshNew.RecalculateNormals();return meshNew;
}
目前我能想到的这么做的用处有两个,第一个是在一种描边方法中,Shader需要将要描边的物体增加一次渲染,在这次渲染中,Shader会将网格的每个顶点沿着该顶点的法线方向向外挤出一点点,形成一个更大的轮廓,然后反面渲染,这样就形成了原物体的勾边效果,不过这里有个问题,如果使用的Unity默认的Mesh,当物体的光滑组是连续的时候,挤出的效果是没问题的,当光滑组不连续(例如一个Box),顶点挤出后会出现分离问题。你当然可以在类似max这种软件中将Box的光滑组强行编成一组,这样导入Untiy中之后挤出效果倒是没问题了,但是本身显示效果就不是硬边的效果了,这也不对啊。但是如果在使用Shader渲染Mesh的时候,使用的不是原始的Mesh,而是通过上述代码生成的新的Mesh,由于顶点是共用的,就不存在出现挤出分离的问题。关于具体的Shader就不说了,玩过Shader的都懂。
第二个是查找物体的边界,一般来说查找物体边界的方法是找到不被两个三角形共用的边,但是Unity中Mesh默认所有的顶点都是单独被面列表使用的,根本没有所谓被两个三角形共用的边的情况,这中情况会发现所有的三角形的边都是边界,貌似行不通,所以还是需要上述方法将Unity中Mesh重叠顶点合并再去查找边界。其查找边界的代码参考如下(由于Mesh的编辑可能不止一组,所以用的返回值是List<int[]>,是顶点编号数组的列表):
List<int[]> GetEdgeIndexes(Mesh mesh){Vector3[] vs = mesh.vertices;int[] ts = mesh.triangles;Dictionary<Edge, int> dicEdge = new();for (int i = 0; i < ts.Length; i += 3){int indexA = ts[i];int indexB = ts[i + 1];int indexC = ts[i + 2];bool hasSameEdge01 = false;bool hasSameEdge02 = false;bool hasSameEdge03 = false;KeyValuePair<Edge, int>[] kvs = dicEdge.ToArray();for (int j = 0; j < kvs.Length; j++){Edge edge = kvs[j].Key;if (edge.IsSame(indexA, indexB)) { hasSameEdge01 = true; dicEdge[edge] += 1; continue; }if (edge.IsSame(indexB, indexC)) { hasSameEdge02 = true; dicEdge[edge] += 1; continue; }if (edge.IsSame(indexC, indexA)) { hasSameEdge03 = true; dicEdge[edge] += 1; continue; }}if (!hasSameEdge01) { dicEdge.Add(new Edge(indexA, indexB), 1); }if (!hasSameEdge02) { dicEdge.Add(new Edge(indexB, indexC), 1); }if (!hasSameEdge03) { dicEdge.Add(new Edge(indexC, indexA), 1); }}List<Edge> edges = new List<Edge>();foreach (var kv in dicEdge){if (kv.Value == 1){edges.Add(kv.Key);}}foreach (var item in edges){Debug.Log(item.a + " , " + item.b);}List<int[]> listEdgeIndexes = new();while (edges.Count > 0){List<int> listTriangle = new(){edges[0].a,edges[0].b};edges.RemoveAt(0);while (true){int indexLast = listTriangle[^1];bool hasAddIndex = false;for (int i = 0; i < edges.Count; i++){Edge edge = edges[i];if (indexLast == edge.a){listTriangle.Add(edge.b);hasAddIndex = true;edges.RemoveAt(i);break;}if (indexLast == edge.b){listTriangle.Add(edge.a);hasAddIndex = true;edges.RemoveAt(i);break;}}if (!hasAddIndex){listEdgeIndexes.Add(listTriangle.ToArray());break;}}}return listEdgeIndexes;}
其中所用到的Edge类代码如下:
public class Edge
{public readonly int a;public readonly int b;public Edge(int a, int b){this.a = a;this.b = b;}public bool IsSame(Edge edge) { return (a == edge.a && b == edge.b) || (a == edge.b && b == edge.a); }public bool IsSame(int a, int b) { return (a == this.a && b == this.b) || (a == this.b && b == this.a); }
}
下图将一个球体挖了三个洞洞,通过上述方法查找到边界,并用LineRenderer渲染了边界。
相关文章:

Unity中Mesh重叠顶点合并参考及其应用
在Unity中,如果将一个模型文件(比如从max里面导出一个fbx文件)导入到编辑器中之后,Unity会把所有在原来在面列表中公用的顶点复制一份,保证每个三角形使用的顶点都是单独的,不与其它三角形共用顶点…...

倚光科技助力自由曲面设计与加工
近年来,自由曲面因其在光学、汽车、航空航天等领域的广泛应用,受到设计师和工程师的高度关注。自由曲面作为一种具有更高自由度的非球面透镜,能够在光学系统中实现更加精确的光线控制,优化像差校正,并且在满足功能需求…...

PWM调节DCDC参数计算原理
1、动态电压频率调整DVFS SOC芯片的核电压、GPU电压、NPU电压、GPU电压等,都会根据性能和实际应用场景来进行电压和频率的调整。 即动态电压频率调整DVFS(Dynamic Voltage and Frequency scaling),优化性能和功耗。 比如某SOC在…...

[Pro Git#3] 远程仓库 | ssh key | .gitignore配置
目录 1. 分布式版本控制系统的概念 2. 实际使用中的“中央服务器” 3. 远程仓库的理解 4. 新建远程仓库 5. 克隆远程仓库 6. 设置SSH Key 实验 一、多用户协作与公钥管理 二、克隆后的本地与远程分支对应 三、向远程仓库推送 四、拉取远程仓库更新 五、配置Git忽略…...

Freertos任务切换
一、操作系统进行任务切换的时机: 采用信号量实现任务的互斥: 二、FreeRTOS 任务切换场合 PendSV 中断的时候提到了上下文(任务)切换被触发的场合: ● 可以执行一个系统调用 ● 系统滴答定时器(SysTick)中断。 1、执行系统调用 执行系统…...
go开发中interface和方法接收器的使用
Go 语言中的接口和方法接收器学习 Go 中的 interface 就像是一个神奇的魔法杖,能让你轻松地将不同的类型拉到同一个阵营里。与其他语言的接口不同,Go 的接口无需显式声明“我实现了你”,只要你满足了接口规定的方法,Go 就会自动认…...

vue3-tp8-Element:对话框实现
效果 参考框架 Dialog 对话框 | Element Plus 具体实现 一、建立view页面 /src/views/TestView.vue 二、将路径写入路由 /src/router/index.js import { createRouter, createWebHistory } from vue-router import HomeView from ../views/HomeView.vueconst router create…...

高中数学:随机变量-正态分布
文章目录 一、连续性随机变量二、大致图像三、正态分布图像及解析式图像特点均值与方差公式正态分布各区域概率 一、连续性随机变量 二、大致图像 三、正态分布图像及解析式 图像特点 均值与方差公式 正态分布各区域概率...

游戏引擎学习第47天
仓库: https://gitee.com/mrxiao_com/2d_game 昨天我们花了一点时间来修复一个问题,但基本上是在修复这个问题的过程中,我们决定添加一个功能,那就是在屏幕上控制多个实体。所以如果我有一个手柄,我可以添加另一个角色࿰…...

Git 仓库托管教程
git远程仓库 常用的远程仓库-->托管服务:github、码云、gitlab等 github需要魔法上网,速度较慢因为在国外且仅仅支持Git,如果不是Git项目是不支持的;码云--gitee国内的代码托管平台,服务器在国内速度快一些&#…...

基于51单片机的简易时钟/定时器闹钟proteus仿真
地址: https://pan.baidu.com/s/1uez4cwZuXpchmihmRqnLEg 提取码:1234 仿真图: 芯片/模块的特点: AT89C52/AT89C51简介: AT89C52/AT89C51是一款经典的8位单片机,是意法半导体(STMicroelectro…...
Jackson @JsonProperty 注解
1. 概述 Jackson 是一个流行的Java库,用于将Java对象转换为JSON格式以及从JSON反序列化回Java对象。一种常见的需求是在序列化为JSON或从JSON反序列化时自定义字段的命名。Jackson 的 JsonProperty 注解正好满足了这一需求。 JsonProperty 注解概览 JsonProperty…...
【Excel学习记录】02-单元格格式设置
1.单元格格式工具美化表格 单元格格式位置 选中单元格,右键→设置单元格格式 合并居中 跨越合并 字体类型、大小、颜色、填充底纹、边框 斜线 软回车:alt enter 格式刷 2.单元格数字格式 格式不影响数值,只是展示形式 日期本质也是数…...

支持自定义离线地图地理区域,查询组件及数据源功能增强,DataEase开源BI工具v2.10.3 LTS发布
2024年12月9日,人人可用的开源BI工具DataEase正式发布v2.10.3 LTS版本。 这一版本的功能变动包括:数据源方面,API数据源和Excel数据源支持对字段类型和长度进行设置;图表方面,离线类地图支持自定义地理区域设置&#…...
LF CRLF
这个提示的含义是:Git 检测到你当前的 file3.txt 文件中使用了 LF(换行符,Line Feed,\n) 作为换行符,但在你系统的 Git 配置中,指定要将其转换为 CRLF(回车换行,Carriage…...

【从零开始入门unity游戏开发之——C#篇01】理论开篇,理解什么是编程
文章目录 前言前置条件进制什么是十进制、二进制二进制有什么用?为什么计算机用二进制而不用十进制?二进制转十进制十进制转二进制二进制运算 计算机中的数据存储单位什么是编程?什么是代码?什么是编程语言?常见的编程…...

怎样提升企业网络的性能?
企业网络的稳定性和高效性直接影响员工的工作效率。以下从多维度分析了一些有效策略,帮助公司提升网络性能,营造更高效的办公环境。 1. 升级网络设备 采用性能更高的网络硬件是优化网络体验的重要基础。选择支持高吞吐量、低延迟的设备(如企业…...

1. 机器学习基本知识(2)——机器学习分类
1.4 机器学习分类 1.4.1 训练监督 1. 监督学习:已对训练数据完成标记 分类:根据数据及其分类信息来进行训练,使模型能够对新的数据进行分类 回归:给出一组特征值来预测目标数值 2. 无监督学习:没有对训练数据进行任…...
四叉树算法在游戏碰撞检测中的应用
简介 在游戏开发中,碰撞检测是一个非常重要但计算成本较高的环节。如果采用简单的暴力检测方法,需要对场景中的每个物体与其他所有物体进行碰撞检测,时间复杂度为O(n)。四叉树(Quadtree)算法通过空间划分的方式,可以显著降低碰撞…...

IDEA 打包普通JAVA项目为jar包
需求:普通java项目(有添加依赖的jar包),没有用maven管理依赖和打包,要打成jar包,包可以用“java -jar 包名” 启动程序。 讲如何打包前,先记录下普通项目的目录结构和怎么添加依赖包 1.目录结…...

vue中Echarts的使用
文章目录 Echarts概述什么是EchartsEcharts的好处 Vue中Echarts的使用Echarts的安装Echarts的引入 Echarts概述 什么是Echarts Apache ECharts:一个基于 JavaScript 的开源可视化图表库。 其官网如下:https://echarts.apache.org/zh/index.html Echar…...
Spring Boot 2 中 default-autowire 的使用
Spring Boot 2 中 default-autowire 的使用 在 Spring Boot 2 中,default-autowire 这个来自传统 XML 配置的概念仍然存在,但它的使用已经大大减少,因为现代 Spring Boot 应用主要使用注解驱动的配置方式。 default-autowire 在 Spring Boo…...
MongoDB慢查询临时开启方法讲解
1、首先连接数据库 mongosh "mongodb://localhost:27017" 2、选择目标数据库 show databases;#显示所有数据库 use lidb;#使用某数据库 3、查看当前分析级别 db.getProfilingStatus() 输出 { was: 0, slowms: 100, sampleRate: 1, ok: 1 } #was0表示关闭&…...

Facebook接入说明
Facebook 原生 Messenger 聊天消息接入到一洽对话中 1、创建 Facebook 主页 进入 https://www.facebook.com/pages/create 页面根据提示创建主页(如果已经有待用主页,可跳过) 2、授权对话权限 1、向您的一洽负责人获取 Facebook 授权链接 2、…...

在Vue或React项目中使用Tailwind CSS实现暗黑模式切换:从系统适配到手动控制
在现代Web开发中,暗黑模式(Dark Mode)已成为提升用户体验的重要功能。本文将带你使用Tailwind CSS在React项目(Vue项目类似)中实现两种暗黑模式控制方式: 系统自动适配 - 根据用户设备偏好自动切换手动切换 - 通过按钮让用户自由选择 一、项目准备 使…...
OD 算法题 B卷【水果摊小买卖】
文章目录 水果摊小买卖 水果摊小买卖 小王手里有点闲钱,想做点水果买卖,给出两个数组m, n, m[i]表示第i个水果的成本价,n[i]表示第i个水果能卖出的价格;假如现在有本钱k,试问最后最多能赚多少钱࿱…...
DAY 48 随机函数与广播机制
知识点回顾: 随机张量的生成:torch.randn函数卷积和池化的计算公式(可以不掌握,会自动计算的)pytorch的广播机制:加法和乘法的广播机制 ps:numpy运算也有类似的广播机制,基本一致 作…...
Java八股文——MySQL篇
文章目录 Java八股文——MySQL篇慢查询如何定位慢查询?如何分析慢SQLExplain标准答案 索引索引类型索引底层数据结构什么是聚簇索引什么是非聚簇索引?(二级索引)(回表)聚集索引选取规则回表查询 什么是覆盖…...

【设计模式-5】设计模式的总结
说明:介绍完所有的设计模式,本文做一下总结 设计模式介绍 博主写的设计模式博客如下: 【设计模式-1】UML和设计原则 【设计模式-2.1】创建型——单例模式 【设计模式-2.2】创建型——简单工厂和工厂模式 【设计模式-2.3】创建型——原型…...

链表好题-多种实现
143. 重排链表 - 力扣(LeetCode) 这道题非常经典,很多大厂都作为面试题。 方法一:寻找中点翻转链表合并链表 class Solution { public:void reorderList(ListNode* head) {if (head nullptr) {return;}ListNode* mid middleNo…...