Unity的PBR擴展(轉(zhuǎn))

Unity的PBR擴展

基于物理的渲染(PBR, Physically Based Rendering)采用了物理真實的光照模型,符合自然世界的直觀認知規(guī)律,近年來比較流行换况。

PBR可理解為是一套渲染標準醉者,標準化有利于簡化美術(shù)流程玛追,但是只使用標準PBR達不到具體項目的渲染需求,因為游戲風格差異化大多來自對渲染的自定義俱萍,比如卡通化PBR渲染:

異度之刃2的游戲截圖

上圖為NS主機游戲異度之刃2的游戲截圖端壳,角色臉部和頭發(fā)都是比較卡通的,其它部分的金屬質(zhì)感盔甲和場景也都是寫實風格的枪蘑。

異度之刃2在非真實建模的前提下损谦,使用真實渲染和非真實渲染結(jié)合岖免,配合后期調(diào)色,形成了游戲特有的卡渲風格照捡。游戲中角色臉部和頭發(fā)渲染這種具有PBR屬性颅湘,但又風格化的渲染就屬于擴展PBR。


一栗精、標準PBS

PBR的核心是PBS(Physically Based Shading)著色模型闯参,具體實現(xiàn)由各大引擎自己負責,Unity的PBS實現(xiàn)封裝為Standard悲立,Unreal 4(下稱UE4)中實現(xiàn)封裝為Default Lit知市。本文的擴展是在Unity的標準PBS的基礎(chǔ)上去修改和擴展渠抹,這里先剖析Unity的標準PBS的代碼實現(xiàn)。

1.1 BRDF

BRDF(雙向反射分布函數(shù))光照模型是PBS的重要組成部分系瓢,用于描述光在物體表面的反射情況竿奏。該模型基于微表面理論寺酪,認為光在物體表面反射的光量是物體表面的所有微小表面漫反射和鏡面反射光量的總和输涕,符合能量守恒:

1) 反射的光總量不大于入射的光總量盐数,且漫反射和鏡面反射是互斥關(guān)系;
2) 粗糙的表面反射的光線分散且暗姑隅,光滑的表面反射集中且亮写隶。

Unity的BRDF的內(nèi)部實現(xiàn)文件為UnityStandardBRDF.cginc,主要實現(xiàn)函數(shù)為BRDF?_Unity_PBS讲仰。Unity的BRDF實現(xiàn)按平臺分為3個檔次樟澜,這里討論的是針對Console/PC平臺,光照模型更加精確的第1檔實現(xiàn)BRDF1_Unity_PBS叮盘。

BRDF的漫反射部分為Disney漫反射模型,該計算模型基于表面粗糙度霹俺,主要實現(xiàn)代碼為:

 half nlPow5 = Pow5 (1-nl);
    half nvPow5 = Pow5 (1-nv);
    half Fd90 = 0.5 + 2 * lh * lh * roughness;
    half disneyDiffuse = (1 + (Fd90-1) * nlPow5) * (1 + (Fd90-1) * nvPow5); 

    half diffuseTerm = disneyDiffuse * nl; 

代碼中diffuseTerm 為計算得到的漫反射部分柔吼。

Disney漫反射模型與不考慮表面粗糙度的Lambert漫反射模型實際效果區(qū)別不大,所以在Unity的第2,3檔中diffuse計算用的是更簡單的Lambert模型丙唧。

BRDF的鏡面反射部分基于Torrance-Sparrow微表面模型愈魏,公式類似為:


主要實現(xiàn)代碼為:

#if UNITY_BRDF_GGX
    half V = SmithJointGGXVisibilityTerm (nl, nv, roughness);
    half D = GGXTerm (nh, roughness);
#else
    half V = SmithBeckmannVisibilityTerm (nl, nv, roughness);
    half D = NDFBlinnPhongNormalizedTerm (nh, RoughnessToSpecPower (roughness));
#endif 
F = FresnelTerm (specColor, lh);
half specularTerm = (V * D * F) * (UNITY_PI/4); 

代碼中SpecularTerm 為計算得到的鏡面反射部分,實現(xiàn)上基本遵守了Torrance-Sparrow的公式想际。
其中培漏,法線分布D和幾何衰減G按是否采用GGX計算模型會有些不同。這里附一張不同粗糙度的法線分布函數(shù)(NDF)曲線示意圖:


