☰
Unreal Groom与Strands发丝渲染实战:从夺宝奇兵到项目复现
2026/9/29 18:11:49 网站建设 项目流程

《夺宝奇兵:古老之圈》里的琼斯博士,帽子一戴、鞭子一甩,最抢戏的其实不是那张脸,而是他后脑勺那撮被风吹得乱七八糟的头发。我第一次在游戏里把镜头怼到他后脑勺的时候,愣了几秒——这发丝不是贴图糊上去的,是一根一根有体积、有高光、有透光的。后来翻资料才知道,MachineGames 这套头发用的是 Unreal Engine 的 Groom 系统,配合 Strands 做光栅化渲染。这篇就聊聊这套发丝渲染到底是怎么跑起来的,以及我在自己项目里复现类似效果时踩过的那些坑。

1. 为什么这撮头发值得单独拿出来讲

1.1 发丝渲染在游戏里一直是个"吃力不讨好"的活

先说个背景。游戏里的头发,过去十几年基本是三种做法:一种是卡片面片(hair cards),用几片带透明通道的贴图叠出头发形状;一种是壳层(shell),一层层套壳模拟体积;还有一种是直接拿模型雕一坨,靠法线贴图糊弄。这三种方案有个共同问题——近看就露馅。卡片面片在特写镜头下边缘是硬的,壳层会有明显的层间缝隙,雕出来的模型则完全没有发丝该有的透光和散射。

《夺宝奇兵:古老之圈》是个大量使用过场动画和特写镜头的游戏,琼斯博士的脸经常占满屏幕三分之一。这种情况下,传统方案根本撑不住。所以 MachineGames 选了 Groom——这是 Unreal Engine 里专门做毛发和发丝的系统,核心思路是把头发当成一根根真实的曲线(strand)来处理,而不是面片。

1.2 Groom 和 Strands 到底解决了什么问题

Groom 解决的是"头发怎么被描述"的问题。它用一组引导曲线(guide curves)来控制整体发型,再用插值生成成千上万根发丝。每根发丝是一条带厚度的曲线,有根部和尖端的粗细变化,有沿着发丝走向的 UV,可以采样颜色、粗糙度、透光等属性。

Strands 解决的则是"这些发丝怎么被画到屏幕上"的问题。传统光栅化管线处理三角形很在行,但发丝是曲线,直接光栅化要么把曲线拆成极细的三角形带(性能爆炸),要么就得用专门的 strand 渲染路径。Unreal 的 Strands 方案本质上是一套针对发丝的几何着色和光栅化流程,把曲线在屏幕空间里展开成可着色的像素。

这两者配合起来,才有了琼斯博士那撮近看依然成立的头发。

1.3 这套方案适合谁来参考

如果你在做写实向的角色特写、过场动画,或者任何需要"头发经得起镜头怼脸"的项目,这套东西值得研究。但如果你做的是移动端、风格化卡通、或者角色永远在中远景,那 Groom 大概率是杀鸡用牛刀,性能和制作成本都不划算。我下面会具体讲这个边界在哪。

2. Groom 资产从 DCC 到引擎的完整链路

2.1 在 DCC 里做头发:引导曲线才是真正的"骨架"

Groom 资产的源头不在引擎里,而在 DCC 工具里(常见的是 Maya 的 XGen、Houdini 的毛发工具,或者专门的毛发插件)。核心产物是一组引导曲线,通常几百到几千根不等。这些引导曲线决定了发型的整体走向、分缝、层次。

这里有个很多人第一次做会犯的错:以为引导曲线越多越好。实际上引导曲线太多,插值出来的发丝会失去"随机感",看起来像一顶假发套。琼斯博士那种略带凌乱、有碎发的效果,靠的是适量引导曲线 + 插值时的随机扰动。我一般会把引导曲线控制在 800 到 1500 根之间,具体看发型复杂度,然后靠插值参数去补密度。

每根引导曲线上还会带属性,常见的有:

  • Root/Tip 粗细:根部粗、尖端细,模拟真实发丝
  • UV 走向:沿着发丝长度方向的坐标,用来采样渐变
  • 颜色与粗糙度:可以逐段变化,做出发根深、发梢浅的效果
  • 透光属性:控制光线穿过发丝时的散射强度

这些属性在导出成 Groom 资产(通常是.groom文件或 Alembic 缓存)时会一并带过去。

2.2 导入引擎后的第一道坎:插值密度和性能的平衡

Groom 导入 Unreal 之后,引擎会根据引导曲线插值出最终的渲染发丝。这个插值倍率是个关键参数。插值倍率低,发丝稀疏,近看能看到缝隙;插值倍率高,发丝密到爆炸,性能直接跪。

我实测下来的经验是:先按目标镜头距离定密度。如果角色最近只会出现在中景,插值倍率可以压得很低;如果是特写,就得往上堆。琼斯博士这种要怼脸的,插值出来的发丝数量是相当可观的,这也是为什么这套方案对性能有要求。

