> > 技术-专家视野 > Henrik Enqvist
 

Henrik Enqvist

2011-01-26 18:28:00 来源:爱酷游
 

[Remedy公司的动画程序员Henrik Enqvist以该公司开发的《艾伦觉醒》为例,详细说明了主角Alan Wake的夹克的开发过程,让夹克有真实的斜纹软呢的感觉,并让它随着主角动作自然的摆动。]

我们的动作惊悚片的主角是Alan Wake,一名被困在噩梦场景中的作家,他被强迫去同黑暗势力斗争,同时他尝试着解开失踪妻子的谜团。他不是个训练有素的英雄,而是个普通人。

为建立他的角色形象,我们的美术总监想要他穿一件旧粗斜纹软呢夹克,袖子上带着贴片。由于游戏是在真实世界场景进行的,相比于幻想游戏或空间射击游戏,能够给予玩家个性的工具非常有限。因此角色所穿的衣服就变得尤为重要。

Alan Wake的夹克必须尽可能的逼真以保持为玩家营造的惊悚氛围。夹克需要在风中飘动并在角色在森林中奔跑时为角色加入漂亮的二级动作。作为一名程序员,你马上就会开始考虑衣服仿真。

我们之前发布了很多带有衣服模拟的游戏,但通常那些给予情感表达的方法不是丝绸就是橡胶 – 那些是我们不想要的。就在最近,一些优秀的第三方的衣服仿真解决方案发布了,但是那个时候,我们需要的这种带有稳定解决方案的工具尚未出现,或者不能满足我们的需求。

本文将回顾我们曾经面临的挑战并给出我们对衣服仿真的解决方案。即使你对文章中描述的方法并不陌生,我们整合资源的方式仍然会让你感兴趣。

夹克配置

夹克作为一般蒙皮网格同角色的其它部分一起建模。拉伸夹克网格的骨头分层排列在正常骨架上。夹克的袖子使用正常的上下臂设置。上下臂均被分为主要的骨头和一个扭转的骨头。夹克的上半部由视觉跟随约束带动,下半部分则由Verlet模拟带动。


图1 正常游戏骨架顶端的夹克配置

夹克上半部

夹克骨骼为上下母子排列,当上半部骨骼动作时,下半部就会随之动作。我们曾想让下半部骨骼直接作为子部分连接至胸部,但是那会造成动作丢失 – 尤其是当角色抬起肩膀时夹克中纵向动作的遗失。

在夹克上半部,我们通过使用上臂动点的视觉跟随约束拉动肩膀骨骼来模仿真正的肩垫。通过这种方式,肩垫会跟随上臂动作,当手臂抬起时,肩垫会提起其余的骨头 – 就像真实的夹克。

链条中跟随的骨头作为夹克上半部和模拟的夹克下半部之间的层次。这些骨头由垂直向下的视觉跟随约束带动,以此补充肩部引起的旋转。我们还在左边和右边骨头间添加了位置约束来补充当肩垫运动时发生的拉伸。


图2 当角色举起他的胳膊时骨头随之而动

对于在动画输出上植入约束并将结果传送至动画数据来说,这也许已经是个很好的解决方案了,但是我们仍然选择在游戏引擎中实时的带动骨头。

通过这种方法,我们能够在动画数据中节省一些字节,还有,不管角色是否穿着夹克,都能容易的共享角色间的动画。同时,当解决实时约束时,就能够恰到好处的应用游戏内IK(比如瞄准的时候)生成的肩部运动了。

夹克下半部

在夹克上半部分解决后,我们来看看下半部分的仿真。大多数游戏中的衣服仿真都有衣服仿真顶点和渲染网格顶点之间的一对一映射。

在我们的案例中,我们想要维持夹克网格的逼真性,而不会被任何代码指令约束牵制。例如,如果我们在模拟和渲染中设置同样的网格,口袋的轮廓和夹克前半部分就会丢失。

Normal maps(法线贴图)本可以用来给夹克塑形,但是我们认为这不够。取而代之,我们想让我们的美术根据他们的想法自由的赋予夹克形状,然后让他们用法线贴图添加褶皱或其它细节,而不是掩饰缺少的几何细节。

我们的解决方案是,将低分辨率衣服网格模型用于模拟夹克,然后将它关联到用于驱动蒙皮网格的骨骼上。

 



图3 我们夹克的轮廓 vs. 同模拟共享顶点的夹克

