传送门:[UOD2021]虚幻引擎中Groom毛发系统的流程和应用 | Epic Games 孙丹璐_哔哩哔哩_bilibili
一. 资产与导入
1.1 Groom 毛发系统中常见名词
- Strand:生成的最终视觉上看到的毛发
- 人类的毛发尺寸大约在 0.0017 - 0.0018 cm,建议控制在 0.008 cm
- 发际线、鬓角、碎发的尺寸,建议控制在 0.0065 -0.007 cm
- Guide:引导线,用于动画和模拟
- 人类大约有 10 万根头发,用于引擎渲染,建议控制 Guide 数量在 5 万左右
- Curve:曲线(毛发/引导线数量)
- Vertices:毛发组的顶点数量(由 DCC 软件中设置的 CV 值决定,Vertices/Curves = CV count)
- CV 值:在 DCC 软件中制作毛发时,设置的控制顶点
- CV 值越多,毛发表现效果越好,相应的处理时间越久,性能开销越大
- 为了在实时领域应用 Groom 毛发,需要在 DCC 软件中控制 CV 的数量(启用 Uniform CVs 则毛发形态更平均;禁用,则利于表现如卷发等的程序化生成的毛发)
- CV 值的设计,需要综合考虑毛发形态及应用平台,建议:
- 影视项目,卷发不超过 80
- 游戏项目,长直发不超过 30
- 毛发资产的通用优化思路:
- 在 DCC 软件中,控制 CV 点数量,控制最终毛发顶点的整体数量
- 优化密度、宽度(参考真实世界中毛发尺寸,以密度减半,宽度*2为起点,根据效果进行调整)
1.2 毛发资产导入
- 导出为 Alembic 格式
- 以 Maya Xgen 毛发为例,将毛发转换为可交互式毛发后,以 Alembic 格式,只导出 Strand(单帧或多帧)
- 在 DCC 软件中制作的毛发,是根据 Guide 和修改器规则生成的,导入引擎的是最终生成好的发丝,即 Strand
- 默认情况下,Alembic 中只有 Strand 信息,可以根据命名规则写入特定数据(如:groom_guide、groom_color、groom_root_uv、groom_closest_guide、groom_guides_weights)
- 导入引擎后,依据 Alembic 写入的数据生成:
- 单帧 Groom Asset
- 多帧 Groom Cache(默认导入会生成 2 个 Cache 文件)
- guides_cache:记录每帧每个 guide curve 的 position 数据(需打开 Groom 毛发的 Simulation 模拟,从第 0 帧开始播放)
- strands_cache:记录每帧每个 strand curve 的 position 数据
- 转换为三角面
- 将 Groom Cache 附着在蒙皮表面,还需提供一个绑定资产
- 绑定资产存储了毛发发丝在目标骨骼上投射的信息,计算的是 Strand 根部与蒙皮后最近的三角面重心坐标
- 皮肤发生形变后,默认在皮肤的 Local Space 计算毛发位置,再转换到蒙皮后的世界空间,为了防止特殊部位毛发做表情时发散(如中间图)导致表情不自然
- 添加 RBF 径向基约束,以保证夸张表情下的毛发形态自然
- 在 Groom 毛发资产中,启用 RBF 径向基约束
二. 裁剪与 LOD
- 为了高效绘制 Groom 资产,Strand 会被引擎自动划分为多个 Cluster,以在 GPU 上运行 LOD 和剔除信息
- 渲染 Groom 时,Cluster 会根据屏幕覆盖率和可见性,进行细粒度 LOD 选择,方便以屏幕实际的显示为基础, 相应的平衡渲染与性能开销
- 如果在 Groom Editor 中手动创建了多层 Strand LOD,每层都有自己的 Cluster 信息和 Cluster LOD 信息
- 基于 Groom 毛发的物理模拟,每帧对 Cluster 生成 AABB ,获取 AABB 数据,在 GPU 上进行细粒度裁剪和 LOD 切换
- 裁剪顺序依次按:视锥裁剪 -> 距离裁剪 -> HZB 遮挡裁剪
- 对裁剪后的结果计算 LOD,在 Cluster 的 LOD 信息,减少了每个 Cluster 中的 Strand 数量,和每个 Curve 中的顶点数量,从而产生的毛发空隙感,用宽度补足
- 由于上述引擎的 Cluster LOD 切分机制,因此在创作毛发资产时,需按真实世界中的毛囊分布制作毛发,避免从一个点生成全头的毛发,避免通过毛发长度制作发型
- Cluster 默认按距离生成,详见 GroomBuilder.cpp 中的 BuildClusterData 部分
三. 抗锯齿与 OIT
- 根据 Cluster 完成了裁剪和 LOD 后,需要根据对应的数据绘制发丝,常碰到如下问题:
- 当像素尺寸远大于头发尺寸,产生锯齿问题
-
- 头发尺寸一般引擎内设置为:0.06-0.12 毫米
- 像素尺寸:1080P像素分辨率,在 1 米处的一个像素大小约为 1 毫米
- 光线照射到高散射率的头发,产生 OIT 问题
-
- 头发本身不透明,但够薄且散射率高,光照效果呈透明物体状
- 一个像素中有多根头发,需要确定多根头发的前后关系以计算散射情况
- 引擎实现了如下 3 种光栅化抗锯齿的解决方案:
- MSAA
-
- 效果和效率综合最好
- 原理:光栅化阶段,每个像素有多个采样点
- 在引擎内,构成毛发的 Groom 是由一段一段四边形(两个三角面)组成,渲染处理时,四边形面向相机进行 MSAA 计算,在亚像素空间,计算并记录距相机最近的 N 个毛发片段数据,最终,通过 2 个 Pass 获取 Visibility 和 Transmittance 信息
-
- 配置 MSAA 数据:
r.HairStrands.Visibility.MSAA.SamplePerPixel 2/4/8
(几倍的 MSAA 就相当于光栅化阶段要申请几倍的分辨率,显存开销大)
- 配置 MSAA 数据:
-
- 在毛发资产上勾选 Use Stable Resterization,改善孤立毛发的锯齿问题
-
- 改善毛发边缘,发梢末端形成半透效果:
r.HairStrands.ViewTransmittancePass
(在 4K 分辨率下,开启 4xMSAA,可以节省 1-2 ms )
- 改善毛发边缘,发梢末端形成半透效果:
- PPLL
-
- 效果最好,性能最低
- Per-Pixel Link List,链表式存储每个像素绘制到的 N 根毛发信息(N 取决于设置的最大数值),但链表式结构对 GPU 不友好
- 配置 PPLL 数据:
r.HairStrands.Visibility.PPLL 1
(默认 0)r.HairStrands.Visibility.PPLL.SamplePerPixel 4/8/16
- Raster Compute
-
- 低配置下效率最高,可以精确计算每段毛发渲染在哪几个像素,抗锯齿效果好
- 利用 Compute Shader 做了光栅化,并记录毛发数量和毛发平均半径,用于计算毛发透射率
- 开启 Raster Compute:
r.HairStrands.Visibility.ComputeRaster 1
- Raster Compute 中毛发每个片段的最大长度:
r.HairStrands.Visibility.ComputeRaster.MaxPixelCount
- Raster Compute 中毛发每个像素的采样最大数量:
r.HairStrands.Visibility.ComputeRaster.SamplePerPixel 1/2/3/4
- 方案选择:
- 高配置方案参考
- 中配置方案参考
- 由于上面 3 种光栅化抗锯齿方案处理的毛发片段数量不一致,因此都会被写入一个线性 Buffer,记录每个像素上最近的 N 个毛发片段信息(如:Coverage、Base Color、Tangent等),方便后续用 Compute Shader 高效并行的对每个片段着色
- 总采样数量决定了效率,速度变化较快时,可以相应调整发丝宽度以减少重投影问题
Stat GPU
或 GPU 抓帧中 HairStrandsVisibility 的开销参考:
四. 光影
4.1 光照
- 通过上面计算,可以初步确定哪些像素需要绘制毛发,及绘制毛发的必要信息,接下来进行光影计算
- 头发光照模型:
- Single Scattering - Marschner
-
- 材质中的 Hair Shadering Mode
- 主流基于物理的头发丝的光照模型 BSDF,从微观角度,抽象毛发为一节一节的圆柱体,分析毛发光照为以下 3 个主要部分:
-
- R 光线直接反射
-
- TT 穿过头发内部的透射
-
- TRT 穿过头发内部的折射
-
- 最终头发光照 = R+TT+TRT
- 其中,R 相当于头发的第一层高光,TRT 相当于头发的第二层高光,TT 相当于头发背面看到的高光
-
r.HairStrands.Components.R
r.HairStrands.Components.TRT
r.HairStrands.Components.TT
- Multi Scattering - Dual Scattering
-
- 浅发的吸收率低,发丝间的散射效果很重要
-
- 采用Dual Scattering 双重散射模型:将复杂散射现象分为 2 部分:
-
- Global Scattering 全局散射:宏观评估光线穿过纤维毛发的方式
- Local Scattering 局部散射:通过简单的模型评估光线在单根毛发周围的反弹
-
r.HairStrands.Components.GlobalScattering
r.HairStrands.Components.LocalScattering
- Deep Opacity Maps(Deep Shadow):
- 为每个光源创建专属阴影贴图,将毛发的吸收信息存储在多个 Deep Opacity Maps 中:将头发光栅化生成深度纹理,对毛发进行二次光栅,根据深度纹理距离,累加 4 层头发数量到不同 Deep Opacity Maps 的不同通道中,用 PCF/PCSS Kernel 查询毛发数量
- 在灯光的细节面板上,开启 Deep Shadow
r.HairStrands.DeepShadow.DebugMode 1
:查看 Deep Opacity Maps 的 4 层数据的对应情况
r.HairStrands.DebugMode 4
:查看开启了 Deep Shadow 的灯光,渲染生成的 Deep Opacity Maps
r.HairStrands.DeepShadow.DebugDOMIndex
:选择观察具体某盏灯光的 Deep Opacity Maps 信息
- Density Volume,Voxelization 密度体积
- 密度体积是引擎毛发着色、环境光遮蔽、环境光照的默认机制
- 密度体积会提供当前视口中毛发的密度/厚度信息,并迅速计算出光线在毛发密度上产生的渲染效果
- 将毛发在密度体积中体素化( 体素化是一种将3D物体分解成小立方体(体素)的过程,以便于进行更复杂的渲染计算 ),可以复用给多个灯光,并提供天光对头发的照明
r.HairStrands.Voxelization 1
:引擎默认开启密度体积r.ShaderPrintEnable 1
:启用着色器打印功能
r.HairStrands.Voxelization.Virtual.VoxelWorldSize
: 设置头发体素化过程中使用的世界空间大小(体素大小,大体素,降低精度但提高性能;小体素,提高精度但降低性能)r.HairStrands.Voxelization.Virtual.DensityScale
: 调整头发体素化过程中毛发密度的比例(增加密度,毛发浓密;减少密度,毛发稀疏)r.HairStrands.Voxelization.Virtual.DrawDebugPage
:绘制体素化调试
r.HairStrands.DebugMode 8
:查看毛发的密度体积,需要同时开启r.ShaderPrintEnable 1
4.2 阴影
- 不透明物体对毛发的投影:Shadow Map
- 毛发对不透明物体的投影:Deep Shadow(Deep Opacity Maps)、Voxel Tracing 密度体积
- Deep Shadow(Deep Opacity Maps)
- 毛发间的阴影:
r.HairStrands.DeepShadow.Resolution
: 设置用于渲染头发深度阴影贴图的分辨率r.HairStrands.DeepShadow.SuperSampling
: 控制 SuperSampling 超采样设置(超采样是一种反走样技术,通过以比最终输出更高的分辨率渲染图像,然后缩小到目标分辨率来平滑边缘和减少锯齿效果)r.HairStrands.DeepShadow.KernelType 0/1/2/3/4
0
:linear1
:PCF_2*22
:PCF_6*4(默认为 2)3
:PCSS4
:PCF_6*6_Accurate
- 当灯光据毛发过近时,开启 DeepShadow,则毛发间的阴影会过于明显,显得毛发粗糙,因此不需要每盏灯都开启 DeepShadow
- 毛发对其他物体的投影:
r.HairStrands.DeepShadow.ShadowMaskKernelType 0/1/2/3/4
0
:2*21
:4*42
:Gaussian 83
:Gaussian 164
:Gaussian 8 with transmittance(默认为 4,细发丝不投影)
- Voxel Tracing 密度体积
- 毛发间的投影:
r.HairStrands.Voxelization.Raymarching.SteppingScale
:调整 Raymarching 光线行进过程中的步长比例(值越大,性能提高但渲染质量降低;值越小,渲染质量提高但性能消耗增加)r.HairStrands.Voxelization.Virtual.Jitter
: 调整体素化过程中的 Jitter 抖动效应(0 为完全关闭,1 为有规律随机抖动,2 为持续抖动)
- 毛发对其他物体的投影:
r.HairStrands.Voxelization.DensityScale.Shadow
:调整影响头发阴影密度的比例( 值越大,头发阴影越密集;值越小,阴影越空)
- Deep Shadow(Deep Opacity Maps)和 Voxel Tracing 的方案对比:
- 光影表现:
- Deep Shadow 不会计算天光,因此背光面较暗
- Voxel Tracing 的分辨率不足,阴影处会有噪点问题
- 建议:用 Voxel Tracing 密度体积计算透射,用 Deep Shadow 计算阴影
- 性能开销:
- Deep Shadow(Deep Opacity Maps)和 Voxel Tracing 生成的开销都较高
- 在单个灯光的情况下(场景中包含一个平行光和一个天光),Voxel Tracing 性能持平或略优于 Deep Shadow:
- 只使用 Deep Shadow:总耗时 25.2ms,开启时会降低 HairStrands TransmittanceMask 和 Shadow Projection 的耗时
- 只使用 Voxel Tracing:总耗时 25.87ms,比 Deep Shadow 多计算一个天光
- 使用 Deep Shadow + Voxel Tracing:总耗时 28.38ms
- 在多个灯光的情况下(场景中包含一个平行光、一个投射光和一个天光),Voxel Tracing 的可复用的优势更明显
- 只使用 Deep Shadow:(平行光和投射光均开启 Deep Shadow)总耗时 39.10ms
- 只使用 Voxel Tracing:总耗时 33.74ms
- 使用 Deep Shadow + Voxel Tracing:(仅主光源开启 Deep Shadow,其他使用 Voxel Tracing)总耗时 35.17ms
- Deep Shadow(Deep Opacity Maps)和 Voxel Tracing 的使用建议
- 用于实时渲染
- 中远视角下,仅使用 Voxelization 密度体积的方案
- 特写镜头中,仅主光源开启 Deep Shadow,并限制区间为 512~1024 分辨率
- 用于离线渲染
- 常规使用 Voxelization 密度体积 + 主光源开启 Deep shadow (区间为 1024~2048 分辨率)
- 特写镜头中依据 Sequencer 效果,逐个决定是否开启光源的 Deep Shadow(视角拉远时关闭)
- 渲染输出时(酌情):
- 提高
r.HairStrands.DeepShadow.Resolution
体素分辨率 - 开启
r.HairStrandsDeepShadow.SuperSampling
- 选择
r.HairStrands.DeepShadow.KernelType
- Debug 调试:
r.HairStrands.StrandWidth
:设置毛发宽度r.HairStrands.StrandDebug
:等价于 Show 视图下的分页
0
:NoneDebug1
:SimHairStrands2
:RenderHairStrands3
:RenderHairRootUV4
:RenderHairRootUDIM5
:RenderHairUV6
:RenderHairSeed7
:RenderHairDimension8
:RenderHairRadiusVariation9
:RenderHairBaseColor10
:RenderHairRoughness11
:RenderVisCluster12
:RenderHairTangent
r.HairStrands.DebugMode 1
:查看毛发总体采样数量、修改的 MSAA 数据、鼠标对应的毛发像素采样数据等
r.HairStrands.DebugMode 4
:查看绘制的 Deep Shadow(Deep Opacity Maps)
r.HairStrands.DebugMode 5
:查看亚像素的采样数量(蓝色:1-2 个;黄色:3-4 个;红色:8 个)
r.HairStrands.DebugMode 7
:查看 Coverage 信息(绿色:完全覆盖;红色:部分覆盖,有半透感)
r.HairStrands.DebugMode 8
:查看密度体积信息,需要同时打开r.ShaderPrintEnable 1
标签:UOD2021,HairStrands,孙丹璐,Tracing,Groom,毛发,Deep,Shadow From: https://www.cnblogs.com/ZWJ-zwj/p/18005428