games103——作业2

2023-11-08

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

完整项目已上传至github


基于物理的方法

首先介绍一下弹簧系统

弹簧系统

单个弹簧

一个理想弹簧满足虎克定律:弹簧力尽力恢复原长,下面分别一端点为固定点(左图)和两端都不为固定点(右图)的能量 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 Ω 是满足这个约束的空间。当约束被破坏时, x ⃗ = x i ⃗ \vec{x}={\vec{x_i}} x =xi , x j ⃗ \vec{x_j} xj Ω \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;
    }

结果

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

games103——作业2 的相关文章

随机推荐

  • PO模式-unittest

    PO模式是指将页面元素的定位以及元素的操作分离出来 测试用例脚本直接调用这些封装好的元素操作来组织测试用例 从而实现了测试用例脚本和元素定位 操作的分离 本文采用PO模式和unittest框架 对readmine系统执行了两条测试用例 文件
  • MATLAB三维绘图基础meshgrid函数的用法解析

    MATLAB三维绘图基础meshgrid函数的用法解析 MATLAB中meshgrid函数是用来生成网格的 函数用法是 X Y meshgrid x y 这种是最常用的一种用法 x和y分别是两个向量 使用示例 结果 A中的每个点对应的是x轴
  • STM32学习--中断

    这里写目录标题 什么是中断 中断的作用 中断的特点 STM32与中断 NVIC 中断通道 中断优先级 中断服务函数 SysTick中断 内核中断 SysTick中断函数 NVIC库函数 NVIC初始化函数 抢断优先级分组 USART使能中断
  • 博客是个好东西

    到底有么有必要写blog 从热情到荒废 最近几年老觉得个人写blog是个费时费力的事情 所以荒废了好长时间没有写blog了 加之CSDN那几年不思进取 博客搞得一塌糊涂 维护起来麻烦的很 所以更是转到博客园去了 但是 但是 随着工作的事情越
  • C# webBrowser

    webBrowser功能 10 屏蔽脚本错误 将WebBrowser控件ScriptErrorsSuppressed设置为True即可 webBrowser1 ScriptErrorsSuppressed true HtmlElementC
  • ubuntu制作开机自启动service

    我们在使用java时 常常需要把jar包设置为开机自启动 保证机器在断电重启后仍然能正常的执行jar提供的服务 在部署再ubuntu系统中的操作过程如下 1 上传jar到指定的路径下 如上传到 home test jarTest test
  • Markdown操作——代码块内如何添加代码块+如何引用代码块符号

    目录 例子 尝试 用途 例子 以引用Markdown的数学公式为例 预览 E mc 2 这是个数学公式 但是想打出源代码 比如说想介绍一下这个代码的使用 的时候却不知道该怎么操作了 其实有的人想到了 直接在外面再加上 那就可以了吧 备注 c
  • Mysql实战详解15:mysql错误Please use SHOW DDL to check it, and then recover or rollback it

    4644 129f3d45d0265000 100 64 106 105 3306 common counter ERR CODE TDDL 4644 ERR PENDING DDL JOB EXISTS Another DDL job 1
  • 重磅!中国网络空间安全协会发布《2020年中国网络安全产业统计报告》

    6月29日 中国网络空间安全协会 以下简称 协会 发布了 2020年中国网络安全产业统计报告 以下简称 报告 共有4000余人出席线上发布会 报告 对国内绝大多数具备网络安全技术和产品自有研发能力的网络安全企业进行了梳理 统计和分析 力图全
  • ASP.NET页面之间传值的五种常用方法

    1 使用QueryString变量 QueryString是一种非常简单的传值方式 他可以将传送的值显示在浏览器的地址栏中 如果是传递一个或多个安全性要求不高或是结构简单的数值时 可以使用这个方法 但是对于传递数组或对象的话 就不能用这个方
  • Linux下GDB中的 attach pid 如何使用?

    linux下使用gdb可以很好的跟踪代码 当然 让我觉得神奇的是它竟然能跟踪正在运行的进程 下面 我将用我的例子演示一下怎么使用的 第一步 获得正在运行的进程的进程号 ps ef grep lt 进程名 gt 我的就是 找到该进程的进程id
  • O(nlogn)在数组S中找存在相加可得到x的算法

    题目 设计一个运行时间为O nlogn 算法 给定n个整数的集合S和另一个整数x 该算法能确定S中是否存在两个和相加刚好为x 的元素 思想 O nlogn O n O nlogn O nlogn 就是快排的时间复杂度 O n 就是查找的时间
  • 因果推断学习笔记(一)

    在日常生活中 我们常常会用到因果推断 比如 你淋雨了 赶紧去洗澡 不然容易着凉 感冒 这里我们认为淋雨是感冒的因 通过原因 来推断可能得结果 我拉肚子了 可能是昨天海鲜吃多了 这里我们认为海鲜吃多了是拉肚子的因 并且通过拉肚子反推可能得原因
  • N皇后问题

    久闻N皇后问题在算法界的赫赫大名 今天晚上有空我也来试一下 ps 至于我的作业 哈哈哈 请读者自行领悟 一 问题描述 在n n格的棋盘上放置彼此不受攻击的n个皇后 按照国际象棋的规则 皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子 n
  • 工作组 文件服务器,工作组文件服务器

    工作组文件服务器 内容精选 换一换 媒体处理包括媒体素材的上传 下载 编目 节目转码和数据归档等工作 涉及音视频数据的存储 调用和管理 根据其业务特性对共享的文件系统有如下要求 媒体素材的视频码率高 文件规模大 要求文件系统容量大且易于扩展
  • 琢磨下python装饰器的例子

    首先得强调 python中的引用是无处不在的 建议先看引文再回来琢磨例子 简单概括装饰器 对象 装饰器名字 A 任意函数名字 B 装饰语句 A B 若执行函数B B 则可理解为 带有 A的语句可将函数B 被装饰函数 作为参数传入A 装饰器
  • 【转】英文论文审稿意见汇总

    转自 海岩秋沙 的QQ空间 以下是从一个朋友转载来的 关于英文投稿过程中编辑给出的意见 与大家一起分享 以下12点无轻重主次之分 每一点内容由总结性标题和代表性审稿人意见构成 1 目标和结果不清晰 It is noted that your
  • 亿流量大考(4):自研ES+HBase+纯内存的高性能毫秒级查询引擎

    V xin ruyuanhadeng获得600 页原创精品文章汇总PDF 一 前情回顾 上篇文章 亿流量大考 3 不加机器 如何抗住每天百亿级高并发流量 聊了一下系统架构中 百亿流量级别高并发写入场景下 如何承载这种高并发写入 同时如何在高
  • 7款家用智能摄像头横评:小米、乐橙、TP-LINK、海康威视、360、智汀、华为

    相信很多人都买过家用监控摄像头 有的是为了及时查看家里的老人孩子的动态 有的是为了看家里宠物 遇到小偷时还能拍下面貌 但市场上五花八门的监控摄像头 各种功能让人看花了眼 于是呢 为了让大家更了解智能摄像头 今天我们来测下市面上比较靠前小米
  • games103——作业2

    实验二主要使用隐式积分法以及PBD法完成布料仿真 完整项目已上传至github 文章目录 基于物理的方法 弹簧系统 单个弹簧 多个弹簧 弹簧网络 结构化弹簧网络 Structured Spring Networks 非结构化弹簧网络 Uns