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

    科技2025-09-24  64

    接上文,本文主要介绍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:

    UnlitDefault LitSubsurfacePreintegrated SkinClear CoatSubsurface ProfileTwo Sided FoliageHairClothEyeSingleLayerWaterThin TranslucentFrom 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 ColorMetallicSpecularRoughnessEmissive ColorNormalAmbient 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 ColorMetallicSpecularRoughnessEmissive ColorOpacityNormalSubsurface ColorAmbient 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在皮肤染情况下的特化,效果类似但是效率更高。参数与Subsurface model一致,不再列举。

    Clear Coat

    Clear coat模型包括以下参数。

    Base ColorMetallicSpecularRoughnessEmissive ColorNormalAmbient OcclusionClear CoatClear 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 ColorMetallicSpecularRoughnessEmissive ColorNormalAmbient OcclusionSubsurface 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 ColorScatterSpecularRoughnessEmissive ColorTangentAmbient OcclusionBacklit

    具体实现过于复杂,不在这里展开叙述。

    Cloth

    Cloth模型添加了Cloth和Fuzz Color参数,用于控制高光的程度和颜色:

    Base ColorMetallicSpecularRoughnessEmissive ColorOpacityNormalFuzz ColorClothAmbient 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 ColorMetallicSpecularRoughnessEmissive ColorOpacityNormalAmbient OcclusionIris MaskIris Distance

    具体的实现比较复杂,也不再展开。

    others

    上面列出的9种shading model,都有对应的BxDF公式。除此之外,还有其他的一些简化模型,这些模型没有单独的BxDF算法,但是也有比较重要的应用场景,因此也被归为shading model。

    Unlit

    Unlit model严格来说不是对入射光线的映射函数,它只有一个参数——

    emissive color

    即自发光。通常它是用来展示火焰或者发光体的特效的模型。

    SingleLayerWater

    SingleLayerWater模型用于模拟透明水面的效果,降低使用透明模式混合的开销和复杂度。SingleLayerWater模型与Default Lit模型公用同一套BxDF公式,它的参数比Default Lit多两个:

    OpacityRefraction
    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

    UE4 DocumentationSeparable Subsurface ScatteringA Microfacet-Based BRDF GeneratorProduction Friendly Microfacet Sheen BRDFFilament文档Real-time Rendering, 4th edition
    Processed: 0.009, SQL: 8