games103——作业2

实验二主要使用隐式积分法以及PBD法完成布料仿真

完整项目已上传至github。

文章目录

  • 基于物理的方法
    • 弹簧系统
      • 单个弹簧
      • 多个弹簧
      • 弹簧网络
        • 结构化弹簧网络(Structured Spring Networks)
        • 非结构化弹簧网络(Unstructured Spring Networks)
        • 三角网格表示
      • 代码
    • 求解质量弹簧系统的显示积分法
    • 隐式积分法
      • 牛顿法
      • 使用牛顿法的仿真过程
      • Jacobi迭代
      • 代码
    • 结果
  • Position Based Dynamics
    • 刚度问题(Stiffness Issue)
    • 单个弹簧
    • 多个弹簧
      • Guass-Seidel 方法
      • Jacobi 方法
    • 仿真过程
    • PBD的优点及缺点
    • 代码
    • 结果


基于物理的方法

首先介绍一下弹簧系统

弹簧系统

单个弹簧

一个理想弹簧满足虎克定律:弹簧力尽力恢复原长,下面分别一端点为固定点(左图)和两端都不为固定点(右图)的能量 E ( x ⃗ ) E(\vec{x}) E(x )与力关于位矢的函数 F ⃗ ( x ⃗ ) \vec{F}(\vec{x}) F (x )。对于两端点都不是固定点情况(这个系统受两个端点的位矢影响), F ⃗ ( x ⃗ ) = { f i ⃗ ( x ⃗ ) , f j ⃗ ( x ⃗ ) } \vec{F}(\vec{x})=\{\vec{f_i}(\vec{x}),\vec{f_j}(\vec{x})\} F (x )={fi (x ),fj (x )}。这里的 x ⃗ = { x i ⃗ , x j ⃗ } \vec{x}=\{\vec{x_i},\vec{x_j}\} x ={xi ,xj }

多个弹簧

当有多个弹簧时,能量和力可以被简单累加(对于包含 x i ⃗ \vec{x_i} xi 的系统),注意不同的系统不是直接累加的。对于 x i x_i xi的力 f i ⃗ \vec{f_i} fi 可以通过能量 E E E对位矢 x i ⃗ \vec{x_i} xi 求梯度再取负数就能得到。

弹簧网络

结构化弹簧网络(Structured Spring Networks)

我们在横向与纵向(Horizontal and vertical)添加弹簧可以抵抗横向与纵向的拉伸(streching)。在对角增加弹簧可以抵抗在对角(diagonal)方向上的皱褶(实际应用中可以简化为只在一个小格子的其中一个对角线上添加弹簧)。跳过一个顶点连接的弹簧,抵抗沿中间的长边产生大的翻折(Bending)。

非结构化弹簧网络(Unstructured Spring Networks)

对于不规则形状的三角形 Mesh 布料,同样在三角网格的每个边上都加一根弹簧,然后跨过所有内部边添加弹簧(称任意两个相邻三角形的公共边为内部边,两个相邻三角形组成一个四边形,新添加的弹簧为这个四边形的对角线)来抵抗弯曲。

三角网格表示

基础的三角形网格使用表示使用顶点和三角形列表,顶点列表存放顶点位置,三角形列表存放每个三角形三个顶点在顶点列表的索引。

但如果只存储这些信息,我们在计算每条边的对应弹簧系统的力与能量会把内部边重复计算。因此还需要构建一个三元组的列表,其中每个三元组的三个元素分别为边两个顶点的索引及三角形的索引。然后进行一次排序(按两个顶点索引大小,以小的顶点为主序),这样我们就可以找到所有内部边,将重复的剔除,得到最后的边列表。再计算相邻三角形对,就可以在之后用于bending的计算了。

代码

