百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

使用Unity为数字人添加逼真的毛发细节

bigegpt 2024-08-20 10:55 2 浏览

数字人技术现在已经是一项比较完善的技术,通过Unity引擎的烘焙与深度渲染,现在的数字人已经变得相当逼真。


在本文中来自完美世界移动项目支持部的徐行跟大家分享了他在完美世界研发的 Unity 毛发系统。



“其实这个作品还在很初期的阶段,还有很大的提升空间,这次主要是让她帮忙展示一下毛发。这个是本工作的大概时间线,其实前期还不配被称之为自研毛发系统,只能称得上是一个英伟达 HairWorks SDK 的集成。随着后期需求越来越深入,自研的部分也越来越多。现在只是思想上部分参考了 HairWorks,代码已经全部重写了。”徐行说道。


现在游戏中比较传统的毛发解决方案有两类,第一类就是网格头发,这类头发其实渲染效果还不错,但是往往物理效果不太好,而且也不太适合做短毛。


Uncharted by NaughtyDog


另一类是 FurShell,专门用来做短毛的。但是凑近了看,层状瑕疵也非常明显。做毛发的终极解决方案,肯定就是基于发丝的毛发系统。这是一些有代表性的案例,比如《最终幻想》、《怪兽公司》、《长发奇缘》、《古墓丽影》和《巫师 3》。


虽然基于发丝的方案效果很棒,但是问题也是显而易见的。那就是发丝数量太多,导致无论是物理模拟还是渲染还是存储等等都有比较大的困难。


HairWorks 是如何应对这些挑战的呢?这就得从它的资源表示方式讲起了。HairWorks 的毛发资源并不是直接存储每根发丝的信息,而是主要存储了两个东西,图中的黄线被称之为导发,图中的网格被称为生长网格。而物理模拟则在导发上进行,发丝则是导发间插值出来的,这样就能极大的减少计算量和存储间的需求。



发丝具体是怎么插值的呢?其实生长网格是由一些三角面构成的。生长网格上面每一个顶点对应一根导发,随机生成一定数量的重心坐标,在渲染的时候就可以利用重心坐标作为权重,在三根导发间进行插值了。


为了让生成的发丝更加平滑,在生成发丝前还可以对导发做一些平滑,使用平滑过的导发进行插值。


物理模拟又是怎么做的呢?HairWorks 选择了一种非常易于理解的物理模拟方法,也就是质点弹簧法。图中的红线就是进行物理模拟的导发,红线上的小圆圈就是质点,发根处的质点是完全受骨骼蒙皮控制的,其他的质点则会受到诸如风力、重力等等的影响,也会跟碰撞体发生碰撞。


为了使基于质点的物理模拟能够体现毛发的感觉,HairWorks 为质点间增加了一系列的约束,也就是俗称的弹簧。比如图左中,同一个头发上相邻质点间有长度约束,太近了会排斥,太远了会吸引,这有助于保持头发的大致长度。


再比如黄色折线,它表示的是完全受骨骼蒙皮控制的毛发。它跟红色这条线,也就是物理模拟控制的毛发间也有约束,这有助于保持美术所制作的造型。


另外,HairWorks 还有一个比较有特色的约束,就是在相邻的导发之间也有距离约束,有助于保持毛发的体积感,一定程度上避免穿插。当然还有很多其他种类的约束我们就不一一展开提了。


为了加速物理模拟,HairWorks 的物理模拟是在 Computer Shader 中并行进行的。一个控制点,也就是一个质点对应一个线程,一个导发对应一个线程组,这就便于使用效率比较高的共享存储。


但是有些约束是有先后顺序依赖关系的,例如同一个质点上的两个长度约束,如果调换执行顺序,那么执行结果就不一样了。正确的做法只能是串行解算这些约束,但是串行显然是不如并行快的。为了加速物理模拟,HairWorks 还是想办法做到了并行。



如图,它把长度约束分为两组,一组内每个约束都互不相临。这样一组的约束就可以并行解算,一组解算完之后,再解算另一组,交替迭代几次就能获得比较稳定的结果。