上圖中胡本,X軸為Half半角向量和表面Normal的夾角弧度牌柄,Y軸為NDF返回值,可看出Smoothness越高的函數(shù)曲線越陡峭侧甫,可解釋“粗糙的表面反射的光線分散且暗珊佣,光滑的表面反射集中且亮”能量守恒蹋宦。

公式中的Frensnel部分的代碼實現(xiàn)為:

inline half3 FresnelTerm (half3 F0, half cosA)
{
    half t = Pow5 (1 - cosA);    // ala Schlick interpoliation
    return F0 + (1-F0) * t;
} 
F = FresnelTerm (specColor, lh) 

FresnelTerm 的函數(shù)曲線符合之前《理論基礎(chǔ)》文章所示的Fresnel曲線:

其中,F(xiàn)resnelTerm 函數(shù)的第1個參數(shù)specColor對應(yīng)著示意圖中的Base Reflectivities咒锻。

接下來分析“Diffuse和Specular互斥”能量守恒冷冗。UnityStandardUtils.cginc文件包含了主要內(nèi)部實現(xiàn)代碼:

inline half OneMinusReflectivityFromMetallic(half metallic)
{
 // We'll need oneMinusReflectivity, so
 //   1-reflectivity = 1-lerp(dielectricSpec, 1, metallic) = lerp(1-dielectricSpec, 0, metallic)
 // store (1-dielectricSpec) in unity_ColorSpaceDielectricSpec.a, then
 //     1-reflectivity = lerp(alpha, 0, metallic) = alpha + metallic*(0 - alpha) = 
 //                  = alpha - metallic * alpha
    half oneMinusDielectricSpec = unity_ColorSpaceDielectricSpec.a;
    return oneMinusDielectricSpec - metallic * oneMinusDielectricSpec;
} 

inline half3 DiffuseAndSpecularFromMetallic (half3 albedo, half metallic, out half3 specColor, out half oneMinusReflectivity)
{
    specColor = lerp (unity_ColorSpaceDielectricSpec.rgb, albedo, metallic);
    oneMinusReflectivity = OneMinusReflectivityFromMetallic(metallic);
    return albedo * oneMinusReflectivity;
} 

代碼根據(jù)金屬度計算漫反射和鏡面反射比例,當Metallic為1時惑艇,反射率接近1蒿辙,函數(shù)返回的diffColor接近0,表示幾乎不反射漫反射滨巴。

Unity的內(nèi)置變量Unity_ColorSpaceDielectricSpec定義了絕緣體的高光顏色和反射率思灌,不完全為0,是一個經(jīng)驗值兢卵。

Unity還提供了Specular Setup工作流程來控制漫反射和鏡面反射比例习瑰。內(nèi)部實現(xiàn)代碼為:

// Diffuse/Spec Energy conservation
inline half3 EnergyConservationBetweenDiffuseAndSpecular (half3 albedo, half3 specColor, out half oneMinusReflectivity)
{
    oneMinusReflectivity = 1 - SpecularStrength(specColor);
    #if !UNITY_CONSERVE_ENERGY
        return albedo;
    #elif UNITY_CONSERVE_ENERGY_MONOCHROME
        return albedo * oneMinusReflectivity;
    #else
        return albedo * (half3(1,1,1) - specColor);
    #endif
}

代碼中用1減去鏡面反射比例,得到漫反射比例秽荤。當傳入的specColor為白色時甜奄,SpecularStrength返回1,結(jié)果漫反射比例為0窃款,發(fā)生完美鏡面反射课兄。

計算得到的diffColor和specColor作為比例系數(shù)用于最終漫反射和鏡面反射計算:

//   BRDF = kD / pi + kS * (D * V * F) / 4

代碼中的 kD和kS對應(yīng)著diffColor和specColor。

1.2 IBL

在材質(zhì)上反應(yīng)出周圍的環(huán)境也是PBS的重要組成部分晨继。在光照模型中一般把周圍的環(huán)境當作一個大的光源來對待烟阐,不過環(huán)境光不同于實時光,而是作為間接光(Indirect Light)通過IBL(Image Based Lighting)來實現(xiàn)紊扬。間接光計算也包含漫反射部分和鏡面反射部分蜒茄。

