找回密码
 立即注册
查看: 1869|回复: 20

游戏中会自由飘动的头发是怎么做出来的?

[复制链接]
发表于 2020-12-21 09:42 | 显示全部楼层 |阅读模式
人物的衣服、头发等物理效果是怎么制作出来的?
发表于 2020-12-21 09:44 | 显示全部楼层
五月三日更..
同学说这样写下去我之后论文答辩要被查重了怎么办...

五月二日更..

这个圈子实在是太小了,瞬间都被人认出来了。。

更新整个回答框架如下:
1.物理引擎中的布料 (5.1完结)

2.布料建模的一些方法
2.1 现实生活中布料的一些基本问题
2.2 常用的建模方法一览
2.3 几何方法
2.4 三种物理方法 (5月3日更新到这里)
2.5 一些新的模型,包括Yarn-Based Cloth Simulation
2.6 真实不止是因为物理,还有渲染相关

3.实际游戏中的布料
3.1 根本就是骗人的重复播放的帧动画的假布料
3.2 看起来真的好棒的使用物理引擎的布料制作流程

4. 其他
4.1 头发和布料的关系
4.2 一些参考书籍

/* 请不要随便转载 */
// ----------------------------------------------------------------------------
Milo的爱丽丝博文中已经非常详细地讲清楚了一个简单的头发物理系统是如何模拟的。
布料和头发在模拟过程中,早期使用的技术和物理模型基本上是类似的,都是对纤维结构的表现形式的一种抽象,某种程度上来说,布料的结构相对于稍微复杂一点。后来因为头发和布料各自独有的一些特点,逐渐细化。这里我简单描述一下布料模拟的一些历史和技术。

1. 物理引擎中的布料
对于现在的游戏开发来说,使用成熟的物理引擎可以非常简单的建立布料系统。目前被游戏开发者使用的最多的物理引擎包括Bullet, Physx/APEX, Havok都包含了功能非常强大的布料模拟。目前Havok物理引擎中的Cloth组件仍需要商业授权,下面的图片除Havok外来自各个SDK中Demo的截图,这些程序Demo可以非常方便的下载和编译得到。

1 Bullet物理引擎中的SoftBody模拟的布料效果,图示表示5片随风飘动的布料(Bullet 2.82)


2 Physx物理引擎中模拟布料和斗篷的效果,图示表示多个旗帜和一件斗篷(Physx 3.32)


3 APEX制作出的服装效果,图示演示了走路的人身上衬衫和裤子的变化(APEX1.3.2)


4 Havok Cloth制作出的舞女效果(图片来源Youtube视频)

对于普通的游戏开发者来说,只要引入了物理引擎,就可以很方便的规避布料实现的各种细节。包括布料的数据结构,布料各项参数的建模、碰撞检测及积分运算等等。同时,游戏策划和美术可以更加专注地使用商业引擎提供的工具可视化地调节需要的布料的艺术效果,满足所需的游戏性,对于程序员而言也显著地降低了工作量
当然,物理引擎也有自己的局限性,当需要自定义一些特殊的布料效果时,部分物理引擎就显得捉襟见肘了。


2. 那游戏引擎内部是怎么做的呢
上一节的答案肯定是不能满足题主的。物理引擎为游戏开发者提供了一个黑盒子,只要游戏策划和美术设计好游戏角色身上布料相应的参数,丢给物理引擎,它就可以Duang的一下,加好各种特效。本节将会解释布料本身常用的物理模型、简单的碰撞检测内容。
注:本节和本节之后使用的图片都是自己照着一些论文和书籍手动画的,应该不会有版权问题的说…不过有可能有学术错误,欢迎指正

2.1 现实生活中布料的复杂性

图5 不同布料的结构和纺织界对于布料的力学模型的简单建模
现实生活中的布料是非常复杂的,如图5上半部分所示。自珍妮纺织机改进之后,人类对于舒适和时尚的追求也是孜孜不倦。一匹成品布看上去、摸上去不尽相同,主要有三个方面的原因:
    使用了不同的纤维:棉、麻、化纤……
    采用的不同的编织纹:平纹、斜纹、缎纹……
    编织时采用了不同的参数:支数、针数……
