找回密码
 立即注册
查看: 406|回复: 4

[笔记] 基于PBD算法的布料和头发的实时模拟(Unity Demo)

[复制链接]
发表于 2022-1-3 11:55 | 显示全部楼层 |阅读模式

  • 什么是PBD算法
PBD算法的全称是Position-Base-Dynamic,顾名思义,它是一种计算机视觉方面的动态模拟算法,主要用于各种需要实时模拟的场景。相比起过去的基于真实物理规则的模拟来说,PBD算法具有这样的特征:
1、 直接基于位置而非受力情况来计算位置。计算的结果虽然并不是完全真实的物理模拟,但是可信度很高。
2、 性能更好,而且性能消耗的力度可控,可以根据实际应用中对真实性的需求强度来决定计算量。
3、 实时计算,动态模拟。
从这两个特征看出,PBD算法最适合的就是游戏、电影、VR之类的场景,不需要百分之百的完全真实,而是“看起来像”就可以了。由于PBD算法是一种实时模拟,尤其受到游戏行业的青睐。事实上,目前PBD算法在游戏领域也已经得到了广泛的应用,NVIDIA的Flex引擎包、网易的一些游戏都已经在使用该算法,有兴趣的同学可以自行查阅相关资料。相关背景就不再多做介绍,下面我们来直接进入算法环节。

  • 算法流程
在计算机领域,很多惊艳算法的核心流程都称得上是微言大义,并不冗长,例如蒙特卡洛算法、牛顿迭代法等。PBD算法的流程也十分精炼,一张图搞定:


算法的原理涉及到很多数学知识,不在这里赘述,数学好的同学可以参阅下面的论文:
在本文中,我们重点只说明一下这个算法是怎么用的。
首先,在算法的前三行,我们把想要模拟的复杂系统,例如流体、布料、头发等,拆解成一个个质点,质点拆解得越多,消耗的性能就越多,效果也就越逼真,大家可以根据自身的实际需求来进行拆解。x指的是质点i的位置,v指的是质点的初速度,m是质点的质量(并不一定是真实质量,而是在整个系统中的相对值),w是m的倒数。为啥要弄个倒数出来呢,主要是方便计算,把除法变成乘法,而且还可以把一些静止质点视为质量无限大,直接把w设成0即可。
接下来的4到17行,是一个大循环,不断更新每个质点的位置。


第5行,根据系统的受力情况来更新每个质点的速度v,比如说,当系统仅收到重力的时候,这个复杂的式子就变成了vi=vi+t*G,G是重力,在unity中就是一个Vector3(0, -9.8, 0)。t指的是时间的变化量,比如说一秒60帧,每帧迭代3次的话,t就是1/60/3秒。
第6行,对速度施加系统阻尼,阻尼是干什么的就不用多说了。
第7行,p是一个预测位置,是每个质点在整个算法完成后的新位置。该位置的初始值就是质点的初始位置x,这一行的作用是将物体当前的位置加上速度*时间之后得到一个新位置。


接下来的几行就是算法的重点了。这几行的作用是,对质点的新位置p加上碰撞和约束的修正。举例来说,上一步算出来下一行某个质点的位置是(0,-1,0),但是实际上在y=0处有一个平面,这个位置穿墙了,这时候我们就要基于墙面的碰撞来对p做一个修正。那么“约束”又是一个什么概念呢?顾名思义,约束就是对质点运动的一些限制条件,让质点不能随心所欲乱动。举个例子来说,最简单的约束就是刚体约束,你可以想象一下两个质点中间有一个小棍连接,不管两个小球怎么翻滚怎么运动,这个小棍不能断,这两个质点之间的相对距离都是不变的。



刚体约束小球

这是我们中学化学都见过的球棍模型,假设小球就是一个个质点,那么这些质点之间的约束就是典型的刚体约束,整个系统可以旋转可以运动,但是小球和小球之间的相对位置不变。
除了刚体约束之外,很常见的约束还有弹性约束、密度约束等。在根据速度得到新的位置之后,对新的位置进行碰撞和约束的修正,得到修正后的位置,这就是这几行代码的作用。细心的同学可以发现这里有一个细节,在进行约束修正的时候,这里有一个循环,迭代了一定的次数。为什么要迭代呢?最重要的原因之一就是计算机在对所有质点进行计算的时候,是按照循环的顺序从1到n依序计算的,但是在真实环境下这些质点应该是同时进行的,这就导致在计算后面的约束时可能会把之前的计算结果污染掉,因此这里需要反复计算几次来更加接近真实情况。


后面几行就没什么好说的了,本轮计算完毕,根据新位置和旧位置得出新的速度,然后把旧的位置更新,得出结果。

  • 基于PBD算法的unity demo
