Unity——网格变形(制作一个压力球)

news2025/6/20 7:58:45

主要参考链接:Mesh Deformation, a Unity C# Tutorial(本文为其翻译版)

unity项目下载链接:https://download.csdn.net/download/weixin_43042683/87679832

  • 在物体上投射射线并画出调试线。
  • 将力转换为顶点的速度。
  • 用弹簧和阻尼保持形状。
  • 补偿物体变形。

本教程是一个关于网格变形的介绍。我们将把一个网格变成有弹性的质量,并对其进行戳穿。它适用于Unity 5.0.1及以上版本。

1. 场景设置

我们从一个场景开始,这个场景的中心有一个单一的立方体球体对象。

为了得到一个平滑的变形,球体应该包含相当数量的顶点。把球体的网格大小设置为20,半径为1。

从一个普通的立方体球体开始

2. 网格变形器

创建一个新的MeshDeformer脚本来处理变形问题。就像立方体球体组件一样,它需要一个网格过滤器来工作。

using UnityEngine;

[RequireComponent(typeof(MeshFilter))]
public class MeshDeformer : MonoBehaviour {
}

将新的组件添加到立方体球体中。

带有网格变形器组件的立方体球体

 请注意,我们只需要一个网格过滤器。我们并不关心它是如何得到一个网格的。现在我们使用的是程序化的立方体球体,但它可以是任何网格。

2.1 准备工作

要进行变形,我们需要访问网格。一旦我们有了网格,我们就可以提取原始顶点的位置。在变形过程中,我们还需要跟踪位移的顶点。

	Mesh deformingMesh;
	Vector3[] originalVertices, displacedVertices;

在Start方法中获取网格及其顶点,并将原始顶点复制到位移顶点上。

	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];
		}
	}

我们使用Start,所以程序性网格可以在Awake中生成,Awake总是被首先调用的。这种方法依赖于其他组件在Awake中处理他们的事情,所以它不能保证一定被首先调用。你也可以调整脚本的执行顺序来强制执行谁先谁后。

2.2 顶点速度

顶点会随着网格的变形而移动。所以我们也必须存储每个顶点的速度。

	Vector3[] vertexVelocities;

	void Start () {
		…
		vertexVelocities = new Vector3[originalVertices.Length];
	}

现在我们有了支持网格变形的基本要素。

3. 网格变形器的输入

我们需要一些方法来控制网格的变形方式。我们将使用用户的输入,所以它是互动的。每当用户接触到我们的物体时,我们将在该点施加一个力。

MeshDeformer组件负责实际的变形,但它并不关心输入方法。我们应该创建一个单独的组件来处理用户输入的问题。给它一个可配置的输入力。

using UnityEngine;

public class MeshDeformerInput : MonoBehaviour {

	public float force = 10f;
}

把这个组件附加到摄像机上是最合理的,因为它代表了用户的视角。我们不应该把它附加到变形网格对象上,因为场景中可能有多个变形网格。

网格变形器输入连接到相机

3.1  检测输入

只要默认的鼠标按钮被按住,我们就会处理用户的输入。因此,只要有点击或拖动,就认为用户一直按着方形球。

	void Update () {
		if (Input.GetMouseButton(0)) {
			HandleInput();
		}
	}

现在我们必须弄清楚用户的指向。我们通过从摄像机向场景中投射一条射线来完成这个任务。我们将抓取场景中的主摄像机,并使用它来将光标位置转换为射线。

	void HandleInput () {
		Ray inputRay = Camera.main.ScreenPointToRay(Input.mousePosition);
	}

我们使用物理引擎来投射射线并存储它所击中的信息。如果射线撞到了什么东西,我们可以从被撞到的物体中获取MeshDeformer组件。

		Ray inputRay = Camera.main.ScreenPointToRay(Input.mousePosition);
		RaycastHit hit;

		if (Physics.Raycast(inputRay, out hit)) {
			MeshDeformer deformer = hit.collider.GetComponent<MeshDeformer>();
		}

