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

news2024/11/25 10:34:34

在游戏引擎场景中的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 = Fdt
        vertexVelocities[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 = Fdt
        vertexVelocities[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.coloradmin.cn/o/2247234.html

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

相关文章

【ArcGISPro】根据yaml构建原始Pro的conda环境

使用场景 我们不小心把原始arcgispro-py3的conda环境破坏了,我们就可以使用以下方法进行修复 查找文件 在arcgis目录下找到yaml文件 如果没找到请复制以下内容到新的yaml文件 channels: - esri - defaults dependencies: - anyio=4.2.0=py311haa95532_0 - appdirs=1.4.4=p…

【Y20030007】基于java+servlet+mysql的垃圾分类网站的设计与实现(附源码 配置 文档)

网垃圾分类网站的设计与实现 1.摘要2.开发目的和意义3.系统功能设计4.系统界面截图5.源码获取 1.摘要 随着全球环境保护意识的提升&#xff0c;垃圾分类已成为一项紧迫且重要的任务。为了有效推动垃圾分类的实施&#xff0c;提升公众的环保意识和参与度&#xff0c;垃圾分类已…

【Python爬虫五十个小案例】爬取豆瓣电影Top250

博客主页&#xff1a;小馒头学python 本文专栏: Python爬虫五十个小案例 专栏简介&#xff1a;分享五十个Python爬虫小案例 &#x1fab2;前言 在这篇博客中&#xff0c;我们将学习如何使用Python爬取豆瓣电影Top250的数据。我们将使用requests库来发送HTTP请求&#xff0c;…

C++ 优先算法 —— 长度最小的子数组(滑动窗口)

目录 题目&#xff1a;长度最小的子数组 1. 题目解析 2. 算法原理 Ⅰ. 暴力枚举 Ⅱ. 滑动窗口&#xff08;同向双指针&#xff09; 滑动窗口正确性 3. 代码实现 Ⅰ. 暴力枚举(会超时&#xff09; Ⅱ. 滑动窗口&#xff08;同向双指针&#xff09; 题目&#xff1a;长…

C++设计模式——Singleton单例模式

一、单例模式的定义 单例模式&#xff0c;英文全称Singleton Pattern&#xff0c;是一种创建型设计模式&#xff0c;它保证一个类在程序中仅有一个实例&#xff0c;并对外提供一个访问的该类实例的全局接口。 单例模式通常用于需要控制对象资源的开发场景&#xff0c;一个类…

【Linux系统】—— 基本指令(三)

【Linux系统】—— 基本指令&#xff08;三&#xff09; 1 一切皆文件2 重定向操作2.1 初始重定向2.2 重定向的妙用2.3 追加重定向2.4 输入重定向2.5 一切皆文件与重定向结合 3 Linux 中的文件类型4 日志5 「more」命令6 「less」命令7 「head」与「tail」7.1 查看文件开头和结…

探索 Python 任务自动化的新境界:Invoke 库揭秘

文章目录 探索 Python 任务自动化的新境界&#xff1a;Invoke 库揭秘背景&#xff1a;为何选择 Invoke&#xff1f;什么是 Invoke&#xff1f;如何安装 Invoke&#xff1f;5个简单的库函数使用方法1. 定义任务2. 带参数的任务3. 运行 Shell 命令4. 任务参数化5. 列出任务 场景应…

【C++】list模拟实现(详解)

本篇来详细说一下list的模拟实现&#xff0c;list的大体框架实现会比较简单&#xff0c;难的是list的iterator的实现。我们模拟实现的是带哨兵位头结点的list。 1.准备工作 为了不和C库里面的list冲突&#xff0c;我们在实现的时候用命名空间隔开。 //list.h #pragma once #…

shell脚本(6)

声明&#xff1a;学习视频来自b站up主 泷羽sec&#xff0c;如涉及侵权马上删除文章 感谢泷羽sec 团队的教学 视频地址&#xff1a;shell(6)if条件判断与for循环结构_哔哩哔哩_bilibili 本文主要讲解shell脚本中的if条件判断和for循环结构。 一、if语句 Shell 脚本中的 if 语句…

JavaScript基础 document.write()方法

JavaScript基础 document.write方法 1.简单认识document.write()2.document.write() 的使用 1.简单认识document.write() document.write() 是一种 JavaScript 方法&#xff0c;用于将内容直接写入到 HTML 文档中。它可以用来动态地在页面加载时插入文本、HTML 代码、图片等内…

Linux笔记---进程:进程切换与O(1)调度算法

1. 补充概念 1.1 并行与并发 竞争性&#xff1a;系统进程数目众多&#xff0c;而CPU资源只有少量&#xff0c;甚至只有1个&#xff0c;所以进程之间是具有竞争属性的。为了高效完成任务&#xff0c;更合理竞争相关资源&#xff0c;便具有了优先级。独立性&#xff1a;多进程运…

使用ENSP实现浮动静态路由

一、项目拓扑 二、项目实现 1.路由器AR1配置 进入系统试图 sys将路由器命名为R1 sysname R1关闭信息中心 undo info-center enable 进入g0/0/0接口 int g0/0/0将g0/0/0接口IP地址配置为1.1.1.1/24 ip address 1.1.1.1 24进入g0/0/1接口 int g0/0/1将g0/0/1接口IP地址配置为2.…

GoF设计模式——结构型设计模式分析与应用

文章目录 UML图的结构主要表现为&#xff1a;继承&#xff08;抽象&#xff09;、关联 、组合或聚合 的三种关系。1. 继承&#xff08;抽象&#xff0c;泛化关系&#xff09;2. 关联3. 组合/聚合各种可能的配合&#xff1a;1. 关联后抽象2. 关联的集合3. 组合接口4. 递归聚合接…

【论文复现】深度知识追踪

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀ 深度知识追踪 1. 论文概述2. 论文方法3. 实验部分3.1 数据集3.2 实验步骤3.3 实验结果 4 关键代码 1. 论文概述 知识追踪的任务是对学生的知…

Linux: 进程地址空间(理解虚拟地址和页表)

目录 1. 虚拟地址 2. 进程地址空间分布 3. 描述进程地址空间 4. 内存管理——页表 5. 父子进程的虚拟地址关系 6. 页表标记位 6.1 读写权限 6.2 命中权限 7.为什么存在进程地址空间 1. 虚拟地址 #include <stdio.h> #include <unistd.h> #include <sy…

C语言:深入理解指针

一.内存和地址 我们知道计算机上CPU&#xff08;中央处理器&#xff09;在处理数据的时候&#xff0c;需要的数据是在内存中读取的&#xff0c;处理后的数据也会放回内存中&#xff0c;那我们买电脑的时候&#xff0c;电脑上内存是 8GB/16GB/32GB 等&#xff0c;那这些内存空间…

transformer.js(一):这个前端大模型运行框架的可运行环境、使用方式、代码示例以及适合与不适合的场景

随着大模型的广泛应用&#xff0c;越来越多的开发者希望在前端直接运行机器学习模型&#xff0c;从而减少对后端的依赖&#xff0c;并提升用户体验。Transformer.js 是一个专为前端环境设计的框架&#xff0c;它支持运行基于 Transformer 架构的深度学习模型&#xff0c;尤其是…

uni-app 发布媒介功能(自由选择媒介类型的内容) 设计

1.首先明确需求 我想做一个可以选择媒介的内容&#xff0c;来进行发布媒介的功能 &#xff08;媒介包含&#xff1a;图片、文本、视频&#xff09; 2.原型设计 发布-编辑界面 通过点击下方的加号&#xff0c;可以自由选择添加的媒介类型 但是因为预览中无法看到视频的效果&…

行业分析---2024年小鹏汽车AI Day及三季度财报

1 背景 在之前的博客中&#xff0c;笔者撰写了多篇行业类分析的文章&#xff08;科技新能源&#xff09;&#xff1a; 《行业分析---我眼中的Apple Inc.》 《行业分析---马斯克的Tesla》 《行业分析---造车新势力之蔚来汽车》 《行业分析---造车新势力之小鹏汽车》 《行业分析-…

数据可视化复习1-Matplotlib简介属性和创建子图

1.Matplotlib简介 Matplotlib是一个Python的2D绘图库&#xff0c;它可以在各种平台上以各种硬拷贝格式和交互环境生成具有出版品质的图形。通过Matplotlib&#xff0c;开发者可以仅需要几行代码&#xff0c;便可以生成绘图、直方图、功率谱、条形图、错误图、散点图等。 以下…