Verlet物理

首先,我们来看一下Verlet物理,随后看看我们是如何将布匹模拟映射到骨骼的。Verlet物理算法是目前游戏中衣服仿真解决方案的标准。


图4 A4 x4定点网格和其中一个顶点的约束

对于其余部分,我会简短的概括它是如何工作的。图4展示了衣服网格和其中一个顶点的Spring约束。如图所示,网格中每个顶点都连接于所有的相邻顶点或者称它们为邻居。

对直接邻接点的约束称作拉伸约束,显示为蓝色。较长的约束,显示为红色,称为剪切/弯曲约束。

将这些约束维持在两组中十分重要,因为我们随后将使用不同的参数来解它们。要注意我们的夹克顶部布料将作为角色蒙皮,而不是由模拟带动。

具备格子状的网格并不是算法本身的要求,尽管如此,对于衣服模拟来说,这种拓扑是最简单的方式。衣服仿真的核心由两部分组成。第一部分是Verlet积分,我们对每个顶点计算出速率并将它应用在特定位置上。

Vector3 vVelocity = vertex.vCurrentPosition - vertex.vPreviousPosition;
vertex.vPreviousPosition = vertex.vCurrentPosition;
vertex.vCurrentPosition += vVelocity * ( 1.0f - fDampingFactor ) + vAcceleration * fDeltaTime * fDeltaTime;

在我们的项目中,vAcceleration的值由引力和风力的和赋予。阻尼用于夹克在引力和风力作用下形成的外观上的扭曲以及让模拟稳定。高阻尼因子让夹克纤维感觉上非常轻,让夹克慢慢的柔和的停下来;而低阻尼因子则让夹克更重,让它在动作后保持更长时间的摆动和晃动。

第二部分算法涉及Spring约束(松弛)求解。对于每一个约束,我们将顶点相互拉近或拉远,以便让它们满足他们初始的长度。以下是一段可读格式的代码片断。

Vector3 vDelta = constraint.m_vertex1.m_vCurPos - constraint.m_vertex0.m_vCurPos;
float fLength = vDelta.length();
vDelta.normalize();
Vector3 vOffset = vDelta * ( fLength - constraint.m_fRestLength );
constraint.m_vertex0.m_vCurrentPosition += vOffset / 2.0f;
constraint.m_vertex1.m_vCurrentPosition -= vOffset / 2.0f;

拉伸约束会让衣服保持完整,剪切/弯曲约束则有助于保持衣服的形状。你能看出,完全解出这一系统会带给我们移动僵硬的衣服。这也是我们为什么要在解新的位置之前给剪切/弯曲约束添加了一个系数。

vOffset *= fStiffness;
constraint.m_vertex0.m_vCurrentPosition += vOffset / 2.0f;
constraint.m_vertex1.m_vCurrentPosition -= vOffset / 2.0f;

1.0的硬度因子会带来僵硬的衣服,而0.0则会给你没有任何束缚而弯曲的衣服。

修正的时间步长

你也许已经注意到,Verlet积分假设了之前的时间步长同当前的相同,否则计算出的速率就不会正确。在运用Verlet积分时可能避免反复变化的时间步长,但求解就不会对时间不长的改变这么宽容了。

因为求解通过约束迭代工作,你会看到约束永远不会被完全解出。在游戏中,这一不准确会通过拉伸被看到,你的时间步长越短,就会看到越少的拉伸。

在最后,这会转换成你想在衣服上花费多少CPU时间。如果时间步长不是持续的,衣服的拉伸会变化,我们会在我们的系统中引入多余的波动。更重要的是,时间步长会影响硬度因子和其他衣服参数,较短的时间步长甚至会在你运用相同的硬度因子时也会自然而然的让衣服有更多网格。

在实践中,这意味着你必须在开始运用你的衣服参数扭转外观之前设置固定的时间步长。我知道有很多游戏在物理上有可变化的时间步长,但是我的个人经验告诉我,如果物理时间步长像游戏逻辑一样是固定的,你的生活会简单得多。

兜帽

在深入到衣服仿真的细节之前,我们快速的看一下是如何模拟兜帽的。我们将一根额外的骨头用于兜帽网格顶点的蒙皮。我们从骨头中心到兜帽后面的一个位置上创建了一个单摆。在单摆的末端是由Verlet物理带动的单一粒子。随后用视觉跟随约束瞄准骨头到单摆。

 
图 5 兜帽和单摆

