接上文,本文主要介绍UE4、Filament以及Disney对前面介绍的几种shading model的实现。
实现
着色模型(Shading model)指的是材质如何对入射光线做出反应,可以理解为从入射光线到出射光线的映射函数。前面介绍的BRDF理论也是shading model的一部分。除了BRDF,还有BSDF、BTDF模型,统称为BxDF。BxDF的公式的选取决定了shading model的效果。这一节我们将讨论主流的渲染引擎、方法都采用了哪些shading model,以及他们的具体实现是什么。
UE4
UE4一共支持了13种不同的shading model,用到了9中不同的BxDF model:
- Unlit
- Default Lit
- Subsurface
- Preintegrated Skin
- Clear Coat
- Subsurface Profile
- Two Sided Foliage
- Hair
- Cloth
- Eye
- SingleLayerWater
- Thin Translucent
- From Material Expression
其中没有各向异性模型,是因为UE4中各向异性不属于shading model,而是通过添加anisotropy
参数引入各向异性计算,方便和任意一个shading model组合。
shading model的设置在Material里(如上图),shader层面的控制在文件Engine\Shaders\Private\ShadingModels.ush
的IntegrateBxDF
函数里:
FDirectLighting IntegrateBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow )
{
switch( GBuffer.ShadingModelID )
{
case SHADINGMODELID_DEFAULT_LIT:
case SHADINGMODELID_SINGLELAYERWATER:
case SHADINGMODELID_THIN_TRANSLUCENT:
return DefaultLitBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
case SHADINGMODELID_SUBSURFACE:
return SubsurfaceBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
case SHADINGMODELID_PREINTEGRATED_SKIN:
return PreintegratedSkinBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
case SHADINGMODELID_CLEAR_COAT:
return ClearCoatBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
case SHADINGMODELID_SUBSURFACE_PROFILE:
return SubsurfaceProfileBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
case SHADINGMODELID_TWOSIDED_FOLIAGE:
return TwoSidedBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
case SHADINGMODELID_HAIR:
return HairBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
case SHADINGMODELID_CLOTH:
return ClothBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
case SHADINGMODELID_EYE:
return EyeBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
default:
return (FDirectLighting)0;
}
}
其中的GBuffer.ShadingModelID
记录当前材质的shading model类型,接下来具体看一下UE支持的这些模型。
Default Lit
Default lit是最常用的标准shading model,能够处理基础的直接光照、间接光照,它包含以下参数:
- Base Color
- Metallic
- Specular
- Roughness
- Emissive Color
- Normal
- Ambient Occlusion
代码实现如下。UE4的Default Lit模型采用的是Lambert作为Diffuse项,法向分布函数GGX、遮挡项SmithJoint和菲涅尔项Schlick组合成为Specular项。
FDirectLighting DefaultLitBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow )
{
// Prepare BxDFContext Context;
// ...
FDirectLighting Lighting;
Lighting.Diffuse = AreaLight.FalloffColor * (Falloff * NoL) * Diffuse_Lambert( GBuffer.DiffuseColor );
if( AreaLight.bIsRect )
Lighting.Specular = RectGGXApproxLTC( GBuffer.Roughness, GBuffer.SpecularColor, N, V, AreaLight.Rect, AreaLight.Texture );
else
Lighting.Specular = AreaLight.FalloffColor * (Falloff * NoL) * SpecularGGX( GBuffer.Roughness, GBuffer.Anisotropy, GBuffer.SpecularColor, Context, NoL, AreaLight );
Lighting.Transmission = 0;
return Lighting;
}
其中,UE4采用的Diffuse项为Lambert漫反射项,法向分布函数是GGX,遮挡函数是近似的SmithJoint。
float3 Diffuse_Lambert( float3 DiffuseColor )
{
return DiffuseColor * (1 / PI);
}
float3 SpecularGGX( float Roughness, float3 SpecularColor, BxDFContext Context, float NoL, FAreaLight AreaLight )
{
float a2 = Pow4( Roughness );
float Energy = EnergyNormalization( a2, Context.VoH, AreaLight );
// Generalized microfacet specular
float D = D_GGX( a2, Context.NoH ) * Energy;
float Vis = Vis_SmithJointApprox( a2, Context.NoV, NoL );
float3 F = F_Schlick( SpecularColor, Context.VoH );
return (D * Vis) * F;
}
// GGX / Trowbridge-Reitz
// [Walter et al. 2007, "Microfacet models for refraction through rough surfaces"]
float D_GGX( float a2, float NoH )
{
float d = ( NoH * a2 - NoH ) * NoH + 1; // 2 mad
return a2 / ( PI*d*d ); // 4 mul, 1 rcp
}
// Appoximation of joint Smith term for GGX
// [Heitz 2014, "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs"]
float Vis_SmithJointApprox( float a2, float NoV, float NoL )
{
float a = sqrt(a2);
float Vis_SmithV = NoL * ( NoV * ( 1 - a ) + a );
float Vis_SmithL = NoV * ( NoL * ( 1 - a ) + a );
return 0.5 * rcp( Vis_SmithV + Vis_SmithL );
}
// [Schlick 1994, "An Inexpensive BRDF Model for Physically-Based Rendering"]
float3 F_Schlick( float3 SpecularColor, float VoH )
{
float Fc = Pow5( 1 - VoH ); // 1 sub, 3 mul
//return Fc + (1 - Fc) * SpecularColor; // 1 add, 3 mad
// Anything less than 2% is physically impossible and is instead considered to be shadowing
return saturate( 50.0 * SpecularColor.g ) * Fc + (1 - Fc) * SpecularColor;
}
Subsurface
Subsurface model依赖于subsurface color
参数来控制:
- Base Color
- Metallic
- Specular
- Roughness
- Emissive Color
- Opacity
- Normal
- Subsurface Color
- Ambient Occlusion
UE4的subsurface model是基于DefaultLit model的,仅仅是将subsurface的参数赋给Lighting.Transmission
。
FDirectLighting SubsurfaceBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow )
{
FDirectLighting Lighting = DefaultLitBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
float3 SubsurfaceColor = ExtractSubsurfaceColor(GBuffer);
float Opacity = GBuffer.CustomData.a;
float3 H = normalize(V + L);
// to get an effect when you see through the material
// hard coded pow constant
float InScatter = pow(saturate(dot(L, -V)), 12) * lerp(3, .1f, Opacity);
// wrap around lighting, /(PI*2) to be energy consistent (hack do get some view dependnt and light dependent effect)
// Opacity of 0 gives no normal dependent lighting, Opacity of 1 gives strong normal contribution
float NormalContribution = saturate(dot(N, H) * Opacity + 1 - Opacity);
float BackScatter = GBuffer.GBufferAO * NormalContribution / (PI * 2);
// lerp to never exceed 1 (energy conserving)
Lighting.Transmission = AreaLight.FalloffColor * ( Falloff * lerp(BackScatter, 1, InScatter) ) * SubsurfaceColor;
return Lighting;
}
Preintegrated Skin
Preintegrated skin模型是Subsurface model在皮肤染情况下的特化,效果类似但是效率更高,通常用在移动端。相对于GPU Pro 2中介绍的完整的Preintegrated Skin Shading,这个model做了一些简化,具体包括:
- 只在Shadow域进行了预积分,Diffuse和Specular都采用了DefaultLit,也就是跟普通渲染没啥区别
- UE4的Preintegrated Texture是一张灰度图,具体的用法是用NoL和Opacity(实际是1-Opacity)对它进行采样,然后乘上Subsurface Color
- UE4的这个模型其实是一个高效但是并不精确的方法,因此Epic官方推荐使用Subsurface Profile模型渲染高精度的皮肤
BxDF代码如下:
FDirectLighting PreintegratedSkinBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow )
{
FDirectLighting Lighting = DefaultLitBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
float3 SubsurfaceColor = ExtractSubsurfaceColor(GBuffer);
float Opacity = GBuffer.CustomData.a;
float3 PreintegratedBRDF = Texture2DSampleLevel(View.PreIntegratedBRDF, View.PreIntegratedBRDFSampler, float2(saturate(dot(N, L) * .5 + .5), 1 - Opacity), 0).rgb;
Lighting.Transmission = AreaLight.FalloffColor * Falloff * PreintegratedBRDF * SubsurfaceColor;
return Lighting;
}
Clear Coat
Clear coat模型包括以下参数。
- Base Color
- Metallic
- Specular
- Roughness
- Emissive Color
- Normal
- Ambient Occlusion
- Clear Coat
- Clear Coat Roughness
UE4对Clear Coat模型的处理如下所示。
FDirectLighting ClearCoatBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow )
{
const float ClearCoat = GBuffer.CustomData.x;
const float ClearCoatRoughness = max(GBuffer.CustomData.y, 0.02f);
// Some init...
// Begin clear coat layer
// Hard-coded Fresnel evaluation with IOR = 1.5 (for polyurethane cited by Disney BRDF)
float F0 = 0.04;
float Fc = Pow5(1 - Context.VoH);
float F = Fc + (1 - Fc) * F0;
// Generalized microfacet specular
float a2 = Pow4(ClearCoatRoughness);
float ClearCoatEnergy = EnergyNormalization(a2, Context.VoH, AreaLight);
float D = D_GGX(a2, Context.NoH) * ClearCoatEnergy;
float Vis = Vis_SmithJointApprox(a2, Context.NoV, NoL);
float Fr1 = D * Vis * F;
Lighting.Specular = ClearCoat * AreaLight.FalloffColor * (Falloff * NoL * Fr1);
// End clear coat layer
// Begin base layer
// Default Lit
float Alpha = Pow2(GBuffer.Roughness);
float a2 = Pow2(Alpha);
float Energy = EnergyNormalization(a2, BottomContext.VoH, AreaLight);
//Lighting.Diffuse = (FresnelCoeff * Energy * Falloff * BottomContext.NoL) * Transmission * AreaLight.FalloffColor * Diffuse_Lambert(GBuffer.DiffuseColor);
float3 CommonDiffuse = (Energy * Falloff) * AreaLight.FalloffColor * Diffuse_Lambert(GBuffer.DiffuseColor);
float3 DefaultDiffuse = NoL;
float3 RefractedDiffuse = (FresnelCoeff * BottomContext.NoL) * Transmission;
Lighting.Diffuse = CommonDiffuse * lerp(DefaultDiffuse, RefractedDiffuse, ClearCoat);
D2 = D_GGX(a2, Context.NoH);
Vis2 = Vis_SmithJointApprox(a2, BottomContext.NoV, BottomContext.NoL);
float3 F = F_Schlick(GBuffer.SpecularColor, BottomContext.VoH);
//Lighting.Specular += (Energy * Falloff * BottomContext.NoL * D2 * Vis2 * FresnelCoeff) * Transmission * AreaLight.FalloffColor * F;
// Note: reusing D, V, and F from refracted context to save computation for when ClearCoat < 1
float3 CommonSpecular = (Energy * Falloff * D2 * Vis2) * AreaLight.FalloffColor * F;
float3 DefaultSpecular = NoL;
float3 RefractedSpecular = FresnelCoeff * Transmission * BottomContext.NoL;
Lighting.Specular += CommonSpecular * lerp(DefaultSpecular, RefractedSpecular, ClearCoat);
// End base layer
return Lighting;
}
代码经过了一定程度的精简。总的来说,UE4对Clear Coat Layer的specular乘上系数ClearCoat
,对Base Layer的specular和diffuse乘上系数lerp(NoL, FresnelCoeff * BottomContext.NoL * Transmission, ClearCoat)
,其他的计算公式与DefaultLit
模型一致。
Subsurface Profile
Subsurface Profile也是用于渲染皮肤的模型,是Preintegrated Skin模型的加强版,效果更真实,常用于高质量的皮肤渲染上。不再赘述。
Two Sided Foliage
Two Sided Foliage用于渲染较薄的次表面散射材质,例如树叶、花瓣等。它可以模拟光线穿过材质的效果,比Subsurface model更真实。
- Base Color
- Metallic
- Specular
- Roughness
- Emissive Color
- Normal
- Ambient Occlusion
- Subsurface Color
实现上与subsurface model类似:
FDirectLighting TwoSidedBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow )
{
FDirectLighting Lighting = DefaultLitBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
float3 SubsurfaceColor = ExtractSubsurfaceColor(GBuffer);
// http://blog.stevemcauley.com/2011/12/03/energy-conserving-wrapped-diffuse/
float Wrap = 0.5;
float WrapNoL = saturate( ( -dot(N, L) + Wrap ) / Square( 1 + Wrap ) );
// Scatter distribution
float VoL = dot(V, L);
float Scatter = D_GGX( 0.6*0.6, saturate( -VoL ) );
Lighting.Transmission = AreaLight.FalloffColor * (Falloff * WrapNoL * Scatter) * SubsurfaceColor;
return Lighting;
}
Hair
Hair模型用于模拟毛发的渲染效果。
- Base Color
- Scatter
- Specular
- Roughness
- Emissive Color
- Tangent
- Ambient Occlusion
- Backlit
具体实现过于复杂,不在这里展开叙述。
Cloth
Cloth模型添加了Cloth
和Fuzz Color
参数,用于控制高光的程度和颜色:
- Base Color
- Metallic
- Specular
- Roughness
- Emissive Color
- Opacity
- Normal
- Fuzz Color
- Cloth
- Ambient Occlusion
UE4的布料模型采用的是近似的Ashikhmin公式,如下:
D A s h i k h m i n A p p r o x = 1 π ( 1 + 4 α 2 ) ( 1 + 4 α 4 ( cos 2 θ + α 2 sin 2 θ ) 2 ) D_{AshikhminApprox} = \frac{1}{\pi(1+4\alpha^2)}\left(1+\frac{4\alpha^4}{(\cos^2\theta+\alpha^2\sin^2\theta)^2}\right) DAshikhminApprox=π(1+4α2)1(1+(cos2θ+α2sin2θ)24α4)
具体的实现为:
FDirectLighting ClothBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow )
{
const float3 FuzzColor = ExtractSubsurfaceColor(GBuffer);
const float Cloth = saturate(GBuffer.CustomData.a);
BxDFContext Context;
Init( Context, N, V, L );
SphereMaxNoH( Context, AreaLight.SphereSinAlpha, true );
Context.NoV = saturate( abs( Context.NoV ) + 1e-5 );
float3 Spec1;
if( AreaLight.bIsRect )
Spec1 = RectGGXApproxLTC( GBuffer.Roughness, GBuffer.SpecularColor, N, V, AreaLight.Rect, AreaLight.Texture );
else
Spec1 = AreaLight.FalloffColor * (Falloff * NoL) * SpecularGGX( GBuffer.Roughness, GBuffer.SpecularColor, Context, NoL, AreaLight );
// Cloth - Asperity Scattering - Inverse Beckmann Layer
float D2 = D_InvGGX( Pow4( GBuffer.Roughness ), Context.NoH );
float Vis2 = Vis_Cloth( Context.NoV, NoL );
float3 F2 = F_Schlick( FuzzColor, Context.VoH );
float3 Spec2 = AreaLight.FalloffColor * (Falloff * NoL) * (D2 * Vis2) * F2;
FDirectLighting Lighting;
Lighting.Diffuse = AreaLight.FalloffColor * (Falloff * NoL) * Diffuse_Lambert( GBuffer.DiffuseColor );
Lighting.Specular = lerp( Spec1, Spec2, Cloth );
Lighting.Transmission = 0;
return Lighting;
}
float D_InvGGX( float a2, float NoH )
{
float A = 4;
float d = ( NoH - a2 * NoH ) * NoH + a2;
return rcp( PI * (1 + A*a2) ) * ( 1 + 4 * a2*a2 / ( d*d ) );
}
float Vis_Cloth( float NoV, float NoL )
{
return rcp( 4 * ( NoL + NoV - NoL * NoV ) );
}
Eye
Eye模型用于模拟眼睛的表面,这是一个非常精细、专业的模型。相应的,加入了控制虹膜的参数。
- Base Color
- Metallic
- Specular
- Roughness
- Emissive Color
- Opacity
- Normal
- Ambient Occlusion
- Iris Mask
- Iris Distance
具体的实现比较复杂,也不再展开。
others
上面列出的9种shading model,都有对应的BxDF公式。除此之外,还有其他的一些简化模型,这些模型没有单独的BxDF算法,但是也有比较重要的应用场景,因此也被归为shading model。
Unlit
Unlit model严格来说不是对入射光线的映射函数,它只有一个参数——
- emissive color
即自发光。通常它是用来展示火焰或者发光体的特效的模型。
SingleLayerWater
SingleLayerWater模型用于模拟透明水面的效果,降低使用透明模式混合的开销和复杂度。SingleLayerWater模型与Default Lit模型公用同一套BxDF公式,它的参数比Default Lit多两个:
- Opacity
- Refraction
Thin Translucent
Thin Translucent模型用于模拟基于物理原理的半透明材质,能够更真实地还原高光和背景色。Thin Translucent模型与Default Lit模型公用同一套BxDF公式,它的参数比Default Lit多一个:
- Opacity
From Material Expression
From Material Expression模型可以将多个shading model合并到单个材质中,
Filament
Filament作为一款支持PBR的跨平台实时渲染引擎,对基本材质都有实现,但是模型相比于UE4更简单。它的源码可以从github repo上获取到。具体的shader实现都放在shaders/src
下面,所有底层的BRDf公式实现都在shaders/src/brdf.fs
里。
关于Filament的具体介绍可以参考这篇博客。
Standard Model
Filament的Standard model默认采用的是Lambert模型,GGX法向分布函数和遮挡函数。但是Filament其实实现了很多其他函数,通过宏控制具体的使用。这里仅展示默认的函数和模型。
vec3 surfaceShading(const PixelParams pixel, const Light light, float occlusion) {
vec3 h = normalize(shading_view + light.l);
float NoV = shading_NoV;
float NoL = saturate(light.NoL);
float NoH = saturate(dot(shading_normal, h));
float LoH = saturate(dot(light.l, h));
vec3 Fr = specularLobe(pixel, light, h, NoV, NoL, NoH, LoH);
vec3 Fd = diffuseLobe(pixel, NoV, NoL, LoH);
// ...
// The energy compensation term is used to counteract the darkening effect
// at high roughness
vec3 color = Fd + Fr * pixel.energyCompensation;
return (color * light.colorIntensity.rgb) *
(light.colorIntensity.w * light.attenuation * NoL * occlusion);
}
Diffuse部分:
vec3 diffuseLobe(const PixelParams pixel, float NoV, float NoL, float LoH) {
return pixel.diffuseColor * diffuse(pixel.roughness, NoV, NoL, LoH);
}
float Fd_Lambert() {
return 1.0 / PI;
}
float Fd_Burley(float roughness, float NoV, float NoL, float LoH) {
// Burley 2012, "Physically-Based Shading at Disney"
float f90 = 0.5 + 2.0 * roughness * LoH * LoH;
float lightScatter = F_Schlick(1.0, f90, NoL);
float viewScatter = F_Schlick(1.0, f90, NoV);
return lightScatter * viewScatter * (1.0 / PI);
}
//------------------------------------------------------------------------------
// Diffuse BRDF dispatch
//------------------------------------------------------------------------------
float diffuse(float roughness, float NoV, float NoL, float LoH) {
#if BRDF_DIFFUSE == DIFFUSE_LAMBERT
return Fd_Lambert();
#elif BRDF_DIFFUSE == DIFFUSE_BURLEY
return Fd_Burley(roughness, NoV, NoL, LoH);
#endif
Specular部分:
vec3 isotropicLobe(const PixelParams pixel, const Light light, const vec3 h,
float NoV, float NoL, float NoH, float LoH) {
float D = distribution(pixel.roughness, NoH, h);
float V = visibility(pixel.roughness, NoV, NoL);
vec3 F = fresnel(pixel.f0, LoH);
return (D * V) * F;
}
vec3 specularLobe(const PixelParams pixel, const Light light, const vec3 h,
float NoV, float NoL, float NoH, float LoH) {
#if defined(MATERIAL_HAS_ANISOTROPY)
return anisotropicLobe(pixel, light, h, NoV, NoL, NoH, LoH);
#else
return isotropicLobe(pixel, light, h, NoV, NoL, NoH, LoH);
#endif
}
float distribution(float roughness, float NoH, const vec3 h) {
#if BRDF_SPECULAR_D == SPECULAR_D_GGX
return D_GGX(roughness, NoH, h);
#endif
}
float visibility(float roughness, float NoV, float NoL) {
#if BRDF_SPECULAR_V == SPECULAR_V_SMITH_GGX
return V_SmithGGXCorrelated(roughness, NoV, NoL);
#elif BRDF_SPECULAR_V == SPECULAR_V_SMITH_GGX_FAST
return V_SmithGGXCorrelated_Fast(roughness, NoV, NoL);
#endif
}
vec3 fresnel(const vec3 f0, float LoH) {
#if BRDF_SPECULAR_F == SPECULAR_F_SCHLICK
float f90 = saturate(dot(f0, vec3(50.0 * 0.33)));
return F_Schlick(f0, f90, LoH);
#endif
}
float D_GGX(float roughness, float NoH, const vec3 h) {
// Walter et al. 2007, "Microfacet Models for Refraction through Rough Surfaces"
float oneMinusNoHSquared = 1.0 - NoH * NoH;
float a = NoH * roughness;
float k = roughness / (oneMinusNoHSquared + a * a);
float d = k * k * (1.0 / PI);
return saturateMediump(d);
}
float V_SmithGGXCorrelated(float roughness, float NoV, float NoL) {
// Heitz 2014, "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs"
float a2 = roughness * roughness;
float lambdaV = NoL * sqrt((NoV - a2 * NoV) * NoV + a2);
float lambdaL = NoV * sqrt((NoL - a2 * NoL) * NoL + a2);
float v = 0.5 / (lambdaV + lambdaL);
// clamp to the maximum value representable in mediump
return saturateMediump(v);
}
vec3 F_Schlick(const vec3 f0, float f90, float VoH) {
// Schlick 1994, "An Inexpensive BRDF Model for Physically-Based Rendering"
return f0 + (f90 - f0) * pow5(1.0 - VoH);
}
Clear Coat Model
Filament的Clear Coat模型跟UE4有点类似,但是相对简单,Base Layer的系数选取也不一样。
void BRDF(...) {
// compute Fd and Fr from standard model
// remapping and linearization of clear coat roughness
clearCoatPerceptualRoughness = clamp(clearCoatPerceptualRoughness, 0.089, 1.0);
clearCoatRoughness = clearCoatPerceptualRoughness * clearCoatPerceptualRoughness;
// clear coat BRDF
float Dc = D_GGX(clearCoatRoughness, NoH);
float Vc = V_Kelemen(clearCoatRoughness, LoH);
float Fc = F_Schlick(0.04, LoH) * clearCoat; // clear coat strength
float Frc = (Dc * Vc) * Fc;
// account for energy loss in the base layer
return color * ((Fd + Fr * (1.0 - Fc)) * (1.0 - Fc) + Frc);
}
Subsurface Model
Filament的Subsurface Model是根据输入的subsurfaceColor
和系数,直接混合次表面层的Diffuse颜色,其他计算细节跟Standard Model一致。
vec3 surfaceShading(const PixelParams pixel, const Light light, float occlusion) {
vec3 h = normalize(shading_view + light.l);
float NoL = light.NoL;
float NoH = saturate(dot(shading_normal, h));
float LoH = saturate(dot(light.l, h));
vec3 Fr = vec3(0.0);
if (NoL > 0.0) {
// specular BRDF
float D = distribution(pixel.roughness, NoH, h);
float V = visibility(pixel.roughness, shading_NoV, NoL);
vec3 F = fresnel(pixel.f0, LoH);
Fr = (D * V) * F * pixel.energyCompensation;
}
// diffuse BRDF
vec3 Fd = pixel.diffuseColor * diffuse(pixel.roughness, shading_NoV, NoL, LoH);
// NoL does not apply to transmitted light
vec3 color = (Fd + Fr) * (NoL * occlusion);
// subsurface scattering
// Use a spherical gaussian approximation of pow() for forwardScattering
// We could include distortion by adding shading_normal * distortion to light.l
float scatterVoH = saturate(dot(shading_view, -light.l));
float forwardScatter = exp2(scatterVoH * pixel.subsurfacePower - pixel.subsurfacePower);
float backScatter = saturate(NoL * pixel.thickness + (1.0 - pixel.thickness)) * 0.5;
float subsurface = mix(backScatter, 1.0, forwardScatter) * (1.0 - pixel.thickness);
color += pixel.subsurfaceColor * (subsurface * Fd_Lambert());
// TODO: apply occlusion to the transmitted light
return (color * light.colorIntensity.rgb) * (light.colorIntensity.w * light.attenuation);
}
Cloth Model
Filament的布料模型采用的是Charlie方法:
float distributionCloth(float roughness, float NoH) {
return D_Charlie(roughness, NoH);
}
float visibilityCloth(float NoV, float NoL) {
return V_Neubelt(NoV, NoL);
}
float D_Charlie(float roughness, float NoH) {
// Estevez and Kulla 2017, "Production Friendly Microfacet Sheen BRDF"
float invAlpha = 1.0 / roughness;
float cos2h = NoH * NoH;
float sin2h = max(1.0 - cos2h, 0.0078125); // 2^(-14/2), so sin2h^2 > 0 in fp16
return (2.0 + invAlpha) * pow(sin2h, invAlpha * 0.5) / (2.0 * PI);
}
float V_Neubelt(float NoV, float NoL) {
// Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886"
return saturateMediump(1.0 / (4.0 * (NoL + NoV - NoL * NoV)));
}
Disney
Disney对shading model的需求是,不一定严格物理正确,但是要对艺术家直观。Disney设计的原则是:
- 应使用直观的参数,而不是物理类的晦涩参数;
- 参数应尽可能少;
- 参数在其合理范围内应该为0到1;
- 允许参数在有意义时超出正常的合理范围;
- 所有参数组合应尽可能健壮和合理;
本文的Disney源码参考这个实现。
Principled BRDF
Disney的Principled BRDF模型包含以下十一个参数,它其实涵盖了UE4的多个shading model。
- baseColor:向量,基础颜色。
- metallic:标量,0表示电介质,1表示金属。
- roughness:标量,粗糙度。
- anisotropic:标量,各向异性程度,0表示各向同性,1表示最大各向异性。
- specular:标量,入射镜面反射量,用于取代折射率。
- specularTint:标量,镜面反射颜色,利用该变量和baseColor可以控制镜面反射颜色。
- subsurface:标量,使用次表面近似控制漫反射形状。
- sheen:标量,光泽度,布料的属性。
- sheenTint:标量,光泽颜色,布料的属性。
- clearcoat:标量,clearCoat的属性。
- clearcoatGloss:标量,clearCoat的属性。
可以看出,Disney将所有的参数都集中在一个模型里面了。我们直接从源码分析他们的使用。
准备工作:
vec3 mon2lin(vec3 x)
{
return vec3(pow(x[0], 2.2), pow(x[1], 2.2), pow(x[2], 2.2));
}
vec3 BRDF( vec3 L, vec3 V, vec3 N, vec3 X, vec3 Y )
{
float NdotL = dot(N,L);
float NdotV = dot(N,V);
if (NdotL < 0 || NdotV < 0) return vec3(0);
vec3 H = normalize(L+V);
float NdotH = dot(N,H);
float LdotH = dot(L,H);
vec3 Cdlin = mon2lin(baseColor);
float Cdlum = .3*Cdlin[0] + .6*Cdlin[1] + .1*Cdlin[2]; // luminance approx.
vec3 Ctint = Cdlum > 0 ? Cdlin/Cdlum : vec3(1); // normalize lum. to isolate hue+sat
vec3 Cspec0 = mix(specular*.08*mix(vec3(1), Ctint, specularTint), Cdlin, metallic);
vec3 Csheen = mix(vec3(1), Ctint, sheenTint);
// diffuse
// subsurface
// specular
// sheen
// clearcoat
return ((1/PI) * mix(Fd, ss, subsurface)*Cdlin + Fsheen)
* (1-metallic)
+ Gs*Fs*Ds + .25*clearcoat*Gr*Fr*Dr;
}
Diffuse
Diffuse部分用的是Disney自己的模型,考虑到了入射角度、出射角度和粗糙度的影响。
float SchlickFresnel(float u)
{
float m = clamp(1-u, 0, 1);
float m2 = m*m;
return m2*m2*m; // pow(m,5)
}
vec3 BRDF( vec3 L, vec3 V, vec3 N, vec3 X, vec3 Y )
{
//....
// Diffuse fresnel - go from 1 at normal incidence to .5 at grazing
// and mix in diffuse retro-reflection based on roughness
float FL = SchlickFresnel(NdotL);
float FV = SchlickFresnel(NdotV);
float Fd90 = 0.5 + 2 * LdotH*LdotH * roughness;
float Fd = mix(1.0, Fd90, FL) * mix(1.0, Fd90, FV);
// ...
}
Specular
Specular部分分别采用了法向分布函数GGX、遮挡项SmithGGX、菲涅尔项Schlick,并且直接计算的是各向异性的结果。
float GTR2_aniso(float NdotH, float HdotX, float HdotY, float ax, float ay)
{
return 1 / (PI * ax*ay * sqr( sqr(HdotX/ax) + sqr(HdotY/ay) + NdotH*NdotH ));
}
float smithG_GGX_aniso(float NdotV, float VdotX, float VdotY, float ax, float ay)
{
return 1 / (NdotV + sqrt( sqr(VdotX*ax) + sqr(VdotY*ay) + sqr(NdotV) ));
}
vec3 BRDF( vec3 L, vec3 V, vec3 N, vec3 X, vec3 Y )
{
//....
// specular
float aspect = sqrt(1-anisotropic*.9);
float ax = max(.001, sqr(roughness)/aspect);
float ay = max(.001, sqr(roughness)*aspect);
float Ds = GTR2_aniso(NdotH, dot(H, X), dot(H, Y), ax, ay);
float FH = SchlickFresnel(LdotH);
vec3 Fs = mix(Cspec0, vec3(1), FH);
float Gs;
Gs = smithG_GGX_aniso(NdotL, dot(L, X), dot(L, Y), ax, ay);
Gs *= smithG_GGX_aniso(NdotV, dot(V, X), dot(V, Y), ax, ay);
// ...
}
Subsurface
次表面散射项的计算公式与Diffuse类似,除了F90
用的是LdotH*LdotH*roughness
。计算出来的Fss
会经过remapping再跟Diffuse项以系数subsurface
混合。
公式:
f s s ( l , v ) = 1.25 π ( F S u b s u r f a c e ( 1 n ⋅ l + n ⋅ v − 0.5 ) + 0.5 ) F S u b s u r f a c e = F S c h l i c k ( n , l , 1 , f 90 ) F S c h l i c k ( n , v , 1 , f 90 ) F S c h l i c k ( n , l , f 0 , f 90 ) = f 0 + ( f 90 − f 0 ) ( 1 − ( n ⋅ l ) ) 5 f 90 = r o u g h n e s s ⋅ ( n ⋅ h ) 2 \begin{aligned} f_{ss}({\bf{l}},{\bf{v}}) & = \frac{1.25}{\pi}(F_{Subsurface}(\frac{1}{{\bf{n}}\cdot{\bf{l}}+{\bf{n}}\cdot{\bf{v}}}-0.5)+0.5) \\ F_{Subsurface} & = F_{Schlick}({\bf{n}},{\bf{l}},1,f_{90})F_{Schlick}({\bf{n}},{\bf{v}},1,f_{90})\\ F_{Schlick}({\bf{n}},{\bf{l}},f_0,f_{90}) & = f_0+(f_{90}-f_0)(1-({\bf{n}}\cdot{\bf{l}}))^5 \\ f_{90} & = roughness\cdot({\bf{n}}\cdot{\bf{h}})^2 \end{aligned} fss(l,v)FSubsurfaceFSchlick(n,l,f0,f90)f90=π1.25(FSubsurface(n⋅l+n⋅v1−0.5)+0.5)=FSchlick(n,l,1,f90)FSchlick(n,v,1,f90)=f0+(f90−f0)(1−(n⋅l))5=roughness⋅(n⋅h)2
代码:
vec3 BRDF( vec3 L, vec3 V, vec3 N, vec3 X, vec3 Y )
{
//....
// Based on Hanrahan-Krueger brdf approximation of isotropic bssrdf
// 1.25 scale is used to (roughly) preserve albedo
// Fss90 used to "flatten" retroreflection based on roughness
float Fss90 = LdotH*LdotH*roughness;
float Fss = mix(1.0, Fss90, FL) * mix(1.0, Fss90, FV);
float ss = 1.25 * (Fss * (1 / (NdotL + NdotV) - .5) + .5);
// ...
}
Sheen
vec3 BRDF( vec3 L, vec3 V, vec3 N, vec3 X, vec3 Y )
{
//....
// sheen
vec3 Fsheen = FH * sheen * Csheen;
// ...
}
ClearCoat
float GTR1(float NdotH, float a)
{
if (a >= 1) return 1/PI;
float a2 = a*a;
float t = 1 + (a2-1)*NdotH*NdotH;
return (a2-1) / (PI*log(a2)*t);
}
vec3 BRDF( vec3 L, vec3 V, vec3 N, vec3 X, vec3 Y )
{
//....
// clearcoat (ior = 1.5 -> F0 = 0.04)
float Dr = GTR1(NdotH, mix(.1,.001,clearcoatGloss));
float Fr = mix(.04, 1.0, FH);
float Gr = smithG_GGX(NdotL, .25) * smithG_GGX(NdotV, .25);
// ...
}
对比
对比UE4和Disney的shading model,一个最直观的差异在于,UE4将各种不同类型的shading model分开处理了,每个shading model只包含相应的模型和参数,因此UE4的shading model很多,而且每个模型的参数比较精简,比如default lit就是标准的brdf,没有subsurface,没有cloth等等参数;而disney的Principled brdf就囊括了subsurface、cloth等一系列参数在内,一共11个参数,很冗余但是表现力强。
个人理解这些差异都是源于UE4和disney应用场景的不同,UE4希望每个模型尽可能高效,因此会拆分开来,针对性优化,比如它单独设计了针对眼睛的Eye模型,专门渲染毛发的Hair模型,专门渲染皮肤的subsurface模型等等。而Disney的诉求在于希望设计师充分发挥,因此他们的模型参数要尽可能易懂,方便设计师调试。
Reference
- UE4 Documentation
- Separable Subsurface Scattering
- A Microfacet-Based BRDF Generator
- Production Friendly Microfacet Sheen BRDF
- Filament文档
- Real-time Rendering, 4th edition
更新日志
2021.7.5 补充UE4 - Preintegrated Skin的介绍和代码。