草庐IT

[计算机动画]Games103-作业1-刚体动画

HELLO_IHAD 2023-04-05 原文

用unity实现在线课程

GAMES103-基于物理的计算机动画入门-王华民

的作业1

链接:

课程主页https://games-cn.org/games103/

作业内容:

  Angry Bunny: 使兔子模型带有刚体动画效果

参考链接:

  games103,作业1(逻辑梳理)_Elsa的迷弟的博客-CSDN博客

Tips: 文章最下方附有全部源码

目录

一,程序演示

二,公式推导

1. 定义动画中下一时刻的v与w

 2. 计算冲量j

三,代码实现



一,程序演示

  键盘操作:

  • 点击“L”:发射兔子。
  • 点击“R”:重置兔子。

二,公式推导

  使用冲量法(Impulse)实现物体的刚体碰撞动画:物体的动画与两个参数,位置Position与旋转Rotation有关,而这两个参数的更新分别与线速度v与角速度w相关。冲量法的本质就是在动画计算过程中,时刻求解不断改变的v与w。

1. 定义动画中下一时刻的v与w

解释:

  j表示冲量,定义如下:

假设dt无限小,所以在这段时间内的加速度a可以近似看作常量,​所以可以根据牛顿第二定律F=Ma近似认为

(1.1)新线速度

(1.2)新角速度

 <1.2.1> 首先定义刚体绕定点转动时,刚体的动量矩L为:

i表示组成刚体的mesh的第i个顶点,进而将等式L展开,可以得到以下形式,

 <1.2.2> 其中,我们将红框部分称为惯性张量,同时,惯性张量可整理为:

 

 <1.2.3> 上式1为单位矩阵。此时,我们可将动量矩L表示为:

  

  <1.2.4> 因为力矩的物理定义为:

且动量矩L可变形为:

所以,我们可以得出结论,力矩等于动量矩L的一阶导,即:

   

 <1.2.5> 由<1.2.3>知,

  则关于角速度w的一阶导为:

  

 <1.2.6> 所以,最终下一时刻的新角速度为:

<1.2.7> 注意最终一个细节,考虑到要将刚体的局部坐标系转到世界坐标系,所以对上式出现的所有ri左乘一个旋转矩阵,然后进行替换。

  

 <1.2.8> 则新角速度为

 

这时回到开头,根据冲量J的定义,则新角速度为:

 2. 计算冲量j

根据上述公式知,只要知道冲量j,就可以计算出新的,然后更新刚体的新位置和状态。

冲量j可根据假设下一时刻的新速度已知,从而通过计算得到。

2.1 刚体上某一点的新速度与线速度和角速度的关系为

 

 2.2 定义一个新的计算符号,将向量叉乘转换为向量*乘

 2.3 这时,根据新旧速度差( ),我们可以计算得到冲量j

 

2.4 此时,除假设的外,一切参数已知。而可通过提前设定的摩擦力系数计算得到。

note: 由公式知,冲量和当前帧与下一帧的速度差有关,所以速度差越小,冲量越小。表现在动画效果上,即兔子在碰撞多次后,速度不断减缓,并慢慢停了下来

三,代码实现

代码逻辑结构如下,

  1. 首先寻找该帧中所有发生碰撞且未产生回弹的点,然后取这些碰撞点的平均点,参与之后计算。
  2. 计算平均碰撞点的新速度
  3. 根据已知参数,计算系数K与冲量j
  4. 更新线速度和角速度
  5. (最后在update()函数中,根据LeapFrog Integration蛙跳法更新刚体新的位置position与旋转状态rotation)

 脚本源码:

using UnityEngine;
using System.Collections;

public class Rigid_Bunny : MonoBehaviour 
{
	bool launched 		= false;
	float dt 			= 0.015f;
	Vector3 v 			= new Vector3(0, 0, 0); // velocity
    Vector3 w           = new Vector3(0, 0, 0); // angular velocity

	float mass;									// mass
	Matrix4x4 I_ref;							// reference inertia

	float linear_decay	= 0.999f;				// for velocity decay
	float angular_decay	= 0.98f;				
	float restitution 	= 0.5f;                 // for collision
	float restitution_T = 0.2f;                      //水平面的摩擦力(自己给定)

	Vector3 gravity =new Vector3(0.0f, -9.8f, 0.0f);