创建骨头矩阵

兜帽提示了我们如何去做夹克下半部。我们要利用模拟网格中顶点的位置来计算骨头的改变。

我们做的第一件事是配对骨头,从而每个骨头支点都同模拟网格中的顶点配对。通过这一方法,设置矩阵变换部分就轻而易举了。

下面我们需要计算3x3旋转矩阵。矩阵中的每行(或列,取决于你的矩阵设置)都被赋予了骨头的x-,y-,z-轴。

我们将骨头的x-轴定义为从基础顶点到其下面的下一个的方向。y-轴为从左边顶点到右边顶点的矢量。


图 6 附加在衣服网格上的骨头

在图6中,x-轴显示为红色,y-轴显示为绿色。z-轴通过计算这两者之间的叉积获得。最后我们还需要正交并归一化矩阵来处理变换中的偏差。

你能看到,我们在垂直方向中运用衣服网格中的每一行(除了最后一行)来设置骨头。但在水平方向中我们只运用每个第二列。除了之前说明的在美术上的好处,这也是相对快速的方法。这种方式,传统蒙皮技术能够在GPU上渲染网格,同时还能更新大的动态顶点缓冲。

衣服网格可以是相对低的分辨率,这将减轻CPU的负担。我们方案的唯一消耗是从低分辨率模拟到高分辨率网格的转化,但是通过我们的设置,相较于模拟的其它部分,这一消耗可以被忽略。

碰撞

对于解决衣服同大腿和身体上的裁剪,我们使用椭圆体vs.粒子碰撞探测。图7展示了夹克在角色上的剪裁需要解出的椭圆体。

 
图7 椭圆体设置

椭圆体对于粒子碰撞检测非常快。碰撞可通过转移椭圆体和粒子所在的空间来解决,椭圆体从而变成球体。然后我们就能应用快速的球体 vs. 粒子碰撞测试。

在实践中,这通过使用椭圆体长度,宽度和高度值创建反向变换并将它应用在粒子位置上来实现。唯一的问题是在变换回我们原始坐标系统的碰撞垂直线是有偏差的。

们决定,我们能在解决碰撞方向时容忍一点点不精确。除非哪里严重的拉伸了椭圆体并引起很糟糕的后果,我们才会将它分为两个更均匀的椭圆体。

最大粒子距离

另外一个有待解决的问题是夹克的稳定性。快速动作的衣服可能会引起打结或在碰撞列错误的一面出现并穿过身体。我们通过为每个模拟衣服的顶点定义安全距离来解决这一问题。

对于每个顶点,初始位置对最近的骨头蒙皮,我们用这个作为参考点。如果模拟超出了临界点,我们就将顶点移动到更接近参考点的位置。在我们的设置中,我们允许底部的顶点比接近肩膀的顶点移动更多。

在较少出现的打结和剪切开始发生之前,我们所允许的最大顶点移动距离是40厘米。我们还尝试了其它技术,比如碰撞平面,但最大距离法是迄今为止最好的方法。它快速,容易设置,并适用于大多数动作而不引起衣服的视觉错误。

要像斜纹软呢,而不是橡胶

至此,我们已经朝着目标往好的方向发展了。我们美术为夹克建模并为此感到高兴;不需要动画师来为夹克赋予动画,因为所有东西都是在游戏中模拟的,CPU负担较小,因为我们也能在游戏相关的其它事项上使用处理性能。然而还是有一件事情困扰着我们 – 它看起来像胶皮。

打败拉伸

首先,我们想要处理拉伸。正如之前所说,拉伸现象是由算法迭代错误所引起的,这个话题很普遍,你会发现有很多种解决方法。

不幸的是,所有可用的解决方案都会要求我们给衣服分配更多宝贵的CPU资源。取而代之,我们通过在衣服模拟加入最后一步来解决拉伸问题,这一步我们引入了我们称之为“硬约束”的东西。

我们将硬约束定义为所有垂直的拉伸约束。我们由上至下处理这些约束,因此肩膀上的约束会比下半部腿部的约束更早解出。

由于我们以正确的顺序迭代约束,我们知道,配对中上半部顶点已经被解出了,而且不会引起任何拉伸,那么我们只需要朝上方顶点移动下方顶点。通过这种方式我们能够确认,在单一迭代之后,由上至下的长度同其余位置的长度完全一致了。