UnityGlobalIllumination.cginc文件包含了主要內(nèi)部實現(xiàn)代碼:
inline UnityGI UnityGI_Base(UnityGIInput data, half occlusion, half3 normalWorld)
{
    UnityGI o_gi;
    ResetUnityGI(o_gi);
    .....
    #if UNITY_SHOULD_SAMPLE_SH
        o_gi.indirect.diffuse = ShadeSHPerPixel (normalWorld, data.ambient, data.worldPos);
    #endif
    #if defined(LIGHTMAP_ON)
        // Baked lightmaps
        fixed4 bakedColorTex = UNITY_SAMPLE_TEX2D(unity_Lightmap, data.lightmapUV.xy);
        half3 bakedColor = DecodeLightmap(bakedColorTex);
        #ifdef DIRLIGHTMAP_COMBINED
            ......
        #elif DIRLIGHTMAP_SEPARATE
            .....
        #else // not directional lightmap
            o_gi.indirect.diffuse = bakedColor;
            ......
        #endif
    #endif
    #ifdef DYNAMICLIGHTMAP_ON
        ......
    #endif
    o_gi.indirect.diffuse *= occlusion;
    return o_gi;
}

Unity內(nèi)置了Unity_Lightmap、Unity_SHAr等全局變量餐屎,來從預(yù)先烘焙好的Lightmap貼圖或Light Probe中讀取顏色檀葛,其中UNITY_SHOULD_SAMPLE_SH代碼段處理的是從light probe中讀取顏色值。一般渲染時靜態(tài)物體讀取Lightmap腹缩,非靜態(tài)物體讀取Light Probe屿聋。

UnityGI_Base函數(shù)返回的顏色值為間接光的漫反射部分。

inline half3 UnityGI_IndirectSpecular(UnityGIInput data, half occlusion, half3 normalWorld, Unity_GlossyEnvironmentData glossIn)
{
    half3 specular;

    #if UNITY_SPECCUBE_BOX_PROJECTION
        // we will tweak reflUVW in glossIn directly (as we pass it to Unity_GlossyEnvironment twice), so keep original to pass into BoxProjectedCubemapDirection
        half3 originalReflUVW = glossIn.reflUVW;
    #endif

    #if UNITY_SPECCUBE_BOX_PROJECTION
        glossIn.reflUVW = BoxProjectedCubemapDirection (originalReflUVW, data.worldPos, data.probePosition[0], data.boxMin[0], data.boxMax[0]);
    #endif

    #ifdef _GLOSSYREFLECTIONS_OFF
        specular = unity_IndirectSpecColor.rgb;
    #else
        half3 env0 = Unity_GlossyEnvironment (UNITY_PASS_TEXCUBE(unity_SpecCube0), data.probeHDR[0], glossIn);
        #if UNITY_SPECCUBE_BLENDING
            ......
        #else
            specular = env0;
        #endif
    #endif

    return specular * occlusion;
}

Unity用Reflection Probe來保存預(yù)先烘焙好的環(huán)境光反射貼圖藏鹊,通過內(nèi)置變量Unity_SpecCube0润讥,Unity_SpecCube1訪問。

UnityGI_IndirectSpecular返回的顏色值為間接光的鏡面反射部分盘寡。

另外楚殿,“粗糙的表面反射的光線分散且暗,光滑的表面反射集中且亮”能量守恒在這里同樣被遵守宴抚,函數(shù)輸入?yún)?shù)包含粗糙度信息勒魔,用于環(huán)境光貼圖的LOD取值:

    half mip = roughness * UNITY_SPECCUBE_LOD_STEPS;
    half4 rgbm = UNITY_SAMPLE_TEXCUBE_LOD(tex, glossIn.reflUVW, mip);

表面越粗糙甫煞,用于采樣Mipmap貼圖的LOD值越高,UNITY_SAMPLE_TEXCUBE_LOD采樣的結(jié)果越模糊冠绢,反之亦然抚吠。

1.3 BRDF+IBL

正如光照計算公式中多個光源的強度是疊加關(guān)系,PBS模型光照計算的結(jié)果是實時光BRDF與間接光IBL之和弟胀。BRDF1_Unity_PBS函數(shù)最后的顏色返回值代碼:

  half grazingTerm = saturate(oneMinusRoughness + (1-oneMinusReflectivity));
    half3 color =   diffColor * (gi.diffuse + light.color * diffuseTerm)
                    + specularTerm * light.color * FresnelTerm (specColor, lh)
            + surfaceReduction * gi.specular * FresnelLerp (specColor, grazingTerm, nv);

