SIGGRAPH 粗读——看看Frostbite引擎如何做头发丝


3楼猫 发布时间:2024-04-19 13:33:18 作者:Hakumen Language

前言

这期继续头发的话题,以读2019年Frostbite分享的头发丝(Hair Strand)渲染方案为主,中间会简单结合一些我个人的感受和评论。
上期粗读的分享文档毕竟是2016年的,虽然成品游戏《神秘海域4》里感觉是有电影级的真实感的,但是深入细节我们发现他们还处于用面片(Hair Cards)各种做trick调参数的阶段。
上期的评论里有人提到Groom(梳头发)工具链,这在3D建模或离线渲染软件中都是有成熟的工具或插件的,而游戏引擎中在我了解的范围目前就只有Unreal支持的比较好了。简单来说就是头发的编辑过程是基于头发丝的真实物理模拟来进行的,也能比较容易的对其进行程序化、参数化的定制——如毛囊大小、浓密度、长短、卷度等;编辑过程中数据存储成点集或曲线,最终使用的结果可以是导出Hair Card模式的生成式纹理,也可以是Hair Strand模式的头发丝。这显然是一个美术人员友好的工具,因为不需要有那么多人工干预(调整头发片位置)的痕迹了,出写实的头发可以很快;当然劣势就是得到的结果往往不是性能最优的。
头发的物理模拟超出了我个人能理解的范围,我能了解的是其中除了有力学问题,还涉及一个如何进行多线程运算的问题。
*本文的粗读还是以渲染部分为主,由我自己进行翻译 ,并力求准确。如需转载请告知我。

1 头发丝的优劣势及渲染方案概述

原文档的PDF配了解说稿的原文,因此这里把部分重点的原文摘录下来并翻译;括号或星号部分则是我自己的补充说明。
(采用头发丝渲染的)动机

(采用头发丝渲染的)动机

红字部分分别是:消耗人工、有限的发型、(不能达到)真实感的模拟、(只能采用)简化的着色
In games it is currently very common to model human hair using hair cards or shells where multiple hair fibers are clumped together as a 2d surface strips. These cards are generated using a combination of tooling and manual authoring by artists. This work can be very time consuming and it can be very difficult to get right.
游戏中使用2D的头发片来建模头发是很普遍的,这通常由美术人员手动制作完成。这项工作可能非常耗时且容易出错。
If done well these can look good, but they do not work equally well for all hairstyles and therefore limit possible visual variety. And since it is a very coarse approximation of hair there are also limits on how realistic any physics simulation or rendering can be.
即使调出了不错的效果,也不能保证对所有发型都有好效果,在视觉变换(*如动画)上也有很多局限性。并且由于采用了非常粗糙的预估模型,导致在表现物理和渲染的真实感上其实都还有很多限制和不足。
It is also common to use very simplified shading models which do not capture the full complexity of lighting in human hair, this can be especially noticeable when shading lightly colored hair.
由于通常都只能采用比较简单的渲染模型,导致渲染时也无法搞定复杂的光照情况,尤其是为浅色头发着色时。
为什么使用头发丝

为什么使用头发丝

Strand based rendering, where hair fibers are modelled as individual strands, or curves, is the current state of the art when rendering hair offline. ...
发丝渲染,指头发纤维被建模成独立的丝或曲线,这是目前离线渲染中美术人员制作头发的方式。
It also requires less authoring time than hair cards since you do no longer need to create them. And since the process of creating hair cards usually also involves creating hair strands for projecting onto the hair cards, you basically save time on that whole step. ...
这比使用头发面片需要的制作时间更少——因为不需要制作和设置头发面片了。同时由于制作头发面片往往也是通过把头发丝投影倒面片上来完成的,这就节省了整个步骤的时间。
With hair-strands it is also possible to do more granular culling for both physics and simulation, and easier to generate automatic LODs via decimation.
头发丝方案还可以在物理和模拟方面做更多颗粒度的剔除,也能更容易的通过降采样的方式来自动生成LOD。
The goal of this project is to get as close as possible to movie quality hair, using-hair strands, while still achieving real time frame rates.
这个项目的目标是用头发丝尽量达到电影级的质量,同时达到实时渲染的帧数要求。
*可以看出头发丝是一个综合了美术制作成本和仿真效果的考虑
方案概述

