物体网格弹性变形---Unity中实现

在游戏引擎场景中的3D物体是由一定数量的点、面组成的,如下图:

要使这些物体变形就是改变3D物体每个顶点状态。

1.首先在Unity场景中增加一个球体,如下图

3D组件默认拥有MeshFilter、meshRenderer、Collider组件,分别用来获取Mesh顶点、渲染物体、返回射线碰撞位置信息

新建物体形变脚本MeshDeformer,并在游戏开始时缓存形变的网格和顶点信息,新建完成后,将脚本挂载到要形变的物体上。

public class MeshDeformer : MonoBehaviour
{//需要变形的mesh网格Mesh deformingMesh;//顶点原始位置,移动后的顶点位置Vector3[] originalVertices, displacedVertices;void Start(){//获取变形网格deformingMesh = GetComponent<MeshFilter>().mesh;//获取变形网格的所有顶点位置originalVertices = deformingMesh.vertices;displacedVertices = new Vector3[originalVertices.Length];for (int i = 0; i < originalVertices.Length; i++){displacedVertices[i] = originalVertices[i];}}
}

新建输入脚本MeshFormerInput,并在Update函数中检测输入

public class MeshDeformerInput : MonoBehaviour
{void Update(){if (Input.GetMouseButton(0)){HandleInput();}}void HandleInput(){//获得从相机位置往鼠标点击屏幕点方向的射线Ray inputRay = Camera.main.ScreenPointToRay(Input.mousePosition);RaycastHit hit;if (Physics.Raycast(inputRay, out hit)){MeshDeformer deformer = hit.collider.GetComponent<MeshDeformer>();if (deformer){Debug.Log(deformer);}}}
}

 如果一切顺利则,在场景中点击物体就会在Unity控制台中打印获取到的组件信息

在MeshDeformer脚本中增加施加力的作用效果的方法,

    /// <summary>/// 给形变物体施加力/// </summary>/// <param name="point"></param>/// <param name="force"></param>public void AddDeformingForce(Vector3 point, float force){for (int i = 0; i < displacedVertices.Length; i++){//施加力到顶点AddForceToVertex(i, point, force);}}/// <summary>/// 给某个顶点添加力,将力转化为顶点的速度/// </summary>/// <param name="i"></param>/// <param name="point"></param>/// <param name="force"></param>void AddForceToVertex(int i, Vector3 point, float force){}

网格变形是因为对其每个顶点施加了力。当顶点被推时,它们会获得速度。随着时间的推移,顶点都会改变它们的位置。如果所有顶点都受到完全相同的力,则整个对象将移动而不改变其形状,所以我们需要知道每个顶点的变形力的方向和距离,两者都可以从指向力点到顶点位置的向量中导出。使用平方反比定律找到衰减的力,只需将原始力除以距离的平方,就可以得到衰减的力。 

    /// <summary>/// 给某个顶点添加力,将力转化为顶点的速度/// </summary>/// <param name="i"></param>/// <param name="point"></param>/// <param name="force"></param>void AddForceToVertex(int i, Vector3 point, float force){//计算施加的力Vector3 pointToVertex = displacedVertices[i] - point;//实际上,如果只用F/d*d,再d=0时,衰减的力会变成无穷大,所以除以 1 加上距离的平方,保证了当距离为零时力处于全强度状态。float attenuatedForce = force / (1f + pointToVertex.sqrMagnitude);float velocity = attenuatedForce * Time.deltaTime;//a = F/m,忽略每个质点的质量,将质量都设为1,则dv = FdtvertexVelocities[i] += pointToVertex.normalized * velocity;}

计算了每个顶点的速度接下来,在MeshDeformer脚本的Update方法中移动顶点

   void Update(){for (int i = 0; i < displacedVertices.Length; i++){UpdateVertex(i);}deformingMesh.vertices = displacedVertices;deformingMesh.RecalculateNormals();}/// <summary>/// 更新顶点/// </summary>/// <param name="i"></param>void UpdateVertex(int i){Vector3 velocity = vertexVelocities[i];displacedVertices[i] += velocity * Time.deltaTime;}

一切顺利会得到以下效果: 

增加弹力和阻尼:在MeshFormer.cs中的UpdateVertex中增加弹力和阻尼的计算