“当然后面我们还做了一些物理模拟的拓展和优化,例如加入了 3D 风场以及允许传入不稳定的物理模拟 TimeStep 等等。”徐行说道。



“接下是关于引擎与跨平台的一些分享,这张图是我们把 HairWorks 集成到自研引擎之后,在《笑傲江湖》和一些 Demo 中的效果。当时另一个端游项目看到了,觉得效果不错,挺想用。他们是基于 Unity 开发的,所以接下来我就开始了向 Unity 的集成。”徐行说。


其实远在完美世界之前,已经有很多团队进行了这项工作。比如图中是 Unity Japan 团队的成果,但是他们做的集成时间都比较早了,当时条件有限,所以也存在一些问题。比如无法使用 Unity 内部的材质,或者光照不全,没有平行光的投影等等。


他们为什么会遇到问题呢?这就要讲到他们集成的实现原理。


他们选择了原生插件这种集成方式,是由于 HairWorks 要用到诸如 Computer、Tessellation 这类很底层的图形功能。所以说使用能达到底层图形设备接口,例如 D3D Device 的原生插件机制还是比较稳妥的。


但是也正是因为他们是使用底层图形设备接口,例如 D3D Device,进行渲染,而不是使用 Unity 提供的接口例如 DrawRenderer,所以 Unity 内的材质、灯光、环境以及渲染管线内部的很多信息,都很难传过去,也就很难在原生插件里面重现 Unity 的渲染效果,所以原生插件渲染出来的东西往往光照不全。


另外由于 Built-In 管线中提供的插入点有限,所以没有办法渲染阴影深度。上述问题如果按原有思路做下去,是很难解决的。但是这些问题不解决又没有办法实际投产,所以这些集成最后基本上就被搁置了。


徐行的团队最初也是一筹莫展的,差点因此放弃,但是最后还是想到了一个解决方案,那就是既然 Unity 里面的东西很难拿出来,是不是可以不把他们拿出来放到原生插件里面?而是想办法把原生插件生成的 HairWorks 的几何信息传到 Unity 里面,这样就可以在 Unity 里做光照了,这样可行吗?


徐行的办法其实很简单,他使用了一种名为渲染代理的方法。所谓的渲染代理其实就是一个在 Unity 里面创建的头发的包围体,原生插件把 HairWorks 的几何信息渲染到了自己创建的 GBuffer 中。


然后渲染代理挂上了 Unity 内的材质,直接参与 Unity 的渲染。与普通材质有所不同的仅仅是在渲染的时候会读取我的 GBuffer 中的几何信息,并把它伪装成自己的进行光照。这样的话,就可以直接利用 Unity 自带的渲染机制,所以渲染出来的东西跟 Unity 是可以完美融合的。



这个机制还有另外的几个好处,第一是对项目的渲染管线没有影响,可以直接使用 Unity 材质,也支持 Shader Graph。所以不管对美术用户还是技术用户都比较友好。


另外,Unity 很多时候需要把一个物体渲染多次,而使用渲染代理的话,就可以避免多次提交复杂的毛发几何体去渲染了。因为我每次提交的,都只是一个很简单的包围体。


最后一个优势,大家可以看出来这个机制很类似于延迟渲染,每个像素上只需要进行一次着色,所以是比较高效的。渲染可以使用代理,投影也可以使用代理,如此一来集成就变得很简单了,就不存在之前所说的诸多问题了。当然,后来有了 SRP,就可以在渲影子的时候直接调用原生插件渲染了,不再需要投影代理了。


接下来就是跨平台,其实刚刚不管是 Unity Japan,还是集成,一直是通过原生插件对接 Nvidia HairWorks SDK 做的。在 SRP 诞生之前,这可能是唯一能够跑通的方法。HairWorks SDK 预留了跨平台的设计,但是 Nvidia 只实现的 DX11 和 DX12 的版本,剩下所有的图形接口都需要去重新实现一份 HairWorks SDK 的底层,这个工程量是比较大的。而且由于很底层,所以难度也比较高。徐行团队花几个月的时间才实现了一次 PS4 平台,虽然是做完了,但是做完之后觉得不能再用这种方式继续往下做了。


