Unity的PBR扩展——皮毛材质

Unity的PBR扩展——皮毛材质

这是侑虎科技第517篇文章,感谢作者Young供稿。欢迎转发分享,未经作者授权请勿转载。如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:793972859)

作者主页:https://www.zhihu.com/people/chen-yong-59-86,作者也是U Sparkle活动参与者,UWA欢迎更多开发朋友加入U Sparkle开发者计划,这个舞台有你更精彩!


最近被王者荣耀的一篇毛发制作文章刷屏:《真香预警!新版“峡谷第一美”妲己尾巴毛发制作分享》

本文参照其介绍的方法先实现无光照的皮毛,然后再进行PBR扩展,最终实现题图的皮毛效果。


一、层渲染技术

根据模型使用层 (layer) 来渲染毛发长度,在Unity Shader中,每一个Pass即表示一层。当渲染每一层时,使用法线将顶点位置挤出模型表面。然后将Noise贴图根据layer做衰减,来当做Alpha值。

实现原理本文就不再详述,这里列出一些实现细节。

1、Shell层
向外突出的层,通过在顶点着色器中对顶点位置沿法线做偏移达到多层的效果,需要用当前层作为偏移系数,使Pass越靠后的层向外偏移的越多:

v.vertex.xyz += v.normal * _FurLength * FUR_OFFSET;

其中FurLength为总的一个scale变量,FUR_OFFSET为层相关的参数,接下来介绍。

2、Alpha值
层的渲染模式设置为透明渲染。为了符合毛发根部粗,尖部细的特点,所以层越往外,隐藏的部分(Alpha值为0)应该越多:

alpha = step(FUR_OFFSET, alpha);

其中FUR_OFFSET值为当前层/总层数,这样FUR_OFFSET值在0到1之间变化。假设渲染20层,那么由里向外的层FUR_OFFSET值依次为0、0.05、0.1 …… 0.9、0.95、1。Alpha值读取自layer贴图,后面介绍。

这样实现的头发形状就是如下图左的三角形。下图右的抛物线形状,需要修改Step的裁剪边界成非线性:

alpha = step(FUR_OFFSET*FUR_OFFSET, alpha);

毛发形状示意图
毛发形状示意图

毛发还有一个特点,就是尖部会显得更透一些,可以设置层次与Alpha值为递减关系:

color.a = 1 - FUR_OFFSET;

3、密集毛发
通过一张额外的灰度图作为Alpha值,得到圆滑的柱体效果,并通过调整其Tiling,可以控制毛发的密集程度:

fixed alpha = tex2D(_LayerTex, TRANSFORM_TEX(i.texcoord.xy, _LayerTex)).r;

采样得到的Alpha值最后与层Alpha值做乘法:

color.a *= alpha;

layer图
layer图

Tiling分别为1和5疏密效果对比图
Tiling分别为1和5疏密效果对比图

4、外力影响
真实的毛发效果应该会受到外力影响,实现方法为对顶点偏移的方向做修改:

half3 direction = _Gravity * _GravityStrength + v.normal * (1 - _GravityStrength);
v.vertex.xyz += direction * _FurLength;

其中的Gravity方向为half3(0, -1, 0)的重力,得到的direction用于顶点偏移。
考虑到实际中外力对发根的影响相对小,对发尖的影响相对大,对direction做些修改:

half3 direction = lerp(v.normal, direction, FUR_OFFSET);

其中以FUR_OFFSET作为参数的Lerp运算使得毛发根部不受外力影响,尖部完全受重力影响。
下图为绘制20层,layer图Tiling为20,受重力作用的无光照版本皮毛效果:

无光照皮毛效果
无光照皮毛效果


二、PBR扩展

现实中的布料织物大概可分为表面粗糙和光滑两类。表面粗糙的织物有棉布、亚麻布等,表面光滑的织物有丝绸、天鹅绒等。 棉布类织物表面主要为较淡的织物颜色组成的漫反射,丝绸类织物表面有类金属的反射,而且会有多种的高光颜色。 观察还发现棉布类织物纤维周围有一圈很细的绒毛,丝绸类织物的纤维绒毛很少。