附自定義Standard Shader得到的結(jié)果分解圖:



二楷力、擴展

擴展PBR的具體內(nèi)容是對標準PBS代碼進行修改和擴展。除了核心的光照計算之外還需要對標準PBS的其他代碼進行修改和擴展孵户,這里附一張Unity的Standard實現(xiàn)示意圖:

在此基礎(chǔ)上去擴展萧朝,對內(nèi)主要包括修改和擴展上圖中的數(shù)據(jù)結(jié)構(gòu)、光照模型和繪制過程夏哭。對外給用戶提供可選擇的Shading Model:

UE4的Shading Model和Unity擴展代碼文件

上圖左為UE4引擎的Shading Model检柬,右為本文在Unity中按UE4方式擴展實現(xiàn)的代碼文件列表,其中TT_Unity???.cginc文件為Unity內(nèi)部的Unity???.cginc文件的修改擴展版本竖配。

其中何址,TT_UnityStandardBRDF.cginc擴展了各個Shadering Model的實現(xiàn):

 half4 SKIN_BRDF_PBS(half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness,
  float3 normal, float3 viewDir,
  UnityLight light, UnityIndirect gi, half4 sssTex)
 {
  ...
 }
 half4 HAIR_BRDF_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness,
  float3 normal, float3 viewDir,
  UnityLight light, UnityIndirect gi, float3 tangentWorld = float3(0, 0, 1), half2 anisoCtrl = half2(1, 1))
 {
  ...
 }
 half4 CLEARCOAT_BRDF_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness,
  float3 normal, float3 viewDir,
  UnityLight light, UnityIndirect gi, float3 normal_clearcoat, UnityIndirect gi_clearcoat)
 {
  ... 
 }
 half4 FABRIC_BRDF_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness,
  float3 normal, float3 viewDir,
  UnityLight light, UnityIndirect gi)
 {
  ...
 }

TT_UnityStandardCore.cginc根據(jù)Shadering Model的類型選擇實現(xiàn):

 half4 fragForwardBaseInternal (VertexOutputForwardBase i)
 {
  ...
 #if _SKIN
  half4 c = SKIN_BRDF_PBS(s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect, sssTex);
 #elif _HAIR
  half4 c = HAIR_BRDF_PBS(s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect, s.tangentWorld, anisoMap.rg);
 #elif _CLEARCOAT
  half4 c = CLEARCOAT_BRDF_PBS(s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect, s.normalWorld_clearcoat, gi_clearcoat.indirect);
 #elif _FABRIC
  half4 c = FABRIC_BRDF_PBS(s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect);
 #else
  half4 c = UNITY_BRDF_PBS(s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect);
 #endif
  ...
 }
 ...
 half4 fragForwardAddInternal (VertexOutputForwardAdd i)
 {
  ...
 #if _SKIN
  half4 c = SKIN_BRDF_PBS (s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, light, noIndirect, sssTex);
 #elif _HAIR
  half4 c = HAIR_BRDF_PBS(s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, light, noIndirect);
 #elif _CLEARCOAT
  half4 c = CLEARCOAT_BRDF_PBS(s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, light, noIndirect, s.normalWorld_clearcoat, noIndirect);
 #elif _FABRIC
  half4 c = FABRIC_BRDF_PBS (s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, light, noIndirect);
 #else
  half4 c = UNITY_BRDF_PBS (s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, light, noIndirect);
 #endif
  ...
 }

具體的Shader文件去定義Shadering Model的類型,比如TT_Character_Skin.shader定義了_SKIN類型:

 SubShader
 {
  Tags{ "RenderType" = "Opaque" "PerformanceChecks" = "False" }
  LOD 300
  CGINCLUDE
  ...
  #define _SKIN 1
  ...
 }

Unity的原生內(nèi)置著色器cginc文件在類似目錄:C:\Program Files\Unity 2018.1.0b13\Editor\Data\CGIncludes\进胯,也可到官網(wǎng)下載Unity安裝版本對應(yīng)的內(nèi)置著色器代碼用爪。