纺织学家为了对纺织布料的可控,也建立了一些力学模型,最早可以追溯到1937年Peirce的模型[1],该模型其实就是一个剖面图。在此基础上,后续也有许多改进。这些基本的模型也启发了计算机图形学对于布料模拟相应的研究。

2.2 图形学布料仿真模型历史

图6 常见的几种布料模拟的模型
常见的布料模拟模型包括几何模型弹簧质点模型弹力模型粒子模型。接下来我来简要解释一下各个模型的基本思想和适用场合,其中由于弹簧质点模型为现在游戏中的主流解决方案,我将会仔细地介绍其步骤和各项细节。


2.3 几何模型
1986年Weil在当年的Siggraph上发表了关于几何模型构建布料的方法[2]。这个方法将布料描述为由3D点阵构成的2D网格。整个算法步骤也简单,就不详细的讲了。大致的意思可以这样形象的理解(基本上是自己脑洞出来的理解,这样就可以一点都不涉及公式了,所以不见得准确
"╮( ̄▽ ̄"")╭"):


7 两个钉子悬挂绳子

在墙上钉两个钉子,一根很长的绳子搭在两个钉子上,从两头开始分别放松这个绳子。我们可以把中间这一段想象成一块布料的侧视图(比如在学校操场的双杠上晒被子,如图8,把被子跨过两个双杠,然后从双杠两边把被子往中间放松)。这样的几何形状看起来就像一块自然舒展的布料,将这个模型放在三维空间中,假设有很多符合条件的钉子,我们将这些线段进行放松,然后让计算机通过一系列数学公式的迭代,就可以得到一块类似悬挂的布料的几何形状。

8 双杠晒被子(图片来自网络),侧视图看起来如图7一样。


总的来说,这个方法不算是很精确,而且只能提供一个静态的悬挂形状。不过这是非常有用的,比如建模一块静态的布料,或者画画的时候画出一个具有动感的衣服、桌布的图片,这会是一个让人看起来感觉真实的思路。

2.4 物理模型
所谓物理模型,就是在模拟布料的时候,用到了基本的物理定律。主流的物理模型有图6中所示的另外三种:
    弹力模型
    粒子模型
    质点弹簧模型

2.4.1 弹力模型
弹力模型的想法非常简单,它是在1986年由Feynman提出的[3]。我们先可以想一想,为什么上面的几何模型最后的结果看起来像一块布或者绳子呢,这里面一定是物理规律在起着作用。高中时候学过化学的话就知道,在不违背泡利原理的情况下,核外电子总是尽量排布在能量最低的轨道上,电子也相应地处于稳定状态。我们身边的万事万物都想维持自己的稳定,让自己处于「极小能量」的状态,每个物体具有势能,当它的势能极低的时候,它的状态是稳定的。维稳总是神秘又让人着迷的话题:比如爹妈想喊你回家当公务员,这就产生了一定的饭碗势能,如果没有额外的能量输入,就只能往回滚了。又比如身边的同学都有女朋友,家里亲戚隔壁左右的小孩都结了婚,这就产生了一定的结婚势能……呃,扯远了。

作为一块愿望非常淳朴的布料,它的基本修养也是这个样子:无论是被穿在身上还是铺在桌上,它都想维护自己的稳定。那么布料里的势能该如何描述呢?弹力模型借助了经典牛顿力学里面的弹力理论,将布料的势能描述为包含一系列能量函数,包含布料的拉伸、弯曲、重力等等。将得到的公式进行求解,就可以得到布料在哪种状态下能量极低,这个解这就是这块布料看起来十分自然的样子时候的姿势。

2.4.2 粒子模型
= =先占坑码代码去了,回来再写。
(你们快点评论一下,有疑惑我就顺便改( _) )