方案概述

几个步骤分别是:美术制作、离线处理(Frostbite引擎中)、Frostbite引擎运行时
运行时包含:物理模拟(模拟运算每一个点的位置)、渲染(把头发丝的点渲染成tri-strip 三角条状片)
头发(物理)模拟

头发(物理)模拟

——头发丝是作为一系列有约束的点集来模拟
——模拟方式组合了欧拉方法和拉格朗日方法
——头发丝之间的相互作用通过网格的方式来计算(摩擦力、体积保持、aerodynamics 空气动力学)
——整合时每一个点都是独立计算的
——点的位置通过迭代处理约束和碰撞来完成
The simulation is a combination of Eulerian, so simulation on a grid, and Lagrangian simulation on the points
*用欧拉方式计算网格模拟,用拉格朗日方式来计算点模拟。更多的原文也没介绍了,也超出了我个人的理解范围;不过这里面可以想到的是这么多点非常依赖多线程来计算。
三个方面分别是:单次散射、多重散射、细物体可见性
...The final problem relates to the rendering and how to do it in a way that is performant and does not introduce a lot of aliasing due the thin and numerous nature of hair.
(前面概述散射的省略了)细物体可见性是一个渲染问题,即需要找到以更高性能和不产生大量锯齿的方式来渲染数量庞大的细头发的方案。

2 单次散射

*前一篇2016年的《神秘海域4》SIG中,散射还是通过各种高度近似的trick来完成的。在本文中散射就朝贴近光传播规律迈了一大步。
单次散射

单次散射

(讲述的部分是PPT内容的完全扩展,因此PPT内容就不翻了)
For single scattering we use a BSDF based on an original model for hair created by Marschner et.al. in 2003. The model is a far-field model which means that it is meant to model the visual properties of hair when seen from a distance, not for single fiber closeups.
对于单次散射我们使用的BSDF( Bidirectional Scattering Distribution Function 双向散射分布函数 )基于2003年的Marschner模型。这个模型是一个远场模型,意味着这个模型是对远处观察头发的一种归纳,不适合近处观察头发丝。
This model was later improved for path-tracing by Disney and Weta Digital and approximated for real time use by Karis, parts of which work we also incorporate.
这一模型后续被Disney和Weta Digital改进用于路径追踪领域(离线渲染),而在实时渲染领域由Karis做了近似处理,其中也并入了一些我们的工作。
The model is split up as a product between a longitudinal part M and an azimuthal part N.
这一模型被拆分成一个组合公式,包含了纵向的部分M和径向的部分N(azimuthal 直接对应的翻译“方位角”比较绕,本文会将其翻译成径向,意义略微有一些偏差但是更好理解)。
It contains parameters such as surface roughness, absorption and cuticle tilt angle.
它包含了如下参数:表面粗糙度、吸收度和角质层倾斜角
Different types of light paths are evaluated separately and added together.
不同类型的光路径是分别评估计算并加总在一起的。
These are R , which are reflective paths TT which is transmission through the fiber, and TRT which is transmission with a single internal reflection. These paths are also enumerated as p0, p1 and p2.
*这部分与图中一致,主要是说的Marschner模型的3种基础光路。上篇文章中也介绍过,这里就不翻译了。
*纵向可以理解成其切面是粗糙长条形,径向可以理解成其切面是圆形。后面提到如Nr就是在径向计算表面反射的意思。
单次散射

单次散射

——吸收度:从左到右逐步变高
——光滑度:从左到右逐步变高
纵向散射Mp

纵向散射Mp