下面對各個Shading Model的理論模型和具體實現(xiàn)依次大致介紹。

2.1 Subsurface

The Subsurface Shading Model simulates the effect of Subsurface Scattering. This is a real-world phenomenon in which light penetrates a surface and then diffuses throughout it. It can be most readily seen on such objects as ice, wax candles, and skin.
the final colour of our pixels depend is the sum of two components. The first one is the “traditional” lighting. The second one is the light contribution from a virtual light source illuminating the back of our model. This gives the impression that light from the original source actually passed through the material.

Subsurface理論模型

Subsurface模型實現(xiàn)核心代碼:

 half4 SKIN_BRDF_PBS(half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness,
  float3 normal, float3 viewDir,
  UnityLight light, UnityIndirect gi, half4 sssTex)
 {
  ...
  // Translucency
  float3 H = normalize(light.dir + normal * _Distortion);
  float transDot = pow(saturate(dot(viewDir, -H)), _Power) * thickness * _ThicknessScale;
  half3 lightScattering = transDot * _SubColor;
  ...
 }

Subsurface模型實現(xiàn)效果圖

2.2 Skin

The Preintegrated Skin Shading Model is very similar in nature to the Subsurface model, but geared toward low performance cost skin rendering on human characters.
the higher curvature on the nose creates stronger incident light gradient, Which will result in a lot more visible scattering.

皮膚的Ramp圖

Skin模型實現(xiàn)核心代碼:

 inline fixed4 SKIN_BRDF_PBS(half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness,
  float3 normal, float3 viewDir,
  UnityLight light, UnityIndirect gi, 
  half4 sssTex)
 {
  ...
  // Skin Lighting
  float2 brdfUV;
  // Half-Lambert lighting value based on blurred normals.
  brdfUV.x = dotNL * 0.5 + 0.5;
  brdfUV.y = curvature;
  // Curvature amount. Multiplied by light's luminosity so brighter light = more scattering. 
  half3 diffuseTerm = diffColor * light.color * tex2D( _BRDFTex, brdfUV ).rgb;
  ...

Skin模型實現(xiàn)效果圖

2.3 ClearCoat

The Clear Coat Shading Model can be used to better simulate multilayer materials that have a thin translucent layer of film over the surface of a standard material. In addition to this, the Clear Coat Shading Model can also be used with either a metal or nonmetal surfaces. In fact, it was specifically designed to model this second class of smooth colored films over a non-colored metal. Some examples include acrylic or lacquer clear coats, and colored films over metals such as soda cans and car paint.

Paint Layers示意圖

ClearCoat模型實現(xiàn)核心代碼:

half4 fragForwardBaseInternal (VertexOutputForwardBase i)
 {
  ...
 #if _CLEARCOAT
  FragmentCommonData s_clearcoat = (FragmentCommonData) 0;
  s_clearcoat.specColor = _ReflectionSpecular.rgb;
  s_clearcoat.smoothness = _ReflectionGlossiness;
  s_clearcoat.normalWorld = s.normalWorld_clearcoat;
  s_clearcoat.eyeVec = s.eyeVec;
  s_clearcoat.posWorld = s.posWorld;
  UnityGI gi_clearcoat = FragmentGI(s_clearcoat, occlusion, i.ambientOrLightmapUV, atten, mainLight);

  half4 c = CLEARCOAT_BRDF_PBS(s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect, s.normalWorld_clearcoat, gi_clearcoat.indirect);
 #endif 
  ...
 }
 half4 CLEARCOAT_BRDF_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness,
  float3 normal, float3 viewDir,
  UnityLight light, UnityIndirect gi, float3 normal_clearcoat, UnityIndirect gi_clearcoat)
 {
  half4 c = BRDF1_Unity_PBS(diffColor, specColor, oneMinusReflectivity, smoothness, normal, viewDir, light, gi);

  // SPECULAR & SMOOTHNES
  diffColor = 0;
  specColor = _ReflectionSpecular.rgb;
  smoothness = _ReflectionGlossiness;
  oneMinusReflectivity = 1 - SpecularStrength(specColor);
  c += BRDF1_Unity_PBS(diffColor, specColor, oneMinusReflectivity, smoothness, normal_clearcoat, viewDir, light, gi_clearcoat);

  return c;
 }

