games103——作业2

news2024/9/28 23:34:13

实验二主要使用隐式积分法以及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;
    }

结果

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

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

相关文章

UG NX二次开发(C++)-用UF_OBJ_cycle_objs_in_part遍历对象

文章目录 1、前言2、在UG NX中创建多个体3、创建UF_OBJ_cycle_objs_in_part的使用函数3.1 首先声明函数&#xff1a;3.2定义函数代码3.3 函数调用代码3.4 测试结果 1、前言 UG NX二次开发中&#xff0c;比如体、面、边等&#xff0c;在NXOpen中可以通过Collection来实现&#…

华硕ROG|玩家国度冰刃6双屏GX650RX Windows11原厂预装系统 工厂模式恢复安装带ASUSRecevory一键还原

华硕ROG|玩家国度 冰刃6双屏 GX650RX Windows11原厂预装系统 工厂模式恢复安装带ASUSRecevory一键还原 文件地址&#xff1a;https://pan.baidu.com/s/1snKOsH3OMl3GZLqeAf-GLA?pwd8888 华硕工厂恢复系统 &#xff0c;安装结束后带隐藏分区以及机器所有驱动软件 需准备一个…

【告警通知】Java项目统一异常捕获处理【钉钉告警推送消息】

基础描述 在实际业务中&#xff0c;很多时候都是被动获取系统业务异常&#xff0c;如通过业务部门或者通过客户反馈说某个功能不行时&#xff0c;这样显得系统很被动和呆板在SpringBoot中有统一异常处理可以来实现&#xff0c;当我们检测到非业务异常时&#xff0c;比如空指针…

《SpringBoot中间件设计与实战》第2章 服务治理,超时熔断

需求背景 在流量较大的场景下,举个例子,用户在电商平台下单后开始跳转到在线收银台进行支付。由于支付渠道和网络环境随时都有可能发生问题,那么你该怎么保证支付系统的可靠性呢? 保证支付系统的可靠性需要考虑的点非常多,但这里有一个最直接和重点的内容就支付响应时长…

【王道·计算机网络】第三章 数据链路层【未完】

一、功能 研究思想&#xff1a;水平方向个数据链路层的差距 1.1 数据链路层基本概念 结点&#xff1a;主机、路由器链路&#xff1a;网络中两个节点之间的物理通道&#xff0c;传输介质包含&#xff1a;双绞线、光纤、微波。分为&#xff1a;有线链路、无线链路数据链路&…

TIM输入不捕获-STM32

TIM输入不捕获-STM32 IC(Input Capture) 输入捕获 输入捕获模式下&#xff0c;当通道输入引脚出现指定电平跳变时&#xff0c;当前CNT的值将被锁存到CCR中&#xff0c;可用于测量PWM波形的频率、占空比、脉冲间隔、电平持续时间等参数 每个高级定时器和通用定时器都拥有4个输入…

【Linux】进程学习(2)---理解进程操作

文章目录 查看进程通过系统目录查看通过ps命令查看 通过系统调用获取进程标识符通过系统调用创建进程初识fork函数fork函数的返回值 进程状态阻塞与运行状态Linux内核源码中的进程状态运行状态-R浅度睡眠状态-S深度睡眠状态-D暂停状态-T僵尸状态-Z死亡状态-X 查看进程 通过系统…

操作系统引导(开机过程)

操作系统安装在C盘中&#xff0c;其一步步启动的过程如下&#xff1a; 操作系统要启动&#xff0c;操作系统的数据需要先被放入主存里。 如图所示&#xff0c;计算机的主存由RAM和ROM组成&#xff0c;ROM芯片被集成在电脑主板上&#xff0c;里面存储的是BIOS&#xff08;Basic…

【组合数学算贡献+枚举】CF816div2 C. Monoblock

题解都看了半天才懂 Problem - C - Codeforces 题意&#xff1a; 思路&#xff1a; 一开始的思路是这样的&#xff1a; 只能说&#xff0c;想到了更换枚举对象&#xff0c;然后组合数学算贡献 也想到了修改操作与&#xff08;a[i]和a[i-1]&#xff09;有关 但是我想的是枚…

在Linux上搭建gitlab以及自动化编译部署的完整流程

一、安装gitlab 首先下载gitlab的安装包&#xff0c;地址如下&#xff1a; https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/ubuntu/pool/bionic/main/g/gitlab-ce/ 然后安装下载的包即可&#xff0c;一般还需要安装openssh-server等依赖包&#xff0c;在安装gitlab包之前可以…