在学习PBD算法之后,我也参考其他人的例子,做了一些算法的实践。目前初步完成的是布料和弹性体的模拟,实际的运行效果如下面的视频展示:
项目使用的贴图是unity自带贴图,网格则是用代码拼出来的,不涉及第三方知识产权问题。工程配置和代码已经上传gayhub,地址在文末,欢迎大家试用指教。


在工程目录中,Scenes是两个demo的unity场景,Resources下是一个双面的材质和对应的shader,shader的内容很简单,就是一个最普通的顶点和片段shader,只是加了两行代码:Cull off和#pragma multi_compile_instancing,Cull off的作用是支持双面显示,毕竟布料有两面可以展示,#pragma multi_compile_instancing则是为了支持GPU Instancing,在程序中直接调用GPU来进行绘制。而src目录就是代码所在的目录,在src目录下,ClothDemo.cs和BraidDemo.cs分别是布料和弹性体的入口类,下面我们对这代码部分进行详细的说明。

  • Solver.cs


Solver.cs是实现PBD流程的核心控制类,其核心就是上面的solve方法,与前文的PBD核心流程对应。除了这几个核心流程方法外,其他的几个方法就是为系统中添加或移除外力(AEnvironmentForce)、添加或移除计算体(Body)、添加或移除碰撞。

  • Body.cs及其子类
这里的Body是一个基类,Body类是要参与计算的物体抽象出来的计算体,计算出Body中各个顶点的position之后,再用这些position去作为顶点的position调用GPU Instancing来进行实际渲染的绘制。目前实现的Body有两种:ClothBody(布料)和BraidBody(弹性体)。

  • AConstraint及其子类
AConstraint是所有约束的基类,目前实现了三种约束:绝对位移约束(AbsolutelyPosConstraint)、弯曲约束(BendingConstraint)和距离约束(DistanceConstraint)。
绝对位移约束的实现很简单,就是直接设置质点的位置,没有任何计算。举例来说,对于一面旗帜来说,旗帜和旗杆的一排连接点处的质点受到的约束就是绝对位移约束,简单粗暴,旗杆移到哪里这些质点就跟随到哪里,不考虑其他的受力情况。
弯曲约束涉及到三个点,当这三个点组成的角度发生变化的时候,会受到反向的作用力,很好理解。
距离约束是带有弹性的约束,在刚体约束的基础上增加了一定的弹性形变量。

  • ClothDemo.cs
实现布料模拟的思路是:先画一个20*12的网格Mesh出来,网格的顶点是参与渲染的顶点,顶点位置则作为PBD体系中的质点位置来计算。initClothMesh方法的作用就是生成这个网格。initClothBody是初始化PBD系统的body,为系统添加了两个力:一个是朝向(20,2,0)方向的风力,一个是朝向(0,-9.8f,0)的重力,然后又为网格的最左边一排添加了一排绝对位移约束(假设我们这里画出来的是一面旗帜)。在布料系统内部,质点与质点之间的约束比较简单,只有距离约束一种。

  • BraidDemo.cs和BraidGenerator.cs
模拟头发时,使用的是一个代码画出来的六棱柱,由于头发的生成过程比较复杂,因此另外写了一个BraidGenerator类来生成Mesh、Body和约束。对头发来说,不仅有最基础的距离约束,还增加了一个弯曲约束。两个原因:第一头发是有厚度的,如果没有弯曲约束的话,六棱柱就会直接塌陷成一张纸片,第二就是头发在自然状态下的弯曲也是有弧度的,不像布料可以直接180°对折。Demo中的参数也有两个,一个是用于保持厚度的弹性系数,一个是纵向弯曲的弹性系数。
OK,工程差不多就这么多东西,更多的细节可以直接看工程中的注释,打开工程时请使用utf-8编码格式。工程链接在下面,由于本人对unity其实并不是很熟悉,所以难免有一些疏漏,欢迎大家试用指教。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2022-1-3 12:01 | 显示全部楼层
基于这个算法似乎做不出好效果:如何防止打结?如何防止细小的东西穿透网格?
发表于 2022-1-3 12:08 | 显示全部楼层
细小的东西穿透网格指的是?
发表于 2022-1-3 12:10 | 显示全部楼层
比如一个这块布的一个角的质点穿过了组成布的格子。
又比如一个角色身上有好多块布+头发,互相穿透了
发表于 2022-1-3 12:16 | 显示全部楼层
demo里都是单个物体,在实际中多物体之间可以加入基于位置的碰撞检测
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-5-12 10:49 , Processed in 0.140183 second(s), 27 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表