For the longitudinal scattering M, each path type is modelled using a single gaussian lobe with the parameters depending on the longitudinal roughness and the cuticle tilt angle.
对于纵向散射系数M,每一类路径都被建模成单独的高斯波瓣,包含纵向粗糙度和角质倾斜角参数。(*类似球谐光照,但用的建模原件不同,lobe 波瓣可以近似理解为空间中的椭圆)
The motivations for these equations are explained in more detail in the Marschner original paper.
采用这些公式的动机和原理在Marschner 的原始论文中有详细介绍。
*波瓣是辐射和波动或信号领域的常用概念,这里用来进行近似光传播计算。概念背后的数学思想已经完全超出了本人的理解范围,文末会附带 《Real-Time Rendering 4th》中相关的引用介绍
径向散射Np

径向散射Np

The azimuthal scattering is split up into an Attenuation factor A, which accounts for Fresnel reflection and absorption in the fiber, and a distribution D which is the lobe modelling how the light scatters when it is reflected or exits the fiber.
径向散射被拆分为衰减系数A(代表头发纤维的菲涅尔反射度和吸收度),以及分布系数D(用波瓣的方式建模光线如何在头发纤维反射或离开纤维体)。
To properly simulate surface roughness for transmitted paths, this product is then integrated over the width the hair strand to get the total contribution in a specific outgoing direction.
为了正确模拟光传输路径的表面粗糙度,这个公式需要在头发丝宽度这一维度上进行积分,以得到特定出射方向的总贡献度。
Numerically integrating this equation for every shading point and light path is of course not feasible to do in real-time. So we have to approximate.
对每一个着色点和光路都进行这种程度的积分不是一个实时渲染的可行方案,因此我们必须进行预估。
*看到approximate就想到,这里又会引入近似公式和LUT(Look Up Table)了。
径向散射Nr

径向散射Nr

(前一页介绍的是通过情况,这里是反射的情况)
分布公式采用了2016年Karis的方案(应该是Unreal的一篇分享),衰减采用了Schlick菲涅尔公式(一种近似菲涅尔方案)。
wi和wr分别是3D空间的入射和反射方向。
径向散射Np

径向散射Np

Karis的方案对于光滑的头发丝近似效果比较好。(右侧是对应的当年离线渲染的结果,βm和βn是前面提到的吸收度和光滑度系数)
径向散射Np

径向散射Np

但粗糙度较大时得出的效果不理想。(右侧是16年Disney的离线渲染结果)
...and the appearance is getting more dominated by internal absorption.
(效果不理想的原因)渲染的结果中内部吸收的权重太高了。
*下面主要介绍的就是他们对近似方案中的衰减计算进行的改进。
近似计算Att

近似计算Att

*由于通过发丝纤维柱体中心的通道在衰减系数中占绝对权重,因此可以用h等于0来近似计算通过头发光路的衰减系数Att。
Here is a plot showing this approximation for three different absorption values with the reference integral drawn as crosses and the approximation drawn as a solid line.
这里用图表展示了3种不同吸收度值下预估值和积分值的对比情况,虚线是积分值,实线是预估值。
And here is a plot showing how the approximation Karis used stacks up and one can see that it has some problems with grazing angles, especially with more translucent, brighter hair.
另一张图表展示了Karis的方案由于误差堆叠,在计算较大掠射角、更透明明亮的头发时有很大偏差的原因。
近似计算Dtt

近似计算Dtt

For the distribution we use a LUT. The distribution depends on roughness, azimuthal outgoing angle and the longitudinal outgoing angle which means that the LUT becomes be three dimensional.
我们使用LUT来近似计算分布。分布的估算基于粗糙度、径向出射角以及纵向出射角,这意味着使用的LUT需要包含3个维度。
近似计算Dtt

近似计算Dtt