上述内容,在实验中的代码如下(没考虑bending),这里网格的大小为 21x21,X[] 存放网格中每个顶点的位置,UV[] 存放每个顶点在纹理中的位置,triangles[] 存放每个三角形对应顶点的索引(在X[]的索引),_E[] 存放每条边中的顶点的索引(此时还未去除重复边)。我们对 _E[] 排序去除重复边得到 E[]L[] 为每根弹簧的原长。

		Mesh mesh = GetComponent<MeshFilter>().mesh;

        //Resize the mesh.
        int n = 21;
        Vector3[] X = new Vector3[n * n];
        Vector2[] UV = new Vector2[n * n];
        int[] triangles = new int[(n - 1) * (n - 1) * 6];
        for (int j = 0; j < n; j++)
            for (int i = 0; i < n; i++)
            {
                X[j * n + i] = new Vector3(5 - 10.0f * i / (n - 1), 0, 5 - 10.0f * j / (n - 1));
                UV[j * n + i] = new Vector3(i / (n - 1.0f), j / (n - 1.0f));
            }
        int t = 0;
        for (int j = 0; j < n - 1; j++)
            for (int i = 0; i < n - 1; i++)
            {
                triangles[t * 6 + 0] = j * n + i;
                triangles[t * 6 + 1] = j * n + i + 1;
                triangles[t * 6 + 2] = (j + 1) * n + i + 1;
                triangles[t * 6 + 3] = j * n + i;
                triangles[t * 6 + 4] = (j + 1) * n + i + 1;
                triangles[t * 6 + 5] = (j + 1) * n + i;
                t++;
            }
        mesh.vertices = X;
        mesh.triangles = triangles;
        mesh.uv = UV;
        mesh.RecalculateNormals();

        //Construct the original E
        int[] _E = new int[triangles.Length * 2];
        for (int i = 0; i < triangles.Length; i += 3)
        {
            _E[i * 2 + 0] = triangles[i + 0];
            _E[i * 2 + 1] = triangles[i + 1];
            _E[i * 2 + 2] = triangles[i + 1];
            _E[i * 2 + 3] = triangles[i + 2];
            _E[i * 2 + 4] = triangles[i + 2];
            _E[i * 2 + 5] = triangles[i + 0];
        }
        //Reorder the original edge list
        for (int i = 0; i < _E.Length; i += 2)
            if (_E[i] > _E[i + 1])
                Swap(ref _E[i], ref _E[i + 1]);
        //Sort the original edge list using quicksort
        Quick_Sort(ref _E, 0, _E.Length / 2 - 1);

        int e_number = 0;
        for (int i = 0; i < _E.Length; i += 2)
            if (i == 0 || _E[i + 0] != _E[i - 2] || _E[i + 1] != _E[i - 1])
                e_number++;

        E = new int[e_number * 2];
        for (int i = 0, e = 0; i < _E.Length; i += 2)
            if (i == 0 || _E[i + 0] != _E[i - 2] || _E[i + 1] != _E[i - 1])
            {
                E[e * 2 + 0] = _E[i + 0];
                E[e * 2 + 1] = _E[i + 1];
                e++;
            }

        L = new float[E.Length / 2];
        for (int e = 0; e < E.Length / 2; e++)
        {
            int v0 = E[e * 2 + 0];
            int v1 = E[e * 2 + 1];
            L[e] = (X[v0] - X[v1]).magnitude;
        }

求解质量弹簧系统的显示积分法

在对隐式积分法进行说明之前,也介绍下显示积分法。显示积分法就是对当前状态计算出力,然后更新每个顶点的速度与位置。

而使用隐式积分会因为overshooting导致数值不稳定, 因为 △ t \triangle t t不是无穷小,所以产生了莫名的能量;真实世界中当弹簧形变量缩小时弹力会逐渐减小,而离散的积分方法假定了一个时间步内受力都相同。当时间步长太大,或者弹性系数 k 太大时就会在一个时间步内产生过度的位移。


隐式积分法

显式积分是数值不稳定的,因此考虑使用隐式积分法。隐式积分法更新速度时用的是新的位置受到的力,也就是说当弹簧缩小时,显式积分用的弹力一定大于这个时间步内的平均弹力,而隐式积分用的力小于一个时间步内的平均弹力,因此隐式积分法在数值上更稳定一些。如果力只与端点的位置有关,那么 f ⃗ \vec{f} f 只依赖 x [ 1 ] ⃗ \vec{x^{[1]}} x[1]