提示:插值倍率不要一次性拉满再往下调,那样很难判断"够用"的临界点。正确做法是从低往高加,加到近看没有明显缝隙就停,多出来的密度都是浪费。

2.3 LOD 策略:不是所有距离都需要全密度发丝

Groom 支持 LOD,这一点非常关键。远处的角色不可能用全密度发丝去渲染,否则一个场景里几个带 Groom 的角色就能把帧率吃光。常见的 LOD 策略是:

LOD 层级发丝密度适用距离说明
LOD 0100%特写全密度,带完整透光和阴影
LOD 150%-70%中景略降密度,保留体积感
LOD 220%-30%远景大幅降密度,靠整体轮廓
LOD 3卡片替代极远直接换成面片,几乎看不出差别

这套 LOD 不是自动生成的,需要手动配置或者写工具批量处理。我在项目里一般会写个脚本,按距离阈值自动切换,避免美术手动调每一层。

3. Strands 光栅化:发丝是怎么变成屏幕上的像素的

3.1 曲线光栅化和三角形光栅化的本质区别

这是整套方案里最"硬核"的部分,也是很多人搞不清的地方。传统渲染管线光栅化的是三角形——给三个顶点,算出覆盖了哪些像素。但发丝是曲线,一根发丝可能只有几个像素宽,甚至不到一个像素。

Strands 的做法是:把每根发丝在屏幕空间里展开成一条带厚度的线段,然后针对这条线段做光栅化。具体来说,引擎会计算发丝在屏幕上的投影,根据投影长度和发丝半径决定这条发丝覆盖多少像素,再把这些像素交给像素着色器去算光照。

这个过程中有个关键概念叫发丝厚度(strand thickness)。它不是物理意义上的毫米,而是屏幕空间的一个参数,决定了发丝看起来有多粗。厚度调太细,发丝会闪烁(因为采样不足);调太粗,头发会变成一坨塑料。琼斯博士的头发厚度是经过精细调整的,近看每根发丝都清晰,但又不会粗到失真。

3.2 为什么发丝渲染对光照模型有特殊要求

普通的不透明表面,光照模型基本就是 BRDF(双向反射分布函数)那一套。但发丝是半透明的圆柱体,光线打上去会发生:

  • 表面反射:发丝表面的高光,形成头发特有的"光泽带"
  • 透射:光线穿过发丝,形成边缘的透光感
  • 内部散射:光线在发丝内部多次反弹,形成柔和的整体亮度

Unreal 的 Groom 用的是专门的发丝光照模型(基于 Marschner 模型的简化版本),把这三个分量分开计算。这也是为什么 Groom 头发看起来比普通贴图头发"活"——它的高光会随着视角和光线方向移动,透光会在逆光时特别明显。

我在自己项目里试过用普通材质去糊弄发丝光照,结果就是头发像塑料假发,完全没有真实感。后来老老实实换成 Groom 的光照模型,效果立刻不一样。

3.3 阴影和自阴影:发丝之间怎么互相遮挡

发丝渲染另一个难点是阴影。头发是一大堆互相遮挡的细丝,如果每根发丝都去算阴影,性能直接崩。Unreal 的做法是用深度不透明度贴图(Deep Opacity Map, DOM)或者体素化阴影来近似。

DOM 的思路是把发丝的体积信息烘焙成一张贴图,渲染阴影时采样这张贴图,而不是逐发丝计算。这样既能得到头发整体的阴影形状,又能保留一定的透光感(光线穿过头发时会有柔和的半影)。

琼斯博士的头发在逆光下能看到边缘有一圈柔和的亮边,这就是透光加阴影近似共同作用的结果。如果阴影做得太硬,头发会像戴了个铁头盔。

4. 性能账本:这套方案到底吃多少资源

4.1 发丝渲染的开销主要花在哪

很多人以为发丝渲染的开销全在"发丝数量"上,其实不完全是。开销主要分几块:

  • 几何处理:每根发丝都要做曲线细分和屏幕空间展开,发丝越多越慢
  • 像素着色:发丝覆盖的像素要做专门的光照计算,比普通像素贵
  • 阴影计算:如果开逐发丝阴影,开销会翻倍
  • 透明排序:发丝是半透明的,需要正确的排序才能避免穿帮

其中像素着色往往是大头。因为发丝很细,覆盖的像素多但每个像素的利用率低,容易造成 overdraw(同一像素被多次着色)。琼斯博士特写时,头发区域的 overdraw 是相当高的。

4.2 我在项目里实测的优化手段

踩过几次坑之后,我总结了几条比较有效的优化手段:

  1. 控制插值密度:这是最直接的,密度降一半,开销基本也降一半。关键是找到"够用"的临界点。
  2. 限制发丝长度:过长的发丝在屏幕空间展开后覆盖像素多,开销大。可以适当截断或者用 LOD 处理。
  3. 阴影降级:远处角色的发丝阴影可以用更粗糙的近似,甚至直接关掉。
  4. 透明排序优化:把发丝按深度分块排序,减少排序开销。
  5. 异步计算:部分发丝计算可以放到异步管线里,减轻主线程压力。

