Unite大会叠纸揭秘:《恋与深空》华丽服饰、心动的角色表演如何实现?

【GameLook专稿,禁止转载!】

GameLook报道/自2024年1月18日上线以来,《恋与深空》全球玩家数量突破7000万,荣获科隆游戏展2025最佳移动游戏。营收方面,不仅创下过8亿全球月流水峰值,还成功在今年5月份再次成为移动端收入最高的二游。

在10月24日举行的Unite 2025大会上,来自叠纸游戏的资深物理算法工程师 赵英杰分享了《恋与深空》布料模拟实现、实时表演控制、基于Unity DOTS的开发、碰撞检测模块的视线过程。

以下是Gamelook整理润色的内容:

赵英杰:

物理效果的分享主要围绕四个方面,包括布料模拟实现、实时表演控制、基于Unity DOTS的开发、碰撞检测模块。

1. 布料模拟实现

为了解决项目中的一些针对性的问题,我们内部自研了一套布料模拟的系统。

基于骨骼的布料模拟系统:StrayCloth

StrayCloth采用XPBD结合sub step的模拟方式。相比PBD,XPBD的优点是摆脱了迭代次数和时间步长的依赖,结合Substep可以显著提升解算的收敛效果。

比较特殊的地方在于,我们使用骨骼作为模拟粒子,也就说每个粒子除了位置以外还带旋转信息。

在具体的substep实现中,我们针对不同性能压力场景采用动态的子步幅时间,在1/200 -1/300之间。并且对场景中的运动对象进行运行插值,这样碰撞的效果会更加稳定。事实上运动插值虽然性能开销不是很高,但是由于类型众多,比如有静态粒子,碰撞体,风场等,实践起来还是非常麻烦的。

为什么使用骨骼而不是代理网格?主要出于以下三个原因:

恋与深空在剧情、战斗、换装中的表现需求复杂,骨骼方案可以很好的过渡动画和解算;

在可控性需求和移动端性能限制下,骨骼方案给美术的自由调节空间更大;

使用骨骼+约束可以构建类似Mesh的结构来达到相近的效果。

骨骼约束方案

在已有的骨骼布料方案里,骨骼约束实现常采用基于Local和Global形状约束的实现方式,虽然简单快速,但是也有明显的缺点——在用来做布料模拟时,效果偏向卡通风格,不符合《恋与深空》追求的3D写实风格;而且它的参数调整不直观,因为它有gloabl和local两个弯曲强度参数,不利于美术调整以及在不同场景下的效果匹配。

因此,我们在骨骼约束方案上,选择了基于Cosserat Rod的骨骼约束。它的优点包括:

效果上更加自然,贴近恋与深空整体的写实美术表现风格

参数调整上更加直观,并且三个轴向强度分离,在一些场合比如模拟裙子的时候,可以通过各向异性的弯曲强度来近似裙撑的效果。

头发模拟中可以直接复用,所以我们头发和衣服也可以共用一套约束。

具体效果可以参考最新日卡的表现:

布料与角色连接

布料和角色的连接主要通过两种方式:

层级:静态骨骼直接受角色的骨骼动画影响,根据层级关系进行移动。

这种方式比较简单,在一些偏向于刚性的连接部位时表现良好。但是对于一些骨骼交界有多个骨骼影响或者存在一定幅度拉伸和收缩的较为复杂的位置,例如手肘、肩部、腰部,表现上容易出现布料和角色分离。

吸附:静态粒子受角色模型的锚定三角形控制。并行bake mesh,通过重心坐标每帧计算更新。

对于三角形存在的退化的特殊情况,我们使用三角形顶点的蒙皮骨骼的变换,进行加权平权来更新静态粒子的transform。

碰撞方案

碰撞方案上,我们使用一个dynamic Bvh来作为场景碰撞的broad phase管理,每个角色作为sub tree包含其内部的碰撞体作为sub tree node。

同时,我们通过角色id,分享可见性还有部件类型,这个三个规则来实现不同角色、不同部件的碰撞规则的共享规则管理。

在narrow phase 当中,我们不直接生成contact,而是缓存碰撞体对,在substep中再具体的解决,因为我们采用的sub step的优点,大多数情况下直接使用DCD就可以避免一些快速运动下造成的穿透问题,不需要引入ccd或者predictive contact等一些操作。

Mesh Collider实现

对于参数化的几何碰撞体,例如plane、capsule、box,可以比较简单的解决它们和粒子以及edge的碰撞。在肩颈和胸背部等复杂部位,参数化的几何体难以准确的表达角色模型形态,表现上容易发生穿透,所以在这些部位我们大量的使用Mesh collider。