得益于近些年 Unity 的进化,特别是 SRP 的加入,让直接在 Unity 内实现这种复杂功能变成了可能。我利用近期 Unity 提供的一些新功能,例如 Computer Shader、CommandBuffer、RenderFeature、CustomPass 等等,直接把红框内的整个流程包括资源加载、约束初始化、物理模拟、几何体渲染,这些全部都在 Unity 内部重新实现了一遍。


这个流程比之前的 PS4 移植要顺利得多,因为现在 Unity 的渲染开发是很高效的,做任何修改都不需要关编辑器,也不需要花很长的时间来编译,可以即时看到效果。


另外由于有像 CustomPass 和 RenderFeature 这样方便的机制,对 SRP 的源码修改其实只有几行。


接下来讲一下毛发着色,我们可以先看一下最终的效果。



说到毛发着色,我们首先会想到的就是 Kajiya-Kay 模型,它用毛发的切线替代了常用的法线,把 cos 换成了 sin,快速实现了类似于头发的效果。


但是如图所示,虽然它产生了类似于头发的高光,但是塑料感很强。在 2003 年 Marschner 提出了一种更接近物理真实的毛发着色模型,中间就是 Marschner 成果,右边是真人照片。在不考虑造型的情况下,可以说效果已经很接近真实的照片了。



它首先是对与毛发产生的交互光线进行分类,打在毛发表面就被反射的光线被称为 R。打入头发,然后又从头发背面射出的光线被称为 TT。打入头发,在头发内部被反射,又从头发正面透出来的光线被称为 TRT,这里面 R 和 T 分别代表反射和透射。



光线在头发内部行进的过程中,他的部分能量会被头发的内核吸收,所以说图中的 TT 和 TRT 都会带有头发的颜色,而R就类似于一般的高光,不受头发颜色的影响。


另外,由于头发表面的鳞片与毛发切线形成了一定的角度,这导致出射的 R 和 TRT 的角度产生了一定的偏差。直观地来说,就是他们两个的中心是分离的。


如中间的部分所示,白色的高光是 R,最上面呈现发色的高光就是 TRT,可以看出他们的中心是有一定偏差的。


Marschner 的另一项重要贡献,就是将散射光线在毛发横截面和纵截面的能量分布分开建模,这极大的简化了建模的难度,减少了单个分布函数的参数。



尽管如此,这个模型的数学计算还是十分复杂的。在 Shader 实现一套比较麻烦,性能也比较低。



为了提高计算性能,英伟达很早就提出了一种简便的方法,就是把横截面和纵截面出射光的能量分布烘培到两张纹理里面,渲染的时候查询即可。这种方法又被称之为查找表法。图中就是英伟达用这种技术制作的美人鱼 Demo。



但是这个方法有一个的问题,就是只支持一种头发颜色和一种粗糙度。


受到英伟达思路的启发,徐行写了一个很简单的光追小程序,从各个方向向毛发发射光线,再在各个方向统计出射光线的能量分布,最后把能量分布存入纹理中。



但是在这个思路的基础上,他又做了两个扩展。第一是徐行希望美术能够调节粗糙度,所以他将 32 个不同粗糙度的能量分布都烘焙了,排成了一个序列。


第二是在能量分布图中,没有包含毛发中心对光线吸收,而是用了一组新的纹理来记录光线在毛发中行进的平均距离。根据这个距离和美术设定的毛发颜色换算得到的吸收率,就可以对毛发光线能量进行衰减,从而体现不同的毛发颜色。



最终的效果如图,大家可以从图左中观察到 R 和 TRT,可以在图右中观察到明显的 TT。



想要了解更多关于Unity的最新消息与应用案例,请继续关注我们的账号。我们会在第一时间快速整理有关Unity的最新动态。欢迎大家关注转发~

相关推荐

悠悠万事,吃饭为大(悠悠万事吃饭为大,什么意思)

新媒体编辑:杜岷赵蕾初审:程秀娟审核:汤小俊审签:周星...