注意:优化发丝渲染时,不要只看平均帧率,要看最坏情况。因为发丝开销和镜头距离强相关,特写镜头下的帧率才是真正要关注的。

4.3 什么情况下不该用 Groom

说了这么多好处,也得说说边界。以下情况我建议别上 Groom:

  • 移动端:Groom 的开销对移动 GPU 来说太重,除非是旗舰机型且只用于主角特写
  • 风格化卡通:卡通头发往往需要夸张的形状和硬边高光,Groom 的写实光照反而不合适
  • 角色永远在中远景:这种情况下卡片面片完全够用,Groom 是浪费
  • 团队没有毛发制作经验:Groom 的制作流程和传统头发差别很大,需要专门的美术投入

琼斯博士能用 Groom,是因为这是个写实向、有大量特写、且团队有相应制作能力的项目。换个场景,这套方案未必是最优解。

5. 复现这套效果时我踩过的坑

5.1 导入 Groom 后发丝方向全乱了

第一次导入 Groom 资产时,发丝方向完全不对,有的朝上有的朝下,像被电击过。排查了半天,发现是坐标系不一致——DCC 工具里的 Y 轴向上,Unreal 里是 Z 轴向上,导出时没有正确转换。

解决办法是在导出设置里明确指定坐标系,或者在导入时用引擎的转换选项。这个坑很基础,但第一次做的人几乎都会踩。

5.2 发丝在特定角度下闪烁

发丝闪烁(flickering)是另一个常见问题,尤其在发丝很细、镜头移动时。根本原因是采样不足——发丝在屏幕上的投影小于一个像素时,光栅化会时有时无。

解决办法有几个:一是适当增加发丝厚度,让它在屏幕上至少占一个像素;二是开启时间性抗锯齿(TAA)或类似的时间性采样,用多帧累积来稳定;三是调整发丝的屏幕空间最小宽度参数。我一般会组合使用,先加厚度,再靠 TAA 兜底。

5.3 透光效果在逆光时过曝

发丝的透光效果很漂亮,但很容易过曝。逆光时如果透光强度调太高,头发会变成一团白光,细节全丢。

这个问题的根源是透光模型没有考虑能量守恒。真实发丝的透光是有上限的,不可能无限亮。解决办法是给透光强度设一个合理的上限,并且结合发丝的颜色和粗糙度去调。琼斯博士的头发在逆光下能看到透光,但不会过曝,说明这个参数调得很克制。

5.4 阴影接缝和穿帮

发丝阴影用近似方法做,容易出现接缝和穿帮。比如头发和头皮交界处,阴影可能突然断掉;或者头发投在肩膀上的阴影形状不对。

这类问题往往需要手动修,比如调整阴影贴图的分辨率、增加过渡区域、或者用额外的几何体去补阴影。没有一劳永逸的自动方案,得靠美术一点点调。

6. 从这套方案里能学到什么通用的东西

6.1 "用对工具"比"用牛工具"更重要

Groom 是个很强的工具,但它不是万能的。琼斯博士的头发效果好,是因为项目需求(写实、特写)和工具能力(Groom 的写实发丝)匹配。如果需求不匹配,再牛的工具也出不来好效果。

这个道理放到任何渲染技术上都是一样的。选方案之前先问自己:我的项目到底需要什么?是写实还是风格化?是特写还是中远景?是 PC 还是移动端?想清楚这些,再决定用什么。

6.2 性能优化要抓主要矛盾

发丝渲染的优化手段很多,但真正有效的往往是那一两个。在我的项目里,控制插值密度和优化透明排序贡献了大部分性能提升,其他手段都是锦上添花。

优化的时候不要眉毛胡子一把抓,先用性能分析工具找到瓶颈,再针对性地解决。发丝渲染的瓶颈通常在像素着色和 overdraw 上,先看这两块。

6.3 美术和程序的配合是成败关键

Groom 这套东西,美术负责做头发资产,程序负责调渲染参数和优化性能,两边必须紧密配合。我见过美术做了一顶超精细的头发,结果程序那边性能扛不住,最后只能砍密度,美术白干。

正确的做法是:美术做头发时就考虑性能预算,程序提前给出密度上限和 LOD 策略,两边一起定标准。琼斯博士的头发能做到既好看又跑得动,背后肯定是这种配合的结果。

最后分享一个我自己的小习惯:每次调发丝参数,我都会把镜头拉到最近,然后慢慢拉远,观察发丝在不同距离下的表现。因为发丝渲染的问题往往在特定距离才暴露——太近看不出 LOD 问题,太远看不出密度问题。只有把整个距离范围都过一遍,才能确保这套头发在任何镜头下都成立。这个习惯帮我省了很多返工的时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询