参考文献
[1] Peirce F T. Thegeometry of cloth structure[J]. J. Text. Inst., 1937, 28: T45.
[2] Weil, Jerry. "Thesynthesis of cloth objects." ACM Siggraph Computer Graphics 20.4 (1986):49-54.
[3] Feynman, Carl Richard.Modeling the appearance of cloth. Diss. Massachusetts Institute of Technology,1986.

本帖子中包含更多资源

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

×
发表于 2020-12-21 09:53 | 显示全部楼层
早期是用简单几何+贴图硬做的,可以看到很明显的折角和不自然的光照(请大家自己看红圈内的部分)


后来,AMD做了TressFX,代码都有:http://developer.amd.com/wordpress/media/2013/11/TressFX11_v2.0.zip
神奇的是,这个技术在同级别的NV卡上跑得更快。


NV做了HairWorks NVIDIA HairWorks


用于那个游戏不用说了吧,通过图大家就能自己看出来。

来,欢乐一下


本帖子中包含更多资源

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

×
发表于 2020-12-21 09:57 | 显示全部楼层
不知道是哪里看到的冷知识
GTA V里面主角衣服的皱褶不是用物理引擎即时运算的动态凹凸纹理,而是两张交错的凹凸纹理,一张表示向左一张表示向右。随着主角躯干的转动,改变两边的透明度,躯干向左的时候把向右的纹理透明度变高,向左的变低,反之亦然。这样就用简简单单两个透明度参数体现了非常复杂的计算才能实现的视觉效果。
例图是躯干前部,但是原理一样。

PS3、360版在这么破的硬件上跑出这样的视觉效果,必须靠这种黑科技(耍赖开挂)的方式实现。

本帖子中包含更多资源

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

×
发表于 2020-12-21 10:00 | 显示全部楼层
爱丽丝的发丝──《爱丽丝惊魂记:疯狂再临》制作点滴

@Milo Yip 大大倒也是谦虚,只是关注了该问题:)
发表于 2020-12-21 10:05 | 显示全部楼层
游戏开发的小透明斗胆回答一发:

我为虚幻4引擎写了一个海飞丝插件,参考了TressFX的论文。

开发日志在此:Unreal Engine 4 —— HairStrandPlugin开发日志
发表于 2020-12-21 10:09 | 显示全部楼层
为了给大家更直观的印象,我分享一个用 JavaScript 写的 Demo,大家可以在浏览器里看(简陋的)效果。
https://cs275-try.glitch.me源码在这里:Glitch : ,是很简单的 mass-spring model:
为节省时间,我用 A-Frame 框架来处理非物理模拟的部分,首先介绍一下这一部分:
  1. <a-entity id="head" gltf-model="#head-gltf" position="0 1 -5" scale="0.75 0.75 0.75">
  2.         <a-animation attribute="rotation"
  3.           dur="1000"
  4.           fill="forwards"
  5.           direction="alternate"
  6.           from="0 0 -60"
  7.           to="0 0 60"
  8.           repeat="indefinite"></a-animation>
  9.         <a-entity append-hair id="hair-1" line="start: 0 2.5 0; end: 0 4 0"></a-entity>
  10.         <a-entity append-hair id="hair-2" line="start: 0 2.5 0; end: 0.3 4 0"></a-entity>
  11.         <a-entity append-hair id="hair-3" line="start: 0 2.5 0; end: -0.3 4 0"></a-entity>
  12. </a-entity>
复制代码
<a-entity id="head" 是人头的模型,为节省时间我找了个球当头。
<a-animation attribute="rotation" 则让它摇头晃脑,这样可以带动简陋的头发朴素地飘扬。
<a-entity append-hair 是带上了  append-hair component 的一个 entity,我在这个 component 里面写了个循环来添加头发,并进行物理模拟更新头发的位置。然后这个 entity 还带有 line component,这个 component 会画出头发根部的一小段,也就是毛囊了,模拟开始后的一瞬间我们会在毛囊上接发,接上十根 line 当真正的头发。