	// Use this for initialization
	void Start () 
	{		
		Mesh mesh = GetComponent<MeshFilter>().mesh;
		Vector3[] vertices = mesh.vertices;

		float m = 1;
		mass=0;
		for (int i=0; i<vertices.Length; i++) 
		{
			mass += m;
			float diag=m*vertices[i].sqrMagnitude;
			I_ref[0, 0]+=diag;
			I_ref[1, 1]+=diag;
			I_ref[2, 2]+=diag;
			I_ref[0, 0]-=m*vertices[i][0]*vertices[i][0];
			I_ref[0, 1]-=m*vertices[i][0]*vertices[i][1];
			I_ref[0, 2]-=m*vertices[i][0]*vertices[i][2];
			I_ref[1, 0]-=m*vertices[i][1]*vertices[i][0];
			I_ref[1, 1]-=m*vertices[i][1]*vertices[i][1];
			I_ref[1, 2]-=m*vertices[i][1]*vertices[i][2];
			I_ref[2, 0]-=m*vertices[i][2]*vertices[i][0];
			I_ref[2, 1]-=m*vertices[i][2]*vertices[i][1];
			I_ref[2, 2]-=m*vertices[i][2]*vertices[i][2];
		}
		I_ref [3, 3] = 1;

    }
	
	Matrix4x4 Get_Cross_Matrix(Vector3 a)
	{
		//Get the cross product matrix of vector a
		Matrix4x4 A = Matrix4x4.zero;
		A [0, 0] = 0; 
		A [0, 1] = -a [2]; 
		A [0, 2] = a [1]; 
		A [1, 0] = a [2]; 
		A [1, 1] = 0; 
		A [1, 2] = -a [0]; 
		A [2, 0] = -a [1]; 
		A [2, 1] = a [0]; 
		A [2, 2] = 0; 
		A [3, 3] = 1;
		return A;
	}

	// In this function, update v and w by the impulse due to the collision with
	//a plane <P, N>
	void Collision_Impulse(Vector3 P, Vector3 N)
	{
        // 0. 检测mesh的所有点,是否与平面发生碰撞
        Matrix4x4 mat_R = Matrix4x4.Rotate(transform.rotation);
		Vector3 pos = transform.position;

        Mesh mesh = GetComponent<MeshFilter>().mesh;
        Vector3[] vertices = mesh.vertices;

		Vector3 avg_Collision_Point = new Vector3(0.0f, 0.0f, 0.0f); //平均碰撞点
		int num_Collision = 0;

		Vector3 ri = new Vector3(0.0f, 0.0f, 0.0f);
		Vector3 vi = new Vector3(0.0f, 0.0f, 0.0f);

		//计算在该帧中,模型一共有多少点发生了碰撞,最终取这些碰撞点的平均点参与计算
		for (int i = 0; i < vertices.Length; i++)
		{
			//0.0 计算xi在世界坐标中的位置
			ri = vertices[i];
			Vector3 xi = pos + mat_R.MultiplyVector(ri);

			//0.1 计算是否发生碰撞
			if (Vector3.Dot((xi - P), N) >= 0.0f)
				continue;


			//0.2 计算碰撞后mesh是否已经处在回弹状态
			vi = v + Vector3.Cross(w, mat_R.MultiplyVector(ri));
			if (Vector3.Dot(vi, N) >= 0.0f)
				continue;

			avg_Collision_Point += ri;
			num_Collision++;
		}
		if (num_Collision == 0) //如果模型没有发生碰撞,则返回
			return;

		// 1. 如果发生碰撞,则进行模型回弹处理

		ri = avg_Collision_Point / num_Collision; //此时ri为模型的平均碰撞点

		Vector3 Rri = mat_R.MultiplyVector(ri);
		vi = v + Vector3.Cross(w, Rri);


		// 1.0 计算碰撞后的新速度 vi_new
		Vector3 vi_N = Vector3.Dot(vi, N) * N;
        Vector3 vi_T = vi - vi_N;
		float a = Mathf.Max(1.0f - (restitution_T * (1.0f + restitution) * vi_N.magnitude / vi_T.magnitude), 0.0f);

		Vector3 vi_new_N = -restitution * vi_N;
        Vector3 vi_new_T = a * vi_T;

        Vector3 vi_new = vi_new_N + vi_new_T;

        // 1.1 计算冲量J
        Matrix4x4 I = mat_R * I_ref * mat_R.transpose;
        Matrix4x4 K_temp = Get_Cross_Matrix(Rri) * I.inverse * Get_Cross_Matrix(Rri);

        因为 unity没有提供matrix的加减法,所以手动计算
        Matrix4x4 K = Matrix4x4.zero;
        K[0, 0] = 1.0f / mass - K_temp[0, 0];
        K[0, 1] = -K_temp[0, 1];
        K[0, 2] = -K_temp[0, 2];
        K[0, 3] = -K_temp[0, 3];

        K[1, 0] = -K_temp[1, 0];
        K[1, 1] = 1.0f / mass - K_temp[1, 1];
        K[1, 2] = -K_temp[1, 2];
        K[1, 3] = -K_temp[1, 3];

        K[2, 0] = -K_temp[2, 0];
        K[2, 1] = -K_temp[2, 1];
        K[2, 2] = 1.0f / mass - K_temp[2, 2];
        K[2, 3] = -K_temp[2, 3];

        K[3, 0] = -K_temp[3, 0];
        K[3, 1] = -K_temp[3, 1];
        K[3, 2] = -K_temp[3, 2];
        K[3, 3] = 1.0f / mass - K_temp[3, 3];

        Vector3 J = K.inverse.MultiplyVector(vi_new - vi);

        //1.2 更新v and w
        v += 1.0f / mass * J;
        w += I.inverse.MultiplyVector(Vector3.Cross(Rri, J));
    }