   /// <summary>/// 更新顶点/// </summary>/// <param name="i"></param>void UpdateVertex(int i){Vector3 velocity = vertexVelocities[i];//胡克定律 F = -kx,k是常数,是物体的劲度系数(倔强系数)(弹性系数)x是弹簧的伸长量(或压缩量)//x = displacedVertices[i] - originalVertices[i]//F = -springForce * displacement; Vector3 displacement = displacedVertices[i] - originalVertices[i]; velocity -= displacement * springForce * Time.deltaTime;vertexVelocities[i] = velocity;//通过不断减慢顶点的速度来防止这种永恒的振荡。此阻尼效果可替代阻力、阻力、惯性等//阻尼越高,对象的弹性就越小,看起来越迟缓。//v = velocity(1-damping)velocity *= 1f - damping * Time.deltaTime;displacedVertices[i] += velocity * Time.deltaTime;}

最后处理:

现在的变形体是放在原点的,而变形体的顶点坐标都是模型坐标系的本地坐标,我们通过射线碰碰撞得到的着力点是在世界坐标系下,因此我们需要将二者变换到同一坐标系下进行力的计算。

   /// <summary>/// 给形变物体施加力/// </summary>/// <param name="point"></param>/// <param name="force"></param>public void AddDeformingForce(Vector3 point, float force){point = transform.InverseTransformPoint(point);Debug.DrawLine(Camera.main.transform.position, point);for (int i = 0; i < displacedVertices.Length; i++){AddForceToVertex(i, point, force);}}

物体放缩后,顶点之间的距离会相应的变大或者缩小,如下图:一个球体没有放大和放大两倍的时候的顶点位置。

由上在变形体放缩后需要调整一下每两个顶点之间的作用力,否则用平方反比的计算出来的力,在不同的放缩下,大小会有不同,因此,需要变化三个地方,一个是施加在顶点上的力需要放缩,一个是相互作用力的距离计算时需要放缩,最后一个是顶点移动的距离需要放缩。

最终效果:

完整代码:

public class MeshDeformerInput : MonoBehaviour
{//施加的力public float force = 10f;//力的偏移public float forceOffset = 0.1f;void Update(){if (Input.GetMouseButton(0)){HandleInput();}}void HandleInput(){//获得从相机位置往鼠标点击屏幕点方向的射线Ray inputRay = Camera.main.ScreenPointToRay(Input.mousePosition);RaycastHit hit;if (Physics.Raycast(inputRay, out hit)){MeshDeformer deformer = hit.collider.GetComponent<MeshDeformer>();Debug.Log(deformer);if (deformer){Vector3 point = hit.point;point += hit.normal * forceOffset;deformer.AddDeformingForce(point, force);}}}
}
public class MeshDeformer : MonoBehaviour
{//需要变形的mesh网格Mesh deformingMesh;//顶点原始位置,移动后的顶点位置Vector3[] originalVertices, displacedVertices;//顶点的速度Vector3[] vertexVelocities;//弹力public float springForce = 20f;//阻尼public float damping = 5f;//放缩比例float uniformScale = 1f;void Start(){//获取变形网格deformingMesh = GetComponent<MeshFilter>().mesh;//获取变形网格的所有顶点位置originalVertices = deformingMesh.vertices;displacedVertices = new Vector3[originalVertices.Length];for (int i = 0; i < originalVertices.Length; i++){displacedVertices[i] = originalVertices[i];}vertexVelocities = new Vector3[originalVertices.Length];}/// <summary>/// 给形变物体施加力/// </summary>/// <param name="point"></param>/// <param name="force"></param>public void AddDeformingForce(Vector3 point, float force){point = transform.InverseTransformPoint(point);Debug.DrawLine(Camera.main.transform.position, point);for (int i = 0; i < displacedVertices.Length; i++){AddForceToVertex(i, point, force);}}/// <summary>/// 给某个顶点添加力,将力转化为顶点的速度/// </summary>/// <param name="i"></param>/// <param name="point"></param>/// <param name="force"></param>void AddForceToVertex(int i, Vector3 point, float force){//计算施加力的方向Vector3 pointToVertex = displacedVertices[i] - point;pointToVertex *= uniformScale;//实际上,如果只用F/d*d,再d=0时,衰减的力会变成无穷大,所以除以 1 加上距离的平方,保证了当距离为零时力处于全强度状态。float attenuatedForce = force / (1f + pointToVertex.sqrMagnitude);float velocity = attenuatedForce * Time.deltaTime;//a = F/m,忽略每个质点的质量,将质量都设为1,则dv = FdtvertexVelocities[i] += pointToVertex.normalized * velocity;}void Update(){uniformScale = this.transform.localScale.x;for (int i = 0; i < displacedVertices.Length; i++){UpdateVertex(i);}deformingMesh.vertices = displacedVertices;deformingMesh.RecalculateNormals();}/// <summary>/// 更新顶点/// </summary>/// <param name="i"></param>void UpdateVertex(int i){Vector3 velocity = vertexVelocities[i];//胡克定律 F = -kx,k是常数,是物体的劲度系数(倔强系数)(弹性系数)x是弹簧的伸长量(或压缩量)//x = displacedVertices[i] - originalVertices[i]//F = -springForce * displacement; Vector3 displacement = displacedVertices[i] - originalVertices[i];displacement *= uniformScale;velocity -= displacement * springForce * Time.deltaTime;vertexVelocities[i] = velocity;//通过不断减慢顶点的速度来防止这种永恒的振荡。此阻尼效果可替代阻力、阻力、惯性等//阻尼越高,对象的弹性就越小,看起来越迟缓。//v = velocity(1-damping)velocity *= 1f - damping * Time.deltaTime;displacedVertices[i] += velocity * Time.deltaTime / uniformScale;}
}

参考链接:

网格变形,Unity C# 教程 (catlikecoding.com)

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java爬虫:获取商品详情的实践之旅

在当今这个信息爆炸的时代&#xff0c;数据的价值日益凸显。对于电商行业来说&#xff0c;商品详情的获取尤为重要&#xff0c;它不仅关系到产品的销售&#xff0c;还直接影响到用户体验。传统的人工获取方式耗时耗力&#xff0c;而自动化的爬虫技术则提供了一种高效解决方案。…

【LLM】一文学会SPPO

博客昵称&#xff1a;沈小农学编程 作者简介&#xff1a;一名在读硕士&#xff0c;定期更新相关算法面试题&#xff0c;欢迎关注小弟&#xff01; PS&#xff1a;哈喽&#xff01;各位CSDN的uu们&#xff0c;我是你的小弟沈小农&#xff0c;希望我的文章能帮助到你。欢迎大家在…

腾讯云 AI 代码助手:产品研发过程的思考和方法论

一、文章摘要 本文将详细阐述 腾讯云 AI 代码助手的历史发展形态与产品整体架构&#xff0c;并从技术、研发方法论的角度分别阐述了产品的研发过程。 全文阅读约 5&#xff5e;8 分钟。 二、产品布局 AI 代码助手产品经历了三个时代的发展 第一代诸如 Eclipse、Jetbrains、V…

RabbitMQ实现异步下单与退单

前言&#xff1a; 在电商项目中的支付模块也是一个很重要的模块&#xff0c;其中下订操作以及退订操作就是主要的操作。其次的下单是同步下单&#xff0c;也就是第三方支付、数据库扣减、积分增加、等等其他业务操作&#xff0c;等待全部执行完毕后向用户返回成功响应请求。对…

SQL99版全外连接和交叉连接和总结

全外连接MySQL不支持 elect 查询列表 from 表名1 表别名1 cross join 表名2 表别名2 on 连接条件 ...... ; 交叉连接 就两个记录做笛卡尔积&#xff01;没什么好说的&#xff0c;基本也没用过&#xff01; 总结

从〇开始深度学习(0)——背景知识与环境配置

从〇开始深度学习(0)——背景知识与环境配置 文章目录 从〇开始深度学习(0)——背景知识与环境配置写在前面1.背景知识1.1.Pytorch1.2.Anaconda1.3.Pycharm1.4.CPU与GPU1.5.整体关系 2.环境配置2.1.准备工作2.1.1.判断有无英伟达显卡2.1.2.清理电脑里的旧环境 2.1.安装Anaconda…

PHP屏蔽海外IP的访问页面(源代码实例)

PHP屏蔽海外IP的访问页面&#xff08;源代码实例&#xff09;&#xff0c;页面禁用境外IP地址访问 <?php/*** 屏蔽海外ip访问* 使用ip2long函数得到ip转为整数的值&#xff0c;判断值是否在任一一个区间中* 以下是所有国内ip段* 调用方法&#xff1a;IschinaIp($ALLIPS)* …

“iOS profile文件与私钥证书文件不匹配”总结打ipa包出现的问题

目录 文件和证书未加载或特殊字符问题 证书过期或Profile文件错误 确认开发者证书和私钥是否匹配 创建证书选择错误问题 申请苹果 AppId时勾选服务不全问题 ​总结 在上线ios平台的时候&#xff0c;在Hbuilder中打包遇见了问题&#xff0c;生成ipa文件时候&#xff0c;一…

VUE 的前置知识

一、JavaScript----导图导出 1. JS 提供的导入导出机制&#xff0c;可以实现按需导入 1.1 在html页面中可以把JS文件通过 <script src"showMessage.js"></script> 全部导入 1.2 通过在JS文件中写export关键字导出通过 <script src"showMessage…

量子卷积神经网络

量子神经网络由量子卷积层、量子池化层和量子全连接层组成 量子卷积层和量子池化层交替放置&#xff0c;分别实现特征提取和特征降维&#xff0c;之后通过量子全连接层进行特征综合 量子卷积层、量子池化层和量子全连接层分别由量子卷积单元、量子池化单元和量子全连接单元组…

学习编程,学习中间件,学习源码的思路

01 看的多&#xff0c;内化不足 最近想复习一下编程相关的知识&#xff0c;在复习前我翻开了之前的一些笔记&#xff0c;这些笔记基本都是从书本、视频、博客等摘取记录的&#xff0c;看着这些笔记心里总结&#xff1a;看的多&#xff0c;内化不足。 02 整理大纲 为了解决这个…

MyBatis框架

1. 什么是MyBatis框架 MyBatis框架是一个优秀的持久层框架&#xff0c;为了简化JDBC开发。传统的JDBC编程编写起来很麻烦。 MyBatis框架使用了数据库连接池技术&#xff0c;避免了频繁的创建和销毁操作。 初始情况下&#xff0c;数据库连接池会默认创建一定数量的connection对…

IDEA配置本地maven

因为idea和maven是没有直接关系的。所以使用idea创建maven工程之前需要将本地的maven配置到idea环境中&#xff0c;这样才可以在idea中创建maven工程。配置方法如下&#xff1a; 1.1 配置本地maven 第一步&#xff1a;关闭当前工程&#xff0c;回到idea主界面找到customize--…

论文阅读——Intrusion detection systems using longshort‑term memory (LSTM)

一.基本信息 论文名称&#xff1a;Intrusion detection systems using longshort‑term memory (LSTM) 中文翻译&#xff1a;基于长短期记忆(LSTM)的入侵检测系统 DOI&#xff1a;10.1186/s40537-021-00448-4 作者&#xff1a;FatimaEzzahra Laghrissi1* , Samira Douzi2*, Kha…

企业OA管理系统:Spring Boot技术实现与案例研究

摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了企业OA管理系统的开发全过程。通过分析企业OA管理系统管理的不足&#xff0c;创建了一个计算机管理企业OA管理系统的方案。文章介绍了企业OA管理系统的系统分析部…

递归算法专题一>Pow(x, n)

题目&#xff1a; 解析&#xff1a; 代码&#xff1a; public double myPow(double x, int n) {return n < 0 ? 1.0 / pow(x,-n) : pow(x,n); }private double pow(double x, int n){if(n 0) return 1.0;double tmp pow(x,n / 2);return n % 2 0 ? tmp * tmp : tmp …

阿里云私服地址

1.解压apache-maven-3.6.1-bin 2.配置本地仓库&#xff1a;修改conf/dettings.xml中的<localReoisitory>为一个指定目录。56行 <localRepository>D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\mvn_repo</localRepository> 3.配置阿里云私服&#xff1a;…

基于之前的秒杀功能的优化(包括Sentinel在SpringBoot中的简单应用)

这篇博客主要是对自己之前写的博客的一次优化&#xff0c;可以结合下面两篇博客进行这篇博客的阅读&#xff1a; 对自己关于秒杀功能的一次访谈与实战-CSDN博客 SpringBoot中使用Sharding-JDBC实战&#xff08;实战版本兼容Bug解决&#xff09;-CSDN博客 开始正题&#xff1a…

Redis Search系列 - 第七讲 Windows(CygWin)编译Friso

目录 一、背景二、安装CygWin三、编译Friso四、运行Friso五、Friso分词效果测试 一、背景 最近在做RedisSearch的中文分词效果调研&#xff0c;底层的中文分词插件使用的就是Friso&#xff0c;目前手里的Linux环境上yum镜像仓库有问题导致没法安装gcc&#xff0c;又急于验证Fr…

(动画)Qt控件 QLCDNumer

文章目录 LCD Number1. 介绍2. 核心属性3 . 代码实现:倒计时1. 在界⾯上创建⼀个 QLCDNumber , 初始值设为 10.2. 修改 widget.h 代码, 创建⼀个 QTimer 成员, 和⼀个 updateTime 函数3. 修改 widget.cpp, 在构造函数中初始化 QTimer4. 修改 widget.cpp, 实现 updateTime 4. 动…