Physics.Raycast是如何工作的?
Physics.Raycast是一个静态的方法,用于将射线投射到3D场景中。它有各种不同的变体。最简单的版本有一个射线参数,并返回它是否击中了什么。
我们所使用的版本有一个额外的参数。它是一个类型为RaycastHit的输出参数。这是一个结构,包含关于被击中的东西和接触点的信息。

3.2 施加力

 如果我们撞到了什么东西,而那个东西有一个MeshDeformer组件,那么我们就可以对那个东西进行变形!所以请继续在接触点添加一个变形力。

			MeshDeformer deformer = hit.collider.GetComponent<MeshDeformer>();
			if (deformer) {
				Vector3 point = hit.point;
				deformer.AddDeformingForce(point, force);
			}

当然这要假设我们的MeshDeformer组件有一个AddDeformingForce方法。所以要添加这个方法。不过,我们先不要做任何变形。首先,从主摄像机到该点画一条调试线,以使射线可视化。

	public void AddDeformingForce (Vector3 point, float force) {
		Debug.DrawLine(Camera.main.transform.position, point);
	}
在场景视图中调试光线

我在哪里可以看到调试线?
它显示在场景视图中,所以在游戏模式下,你必须保持游戏视图和场景视图都是可见的。 

3.3 力量偏移

我们试图唤起的体验是,网格被用户捅破了,凹陷了。这就要求靠近接触点的顶点被推到表面。然而,这个变形力并没有一个固有的方向。它将在所有方向上平等地施加。这将导致平面上的顶点被推开,而不是被推入。

我们可以通过把力点从曲面上拉开来增加一个方向。一个轻微的偏移已经保证了顶点总是被推入曲面。接触点的法线可以作为偏移方向。 

用偏移量改变力的方向。
	public float forceOffset = 0.1f;

	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) {
				Vector3 point = hit.point;
				point += hit.normal * forceOffset;
				deformer.AddDeformingForce(point, force);
			}
		}
	}
力点稍稍远离物体

4. 基本变形

现在是时候做一些真正的位移了。MeshDeformer.AddDeformingForce必须循环浏览所有当前位移的顶点,并对每个顶点单独施加变形力。

	public void AddDeformingForce (Vector3 point, float force) {
		for (int i = 0; i < displacedVertices.Length; i++) {
			AddForceToVertex(i, point, force);
		}
	}

	void AddForceToVertex (int i, Vector3 point, float force) {
	}

4.1 将力转换为速度

网格之所以变形,是因为每个顶点都受到了力的作用。当顶点被推动时,它们会获得一个速度。随着时间的推移,这些顶点都会改变它们的位置。如果所有顶点都经历完全相同的力,整个物体就会移动而不改变其形状。

想想看,一个大爆炸。如果你在地面上,你就会死。如果你在附近,你会被击倒。如果你在远处,就没有问题。力量随着距离的增加而减弱。结合方向上的差异,这种衰减是造成我们物体变形的原因。

所以我们需要知道每个顶点的变形力的方向和距离。两者都可以从一个从力点指向顶点位置的矢量中得到。

	void AddForceToVertex (int i, Vector3 point, float force) {
		Vector3 pointToVertex = displacedVertices[i] - point;
	}

现在可以用反平方定律找到衰减的力。只要用原力除以距离的平方就可以了,F_{v}=\frac{F}{d^{2}} 。实际上,我除以1加上距离的平方,F_{v}=\frac{F}{d^{2}+1}。这就保证了当距离为零时,力是全开的。否则,当距离为1时,力就会处于全盛状态,而当你越接近该点时,它就会向无穷远处射去。

        Vector3 pointToVertex = displacedVertices[i] - point;
		float attenuatedForce = force / (1f + pointToVertex.sqrMagnitude);

