着色模型简介和实现(下)

上文,本文主要介绍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组合。

UE4-shadingmodel

shading model的设置在Material里(如上图),shader层面的控制在文件Engine\Shaders\Private\ShadingModels.ushIntegrateBxDF函数里:

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做了一些简化,具体包括:

  1. 只在Shadow域进行了预积分,Diffuse和Specular都采用了DefaultLit,也就是跟普通渲染没啥区别
  2. UE4的Preintegrated Texture是一张灰度图,具体的用法是用NoL和Opacity(实际是1-Opacity)对它进行采样,然后乘上Subsurface Color
  3. 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模型添加了ClothFuzz 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(nl+nv10.5)+0.5)=FSchlick(n,l,1,f90)FSchlick(n,v,1,f90)=f0+(f90f0)(1(nl))5=roughness(nh)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

  1. UE4 Documentation
  2. Separable Subsurface Scattering
  3. A Microfacet-Based BRDF Generator
  4. Production Friendly Microfacet Sheen BRDF
  5. Filament文档
  6. Real-time Rendering, 4th edition

更新日志

2021.7.5 补充UE4 - Preintegrated Skin的介绍和代码。

  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值