But instead of storing this whole 3D texture, we instead reparametrize it down to 2D by fitting a gaussian function to each azimuthal angle slice.
相比于存储数据到3D纹理,我们通过把不同(出射角对应的)方位角切片带入拟合的高斯变换函数的方式,使其重参数化并降维到2D。
So the parameters a and b, in the gaussian, are fitted to the integral and we then store them in a two-channel 2D texture.
函数中的a和b,通过拟合到积分的方式将结果存储到一个两通道的2D纹理中。
方位角散射Ntt

方位角散射Ntt

*这一页主要总结了前面近似推算分布和衰减的思路,分布使用了LUT,衰减假设了h等于0
方位角散射 Ntrt

方位角散射 Ntrt

And now for the final TRT path. For the distribution we improved upon Karis approximation by adding a scale factor 𝑠𝑟 which you can see highlighted in the equation here. This scale factor was manually adapted to approximate the effect of surface roughness, like this.
最终对于TRT路径,在分布上我们在Karis的近似计算上进行了改进,加入了缩放系数Sr。这一系数以手动调整的方式来近似计算不同粗糙度表面的效果。
This approximation is, however, still quite pretty coarse and may need some more work to improve the visual quality in some cases.
这一预估仍然非常粗糙,在某些情况下仍然需要做改进以提高视觉效果。
The attenuation term we approximate in the same way we did for the transmissive path, but here instead we use an h value of square-root of three divided by 2. Which is the same constant used in Karis approximation.
衰减的近似方案和投射光路中的类似,只是这里我们设定h等于二分之根号三。这和Karis的近似计算中使用的常数一致。
渲染——单次散射

渲染——单次散射

渲染——单次散射

渲染——单次散射

*图中展示了不同参数值下的对比情况,中间的参照是当时效果比较好的离线渲染模型的结果。

3 多次散射

多次散射

多次散射

*左侧是单次散射,右边是考虑了多次散射的结果。由于纳入了更多计算中损失的光能,所以结果会显得更亮。
多次散射

多次散射

In contrast with single scattering, which aims at capturing how light behaves in a single fiber, multiple scattering tries to model the effect when light travels through many fibers.
和单次散射相比,多次散射的目标是实现光通过很多头发丝时的效果。
This means that we need to evaluate multiple paths that the light travel between a light source and the camera. This is of course not feasible for real-time rendering, so we need to approximate this effect as well.
这意味着需要计算光源和摄像机之间通过头发的非常多光路。这显然不是一个实时渲染下可行的方案,因此我们也需要做近似计算。
多次散射

多次散射

In our implementation we use an approximation called Dual Scattering. The point of dual scattering is to approximate multiple scattering as a combination of two components.
我们采用的近似方案叫Dual Scattering,它的要点是用两个组件的组合来近似计算多重散射。(就是后面提到的Local scattering和Global scattering)
Local scattering accounts for scattering in the neighborhood of the shading point and accounts for a lot of the visible hair coloring.
Local scattering负责处理着色点的相邻元素和大部分头发颜色可见性问题。
Global scattering is meant to capture the effect of outside light travelling through the hair volume.
Global scattering目的是实现光路穿过整个头发体积后的效果。
The reason that the dual scattering approximation works well for hair is because most light is only scattered in a forward direction. So basically because we have more contribution from TT than TRT.
Dual scattering的近似方案能运作较好的原因是因为光传播主要还是前向散射为主,基本上计算时TT和TRT还是有更多权重。
Global scattering is estimated by only considering scattering along a shadow path, or light direction. Therefore we need some way of estimating the amount of hair between two points in the hair-volume in the light direction.
Global scattering的近似方案中,我们只考虑沿着阴影路径或光线方向的散射。因而我们需要能估计光线方向上,在头发体积中两点之间的头发数量的方式。
多次散射

多次散射