现在我们有了我们的力,我们可以把它转换为速度Δ。实际上,这个力首先通过以下方式转换为一个加速度a=F/m  那么速度的变化可以通过以下方式找到Δv=aΔt,为了简单起见,我们将忽略质量,就像每个顶点都是一个一样。因此,我们最终会得到Δv=FΔt。

		Vector3 pointToVertex = displacedVertices[i] - point;
		float attenuatedForce = force / (1f + pointToVertex.sqrMagnitude);
		float velocity = attenuatedForce * Time.deltaTime;

在这一点上,我们有一个速度Δ,但还没有一个方向。我们通过对开始时的矢量进行归一化来找到它。然后我们可以把结果加到顶点速度上。

		Vector3 pointToVertex = displacedVertices[i] - point;
		float attenuatedForce = force / (1f + pointToVertex.sqrMagnitude);
		float velocity = attenuatedForce * Time.deltaTime;
		vertexVelocities[i] += pointToVertex.normalized * velocity;

4.2 移动顶点

现在,顶点有了速度,我们可以移动它们。添加一个更新方法来处理每个顶点。之后,将位移顶点分配给网格,使其实际发生变化。因为网格的形状不再是恒定的,我们也必须重新计算它的法线。

	void Update () {
		for (int i = 0; i < displacedVertices.Length; i++) {
			UpdateVertex(i);
		}
		deformingMesh.vertices = displacedVertices;
		deformingMesh.RecalculateNormals();
	}

更新一个顶点是一个调整其位置的问题,通过Δp=vΔt。

	void UpdateVertex (int i) {
		Vector3 velocity = vertexVelocities[i];
		displacedVertices[i] += velocity * Time.deltaTime;
	}

顶点是否一直在更新?
是的,每次更新时,所有顶点都会被移位,分配给网格,法线也会重新计算。即使在没有施加任何力的情况下。如果用户没有对网格进行变形,那么可以认为是在浪费时间。所以只有在需要不断地使网格变形的时候才使用这个功能。

累积速度

 5. 保持体形

现在,只要我们对它们施加一些力,顶点就开始移动。但它们不会停止。它们继续移动,物体的原始形状就会消失。现在让我们使物体反弹到它的原始形状。

真实的物体是固体,在变形时被压缩和拉伸。它们能抵抗这种变形。一旦不受干扰,它们也能恢复到原来的形状。

我们没有一个真正的体积,只是一个描述表面的顶点集合。我们不能用它来进行现实的物理模拟。但这并不是一个问题。我们真正需要的是看起来可信的东西。

5.1 弹簧

我们同时跟踪每个顶点的原始位置和变形位置。想象一下,我们在每个顶点的两个位置之间附加弹簧。每当变形顶点远离原始顶点时,弹簧会把它拉回来。变形顶点离得越远,弹簧的拉力就越大.

标移位的顶点被拉回题

我们可以直接使用位移矢量作为速度调整,乘以一个可配置的弹簧力。这很简单,看起来也很不错。我们在每次更新顶点的时候都会这样做。

	public float springForce = 20f;
	
	void UpdateVertex (int i) {
		Vector3 velocity = vertexVelocities[i];
		Vector3 displacement = displacedVertices[i] - originalVertices[i];
		velocity -= displacement * springForce * Time.deltaTime;
		vertexVelocities[i] = velocity;
		displacedVertices[i] += velocity * Time.deltaTime;
	}

变形后反弹

5.2 衰减

我们的顶点现在可以抵抗变形并跳回原来的位置。但它们会过冲,一直无休止地跳动。发生这种情况是因为弹簧在顶点自我修正时不断拉动,增加了它的速度。只有在它向后移动太远之后才会减慢速度。

我们可以通过不断减缓顶点的速度来防止这种永恒的振荡。这种阻尼效应可以替代阻力、惯性等等。它是一个简单的因素,随着时间的推移,速度会降低,v_{d}=v(1-d\Delta t)