但是mesh collider作为不规则的凹体,有时也可能是非闭合的,想达到精准的碰撞效果相对参数化几何体就比较困难,特别是在移动设备下,因此我们采用散列哈希来作为三角形的粗略查找方式,结合缓存的邻近三角形结果,在迭代开始前生成一次粒子-三角形碰撞对,后续的迭代中判读粒子是否在三角形的范围,如果超出三角形的范围,通过模型的三角形邻接关系进行限制步幅的三角形查找,来获取最近的三角形,并且缓存结果作为下一次使用。

 

Face Collider

面部碰撞体可以看作是特殊的Mesh collider,相对于基本的mesh collider,它形态较为固定,也较为平滑,从模型中心出发基本上没有三角形重叠,所以我们使用16×16的CubeMap来预计算各个方向上的三角形,这样碰撞计算时可以快速查找到邻近的三角形。

层间碰撞

游戏当中布料模拟的自碰撞是最难处理的部分,出于性能上的考虑,我们给出的方案如下:

使用spatial hashing作为查找加速结构

由美术预先分层,只考虑层之间粒子和三角形碰撞

避免层之间卡住的情况,只计算粒子和三角形单法线方向的碰撞

由美术预先对布料进行分层,只考虑这些层之间的碰撞。使用散列哈希作为查找的加速结构,并且为了避免层之间卡住的情况,我们只考虑单法线方向的碰撞,如果已经穿透了则略过,交给后面的步骤来修复。

实际实践中,我们使用上一次substep的粒子位置来和当前的粒子位置进行碰撞,这样可以很简单的就解耦数据避免依赖。

层间穿透分离

对于层碰撞已经穿透的部分,我们参考了untanging cloth的方式,使用了一个轻量的解决办法,通过布料分层,从布料的固定点出发,计算不同层级的边和三角形的交点,因为我们的资产结构必定为一个uniform的网格,因此可以通过网格交点比较简单的推测出其它粒子的推出三角形,最后对穿透的粒子-三角形对施加弹簧约束来解决穿透。在实践中由于substep的关系,穿透的概率相对不大,因此我们采用分帧分块执行来减轻性能压力。

2. 实时表演控制

恋与深空剧情表现中大部分的物理表现,都是依托于cutscene来实现的各种物理效果的控制和调节。我们的工具同学开发和维护了一套非常强大的cutscene工具,在他们的基础上我们开发了多种的功能轨道来具体调控物理效果。

这边是我们一个动卡的Cutscene Physcs Track的例子,因为我们美术同学对于画面表现扣的非常细,所以可以看到整个物理轨道的配置还是非常复杂的。

SmoothBlendPose Track

在表现当中,一个非常常见的问题就是动作瞬切切换带来的物理抖动,无论是在剧情表演中还是换装中,都经常出现。

我们开发了一个较为通用的办法,通过记录初始物理姿态,在切换的时候在初始姿态和当前姿态进行姿态插值计算,这样就可以大幅度的缓解抖动,当然这个会带来一些时间开销,一般会在几十毫秒左右,在大多数情况下都可以接受,提供一些参数例如插值次数,插值的步幅大小来让美术可以根据实际需要来去调整。

Pose Track

当然,SmoothBlendPose存在局限性,不能保证的完全顺畅,特别是在一些剧情表演的复杂镜头切镜下。我们还提供了一个比较直接的方案——离线直接保存某个时间帧的物理状态,在播放时,将保存的物理状态直接应用到布料上,这样就可以完美避免切镜带来的问题。

Edit Param Track

单一的物理资产是很难满足剧情当中的各种不同场景下的表现的,比如有的时候希望布料软一些硬一些,阻尼大一些小一些。我们提供编辑参数的轨道,通过这个轨道来实时的编辑修改参数,绝大部分的参数都可以覆盖大,可以非常方便的针对一小段时间帧进行修改。这个参数修改还可以用来做一些特殊的效果,比如图中的利用编辑约束参数来实现的断裂的效果。

Animation Track

完全的物理效果实际上不足以支持起整个画面方方面面的表现的,很多时候表现上需要动画和物理的结合来做一些互动。我们通过动画轨道来实现动画和物理的衔接和融合,精细的控制不同时间帧范围下的表现。在实际制作流程当中,动作在dcc里和最终进引擎的表现差异是比较大的,包括一些引擎的实时rig系统修改后,动画可能和其它地方有穿透,所以我们在动画融合的基础上,可以叠加上物理的碰撞效果,来避免一些穿插。

动图当中展示是项链在物理和动画的交互效果,包括从物理到动画的状态切换以及在不同动画之间的切换。

Collider Track & Wind Track