我们对上面的等式进行转化,将其等价于求解一个优化问题

牛顿法

我们要求解优化问题 x [ 1 ] = a r g m i n F ⃗ ( x ⃗ ) x^{[1]}=argmin \vec{F}(\vec{x}) x[1]=argminF (x )。就是寻找一个 F ⃗ ′ ( x ⃗ ) = 0 \vec{F}'(\vec{x})=0 F (x )=0 的点 x ⃗ \vec{x} x ,我们将 F ′ ⃗ ( x ⃗ ) \vec{F'}(\vec{x}) F (x ) 使用泰勒展开,保留前两项,之后就可以转化为求解线性方程组的问题,使用求线性方程组解的方法即可。

当然, F ′ ⃗ ( x ⃗ ) = 0 \vec{F'}(\vec{x})=0 F (x )=0 的点也可能是极大值点,因此需要二阶导判断。当然,如果这个函数的二阶导恒大于0,那么此时肯定是极小值点。

使用牛顿法的仿真过程


需要特别说明这里的海森矩阵,当海森矩阵对称正定,A(左边的矩阵)就正定,就一定能收敛(这也是作业中为什么可以使用一个魔法矩阵来代替的原因),这里的海森矩阵,当弹簧是拉伸的时候,就一定是对称正定的。如果是压缩,则不一定对称正定。


△ t \triangle t t 越小,左边的矩阵A越正定。

正定有唯一解,非正定不一定没有唯一解(不是必要条件)

而且当弹簧挤压的时候,其不一定向哪弯曲,所以非正定产生的现象也可解释。一维肯定正定。

正定有时候是出于算法稳定性的角度考虑的,所以如果不正定,可以直接把后面的项删掉,保证正定(作业直接使用一个magic matrix)

Jacobi迭代

上面我们通过牛顿法,把优化问题转化为解一个线性系统,解线性系统分为直接法(高斯消元等)与迭代法。直接法对 A 的要求小,适合在 CPU 上做,有一定内存开销。迭代法对 A 有一定要求,例如要求 A 正定,但可以在 GPU 上实现,有一些加速方法。

Jacobi迭代就是其中一种迭代法, α = 1 \alpha=1 α=1要求 A 是对角占优的。 α \alpha α 取其他值,可以使对 A 的要求没那么高。

其可以使用切比雪夫加速

代码

这里的 G[] 取负号,就是等式右边的项

        float omega = 1.0f;
        for (int k = 0; k < 32; k++)
        {
            //if (k == 0) omega = 1.0f;
            //else if (k == 1) omega = 2.0f / (2.0f - rho * rho);
            //else omega = 4.0f / (4.0f - rho * rho * omega);

            Get_Gradient(X, X_hat, t, G);

            //Update X by gradient.
            for (int i = 0; i < X.Length; i++)
            {
                if (i == 0 || i == 20) continue;
                Vector3 new_x = omega * (X[i] + (1.0f / (mass / (t * t) + 4.0f * spring_k)) * -G[i]) + (1.0f-omega) * last_X[i];
                last_X[i] = X[i];
                X[i] = new_x;
            }
        }

Get_Gradient() 部分如下

    void Get_Gradient(Vector3[] X, Vector3[] X_hat, float t, Vector3[] G)
    {
        //Momentum and Gravity.
        for (int i = 0; i < X.Length; i++)
        {
            G[i] = mass * (X[i] - X_hat[i]) / (t * t) - mass * gravity;
        }
        //Spring Force.
        for (int e = 0; e < L.Length; e++)
        {
            int i = E[e * 2];
            int j = E[e * 2 + 1];
            G[i] = G[i] + spring_k * (1 - L[e] / (X[i] - X[j]).magnitude) * (X[i] - X[j]);
            G[j] = G[j] - spring_k * (1 - L[e] / (X[i] - X[j]).magnitude) * (X[i] - X[j]);
        }
    }

结果


Position Based Dynamics

刚度问题(Stiffness Issue)

真实世界中的布料拉伸超过一定程度后,对拉伸具有很强的抵抗。基于胡可定律的弹簧模型中需要增大弹性系数k来模拟这种现象,但这会造成显式积分和隐式积分都出现问题,增大了模拟计算量。基于约束的方法被提出的动机就是想要解决这个问题。

单个弹簧

假设弹簧的弹性系数无限大,那么弹簧的长度就成了一个约束,即弹簧的形变量 ϕ ( x ⃗ ) = 0 \phi(\vec{x})=0 ϕ(x )=0,下图中的 Ω \Omega Ω 是满足这个约束的空间。当约束被破坏时,KaTeX parse error: Expected '}', got 'EOF' at end of input: …c{x}={\vec{x_i},KaTeX parse error: Expected 'EOF', got '}' at position 10: \vec{x_j}}̲ Ω \Omega Ω 外,我们做一个投影操作把 x ⃗ \vec{x} x 以最近的距离移动到 Ω \Omega Ω 的边界上使弹簧恢复原长。


更新公式如下,其中 m i m_i mi 是端点的质量,默认情况下两端质量相同,拉伸后两个端点都向着中心点移动。如果希望让其中一个端点被固定住不动,只需要把那个端点的质量设为无限大。

当然还可以加其他约束(比如弯曲力约束),在实验中并没有考虑这些,就不细说了,想了解的可以进一步看PBD的论文。

多个弹簧

Guass-Seidel 方法

Guass-Seidel 方法就是依次按每个弹簧更新两个顶点的位置。 存在计算顺序影响最后结果的问题(可能会造成bias问题,整个布料歪向一边,也可能会影响算法收敛的速度);迭代次数越多,越能更好地满足所有弹簧的约束。Gauss-Seidel方法虽然名字交 Gauss-Seidel,但与数学中的 Gauss-Seidel 其实不一样,实际所做操作更接近于随机梯度下降算法。

Jacobi 方法

对每个顶点的更新求和之后再取平均值,这就解决了偏向性问题,且容易并行

仿真过程

迭代次数越多,越没弹性;网格很大,需要更多次数的迭代才收敛,即弹性会变大。这里速度是叠加的原因是x已经是被速度更新后的x,所以计算差值的时候没有考虑原有的速度,所以要把原有的速度加上来

PBD的优点及缺点

  • 优点:容易并行;容易实现;低分辨率效率高(一般1000个点以下效率很好);通用性好;
  • 缺点:没有物理解释;高分辨率效率不好。

代码

代码非常直观,就是一个对力求和取平均的过程。

    void Strain_Limiting()
    {
        Mesh mesh = GetComponent<MeshFilter>().mesh;
        Vector3[] vertices = mesh.vertices;
        Vector3[] sum_x = new Vector3[vertices.Length];
        int[] sum_n = new int[vertices.Length];
        //Apply PBD here.
        for (int i = 0; i < vertices.Length; i++)
        {
            sum_x[i] = new Vector3(0, 0, 0);
            sum_n[i] = 0;
        }
        for (int e = 0; e < L.Length; e++)
        {
            int i = E[e * 2];
            int j = E[e * 2 + 1];
            Vector3 xij = vertices[i] - vertices[j];
            sum_x[i] = sum_x[i] + 0.5f * (vertices[i] + vertices[j] + L[e] * xij * (1.0f / xij.magnitude));
            sum_x[j] = sum_x[j] + 0.5f * (vertices[i] + vertices[j] - L[e] * xij * (1.0f / xij.magnitude));
            sum_n[i]++;
            sum_n[j]++;
        }
        for (int i = 0; i < vertices.Length; i++)
        {
            if (i == 0 || i == 20) continue;
            V[i] = V[i] + (1.0f / t) * ((0.2f * vertices[i] + sum_x[i]) / (0.2f + (float)sum_n[i]) - vertices[i]);
            vertices[i] = (0.2f * vertices[i] + sum_x[i]) / (0.2f + (float)sum_n[i]);
        }
        mesh.vertices = vertices;
    }

结果

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

(别再手动点APP了)UiAutomator2自动化测试框架带你玩转APP操作

目录 前言 一、uiautomator/uiautomator2的前生今世 1.官方文档介绍 2.梳理一下脉络 3.三款框架对比 二、uiautomator2简介 1.项目组成 2.工作原理 三、环境搭建 1.安装uiautomator2 2.初始化设备 3.init时都干了啥&#xff1f; 四、基础操作 1.连接设备 2.命令…

Python——函数

概念 函数是一段具有特定功能&#xff0c;可重复使用的代码&#xff0c;python提供了很多内置函数&#xff0c;如&#xff1a;print()&#xff0c;input()&#xff0c;len()函数等&#xff0c;以及标准库函数&#xff0c;math库中的sqrt()函数等&#xff0c;除此之外用户还可以…

Hive3面试基础

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、基本知识Hive31.表的类型和表的存储格式a)b)c)创建表i&#xff09;ii&#xff09; 2.表 二、使用步骤1.引入库2.读入数据 总结 前言 面试准备之Hive 回顾…