	// Update is called once per frame
	void Update()
	{
		//Game Control
		if (Input.GetKey("r"))
		{
			transform.position = new Vector3(0, 0.6f, 0);
            transform.rotation = Quaternion.Euler(0.0f, 0.0f, 0.0f);

            launched = false;
		}
		if (Input.GetKey("l"))
		{
			v = new Vector3(5, 2, 0);
			w = new Vector3(0, 1, 0); // angular velocit
			launched = true;
		}

		if (launched == false)
			return;

        // Part I: Update velocities
		// -- linear velocity
        v = v + dt * gravity;
        v *= linear_decay;

        // -- angular velocity
        w *= angular_decay;

        //Part II: Collision Impulse
        Collision_Impulse(new Vector3(0, 0.01f, 0), new Vector3(0, 1, 0));
        Collision_Impulse(new Vector3(2, 0, 0), new Vector3(-1, 0, 0));

        // Part III: Update position & orientation
        //Update linear status
        Vector3 x    = transform.position;
        x += dt * v;

        //Update angular status
        Quaternion q = transform.rotation;

		Vector3 wt = 0.5f * dt * w;
		Quaternion dq = new Quaternion(wt.x, wt.y, wt.z, 0.0f) * q;
		q.Set(q.x + dq.x, q.y + dq.y, q.z + dq.z, q.w + dq.w);

		// Part IV: Assign to the object
		transform.position = x;
		transform.rotation = q;
	}
}