Vector3 vDelta = constraint.m_vertexTop.m_vCurPos - constraint.m_vertexDown.m_vCurPos;
float fLength = vDelta.length();
vDelta.normalize();
Vector3 vOffset = vDelta * ( fLength - constraint.m_fRestLength );
constraint.m_vertexDown.m_vCurrentPosition += vOffset;


图8 硬约束

正如你所看到的,我们不考虑夹克的水平拉伸。在水平方向添加硬约束是不可能的,因为这会造成顶点被解了两次,意味着我们会丢失垂直修正阶段的结果并不再保存其余顶点的长度。

不管怎样,我们注意到,对于夹克来说,从肉眼看去,水平拉伸实际上并不引人注意,而垂直拉伸则让夹克看上去非常糟糕。这个方案就足够了。

夹克边缘

我们要解决的第二件事是让夹克边缘比其余部分移动的多一点儿。举例来说,当你奔跑或打开夹克时你会注意到空气阻力对夹克边缘的作用要比夹克中心部位大。这是因为你的身体会为夹克其余部分挡住风。

通过数出多少约束附加在它们上面很容易找到夹克边缘,任何少于四个拉伸约束的顶点就是一个边缘。我们标记这些顶点并用不同的参数模拟它们,像这样。

•降低阻尼因子。
•全局风有更强的效果。
•世界坐标空间动作会有更强的效果(比下面的世界坐标空间动作更强)。
•允许的最大安全距离更高

通过这种方式,边缘的内部频率会跟夹克其余部分不同。不是让整个夹克像一个大钟摆一样反应,而是推动边缘从而在运动上添加好的二级动作。


图 9 边缘顶点

世界坐标空间 vs. 本地空间动作

我们注意到的下一件事情是,当角色走动时,世界空间动作在模拟上所起到的作用相对较大,而小的本地身体在肩膀上的转动或移动则被忽略了。

传统的衣服模拟都是在世界坐标空间中模拟顶点位置。也许有人会争辩说这是正确的衣服模拟方式,但这只是感觉不对。因此,取代这种方法,我们在角色本地空间中模拟夹克并添加了单独的小的世界动作。我们看到100%的本地骨架运动和10 – 30%的世界坐标空间运动共同带给了我们想要的结果。

摩擦

最后一件事是我们想要夸大夹克低幅度动作和高幅度动作之间的对比。当Alan在走路时,我们想让夹克相对平静,然后在他闪避或跳跃时有更大的动作。

我们认为,当夹克接触身体时它的动作幅度应该更小,因为夹克和衬衫之间有摩擦;而当夹克升起时它应该有更大的幅度的动作,因为它能自由运动。我们通过为每个接触椭圆体的顶点设置更高的阻尼值制造了这一效果。这种方法中,接触身体的顶点会感觉上有点贴合,让休闲状态下的夹克和快速动作时的夹克之间有了足够的对比效果。

总结和进一步工作

衣服模拟的第一阶段相对容易实现,在游戏开发资料中搜索“衣服”并应用查到的算法。第二个阶段是让夹克在感觉上和外观上达到对我们的质量条来说足矣的程度,浏览学术文章,大量的实验和错误,甚至几行废弃的代码。

顺理成章,还有改进的空间。一个例子就是除高分辨率网格贴图之外,使用低分辨率模拟,让所有的剪切不能够被完全解出。其他未能及时解决的小得性能是,当夹克折叠时的褶皱贴图,或者让旋风疯狂的吹着夹克旋转。

最后,所有付出的努力都得到了值得的回报,我们的衣服明显跟其他游戏中的衣服模拟不同。它看起来更像是花呢而不是丝绸或橡胶。当模拟其他纤维时,我们的设置也证实了它的灵活性,比如Barry Wheeler的羽绒夹克,还有一位上年纪的女士的面纱都是用同一系统实现的。通过改变参数就能很容易的创建不同外观的纤维。

(本文来源:爱酷游)
 
相关话题推荐
 
相关新闻:
发贴区
  评论仅表达个人看法,并不表明爱酷游同意其观点或证实其描述。
   
icoou资讯76小时热门排行
icoou人气图片推荐
酷友推荐
焦点推荐
24小时前沿动态
标题  
点击数 
主题推荐

话题推荐
视频专题

娱乐专题