Collider Track与Wind Track可以在cutscene中动态的创建、销毁碰撞体和风场。根据不同画面需求,灵活改变碰撞体和风场的状态。通过角色、部件类型、还有布料的层分组来细节控制所要影响的对象范围。

并且,碰撞体和风场轨道的绝大部分参数可以添加动画帧控制,包括碰撞体的形态大小、风场的方向、范围、强度、湍流等,方便美术把控物理效果,精准控制变化。

图中是轨道胶囊体和风场的一些表现例子。

3. 基于Unity DOTS的开发

Jobs + Burst + Mathematics

DOTS这套工具非常强大,在C#层就可以实现高性能的多线程开发。我们的物理系统使用DOTS完全构建在C#层上,功能迭代和debug都非常便利。目前来说我们最高可以支持2000+骨骼粒子的模拟。

当然,我们也针对性的在项目中,做了一些优化进一步提升性能。

Cache Job

模拟中的job数量和依赖关系确定,job data并不频繁变化,帧内一般为相同数量和依赖关系的job组多次循环执行,Unity Jobs 在发起任务时每次都需要重新创建job,虽然可以提前发起任务缓解,但是依然会卡主线程。并且在执行完成job还需要clear。基于以上的观察,我们开发了Cache Job的方案,预先创建好job data,然后每次执行时复用,避免每次重新创建job带来的性能开销。

实现上比较简单,因为是一个专用的结构,只考虑一些固定的使用场景。额外添加了一个Atomic Queue用来存cache job,使用fetch and add array 来存具体的job。右边是worker执行cache job的流程示意图。

Neon Intrinsics

Burst会针对不同平台生成高性能的simd code,在Burst Inspector中可以非常方便的查看。经过检查Burst Inspector和实机测试,在某些场合下也可以通过手写Arm Neon Intrinsics来进一步提升性能。

Dot(float4)

对于点乘,我这里列出了3种方式,使用neon intrinsics相比于mathematics在测试用例中可以获得约30%的性能提升。如果目标机型支持armv8.2的话,可以使用新增的规约加法指令,来进一步的提升性能。一般来说现在市面上的大部分流行机型都是支持armv8.2的。

Transpose(float4x4)

对于转置计算,可以看到mathematics生成的assembly code看起来性能是非常低的,通过手写neon intrinsics, 可以得到一个巨大的性能提升。

如果只是纯粹的需要转置,可以直接使用交错读,这里这样实现因为在我一般的实际使用中是通过对4个float4转置来将点乘变成矢量乘。

这里只是给出这两个项目里比较常用的例子。因为mathematics的代码一般被内联,在具体优化时还需要根据代码的上下文进行具体的优化,可以结合burst inspector和真机测试来进行具体的性能测试。

4. 碰撞检测模块

为什么要脱离Unity成熟的物理模块重新开发?

Unity 本身具有基于physx的一套成熟的物理模块,而脱离Unity成熟的物理模块重新开发,主要基于以下考虑:

恋与深空有相当多的不同种类的玩法,玩法间的layer设置相对独立,非常希望能够各自维护一套layer设置。

有些模块例如战斗需要特殊的Trigger触发和退出机制希望在底层就可以支持,对于执行流程也希望有更灵活的控制。

最后是在性能探索上我们也有一些想法,就是在仅需要碰撞检测的情况下,利用DOTS能否提升性能?

《恋与深空》中的实现包括:

基本实现了所有原生的碰撞查询功能

定制化的Update和Trigger逻辑

线程安全的查询接口,上层可以无负担调用

结合DOTS的轻量化结构实现,在性能测试中,最高可获得~15%的提升

查询流程示例

由于真机上,我们实际的线程数量是固定的为4,所以对于memory allocator可以预先按照线程数量分配好,在分配时可以直接根据当前线程索引来获取。

使用基于SAH的dynamic bvh作为broadphase加速结构,在插入、删除以及超出范围的移动时,对当前操作节点的邻近的几个层级节点进行旋转平衡。

因为碰撞检测的功能目标相对概括,对于精度要求没有那么高,所以我们也适当的牺牲一些精度简化了一些碰撞检测算法来提升性能。

触发流程示例

为了满足战斗模块的需求,我们设计了特殊的trigger触发逻辑,Trigger的 Enter 和 Exit必须要成对出现,可以看到以下流程示意图中,在a触发b的函数中移除b后,会触发所有和b存在overlap的collider,这里和unity原生的有所不同——原生的unity中在trigger逻辑中删除掉b是不会触发其它碰撞体的trigger的。最后,我们通过History计数来标记collider的版本,解决复用逻辑可能会导致的一些潜在问题。

如若转载,请注明出处:http://www.gamelook.com.cn/2025/10/580974/

关注微信