高铁扒门事件升级版!婚宴上‘冲喜’老人团:我们抢的是社会资源

凌晨两点改方案时,突然收到婚庆团队发来的视频——胶东某酒店宴会厅,三个穿大红棉袄的中年妇女跟敢死队似的往前冲,眼瞅着就要扑到新娘的高额钻石项链上。要不是门口小伙及时阻拦,这婚礼造型团队熬了三个月的方案...

微服务架构实战:商家管理后台与sso设计,SSO客户端设计

SSO客户端设计下面通过模块merchant-security对SSO客户端安全认证部分的实现进行封装,以便各个接入SSO的客户端应用进行引用。安全认证的项目管理配置SSO客户端安全认证的项目管理使...

还在为 Spring Boot 配置类加载机制困惑?一文为你彻底解惑

在当今微服务架构盛行、项目复杂度不断攀升的开发环境下,SpringBoot作为Java后端开发的主流框架,无疑是我们手中的得力武器。然而,当我们在享受其自动配置带来的便捷时,是否曾被配置类加载...

Seata源码—6.Seata AT模式的数据源代理二

大纲1.Seata的Resource资源接口源码2.Seata数据源连接池代理的实现源码3.Client向Server发起注册RM的源码4.Client向Server注册RM时的交互源码5.数据源连接...

30分钟了解K8S(30分钟了解微积分)

微服务演进方向o面向分布式设计(Distribution):容器、微服务、API驱动的开发;o面向配置设计(Configuration):一个镜像,多个环境配置;o面向韧性设计(Resista...

SpringBoot条件化配置(@Conditional)全面解析与实战指南

一、条件化配置基础概念1.1什么是条件化配置条件化配置是Spring框架提供的一种基于特定条件来决定是否注册Bean或加载配置的机制。在SpringBoot中,这一机制通过@Conditional...

一招解决所有依赖冲突(克服依赖)

背景介绍最近遇到了这样一个问题,我们有一个jar包common-tool,作为基础工具包,被各个项目在引用。突然某一天发现日志很多报错。一看是NoSuchMethodError,意思是Dis...

你读过Mybatis的源码?说说它用到了几种设计模式

学习设计模式时,很多人都有类似的困扰——明明概念背得滚瓜烂熟,一到写代码就完全想不起来怎么用。就像学了一堆游泳技巧,却从没下过水实践,很难真正掌握。其实理解一个知识点,就像看立体模型,单角度观察总...

golang对接阿里云私有Bucket上传图片、授权访问图片

1、为什么要设置私有bucket公共读写:互联网上任何用户都可以对该Bucket内的文件进行访问,并且向该Bucket写入数据。这有可能造成您数据的外泄以及费用激增,若被人恶意写入违法信息还可...

spring中的资源的加载(spring加载原理)

最近在网上看到有人问@ContextConfiguration("classpath:/bean.xml")中除了classpath这种还有其他的写法么,看他的意思是想从本地文件...

Android资源使用(android资源文件)

Android资源管理机制在Android的开发中,需要使用到各式各样的资源,这些资源往往是一些静态资源,比如位图,颜色,布局定义,用户界面使用到的字符串,动画等。这些资源统统放在项目的res/独立子...

如何深度理解mybatis?(如何深度理解康乐服务质量管理的5个维度)

深度自定义mybatis回顾mybatis的操作的核心步骤编写核心类SqlSessionFacotryBuild进行解析配置文件深度分析解析SqlSessionFacotryBuild干的核心工作编写...

@Autowired与@Resource原理知识点详解

springIOCAOP的不多做赘述了,说下IOC:SpringIOC解决的是对象管理和对象依赖的问题,IOC容器可以理解为一个对象工厂,我们都把该对象交给工厂,工厂管理这些对象的创建以及依赖关系...

java的redis连接工具篇(java redis client)

在Java里,有不少用于连接Redis的工具,下面为你介绍一些主流的工具及其特点:JedisJedis是Redis官方推荐的Java连接工具,它提供了全面的Redis命令支持,且...