UE4 Hair Shading

相关参考:

Pekelis et al. 2015, "A Data-Driven Light Scattering Model for Hair"

Marschner et al. 2003, "Light Scattering from Human Hair Fibers"

Marschner 论文相关的分析 part I part II part III 翻译

《GPU Gems 2》中头发制作的章节

http://www.fseraph.com/?p=683

Marschner 的头发散射

Marschner 将反射分成了三个部分。

UE4 Hair Shading_第1张图片
图1
  • R:在头发表面反射的光线
  • TT:光线折射进头发然后折射出的光线
  • TRT:光线折射进头丝,再在内部反射后最终折射出的光线

R 表示反射 (reflection),T 表示穿过 (transmission)。R 和 TRT 都是正面反射的光,但是形成了两块完全不同的区域。并且,R 因为没有穿过头发表面而呈现白色,TRT 则带有头发的颜色。

UE4 Hair Shading_第2张图片

Marschner 论文中需要的参数如下图:

UE4 Hair Shading_第3张图片
图2
  • u:头发切线,从发根到发梢
  • w:头发法线(v垂直于w,wv平面为法平面)
  • wi:光线方向
  • wr:摄像机方向
  • θi:光线与法平面夹角
  • θr:摄像机与法平面夹角
  • φi:光线方位角(wi在法平面的投影与v的夹角)
  • φr:摄像机方位角(wr在法平面的投影与v的夹角)
UE4 Hair Shading_第4张图片

对于每个像素P都有R,TT,TRT 三项。然后把每项分解成 M 和 N,其中 M 项是纵切面的散射分布(Longitudinal scattering),N 项是横切面的散射分布(Azimuthal scattering)。这样将4D散射,分解成2个2D的部分。

R,TT,TRT 中的 M 项

在 Unreal 的实现中,M 项是一个正态分布

其中 μ 为中位数,代码中为 Shift。

float Shift = 0.035;
float Alpha[] =
{
    -Shift * 2,
    Shift,
    Shift * 4,
};

R 使用的是 Shift, TT 和 TRT 使用的分别是 Alpha[1], Alpha[2]。

σ 是方差,代码中与 Roughness 相关。

float B[] =
{
    Area + Pow2( ClampedRoughness ),
    Area + Pow2( ClampedRoughness ) / 2,
    Area + Pow2( ClampedRoughness ) * 2,
};

B[0],B[1],B[2] 分别对应R,TT,TRT 中的 σ 。Area 固定为0。Roughness 越小反射却强烈,而集中。

x 是 SinThetaL + SinThetaV。与在头发纵切面上,光线和摄像机方向夹角有关。

// R
float Mp = Hair_g( B[0] * sqrt(2.0) * CosHalfPhi, SinThetaL + SinThetaV - Shift );
...

// TT
float Mp = Hair_g( B[1], SinThetaL + SinThetaV - Alpha[1] );
... 

// TRT
float Mp = Hair_g( B[2], SinThetaL + SinThetaV - Alpha[2] );
...

R,TT,TRT 中的其他部分

R

R 部分是菲涅尔项和普通镜面反射相乘。

float Np = 0.25 * CosHalfPhi;
float Fp = Hair_F( sqrt( saturate( 0.5 + 0.5 * VoL ) ) );
S += Mp * Np * Fp * ( GBuffer.Specular * 2 ) * lerp( 1, Backlit, saturate(-VoL) );

Mp 是上面讲的 M 项(下同)。

Fp 是菲涅尔项。

Np 中的 CosHalfPhi,是方位角差的半角,Np 是公式中 N 项。

Specular 越大,反射越强。

因为光线没有进入发丝,所以没有头发的 BaseColor。

TT

float a = 1 / n_prime;
float h = CosHalfPhi * rsqrt( 1 + a*a - 2*a * sqrt( 0.5 - 0.5 * CosPhi ) );
float yt = asin(h / n_prime);
float3 Tp = pow( GBuffer.BaseColor, 0.5 * cos(yt) / CosThetaD );

float f = Hair_F( CosThetaD * sqrt( saturate( 1 - h*h ) ) );
float Fp = Pow2(1 - f);

float s = 0.3;
float Np = exp( (Phi - PI) / s ) / ( s * Pow2( 1 + exp( (Phi - PI) / s ) ) );

S += Mp * Np * Fp * Tp * Backlit;

Tp 相当于上面公式中 pow(cosθd, 2)

Backlit 控制这一项的强度,也就是背光时头发的透光度。

TRT

float f = Hair_F( CosThetaD * 0.5 );
float Fp = Pow2(1 - f) * f;
float3 Tp = pow( GBuffer.BaseColor, 0.8 / CosThetaD );

float Np = (1/PI) * 2.6 * exp( 2 * 2.6 * (CosPhi - 1) );

S += Mp * Np * Fp * Tp;

思路与TT相同,只不过各项的公式不同。

漫反射

float3 FakeNormal = normalize( V - N * dot(V,N) );
N = FakeNormal;

// Hack approximation for multiple scattering.
float Wrap = 1;
float NoL = saturate( ( dot(N, L) + Wrap ) / Square( 1 + Wrap ) );
float DiffuseScatter = (1 / PI) * NoL * GBuffer.Metallic;
float Luma = Luminance( GBuffer.BaseColor );
float3 ScatterTint = pow( GBuffer.BaseColor / Luma, 1 - Shadow );
S += sqrt( GBuffer.BaseColor ) * DiffuseScatter * ScatterTint;

漫反射部分用一个 Hack 的方法模拟次表面散射材质。

公式中 w 是 0~1 的一个参数,用来加亮远离光线方向的面,Unreal 直接用了1。 Metallic 控制了散射的强度。

材质编辑器

UE4 Hair Shading_第5张图片
Material Attributes

在材质编辑器中 Shading Model 属性选择 Hair,就可以使用 Hair 的光照模型。选择 Masked 是使用 Dither Mask 技术代替半透明。

UE4 Hair Shading_第6张图片
Material Inputs

Hair 的 Material Inputs 与普通的有所不同。

Metallic 变成了 Scatter。从上文的分析中也可以看出,Metallic 控制着散射强度。

Normal 变成了 Tangent。这个切线应该是指向发根的。

Custom Data 0 变成了 Backlit。这个输入本应该是控制透光度,但是实际上并没有用到。

Opacity Mask,因为选了 Masked,所以有这个选项。可以使用 DitherTemporalAA 节点将半透明的部分变成网点,配合 TemporalAA 产生半透明的效果。虽然效果比真正的半透明差一点,但是不会产生排序问题。

另外比较重要的输入,Base Color,Specular,Roughness 的作用已经分析过了。

你可能感兴趣的:(UE4 Hair Shading)