设计模式之适配器模式

目录 1、什么是适配器模式 2、为什么用适配器模式 3、适配器模式的结构 4、类适配器模式代码实现 4.1 思想 4.2 代码实现 4.3 问题分析 5、对象适配器模式代码实现 5.1 思想 5.2 代码实现 6、适配器模式应用场景 1、什么是适配器模式 适配器模式&#xff08;Adapter…

19. Unity - 2D游戏开发小记02 --- 伪透视图、2D物体碰撞、瓦片地图碰撞、素材缩放平铺

1. 伪视图 在2D游戏开发当中,当角色移动时,会发生物体与物体之间的前后遮挡。2D视图中的前后关系是由 Y 轴决定,y 值越小物体越靠前。unity的渲染应开启根据 y 值的大小进行渲染才能保证正确的遮挡效果,在菜单栏Editor–>project setting --> Graphic中按照下图方式…

C++三大特性—继承“复杂的菱形继承及菱形虚拟继承”

C的一个大坑&#xff1a;菱形继承 希望这篇文章能让你理解什么是菱形继承&#xff0c;以及菱形继承的注意事项 单继承与多继承 单继承&#xff1a;一个子类只有一个直接父类时称这个继承关系为单继承 多继承&#xff1a;一个子类有两个或以上直接父类时称这个继承关系为多继承…