阻尼越高,物体的弹性就越小,反应也就越迟钝。

	public float damping = 5f;
	
	void UpdateVertex (int i) {
		Vector3 velocity = vertexVelocities[i];
		Vector3 displacement = displacedVertices[i] - originalVertices[i];
		velocity -= displacement * springForce * Time.deltaTime;
		velocity *= 1f - damping * Time.deltaTime;
		vertexVelocities[i] = velocity;
		displacedVertices[i] += velocity * Time.deltaTime;
	}

恢复到原来的形状

6. 处理转换

我们的网格变形现在是完全有效的,除了当我们变换物体时。我们所有的计算都是在局部空间进行的。继续前进,移动或旋转我们的球体。你会看到变形力将被错误地应用。

我们必须对物体的变换进行补偿。我们通过将变形力的位置从世界空间转换到本地空间来做到这一点。

	public void AddDeformingForce (Vector3 point, float force) {
		point = transform.InverseTransformPoint(point);
		for (int i = 0; i < displacedVertices.Length; i++) {
			AddForceToVertex(i, point, force);
		}
	}
正确的位置,但比例不同

6.1 调整比例

现在,力被施加在正确的地方,但其他的东西仍然是错误的。将球体均匀地向上或向下缩放。你会注意到,变形的比例是相同的。这是不正确的。小物体和大物体应该受到相同的物理学影响。

 我们必须对我们的物体的比例进行补偿。首先,我们需要知道它的统一尺度。我们可以通过检查变换的一个局部比例轴来找到它。每次更新都要这样做,这样我们就可以在某种程度上处理动态改变比例的对象。

	float uniformScale = 1f;
	
	void Update () {
		uniformScale = transform.localScale.x;
		…
	}

非均匀比例怎么办?
你可以使用一个三维矢量,而不是一个单一的刻度值。然后分别调整每个维度。但实际上,你并不想处理非均匀比例的问题。

现在固定AddForceToVertex,将pointToVertex向量按统一比例缩放。这可以确保我们使用正确的距离。

	void AddForceToVertex (int i, Vector3 point, float force) {
		Vector3 pointToVertex = displacedVertices[i] - point;
		pointToVertex *= uniformScale;
		float attenuatedForce = force / (1f + pointToVertex.sqrMagnitude);
		float velocity = attenuatedForce * Time.deltaTime;
		vertexVelocities[i] += pointToVertex.normalized * velocity;
	}

 在UpdateVertex中对位移做同样的处理。现在我们的速度是正确的。

	void UpdateVertex (int i) {
		Vector3 velocity = vertexVelocities[i];
		Vector3 displacement = displacedVertices[i] - originalVertices[i];
		displacement *= uniformScale;
		velocity -= displacement * springForce * Time.deltaTime;
		velocity *= 1f - damping * Time.deltaTime;
		vertexVelocities[i] = velocity;
		displacedVertices[i] += velocity * Time.deltaTime;
	}

然而,对于一个没有被缩放的物体,我们的速度现在是正确的。由于我们的对象实际上是按比例的,我们也必须调整顶点运动。这一次我们必须用除法而不是用乘法。

		displacedVertices[i] += velocity * (Time.deltaTime / uniformScale);
不同尺度,同样的物理

就这样,你拥有了它。一个可以在任何位置、旋转和统一比例下工作的变形网格。请记住,这是一个简单和相对便宜的视觉效果。它不是一个软体物理模拟。物体的碰撞器并没有改变,所以物理引擎并不知道物体的感知形状。 

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

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

相关文章

Rust社区引发舆论危机,问题到底出在哪儿?

围绕开源的法律问题&#xff0c;讨论焦点往往集中在开源许可证、软件著作权等方面&#xff0c;商标的讨论却极少引人关注。事实上&#xff0c;关于开源软件以及开源软件的衍生产品的商标使用情况往往处于某种灰色地带。 最近&#xff0c;Rust基金会正在就更新的商标政策征求反馈…

windows命令执行的几种绕过方法

