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

这篇具有很好参考价值的文章主要介绍了Unity——网格变形(制作一个压力球)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

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

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

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

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

1. 场景设置

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

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

Unity——网格变形(制作一个压力球)
从一个普通的立方体球体开始

2. 网格变形器

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

using UnityEngine;

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

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

Unity——网格变形(制作一个压力球)
带有网格变形器组件的立方体球体

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

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

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

Unity——网格变形(制作一个压力球)
网格变形器输入连接到相机

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);
	}
Unity——网格变形(制作一个压力球)
在场景视图中调试光线

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

3.3 力量偏移

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

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

Unity——网格变形(制作一个压力球)
用偏移量改变力的方向。
	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);
			}
		}
	}
Unity——网格变形(制作一个压力球)
力点稍稍远离物体

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

现在可以用反平方定律找到衰减的力。只要用原力除以距离的平方就可以了, 。实际上,我除以1加上距离的平方,Unity——网格变形(制作一个压力球)。这就保证了当距离为零时,力是全开的。否则,当距离为1时,力就会处于全盛状态,而当你越接近该点时,它就会向无穷远处射去。

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

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

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

Unity——网格变形(制作一个压力球)
累积速度

 5. 保持体形

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

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

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

5.1 弹簧

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

Unity——网格变形(制作一个压力球)
标移位的顶点被拉回题

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

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

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

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

5.2 衰减

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

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

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

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

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

Unity——网格变形(制作一个压力球)
恢复到原来的形状

6. 处理转换

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

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

	public void AddDeformingForce (Vector3 point, float force) {
		point = transform.InverseTransformPoint(point);
		for (int i = 0; i < displacedVertices.Length; i++) {
			AddForceToVertex(i, point, force);
		}
	}
Unity——网格变形(制作一个压力球)
正确的位置,但比例不同

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);
Unity——网格变形(制作一个压力球)
不同尺度,同样的物理

就这样,你拥有了它。一个可以在任何位置、旋转和统一比例下工作的变形网格。请记住,这是一个简单和相对便宜的视觉效果。它不是一个软体物理模拟。物体的碰撞器并没有改变,所以物理引擎并不知道物体的感知形状。 文章来源地址https://www.toymoban.com/news/detail-500235.html

到了这里,关于Unity——网格变形(制作一个压力球)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【用unity实现100个游戏之4】手搓一个网格放置功能,及装修建造种植功能(2d3d通用,附源码)

    参考原视频链接 【视频】:https://www.youtube.com/watch?v=l0emsAHIBjU 注意 :本文为学习笔记记录,推荐支持原作者,去看原视频自己手敲代码理解更加深入

    2024年02月13日
    浏览(29)
  • ArcGIS 利用cartogram插件制作变形地图

    注:本图数据并不完全对,只做为测试用例 首先需要下载一个插件cartogram 下载地址在这里 https://www.arcgis.com/home/item.html?id=d348614c97264ae19b0311019a5f2276 下载完毕之后解压将CartogramsHelpFiles下的所有文件复制到ArcGIS安装的Helpgp文件夹中。在64位计算机上典型的ArcGIS 10.7安装中,这将

    2024年02月12日
    浏览(26)
  • Unity汉化一个插件 制作插件汉化工具

    思路: 创建一个存储字典的代码 完成之后我们就可以在项目中创建一个自定义字典了 在字典中添加几对常用AIP用来测试 创建一个编辑器窗口代码 读文件 将改好的数据进行写入 稍稍修正一下编辑器页面,随便导入一个插件试试看 我自己反正没实践过,可以先拿这个玩玩看

    2024年02月09日
    浏览(44)
  • Unity 关于制作一个2D的小地图

    实际效果:   需求:1.实现右上角小地图,实现同步本地地图           2.实现小地图内的角色图标和实际角色同步 实现原理: 1.在所需要的地图上划分为一下结构 图层Tilemap6_Minimap的Layer为Minimap 同时这里说一个问题,在同层级的图片,序列越晚渲染越上层 2.使用另一个摄像

    2024年02月14日
    浏览(29)
  • Unity制作一个简单的登入注册页面

    1.创建Canvas组件 首先我们创建一个Canvas画布,我们再在Canvas画布底下创建一个空物体,取名为Resgister。把空物体的锚点设置为全屏撑开。  2.我们在Resgister空物体底下创建一个Image组件,改名为bg。我们也把它 的锚点设置为全屏撑开状态。接下来我们把我们的图片UI素材导入进

    2024年02月12日
    浏览(45)
  • 【Unity ShaderGraph】| 快速制作一个 抖动效果

    前言 本文将使用ShaderGraph制作一个抖动效果,可以直接拿到项目中使用。 对ShaderGraph还不了解的小伙伴可以参考这篇文章:【Unity ShaderGraph】| Shader Graph入门介绍 | 简介 | 配置环境 | 窗口介绍 | 简单案例 下面就开始看一下具体的制作流程,然后自己动手制作一个吧! 资源下载

    2024年02月03日
    浏览(39)
  • 【Unity ShaderGraph】| 制作一个 高级流体水球效果

    前言 本文将使用ShaderGraph制作一个 高级流体水球 ,可以直接拿到项目中使用。 对ShaderGraph还不了解的小伙伴可以参考这篇文章:【Unity ShaderGraph】| Shader Graph入门介绍 | 简介 | 配置环境 | 窗口介绍 | 简单案例 下面就开始看一下具体的制作流程,然后自己动手制作一个吧! 资

    2024年02月08日
    浏览(39)
  • 【Unity ShaderGraph】| 快速制作一个 卡通阴影色块效果

    前言 本文将使用ShaderGraph制作一个卡通阴影色块的效果,可以直接拿到项目中使用。 对ShaderGraph还不了解的小伙伴可以参考这篇文章:【Unity ShaderGraph】| Shader Graph入门介绍 | 简介 | 配置环境 | 窗口介绍 | 简单案例 下面就开始看一下具体的制作流程,然后自己动手制作一个吧

    2024年02月06日
    浏览(35)
  • 【Unity学习】制作一个可滑动的公告栏

    1.新建Image组件做公告底板(Note) 在Canvas组件(画布)中新建一个Image组件(图片)做公告底板,并在组件属性栏中设置合适的图片大小、颜色及透明度  2.制作公告图标(Title ,可选) 在1建的图片组件(Note)下新建一个image组件做公告标题底图,再在该底图下新建文本,拉伸拖到

    2024年02月12日
    浏览(75)
  • 【Unity】教你如何使用Unity制作一个简单的跑酷游戏

    其实用Unity制作游戏并不难,如果你想学习,那我就建议你想从制作一个简单的跑酷游戏来找到兴趣,因为如果你一开始就一直学习一些没什么必要的语法,这样就会让你一开始就失去了信心,失去了学习Unity的动力,所以如果你先学习如何制作一个简单的跑酷地图,然后你就

    2024年02月21日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包