凌恩生物美文分享|HGTree v2.0:水平基因转移数据库

水平基因转移(HGT)是指遗传物在物种间从一个相邻生物体到另一个生物体横向传递&#xff0c;是原核生物遗传变异的重要过程。HGT是负责塑造原核生物基因组和在自然选择中生存的驱动力之一&#xff0c;对原核生物的进化有很大贡献&#xff0c;但它会使物种进化史复杂化&#xff0…

【Linux】进程信号保存

前言 上篇博客我们了解了进程信号的概念和信号如何产生。 本篇我们将学习进程信号如何保存。 文章目录 前言一. 阻塞信号二. 递达动作三. 信号集四. 信号集操作函数结束语 一. 阻塞信号 首先我们需要一些预备知识 实际执行信号的处理动作称为信号递达&#xff08;Delivery&am…

数字图像处理-绪论

数字图像处理-绪论 文章目录 前言一、闲谈二、什么是数字图像处理&#xff1f;2.1. 什么是数字图像&#xff1f;2.1.1. 可见光图像2.1.2. 不可见光图像 2.2. 什么是数字图像处理&#xff1f; 三、数字图像处理的前世今生3.1. 数字图像处理的前世3.2. 数字图像处理的今生 四、数…

【嵌入式系统】课程复习资料整理

【嵌入式系统】课程复习资料整理 一、绪论 1.定义 从技术的角度定义&#xff1a;以应用为中心、以计算机技术为基础、软件硬件可裁剪、对功能、可靠性、成本、体积、功耗严格要求的专用计算机系统。从系统的角度定义&#xff1a;嵌入式系统是设计完成复杂功能的硬件和软件&a…