windows命令执行的几种绕过方法介绍1、添加特殊符号2、定义变量3、切割字符串4、逻辑运算符在绕过中的作用5、利用for循环拼接命令介绍 反检测、反清理&#xff0c;是红队攻击中的重中之重&#xff0c;本文详细描述了几种windows执行命令的几种绕过手法。 1、添加特殊符号 w…

ERP软件的作用

ERP软件的运用是在企业管理系统的数据基础上实现的&#xff0c;它的应用涉及到企业的各个部门。ERP软件是在制造资源计划的基础上进一步发展而成的对企业供应链的管理软件。ERP是集采购、销售和库存、财务、生产管理和委托加工为一体的企业管理软件。它是集企业管理理念、业务流…

带你玩转Python爬虫(胆小者勿进)千万别做坏事·······

这节课很危险&#xff0c;哈哈哈哈&#xff0c;逗你们玩的 目录 写在前面 1 了解robots.txt 1.1 基础理解 1.2 使用robots.txt 2 Cookie 2.1 两种cookie处理方式 3 常用爬虫方法 3.1 bs4 3.1.1 基础介绍 3.1.2 bs4使用 3.1.2 使用例子 3.2 xpath 3.2.1 xpath基础介…

【计算机图形学】扫描转换算法(Bresenham1/4圆法 椭圆两头逼近法 方形刷子)

一 实验目的 编写弧线的光栅扫描转换算法&#xff0c;并对线宽与线形的算法加以探讨熟悉圆和椭圆画线的算法二 实验算法理论分析Bresenham法&#xff08;1/4圆&#xff09;&#xff1a; 椭圆扫描转换——两头逼近法&#xff1a; 处理线宽问题&#xff1a; 方形刷子宽度存在的…

JS内置对象1

JS中的对象分为&#xff1a;自定义对象、内置对象、浏览器对象内置对象&#xff1a;JS语言自带的一些对象&#xff0c;已经提高最基本的功能常见内置对象&#xff1a;Math、Date、Array、String学习内置对象可通过查阅文档&#xff0c;即MDN/W3C来查阅 …

3.1 微分中值定理

思维导图&#xff1a; 学习目标&#xff1a; 我会按照以下步骤来学习微分中值定理&#xff1a; 理解导数的定义和性质&#xff1a;在学习微分中值定理之前&#xff0c;首先要对导数的定义和性质有一个清晰的理解&#xff0c;包括导数的几何意义和导数存在的条件等。学习拉格朗…

作为大学生,你还不会搭建chatGPT微应用吗?

目录 引言ChatGPT是什么&#xff1f;背景&#xff1a;ChatGPT敢为人先&#xff0c;打破全球僵局示例演示&#xff1a;基于ChatGPT微应用实现的条件及步骤&#xff08;1&#xff09;整体框架&#xff08;2&#xff09;搭建前的准备工作&#xff08;3&#xff09;实际搭建步骤&a…

算法之搜索专题

搜索 深度优先搜索&#xff08;DFS&#xff09;和广度优先搜索&#xff08;BFS&#xff09;都是常见的图搜索算法。它们的目的是访问和遍历图中的所有节点&#xff0c;并找到满足特定条件的节点。虽然这两种算法的实现方式不同&#xff0c;但它们都有以下的特点。 首先&#…

或许能用 ChatGPT 插件实现财富自由

文章目录或许能用 ChatGPT 插件实现财富自由1. 认识一下1.1 是什么1.2 怎么用2. 举个例2.1 Wolfram2.2 Browsing3. 怎么做到的4. 财富自由4.1 生活类插件4.2 品牌推广类5. 限制或许能用 ChatGPT 插件实现财富自由 我们知道&#xff0c;当前 ChatGPT 最大的局限性就是模型训练数…

如何编写一个自己的web前端脚手架

脚手架简介 脚手架是创建前端项目的命令行工具&#xff0c;集成了常用的功能和配置&#xff0c;方便我们快速搭建项目&#xff0c;目前网络上也有很多可供选择的脚手架。 一个"简单脚手架"的构成其实非常少&#xff0c;即 代码模板 命令行工具。其中代码模板是脚手…