We do this the same way the authors did in the dual scattering paper; we use Deep Opacity Maps. Deep opacity maps are similar to Opacity shadow maps, a technique where shadow maps for a volumetric object is generated in a lot of slices over the object.
参照dual scattering论文作者的做法,我们使用了Deep Opacity Maps。它很类似Opacity shadow maps,也是一种为体积物体生成多层切片的阴影纹理的方式。(*Opacity shadow maps上次顽皮狗的文章里提到过,两者之间的区别是分层的方式不同)
The benefit of deep opacity maps is that it require a lot fewer layers and it does not suffer from banding artifacts common with opacity shadow maps.
它的优势是需要更少的层,以及不会有opacity shadow maps的带状伪影问题。
The attenuation due to the scattering is then calculated, averaged and stored into a LUT. The deep opacity maps are also used to determine shadows.
多层散射的衰减被预计算、平均化并存储到LUT中。Deep opacity maps也可以被用来计算阴影值。
As a lower quality fallback one can also estimate the attenuation using a hair density constant and the Beer-Lambert law. But this will of course not adapt with the actual changes of the hair volume.
作为一个较低质量的替代方案,还可以使用Beer-Lambert法则中的头发强度常量,不过这在头发体积变化时就不能自动适应了。
*可以看到多次散射主要还是通过一些经验模型来近似,核心改进思想就是Deep Opacity Maps。下面补了一张图说明两者的区别:
左侧是Opacity Shadow Maps,右侧是Deep Opacity Maps。

4 特细物体的可见度

渲染

渲染

The hair-strands are tessellated and rendered as triangle strips so we must take special care to properly handle aliasing. Since the strands are very thin, they will usually have a width that is less than that of a screen pixel.
头发丝在渲染时被拼构成三角面构成的长条,因此必须特殊关注抗锯齿问题。由于头发丝往往很细,通常都小于一个屏幕像素宽度。(*tessellated没有一个特别合适的中文翻译,在这里拼构比镶嵌合适一些)
We therefore need to take the pixel size into account when tessellating, and increase the width appropriately, or we will risk getting missing or broken up hair strands.
为此我们在拼构时需要把像素尺寸列入考虑,并以合适的方式增加宽度,否则就会有丢失或破坏头发丝的风险。
渲染——头发丝渲染

渲染——头发丝渲染

Unfortunately, this will have another not so nice side effect which can cause the hair to look too thick and more like thicker spaghetti or straw. Another problem is that the amount of overdraw which will be massive and hurt performance a lot.
不幸的是,这(加宽)会带来副作用——使头发看起来太厚了,像面条或是稻草一样;另一个问题是Over draw太多了会影响性能。
渲染——头发丝渲染

渲染——头发丝渲染

Just enabling MSAA does unfortunately not solve all problems. While it does improve on aliasing issues, by taking more samples per pixel, and therefore allows us to keep the thin hair appearance. It will suffer an even bigger performance hit due to overdraw, because there will be more of it.
单纯启用MSAA不能解决所有问题。虽然它确实能改善锯齿问题,但由于采用了更多的像素点用于计算和渲染,因此也会带来更大的性能问题。
To reduce the amount of overdraw we use a visibility buffer.
为了降低overdraw的数量我们使用了一个可见性缓冲。
*常见的减少overdraw的思路就是接近不透明就不继续渲染了,并且把后续屏幕空间渲染的数据准备好,这里的缓冲区应该也是类似的。但具体的计算细节原文没有公布,例如不同面积头发丝对某一像素格着色的权重如何增加之类。
可见性缓冲

可见性缓冲

With the visibility buffer we can do a relatively quick rasterization pass, with MSAA, for all hair strands. We can then use that information to do a screen-space shading pass to get the final antialiased render.
通过可见性缓冲我们可以相对快速的进行光栅化Pass,结合MSAA(还能在性能可接受范围时)渲染所有头发丝。接着我们可以使用光栅化的信息来做屏幕空间的着色来得到最终抗锯齿的渲染效果。
There is still, however, some unnecessary shading going on because we may be shading the same strand multiple times per pixel.
这里仍然会有一些不必要的着色步骤,因为我们可能对某一个像素的同一头发丝渲染了多次(*因为头发丝分段的原因)。
采样去重复