使用crontab定时自动更新DDNS

需求说明&#xff1a; N1盒子的armbian系统配置好了 ipv6 的ddns&#xff0c;实现了域名访问本机&#xff0c;但是本地ipv6可能会发生变化&#xff0c;当发生变化后&#xff0c;需要手动执行指令&#xff0c;将新的ip与域名绑定&#xff0c;现在我们采用定时任务&#xff0c;每…

Nuvoton NK-980IOT开发板 u-boot 编译

前言 最近搭建了 Nuvoton NK-980IOT开发板 的开发编译环境&#xff0c;记录一下 u-boot 的 编译流程 Nuvoton NK-980IOT开发板 资源还是比较的丰富的&#xff0c;可以用于 嵌入式Linux 或者 RT-Thread 的学习开发 开发板上电比较的容易&#xff0c;两根 USB 线即可&#xff0…

计网笔记 01 概述 计算机网络体系结构、参考模型

文章目录 前言1、计网概述1.1 概念、组成、功能、分类1.1.1 概念1.1.2 计网组成1.1.2 计网分类 1.2 标准化工作及相关组织1.2.1 标准的分类 1.3 性能指标★★★1.3.1 速率相关性能指标1.3.2 时延相关指标 2、体系结构&参考模型★★★★★&#xff08;对应王道视频7-10p 相当…

Android Jetpack:利用Palette进行图片取色

与产品MM那些事 新来一个产品MM&#xff0c;因为比较平&#xff0c;我们就叫她A妹吧。A妹来第一天就指出&#xff1a;页面顶部的Banner广告位的背景是白色的&#xff0c;太单调啦&#xff0c;人家不喜欢啦&#xff0c;需要根据广告图片的内容自动切换背景颜色&#xff0c;颜色…

基于CUDA的GPU计算PI值

访问【WRITE-BUG数字空间】_[内附完整源码和文档] 基于CUDA的GPU计算PI值。本项目使用CUDA编程模型并行计算PI值&#xff0c;研究GPU与CPU效率的比较&#xff0c;分析不同GPU线程分块对性能的影响。 异构计算试验报告 —实验1&#xff1a;基于CUDA的GPU计算PI值 第一部分&…

JS逆向 -- 某平台登录加密分析

一、打开网站&#xff0c;使用账号密码登录 账号&#xff1a;aiyou123.com 密码&#xff1a;123456 二、通过F12抓包&#xff0c;抓到如下数据&#xff0c;发现密码加密了 三、加密结果是32位&#xff0c;首先考虑是md5加密。 四、全局搜索pwd&#xff0c;点击右上角&#xf…

【ros2】ros melodic迁移到ros2 dashing过程中碰到的问题及解决方法

序言 总结踩坑经历&#xff0c;以利他人 1. error: forming pointer to reference type … & 报错原因&#xff1a; ros2回调函数的参数不能是引用形式 &&#xff0c;需要去除& 解决方法&#xff1a; 如果是指针引用&#xff0c;直接去除引用 void Callback(con…

javascript中的严格模式

认识严格模式&#xff1a; 在ECMAScript5标准中&#xff0c;JavaScript提出了严格模式的概念&#xff08;Strict Mode&#xff09;: 严格模式很好理解&#xff0c;是一种具有限制性的JavaScript模式&#xff0c;从而是代码隐式的脱离了“懒散&#xff08;sloppy&#xff09;模…

软件测试实战,Web测试详细总结 (覆盖所有测试点),你要的都有

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 Web自动化测试&…

在技术圈超卷的当下,学历到底是敲门砖还是枷锁?

前言 最近&#xff0c;突然之间被“孔乙己文学”刷屏了&#xff0c;短时间内“孔乙己文学”迅速走红&#xff0c;孔乙己是中国文学中的一位经典人物&#xff0c;他的长衫被认为是他的象征之一&#xff0c;孔乙己的长衫折射出很多现象&#xff0c;既有社会的&#xff0c;也有教育…