有关[计算机动画]Games103-作业1-刚体动画的更多相关文章

  1. ruby-on-rails - 使用一系列等级计算字母等级 - 2

    这里是Ruby新手。完成一些练习后碰壁了。练习:计算一系列成绩的字母等级创建一个方法get_grade来接受测试分数数组。数组中的每个分数应介于0和100之间,其中100是最大分数。计算平均分并将字母等级作为字符串返回,即“A”、“B”、“C”、“D”、“E”或“F”。我一直返回错误:avg.rb:1:syntaxerror,unexpectedtLBRACK,expecting')'defget_grade([100,90,80])^avg.rb:1:syntaxerror,unexpected')',expecting$end这是我目前所拥有的。我想坚持使用下面的方法或.join,

  2. Unity 3D 制作开关门动画,旋转门制作,推拉门制作,门把手动画制作 - 2

    Unity自动旋转动画1.开门需要门把手先动,门再动2.关门需要门先动,门把手再动3.中途播放过程中不可以再次进行操作觉得太复杂?查看我的文章开关门简易进阶版效果:如果这个门可以直接打开的话,就不需要放置"门把手"如果门把手还有钥匙需要旋转,那就可以把钥匙放在门把手的"门把手",理论上是可以无限套娃的可调整参数有:角度,反向,轴向,速度运行时点击Test进行测试自己写的代码比较垃圾,命名与结构比较拉,高手轻点喷,新手有类似的需求可以拿去做参考上代码usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;u

  3. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  4. ruby - 如何计算 Liquid 中的变量 +1 - 2

    我对如何计算通过{%assignvar=0%}赋值的变量加一完全感到困惑。这应该是最简单的任务。到目前为止,这是我尝试过的:{%assignamount=0%}{%forvariantinproduct.variants%}{%assignamount=amount+1%}{%endfor%}Amount:{{amount}}结果总是0。也许我忽略了一些明显的东西。也许有更好的方法。我想要存档的只是获取运行的迭代次数。 最佳答案 因为{{incrementamount}}将输出您的变量值并且不会影响{%assign%}定义的变量,我

  5. ruby - 使用 Ruby,计算 n x m 数组的每一列中有多少个 true 的简单方法是什么? - 2

    给定一个nxmbool数组:[[true,true,false],[false,true,true],[false,true,true]]有什么简单的方法可以返回“该列中有多少个true?”结果应该是[1,3,2] 最佳答案 使用转置得到一个数组,其中每个子数组代表一列,然后将每一列映射到其中的true数:arr.transpose.map{|subarr|subarr.count(true)}这是一个带有inject的版本,应该在1.8.6上运行,没有任何依赖:arr.transpose.map{|subarr|subarr.in

  6. ruby-on-rails - 在所有延迟的作业之前 Hook - 2

    是否可以在所有delayed_job任务之前运行一个方法?基本上,我们试图确保每个运行delayed_job的服务器都有我们代码的最新实例,所以我们想运行一个方法来在每个作业运行之前检查它。(我们已经有了“check”方法并在别处使用它。问题只是关于如何从delayed_job中调用它。) 最佳答案 现在有一种官方方法可以通过插件来做到这一点。这篇博文通过示例清楚地描述了如何执行此操作http://www.salsify.com/blog/delayed-jobs-callbacks-and-hooks-in-rails(本文中描述

  7. arrays - 计算数组中的匹配元素 - 2

    给定两个大小相等的数组,如何找到不考虑位置的匹配元素的数量?例如:[0,0,5]和[0,5,5]将返回2的匹配项,因为有一个0和一个5共同;[1,0,0,3]和[0,0,1,4]将返回3的匹配项,因为0有两场,1有一场;[1,2,2,3]和[1,2,3,4]将返回3的匹配项。我尝试了很多想法,但它们都变得相当粗糙和令人费解。我猜想有一些不错的Ruby习惯用法,或者可能是一个正则表达式,可以很好地回答这个解决方案。 最佳答案 您可以使用count完成它:a.count{|e|index=b.index(e)andb.delete_at

  8. ruby-on-rails - 如何计算 Ruby/Rails 中 JSON 对象的数量 - 2

    Ruby中如何“一般地”计算以下格式(有根、无根)的JSON对象的数量?一般来说,我的意思是元素可能不同(例如“标题”被称为其他东西)。没有根:{[{"title":"Post1","body":"Hello!"},{"title":"Post2","body":"Goodbye!"}]}根包裹:{"posts":[{"title":"Post1","body":"Hello!"},{"title":"Post2","body":"Goodbye!"}]} 最佳答案 首先,withoutroot代码不是有效的json格式。它将没有包

  9. ruby - 如何计算自 Ruby 中给定日期以来的周数? - 2

    目标我正在尝试计算自给定日期以来周的距离,而无需跳过任何步骤。我更喜欢用普通的Ruby来做,但ActiveSupport无疑是一个可以接受的选择。我的代码我写了以下内容,这似乎可行,但对我来说似乎还有很长的路要走。require'date'DAYS_IN_WEEK=7.0defweeks_sincedate_stringdate=Date.parsedate_stringdays=Date.today-dateweeks=days/DAYS_IN_WEEKweeks.round2endweeks_since'2015-06-15'#=>32.57ActiveSupport的#weeks

  10. 【Unity大气散射】GAMES104:3A中如何实现大气散射 - 2

    写在前面前两天学习并整理的大气散射基础知识:【Unity大气渲染】关于单次大气散射的理论知识,收获了很多,但不得不承认的是,这其实已经是最早的、90年代的非常古老的方法了,后来也出现了一些优化性的计算思路和方法。因此,我打算先不急着跟各种教程在Unity中实现大气散射,而是再花时间来看看最近的游戏是如何去实现大气渲染的:06.游戏中地形大气和云的渲染(下)|GAMES104-现代游戏引擎:从入门到实践接下来就跟着GAMES104讲地形大气和云渲染的部分学习并做简单的记录,涉及到之前没提到的Mie散射也只选择直接截图PPT的方式记录啦!毕竟对于做作品来说,之后实现出来才是重要的~当然,May佬的

随机推荐