【李宏毅】-生成对抗式网络(GAN)

生成对抗式网络GAN 1. Network as Generator 输入不再是只是x&#xff0c;还有一个simple distribution&#xff08;样本分布&#xff09;&#xff0c;输出也是一个分布 Why distribution 不同的分布即意味着&#xff1a;相同的输入会有不同的输出。 尤其在任务需要创造力的时…

Win+VisualStudio+vcpkg+Zeromq安装方法

1. 缘由 因为工作上要用到Windows上的zeromq来收发消息&#xff0c;所以我在网上搜集了一些资料最终成功地在Visual Studio2022中用c的libzmq库实现了zmq的收发。 2. 基本资料 2.1 ZeroMQ基本介绍 ZeroMQ官网介绍&#xff0c;因为我也不是专门搞网络和通信的&#xff0c;就…

RocketMQ 事务消息 详解

&#x1f34a; Java学习&#xff1a;Java从入门到精通总结 &#x1f34a; 深入浅出RocketMQ设计思想&#xff1a;深入浅出RocketMQ设计思想 &#x1f34a; 绝对不一样的职场干货&#xff1a;大厂最佳实践经验指南 &#x1f4c6; 最近更新&#xff1a;2023年4月9日 &#x1…

VMware ESXi 8.0c - 领先的裸机 Hypervisor (sysin Custom Image)

本站发布 Dell 和 HPE 定制版 ESXi 8.0c 镜像 请访问原文链接&#xff1a;https://sysin.org/blog/vmware-esxi-8/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org 产品简介 VMware ESXi&#xff1a;专门构建的裸机 Hyperviso…

【MATLAB数学建模编程实战】Kmeans算法编程及算法的简单原理

欢迎关注&#xff0c;本专栏主要更新MATLAB仿真、界面、基础编程、画图、算法、矩阵处理等操作&#xff0c;拥有丰富的实例练习代码&#xff0c;欢迎订阅该专栏&#xff01;&#xff08;等该专栏建设成熟后将开始收费&#xff0c;快快上车吧~~&#xff09; 【MATLAB数学建模编…

【MySQL数据库原理】MySQL Community安装与配置

目录 安装成功之后查看版本验证1、介绍、安装与配置数据库2、操作MySQL数据库3、MySQL数据库原理安装成功之后查看版本验证 SELECT VERSION();查看mysql版本号 1、介绍、安装与配置数据库 下载安装包:https://download.csdn.net/download/weixin_41194129/87672588 MySQL…

NumPy 秘籍中文第二版:二、高级索引和数组概念

原文&#xff1a;NumPy Cookbook - Second Edition 协议&#xff1a;CC BY-NC-SA 4.0 译者&#xff1a;飞龙 在本章中&#xff0c;我们将介绍以下秘籍&#xff1a; 安装 SciPy安装 PIL调整图像大小比较视图和副本翻转 Lena花式索引位置列表索引布尔值索引数独的步幅技巧广播数…

比尔·盖茨最新分享:ChatGPT的发展,不止于此

来源: 笔记侠 最近&#xff0c;ChatGPT、GPT 4、文心一言、Copilot&#xff0c;人工智能产品层出不容&#xff0c;一路轰炸&#xff1b;王慧文、王兴、李开复等各位高调以及低调的商业领袖和技术专家&#xff0c;纷纷入局AI赛道。人声鼎沸&#xff0c;十分热闹。 昨天&#xff…

自动写作ai-自动写作神器

自动生成文章 自动生成文章是指使用自然语言处理和人工智能技术&#xff0c;通过算法来自动生成文章的过程。一些自动生成文章的工具可以使用大量数据&#xff0c;学习数据背后的语言规范和知识结构&#xff0c;从而生成高质量和有用的文章。这种技术能够减少写作时间和人力成…