ClearCoat模型實現(xiàn)效果圖

2.4 Cloth

For fabrics, like black velvet, the most distinguishing features are due to rim lighting (both forward and backward scattering). If the light is in the same direction as the viewer then specular contributes most towards the edge of the object due to backscattering and how the fabric is constructed. Tiny fibers are attached to the surface so that they try to stand up straight. When the light and view direction are aligned the light will backscatter when the surface normal is 90 degrees from the light or view direction. Additionally, if the light is behind the objects the fibers will forward scatter light through giving a nice rim light effect.

![理想反射與實際織物的反射對比(https://upload-images.jianshu.io/upload_images/15536448-7dc7ccd743e96ef1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

Cloth模型實現(xiàn)核心代碼:

 inline float FabricD (float NdotH, float roughness)
 {
  return 0.96 * pow(1 - NdotH, 2) + 0.057; 
 }
 inline half FabricScatterFresnelLerp(half nv, half scale)
 {
  half t0 = Pow4 (1 - nv); 
  half t1 = 0.4 * (1 - nv);
  return (t1 - t0) * scale + t0;
 }
 half4 FABRIC_BRDF_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness,
  float3 normal, float3 viewDir,
  UnityLight light, UnityIndirect gi)
 {
  ...
  float D = FabricD (nh, roughness);
  ...
  half3 color = diffColor * (gi.diffuse + light.color * diffuseTerm)
  + specularTerm * light.color * FresnelTerm (specColor, lh)
  + _FabricScatterColor * (nl*0.5 + 0.5) * FabricScatterFresnelLerp(nv, _FabricScatterScale);
  ...

Cloth模型實現(xiàn)效果圖

2.5 Hair

我們?nèi)粘I钪杏泻芏辔矬w呈現(xiàn)各向異性反射效果胁镐。比如:拉絲金屬偎血,毛發(fā),光碟等盯漂。一般這種反射效果是由物體表面的微表面特性導(dǎo)致的:物體表面主要由大量的方向一致的細長劃痕或纖維微表面組成颇玷。 比如,拉絲金屬物件表面由大量平行的絲狀劃痕組成就缆;光碟的表面由一圈一圈的環(huán)形細小軌道(用于存放數(shù)據(jù))組成亚隙;頭發(fā)的表面由大量的頭發(fā)絲組成等。沿著這些劃痕或纖維的法線分布不同于通常的垂直于表面的法線分布违崇, 使得物體整體的光照反射表現(xiàn)呈現(xiàn)各向異性。

各項異性模型

Hair模型實現(xiàn)核心代碼:

inline half AnisoDCore(half smoothness, half3 normalWorld, half3 tangentWorld, half3 halfDir, half nh, half D, half gloss, half spec, half mask)
 {
  half3 Y = cross(normalWorld, tangentWorld);
  half RoughnessX = SmoothnessToRoughness(saturate(smoothness * gloss));
  RoughnessX += !RoughnessX * 1e-4f;
   half mx = RoughnessX * RoughnessX;
  half XdotH = dot(tangentWorld, halfDir);
  half YdotH = dot(Y, halfDir);
  half d = XdotH * XdotH / (mx * mx) + YdotH * YdotH + nh * nh;
  d += !d * 1e-4f;
  half Da = 1 / (UNITY_PI * mx * d * d);
  D = lerp(Da, D, mask);
  D *= lerp(spec, 1, mask);
  return D;
 }
 inline half3 JitterTangent(half3 T, half3 N, float shift)
 {
  half3 shiftedT = T + shift * N;
  return normalize(shiftedT);
 }
 inline half AnisoD(half smoothness, half3 normalWorld, half3 tangentWorld, half3 halfDir, half nh, half D, half2 anisoCtrl)
 {
  half jitter = anisoCtrl.r;
  half mask = anisoCtrl.g;
  half3 tangentWorld1 = JitterTangent(tangentWorld, normalWorld, 0 + _TangentShift1);
  half AnisoDLow = AnisoDCore(smoothness, normalWorld, tangentWorld1, halfDir, nh, D, _AnisoGloss1, _AnisoSpec1, mask);
  half3 tangentWorld2 = JitterTangent(tangentWorld, normalWorld, jitter + _TangentShift2);
  half AnisoDHigh = AnisoDCore(smoothness, normalWorld, tangentWorld2, halfDir, nh, D, _AnisoGloss2, _AnisoSpec2, mask); 
  return AnisoDLow + AnisoDHigh;
 }
 half4 HAIR_BRDF_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness,
  float3 normal, float3 viewDir,
  UnityLight light, UnityIndirect gi, float3 tangentWorld = float3(0, 0, 1), half2 anisoCtrl = half2(1, 1))
 {
  ...
   float D = GGXTerm(nh, roughness);
  D = AnisoD(smoothness, normal, tangentWorld, halfDir, nh, D, anisoCtrl);
  ...
 }