接下来来看 append-hair 是怎么实现的:
  1. var { AFRAME, THREE } = window;
  2. AFRAME.registerComponent('append-hair', {
  3.   schema: {
  4.     initialized: { default: false },
  5.     length: { type: 'int', default: 10 },
  6.     size: { type: 'number', default: 2.5 },
  7.     penalty: { type: 'number', default: 980 },
  8.     initialLength: { type: 'number', default: 0.01 },
  9.     stiffness: { type: 'number', default: 300 },
  10.     damping: { type: 'number', default: 0.01 },
  11.     graverty: { type: 'number', default: 9.8 },
  12.   },
复制代码
可以看到这部分代码就是在 A-Frame 框架里注册了一个叫  append-hair 的 component,注册之后就可以在上面的 HTML 模板里使用这个 component 了。
Schema 描述了这个 component 可以传入的参数,以及参数的默认值,从这些参数就可以看出这个模拟头发的简单模型由哪些变量控制:
length 表示头发的段数,如下图,mass-spring model 中,头发其实是由很多段弹簧组成的,length 越长模拟越精细,相当于对头发无限细分。生产中会用 shader 对它们插值,从而得到亮丽而连续的发丝,这样得到的发丝叫 guide strand(导缕),对应于下图中的第三步,接着要把它复制几次,从用较少的模拟计算量而得到比较密的头发。
size是用来做碰撞检测的,理论上说应该给头做个包围盒,然后在每个 tick 检测头发的每个质点是否和包围盒相交,相交了就给一个惩罚力,让头发远离头。不过既然我的头,是个球,那直接用球的半径来检测碰撞就好了,比较简单。
penalty 就是惩罚力的加速度
initialLength 是弹簧的初始长度,胡克说过,弹力 = 弹性系数 x (当前长度 - initialLength)  
stiffness 就是弹性系数
damping 是阻尼,用它给出与速度方向相反的力,来防止头发动得太快,看起来会很假(当然它不是这个 Demo 显得很假的关键因素)
graverty 头发会自然下落,这个加速度设为经典的 9.8
以上的参数建模了每根头发的简单物理行为。
接着看看每个 tick 会发生什么:
  1.   tock(time, delta) {
  2.     let positionOfHairRoot = null;
  3.     // get local position
  4.     {
  5.       const { x, y, z } = this.el.components.line.data.end;
  6.       positionOfHairRoot = new THREE.Vector3(x, y, z);
  7.     }
  8.     // get global position
  9.     positionOfHairRoot = this.el.object3D.localToWorld(positionOfHairRoot)
  10.     if (this.data.initialized) {
  11.       // select consequent hairs
  12.       const consequentHairs = document.querySelectorAll(`a-scene > a-entity.consequent-hairs-of-${this.el.id} > a-entity[mass]`);
  13.       this.updateConsequentHair(consequentHairs, positionOfHairRoot, delta);
  14.     } else {
  15.       // add consequent hair entities
  16.       const head = document.querySelector('a-scene');
  17.       const consequentHairGroup = document.createElement('a-entity');
  18.       consequentHairGroup.setAttribute('class', `consequent-hairs-of-${this.el.id}`);
  19.       head.appendChild(consequentHairGroup);
  20.       this.initConsequentHair(consequentHairGroup, positionOfHairRoot);
  21.       this.data.initialized = true;
  22.     }
  23.   },
复制代码
由于第一个 tick 时很多 entity 还没完成初始化,所以我选择在 tock 里进行物理模拟,tock 指每个 tick 完成计算后的那些瞬间。
这段代码大意就是取得毛囊的位置,然后如果头发还没初始化就进行接发,用 document.createElement 创建一个实体组,把这个实体组放到 head 实体上,然后用 this.initConsequentHair 往里面塞进 length 根头发。这都是很基本的 DOM 操作,让我想到了用 jQuery 的黑暗日子(虽然我没在生产中用过 jQuery)。
那如果已经初始化完了,就进行物理模拟,this.updateConsequentHair 就是更新毛囊上接的一根接一根的头发。
我首先把每个 mass 的位置放到数组里
  1.   /** calculate consequent hairs' position */
  2.   updateConsequentHair(consequentHairs, positionOfHairRoot, delta) {
  3.     // collect positions
  4.     const positions = [positionOfHairRoot];
  5.     for (let i = 0; i < this.data.length; i += 1) {
  6.       const hairPart = consequentHairs[i];
  7.       positions.push(hairPart.components.line.data.end);
  8.     }
复制代码
然后取出弹簧的参数 initialLength, stiffness 对于每一段弹簧进行模拟:
  1.     const { initialLength, stiffness, damping } = this.data;
  2.     // update components and position
  3.     for (let i = 0; i < this.data.length; i += 1) {
复制代码
用胡克定律可以算出其弹力:
  1.       const distance = (new THREE.Vector3()).copy(positions[i]).sub(positions[i + 1]);
  2.       // f_s = k(x - x_0)
  3.       const kx = Math.max(0, distance.length() - initialLength) * stiffness;
  4.       const springForce = distance.normalize().multiplyScalar(kx);
复制代码
然后取出每个质点的速度,用阻尼系数算出其阻力:
  1.       // only consequentHairs have force and velocity, root hair is moved by animation
  2.       // f_d = -k_d * v
  3.       const hairPart = consequentHairs[i];
  4.       let { x, y, z } = hairPart.components.velocity.data;
  5.       const dampingForce = new THREE.Vector3(x, y, z).multiplyScalar(damping);
复制代码
从而得到合力:
  1.       // f = f_s + f_d + g + penalty
  2.       const force = (new THREE.Vector3()).copy(springForce)
  3.         .add(dampingForce)
  4.         .add(new THREE.Vector3(0, -this.data.graverty, 0))
  5.         .add(this.getCollidingWithHeadPenaltyForce(positions[i + 1])); // note that threejs is not immutable!
  6.       hairPart.setAttribute('force', force); // make it visible in dev tool
复制代码
合力除以质点的质量就得到加速度,加速度乘以每帧的时间的平方就得到了质点运动的距离:
  1.       // a = f / m
  2.       const acceleator = (new THREE.Vector3()).copy(force).divideScalar(hairPart.components.mass.data);
  3.       // v = a * dt
  4.       const velocity = (new THREE.Vector3()).copy(acceleator).multiplyScalar(delta / 1000);
  5.       hairPart.setAttribute('velocity', velocity); // make it visible in dev tool
  6.       // s = v * dt
  7.       const positionDelta = (new THREE.Vector3()).copy(velocity).multiplyScalar(delta / 1000);
  8.       ({ x, y, z } = positions[i + 1]);
复制代码
最后更新一下质点的位置就完事了:
  1.       positions[i + 1] = new THREE.Vector3(x, y, z).add(positionDelta);
  2.     }
  3.     // set positions
  4.     for (let i = 0; i < this.data.length; i += 1) {
  5.       const { x, y, z } = positions[i];
  6.       const start = `${x}, ${y}, ${z}`;
  7.       {
  8.         const { x, y, z } = positions[i + 1];
  9.         const end = `${x}, ${y}, ${z}`;
  10.         const hairPart = consequentHairs[i];
  11.         hairPart.setAttribute('line', { start, end });
  12.       }
  13.     }
复制代码
可以发现 mass-spring 的代码很简单,实际效果可以看到头上的弹簧就像秀发一样舞动,但好像还缺了点什么……
没有做的:
    限制头发伸长的长度,头发段虽然是弹簧,但应该只能在有限范围内伸缩,防止出现 Demo 里变成魔发公主的情况遍历脑袋表面随机生成更多毛囊,目前是手动放了三个毛囊,所以只有三毛对惩罚力进行精细的调参,让头发不至于在头表面蹦跳,也不会栽进头里,我在 Demo 里就随便给了个值shader,插值得到更密更顺的头发,还有对光的反映头发之间的碰撞检测,做了就可以模拟头发的缠绕等行为,而不是任由它们互相穿过与空气的结合,需要用 Navier-Stokes 方程模拟空气,并在与头发接触时传递冲量,用 JS 做效果极差,就删了


模拟头发还有布料这样柔软的物体,除了离散的模型外还有用连续体力学来解的。
那 Mass-Spring 这么好懂为啥不都用它呢?目前仍未得到完美解决的问题在于,如何通过头发布料的变形来反过来得到力。
Continue Model 可以以更大的计算量为代价解决这些问题。主要是描述每一个点处的应力(形变的情况),头发里就是个 1D 的张量,乘以一个方向就能得到这个方向上的力。
当然这些就不是我能弄懂的了……

本帖子中包含更多资源

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

×
发表于 2020-12-21 10:17 | 显示全部楼层
其实很多种做法,那些落后的做法其实有时候也能做到让人看不出来,高大上的做法在某些情况下也很不好用(比如显卡厂出的头发渲染功能,用来做不是那么丝状的日本动漫头发的时候就不一定实用了)
1是纯手工k帧动画,会比较难弄,但是简单快速方便。
2是借助弹簧插件的k帧动画,如果说动画和玩家操作反馈没有这么明显关联,就是纯动画就可以的话,这种方法的效果其实很棒了,但穿插比较难调。这事情工作流程上可以完全甩给美术。
3是游戏内的物理系统,刚体+弹簧骨骼动画,代码脚本控制,一般用cpu计算,高端点还可以用上并行计算,耗费资源比较大,有时候也会各种bug,最终效果类似MMD里面那种头发。Unity和虚幻都有很多插件。
4是各种显卡厂商的技术,本质还是类似于弹簧的,只是可以用gpu去渲染,达到同时渲染很多的效果,类似于草地渲染,但是似乎处理碰撞方面依然不太好,兼容性差,经常并做不出想的效果。目前比较完整的工具链很少,配置起来也很麻烦,学习成本太在高,技术也一直变,不够成熟。很多时候其实也没太大的必要,一般都是有不少沉淀的大公司,人力时间充足,才有精力去弄。达到锦上添花的效果。
如果是想做游戏,项目角度考虑,个人觉得暂时用到3号方案目前就足够90%场合了。 4号方案虽然美好,但是一研究一个月没了,可能会比较蛋疼,学生党学技术的话,最好先把前面的应用情况了解了,然后再去看4号方案搞科研。一般用到4号方案的游戏,1,2,3号方案一般也都同时在使用的。比如巫师里面的小辫子应该是3号方案,然后怪物的毛发是4号方案。
发表于 2020-12-21 10:19 | 显示全部楼层
我不知道游戏里具体是怎么做的
不过我可以贴最近我在看的三篇paper, 希望能有帮助
一个是实时的头发模拟, 基于少量guide hair的模拟, 再用hair skinning model插出头发, 最后修正碰撞获得细节
Chai, M., Zheng, C., & Zhou, K. (2014). A reduced model for interactive hairs. ACM Transactions on Graphics, 33(4), 1–11. doi:10.1145/2601097.2601211
一个是off-line的full simulation, 上面那篇文章simulate guide hair用到了这个技术
Selle, A., Lentine, M., & Fedkiw, R. (2008). A mass spring model for hair simulation. SIGGRAPH '08: SIGGRAPH 2008 Papers. doi:10.1145/1399504.1360663
还有一篇也是mass-spring model, 但我还没有仔细看...
Liu, T., Bargteil, A. W., O'Brien, J. F., & Kavan, L. (2013). Fast simulation of mass-spring systems. ACM Transactions on Graphics, 32(6), 1–7. doi:10.1145/2508363.2508406
发表于 2020-12-21 10:28 | 显示全部楼层
您问的根根动的那种,是游戏cg级别的头发。一般实际游戏场景中的头发只不过是带透明通道的贴图附在一张面片上。
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

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

GMT+8, 2024-12-25 02:11 , Processed in 0.099154 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

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