采样去重复

To reduce this over shading we also run a sample deduplication pass on the visibility buffer so that we only shade samples within a pixel when they are considered different.
为了减少重复着色我们引入了一个采样去重复通道,它在可见性缓冲的基础上只对同一个像素中视为完全不同的着色样本(*如另一个头发丝)进行着色。
This reduces the number of pixel-shader invocations greatly and it gave us roughly a 2 times performance increase compared to just using the visibility buffer.
这极大的减少了像素着色的调用,比起只使用可见性缓冲提升了大约2倍的性能。
渲染总览

渲染总览

*这里就不翻译了,完整的管线对应的就是前面提高的几个部分的组合。

5 性能总览

性能

性能

At Frostbite we usually work very close with the game teams to ease the introduction of new tech. And when they have it, they are usually very good at finding ways to get more with less.
在Frostbite 我们通常和游戏部门走的很近以介绍和引入这些新技术。一旦他们掌握了,他们通常能更擅长找到以少胜多的运用方式。(*结合《圣歌》项目,这句话给人感觉他们的引擎是瘸腿的,太重视渲染而忽视功能了)
In any case, here are some numbers showing what the performance is currently like on a regular PS4 at 900p resolution, without MSAA, with the hair covering a big chunk of the screen.
这里展示了一些头发覆盖画面主要内容时的渲染数据——基于PS4普通版900分辨率,关闭MSAA。
The main reason for the long render times are currently that our GPU utilization is very low, something we are currently investigating different ways to improve.
长头发耗时较长的主要原因时我们对GPU的利用率还很低,这也是我们正在研究改进的方向。
In comparison some of the alternative hair simulation system only simulate about 1% of all hair strands. Early experiments show that we can get a 5x performance boost by simulating only 1/10th of all strands and interpolating the results.
作为对比,某些替代的头发模拟系统仅模拟1%的头发丝。早期的实验表明,仅模拟十分之一的头发丝和通过插值计算能为我们提高5倍的性能。
下一步..

下一步..

更快的着色、更快的光栅化、基于数据抽取的自动LOD、更好的近似计算、更快的物理模拟、支持区域光

结语

总结一下,此方案光传播计算还是基于R、TT、TRT这3个归纳的光路进行,首先对单个头发丝独立运算,对不同的光入射角采用了2种光路模型(纵向、径向),对于其中的一些计算细节如衰减和分布系数等都在前人的基础上做了优化调整。
在多重散射部分,他们在前人论文的基础上进行了改进组合,采用了2个渲染组件分别处理不同着色情况。
最后在屏幕着色部分,他们通过采样去重和可见性缓冲的组合,提高了渲染的性能和效果。
虽然过程中也用了很多近似计算,但近似和Trick最大的区别是,近似是能覆盖绝大多数参数条件的,不会受光源、阴影、发型等原因导致特别失真,它不会产生数量级上完全错误的情况。
即便如此,可以看到基于头发丝的渲染对于大型3A游戏来说还是一件持续进行中的事,耗费了很多精力和制作头发丝带来的画面提升可能对普通玩家来说不那么明显,这也是一个很现实的问题。在今后5年左右的时间内,个人感觉仅渲染少量头发丝还会是一个主流方案——其余的部分可以依据不同发型采用面片或插值计算的方式来渲染。
头发的话题可能暂时告一段落,这个技术后续的提升方向主要在多线程和AI方面,但落实到实际成品游戏中的还太少,毕竟游戏硬件的高速发展和普及也已经过了其黄金年代了;如果要考虑光线追踪,这又会是完全一套不同的方案了。

最后是一些资料链接:
EA官方的相关展示视频
Deep Opacity Map的Paper地址
虚幻5中的头发工作流
知乎上翻译的 Real-Time Rendering 4th(原版是一本纸质书)
本文对应的PDF下载

© 2022 3楼猫 下载APP 站点地图 广告合作:asmrly666@gmail.com