Hair模型實現(xiàn)效果圖

完整代碼見:https://lab.uwa4d.com/lab/5b69a13ed7f10a201f02360ad


三诊霹、Further

目前的代碼框架擴展實現(xiàn)了上面列舉的幾種常見的Shading Model羞延,未來的工作就是在這些Shading Model的基礎(chǔ)上去風格化,或者擴展新的Shading Model脾还。

另外伴箩,說明一下從源碼級別自定義PBR的意義:目前的材質(zhì)節(jié)點編輯器,比如Amplify Shader Editor鄙漏,生成的是Surface中間代碼嗤谚,內(nèi)部走的是Standard流程棺蛛;Shader Forge(目前已停更),生成的代碼調(diào)用的也是內(nèi)置的Standard接口巩步;Unity官方的SRP配套使用的Shader Graph只支持Standard材質(zhì)旁赊。

參考文獻:

https://marmoset.co/posts/physically-based-rendering-and-you-can-too
https://www.alanzucconi.com/2017/08/30/fast-subsurface-scattering-1
https://docs.unrealengine.com/en-us/Engine/Rendering/Materials/MaterialProperties/LightingModels
http://web.engr.oregonstate.edu/~mjb/cs519/Projects/Papers/HairRendering.pdf


這是侑虎科技第497篇文章,感謝作者Young供稿椅野。歡迎轉(zhuǎn)發(fā)分享终畅,未經(jīng)作者授權(quán)請勿轉(zhuǎn)載。如果您有任何獨到的見解或者發(fā)現(xiàn)也歡迎聯(lián)系我們竟闪,一起探討离福。(QQ群:793972859)

作者主頁:https://www.zhihu.com/people/chen-yong-59-86,作者也是U Sparkle活動參與者炼蛤,UWA歡迎更多開發(fā)朋友加入U Sparkle開發(fā)者計劃妖爷,這個舞臺有你更精彩!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末理朋,一起剝皮案震驚了整個濱河市絮识,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌暗挑,老刑警劉巖笋除,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異炸裆,居然都是意外死亡垃它,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進店門烹看,熙熙樓的掌柜王于貴愁眉苦臉地迎上來国拇,“玉大人,你說我怎么就攤上這事惯殊〗戳撸” “怎么了?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵土思,是天一觀的道長务热。 經(jīng)常有香客問我,道長己儒,這世上最難降的妖魔是什么崎岂? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮闪湾,結(jié)果婚禮上冲甘,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好江醇,可當我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布濒憋。 她就那樣靜靜地躺著,像睡著了一般陶夜。 火紅的嫁衣襯著肌膚如雪凛驮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天律适,我揣著相機與錄音辐烂,去河邊找鬼。 笑死捂贿,一個胖子當著我的面吹牛纠修,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播厂僧,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼扣草,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了颜屠?” 一聲冷哼從身側(cè)響起辰妙,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎甫窟,沒想到半個月后密浑,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡粗井,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年尔破,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片浇衬。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡懒构,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出耘擂,到底是詐尸還是另有隱情胆剧,我是刑警寧澤,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布醉冤,位于F島的核電站秩霍,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蚁阳。R本人自食惡果不足惜前域,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望韵吨。 院中可真熱鬧,春花似錦、人聲如沸归粉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽糠悼。三九已至届榄,卻和暖如春纷铣,著一層夾襖步出監(jiān)牢的瞬間窒篱,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工杉辙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留席噩,地道東北人班缰。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像悼枢,于是被迫代替她去往敵國和親埠忘。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,901評論 2 355

推薦閱讀更多精彩內(nèi)容