【MongoDB】二、MongoDB数据库的基本操作

【MongoDB】二、MongoDB数据库的基本操作 实验目的实验内容任务一&#xff1a;&#xff08;1&#xff09;创建数据库newdb&#xff08;2&#xff09;在数据库newdb中创建集合mycollection&#xff08;3&#xff09;在集合mycollection中插入以下数据&#xff1a;&#xff08;4&…

如何安装 Auto GPT 4:分步指南

动动发财的小手&#xff0c;点个赞吧&#xff01; 您对尝试最新最好的文本生成技术感到兴奋吗&#xff1f; Auto GPT 4 因其令人印象深刻的功能而广为人知&#xff0c;但启动和运行它似乎令人望而生畏。幸运的是&#xff0c;我们在这里[1]提供安装 Auto GPT 4 的分步指南。 1. …

快手sig3 48位-unidbg

研究某手app的小伙伴都了解sig3有两个版本&#xff0c;低版本的是42位&#xff0c;高版本的48位。 废话不多说&#xff0c;先抓个包&#xff1a; 上一个当前最新版本的48位sig3&#xff0c;我们以搜索接口为例&#xff0c;效果如图&#xff1a; 在上面可以看到使用unidbg的方式…

【深度学习】计算机视觉(11)——Faster RCNN(工具篇)

文章目录 1 gcc编译报错1.1 错误提示“ld: cannot find -lm/-lc/-lpthread”1.2 解决方法&#xff1a;安装glibc工具1.3 解决方法&#xff1a;修改sources.list1.4 解决方法&#xff1a;软连接 2 Permission denied3 运行报错3.1 module tensorflow has no attribute 3.2 No mo…

火山 xl,xa,xg,xk,xh,xm 六神签名参数

火山 xl,xa,xg,xk,xh,xm 六神签名参数 27/100 发布文章 weixin_38819889 未选择任何文件 new 纯属技术研究&#xff0c;如有侵权&#xff0c;请联系删除。 抓个包&#xff0c;在火山最新的15.6版本中&#xff0c;已经新增加了2个参数x-helios&#xff0c;x-medusa 前段时间do…

IDEA Java 第一个mybatis入门程序

文章目录 准备mysql 开始新建maven项目maven添加引用mybatis配置文件工具类创建实例类添加mappermappermapper.xml 测试类 发现问题org.apache.ibatis.binding.BindingException: Type interface com.cpyy.mapper.UserMapper is not known to the MapperRegistry.The error may…

[计算机图形学]动画与模拟:欧拉方法、刚体与流体(前瞻预习/复习回顾)

一、前言 这是本专栏的倒数第二篇文章了&#xff0c;为什么不是最后一篇&#xff1f;因为我要单独写一篇总结哈哈&#xff0c;不管怎么说&#xff0c;从今年的3.13的MVP变换开始写&#xff0c;写到现在&#xff0c;也是一个很大的工程了&#xff0c;我很高兴能在大二下学期的期…

使用ffmpeg拼接两张图片

最近在工作中遇到了一个需求&#xff0c;就是需要将两张图片拼接在一起&#xff0c;作为一个封面图。如果只是临时拼接一张&#xff0c;我们可以只用photoshop之类的图片编辑工具&#xff0c;将两张图片拼接在一起。而我们的需要是需要实现自动化&#xff0c;由于之前使用过ffm…

KALI入门到高级【第六章】

预计更新第一章 入门 1.1 什么是Kali Linux&#xff1f; 1.2 安装Kali Linux 1.3 Kali Linux桌面环境介绍 1.4 基本命令和工具 第二章 信息收集 1.1 网络扫描 1.2 端口扫描 1.3 漏洞扫描 1.4 社交工程学 第三章 攻击和渗透测试 1.1 密码破解 1.2 暴力破解 1.3 漏洞利用 1.4 特…

Linux网络编程:基础知识

1. MAC地址和IP地址 IPV4&#xff1a;32位&#xff1b;8B 4 32bit IPV6:128位&#xff1b;4B 32 128bit&#xff0c;图中IPV6补全为&#xff1a;fe80:0000:0000:0000:6e3f:77c3:ceca:b5a7 MAC&#xff1a;48位; 4B 12 48bit (图中IPV6和MAC地址使用的16进制表示法&a…