现实织物的物理表现
现实织物的物理表现

这些物理特性中的主体表面部分适合用PBR的Metalic和Rougthness来描述,绒毛和多种高光部分需增加一个Fabric Scatter模型(详细见下文)来描述。 下图为参考文献3的作者给出的描述不同布料织物的PBR经验参数:

模拟织物的PBR参数
模拟织物的PBR参数

注意上图中的Velvet(天鹅绒)的Rougthness和Metalic都为1,这符合天鹅绒表面粗糙,看起来很黑(漫反射很弱)的物理特性。

1、Inverted Gaussian
布料织物有其特有的光照反射模型。以天鹅绒为例,它的高光位置不是灯光和视线的正对区域,而是边缘位置。

理想高光反射与实际织物的反射对比
理想高光反射与实际织物的反射对比

标准PBR的Microfacet GGX分布在0角度下(法线、灯光和视线同向)获得最大的高光强度,然后随着角度的增大做正态(Gaussian)分布, 而天鹅绒高光分布与GGX刚好相反,可见GGX的分布模型并不适合某些布料的模拟。

The Order:1886 游戏采用Inverted Gaussian模型做为布料的高光分布模型,参考文献3的作者在此基础上进一步简化得到:

GGX分布曲线与自定义分布曲线对比
GGX分布曲线与自定义分布曲线对比

Shader代码:

inline float FabricD (float NdotH, float roughness)
{
     return 0.96 * pow(1 - NdotH, 2) + 0.057;
}

用FabricD替换掉UnityStandardBRDF.cginc的BRDF1_Unity_PBS函数的GGXTerm。

Roughness为1时GGXTerm与FabricD结果对比
Roughness为1时GGXTerm与FabricD结果对比

2、Fabric Scatter
布料织物的绒毛效果可由边缘光来模拟,再综合丝绸的高光颜色,参考文献3的作者给出一个Fabric Scatter经验模型:
1、修改标准PBR的Fresnel term,用4倍POW值代替标准的5倍POW值,用于增大Fresnel的范围;
2、再叠加一层跟视角相关的范围更大的、可调的Fresnel颜色用于更好的模拟织物表面的高光;
3、去掉标准PBR的间接光反射,使得Metallic-like的布料不那么Metallic。

Shader代码:

inline half FabricScatterFresnelLerp(half nv, half scale)
{
     half t0 = Pow4 (1 - nv);
     half t1 = 0.4 * (1 - nv);
     return (t1 - t0) * scale + t0; 
}

BRDF1_Unity_PBS函数返回颜色:

half3 color = diffColor * (gi.diffuse + light.color * diffuseTerm)  
            + specularTerm * light.color * FresnelTerm (specColor, lh)
            + _FabricScatterColor * (nl*0.5 + 0.5) * FabricScatterFresnelLerp(nv, _FabricScatterScale);

标准PBR和自定义实现的天鹅绒效果对比
标准PBR和自定义实现的天鹅绒效果对比

把天鹅绒的布料光照模型套用到无光照的皮毛上,得到最终效果:

基于PBR的皮毛效果
基于PBR的皮毛效果

完整工程代码见(包含无光照版本):

https://lab.uwa4d.com/lab/5b69a13ed7f10a201f02360a

参考文献:

1)Generating Fur in DirectX or OpenGL Easily
2)gFur Help - Introduction - gim.studiohttps://www.slideshare.net/jalnaga/custom-fabric-shader-for-unreal-engine-4
3)http://gennyx.blogspot.com/2009/11/its-material-world_11.html
4)http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.105.1847&rep=rep1&type=pdf
5)http://blog.selfshadow.com/publications/s2013-shading-course/rad/s2013_pbs_rad_notes.pdf

文末,再次感谢Young的分享,如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。
QQ群:793972859

也欢迎大家来积极参与U Sparkle开发者计划,简称“US”,代表你和我,代表UWA和开发者在一起!