Unity 内置Standard Shader UNITY_BRDF_PBS函数分析

一、UNITY_BRDF_PBS

改定义在UnityPBSLighting.cginc文件里

// 默认使用的 BRDF 函数:
// 如果没有在自定义 Shader 中显式定义 UNITY_BRDF_PBS,则执行下面的逻辑自动选择
#if !defined (UNITY_BRDF_PBS) // 如果未定义 UNITY_BRDF_PBS,则允许自定义 Shader 显式覆盖 BRDF

    // 为低版本着色器模型增加安全保护,否则可能导致 Shader 编译失败
    #if SHADER_TARGET < 30 || defined(SHADER_TARGET_SURFACE_ANALYSIS) 
        // 对于 Shader 目标低于 3.0 或者处于 Surface Shader 分析阶段(只需要一个简单的 BRDF),选择性能较低但“便宜”的 BRDF 实现
        #define UNITY_BRDF_PBS BRDF3_Unity_PBS

    #elif defined(UNITY_PBS_USE_BRDF3)
        // 如果定义了 UNITY_PBS_USE_BRDF3,则使用 BRDF3_Unity_PBS 实现
        #define UNITY_BRDF_PBS BRDF3_Unity_PBS

    #elif defined(UNITY_PBS_USE_BRDF2)
        // 如果定义了 UNITY_PBS_USE_BRDF2,则使用 BRDF2_Unity_PBS 实现
        #define UNITY_BRDF_PBS BRDF2_Unity_PBS

    #elif defined(UNITY_PBS_USE_BRDF1)
        // 如果定义了 UNITY_PBS_USE_BRDF1,则使用 BRDF1_Unity_PBS 实现
        #define UNITY_BRDF_PBS BRDF1_Unity_PBS

    #else
        // 如果没有匹配到任何预期的 BRDF 选择宏,则报错,提示自动选择 BRDF 时出错
        #error something broke in auto-choosing BRDF
    #endif

#endif

BRDF1_Unity_PBSBRDF2_Unity_PBSBRDF3_Unity_PBS都在UnityStandardBRDF.cginc里

二、BRDF3_Unity_PBS

// 使用传统(非微面模型)的修改版归一化 Blinn-Phong BRDF(基于RDF形式)
// 该实现使用查找纹理以提高性能
// 特点:
//   * 归一化 Blinn-Phong BRDF(RDF形式)
//   * 隐含了可见性项
//   * 没有 Fresnel 项(注:TODO:在线性渲染模式下镜面反射略弱)
half4 BRDF3_Unity_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness,
    float3 normal, float3 viewDir,
    UnityLight light, UnityIndirect gi)
{
    
    
    // 计算反射方向:基于视线向量 viewDir 和表面法线 normal
    float3 reflDir = reflect(viewDir, normal);

    // 计算法线与光源方向之间的点积,保证结果在 [0,1] 范围内
    half nl = saturate(dot(normal, light.dir));
    // 计算法线与视线方向之间的点积,同样归一化到 [0,1]
    half nv = saturate(dot(normal, viewDir));

    // 使用 Pow4 函数向量化计算,以节省指令数量
    // 输入参数为一个 float2,其中:
    //   - 第一个分量为反射向量与光源方向的点积 dot(reflDir, light.dir)
    //   - 第二个分量为 (1 - nv)
    // Pow4 函数返回一个 half2,其中:
    //   - x 分量为点积值的 4 次方(用作镜面高光的幂指数,要求与 GeneratedTextures.cpp 中 NHxRoughness() 的 kHorizontalWarpExp 相匹配)
    //   - y 分量为 (1 - nv) 的 4 次方,作为 Fresnel 近似项(这里没有显式使用 Fresnel,而是以这种方式保存额外信息)
    half2 rlPow4AndFresnelTerm = Pow4(float2(dot(reflDir, light.dir), 1 - nv));
    half rlPow4 = rlPow4AndFresnelTerm.x; // 镜面高光的指数部分
    half fresnelTerm = rlPow4AndFresnelTerm.y; // Fresnel项(虽然在此实现中并未用于额外计算 Fresnel 效果)

    // 计算 grazing term,用于处理视角较低时的边缘高光效果
    // 计算方法为:将 smoothness 与 (1 - oneMinusReflectivity) 相加,并进行饱和处理,确保结果在 [0,1]
    half grazingTerm = saturate(smoothness + (1 - oneMinusReflectivity));

    // 计算直接光照贡献
    // BRDF3_Direct 根据漫反射色、镜面反射色、rlPow4(镜面指数)和 smoothness 计算直接光照下的颜色贡献
    half3 color = BRDF3_Direct(diffColor, specColor, rlPow4, smoothness);
    // 将直接光照颜色乘以光源颜色和 nl(法线与光方向点积),得到最终直接光照效果
    color *= light.color * nl;
    // 计算间接光照贡献,并将其加入到最终颜色中
    // BRDF3_Indirect 根据漫反射、镜面反射、全局光照 gi,以及 grazingTerm 和 fresnelTerm 计算间接光照
    color += BRDF3_Indirect(diffColor, specColor, gi, grazingTerm, fresnelTerm);

    // 返回最终颜色,alpha 固定为 1(完全不透明)
    return half4(color, 1);
}

1.SmoothnessToPerceptualRoughness


float SmoothnessToPerceptualRoughness(float smoothness)
{
    
    
    return (1 - smoothness);
}

2.BRDF3_Direct

// 采样器,用于查找包含法线与粗糙度信息的查找纹理(Lookup Texture)
// 该纹理预先生成,存储了基于切线-粗糙度(NHxRoughness)转换函数中的数据
sampler2D_float unity_NHxRoughness;

// BRDF3_Direct 函数计算直接光照贡献部分
// 参数说明:
//   diffColor:漫反射颜色
//   specColor:镜面反射颜色
//   rlPow4:反射方向与光源方向的点积经过 4 次方运算后的值(用于控制高光强度)
//   smoothness:表面光滑度
half3 BRDF3_Direct(half3 diffColor, half3 specColor, half rlPow4, half smoothness)
{
    
    
    // LUT_RANGE 为查找纹理中数据的有效范围,该值必须与 GeneratedTextures.cpp 中 NHxRoughness() 函数保持一致
    half LUT_RANGE = 16.0;

    // 使用查找纹理来加速 BRDF 的计算,减少指令数量
    // 将 rlPow4(高光项)和光滑度转换为感知粗糙度后构成纹理采样坐标
    // SmoothnessToPerceptualRoughness(smoothness) 函数将光滑度转换为粗糙度(感知上更加合理的数值)
    // tex2D 返回采样结果,其中 .r 分量存储了预计算的 specular 值,然后乘以 LUT_RANGE 进行缩放
    half specular = tex2D(unity_NHxRoughness, half2(rlPow4, SmoothnessToPerceptualRoughness(smoothness))).r * LUT_RANGE;
    
    // 如果定义了 _SPECULARHIGHLIGHTS_OFF,则禁用镜面高光效果
    #if defined(_SPECULARHIGHLIGHTS_OFF)
        specular = 0.0;
    #endif

    // 返回直接光照颜色:
    // 漫反射部分 diffColor,加上镜面高光部分(specular * specColor)
    return diffColor + specular * specColor;
}

3.BRDF3_Indirect

// BRDF3_Indirect 函数计算间接光照贡献部分
// 参数说明:
//   diffColor:漫反射颜色
//   specColor:镜面反射颜色
//   indirect:包含间接光照信息的结构体(如全局环境光照和间接镜面反射)
//   grazingTerm:边缘高光项,用于处理低视角下的额外反射
//   fresnelTerm:Fresnel 近似项,用于描述视角依赖的镜面反射强度变化
half3 BRDF3_Indirect(half3 diffColor, half3 specColor, UnityIndirect indirect, half grazingTerm, half fresnelTerm)
{
    
    
    // 计算漫反射间接光照贡献:间接漫反射光乘以漫反射颜色
    half3 c = indirect.diffuse * diffColor;
    // 计算间接镜面反射贡献:
    // 使用 lerp(线性插值)在 specColor 和 grazingTerm 之间插值,插值系数为 fresnelTerm,
    // 以模拟视角对反射颜色的影响,然后乘以间接镜面光
    c += indirect.specular * lerp(specColor, grazingTerm, fresnelTerm);
    return c;
}

三、BRDF2_Unity_PBS

// 基于 Minimalist Cook-Torrance BRDF 的实现
// 此实现与原始推导略有不同,参考:http://www.thetenthplanet.de/archives/255
//
// 主要特点:
// * NDF(法线分布函数)根据 UNITY_BRDF_GGX 分为两种:
//    a) BlinnPhong
//    b) [Modified] GGX
// * 使用 Modified Kelemen 和 Szirmay-Kalos 方法近似计算 Visibility(可见性)项
// * Fresnel 使用 1/(L·H) 近似(LdotH)
//
// 参数说明:
// diffColor:漫反射颜色
// specColor:镜面反射颜色
// oneMinusReflectivity:反射率的补值(1 - 反射率)
// smoothness:表面光滑度
// normal:表面法线
// viewDir:视线方向(从片元指向摄像机的方向)
// light:主光源结构体(包含方向、颜色等信息)
// gi:间接光照信息(全局光照、环境光、间接镜面光等)
half4 BRDF2_Unity_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness,
    float3 normal, float3 viewDir,
    UnityLight light, UnityIndirect gi)
{
    
    
    // 计算半向量:将光源方向和视线方向相加后归一化,得到半向量(H)
    float3 halfDir = Unity_SafeNormalize(float3(light.dir) + viewDir);

    // 计算法线与光源方向的点积(N·L),并饱和到 [0,1]
    half nl = saturate(dot(normal, light.dir));
    // 计算法线与半向量的点积(N·H),用于NDF计算,饱和到 [0,1]
    float nh = saturate(dot(normal, halfDir));
    // 计算法线与视线方向的点积(N·V),饱和到 [0,1]
    half nv = saturate(dot(normal, viewDir));
    // 计算光源方向与半向量的点积(L·H),用于 Fresnel 近似,饱和到 [0,1]
    float lh = saturate(dot(light.dir, halfDir));

    // ------------------- 计算 Specular 项 -------------------

    // 将 smoothness 转换为感知粗糙度(Perceptual Roughness),再转换为实际粗糙度
    half perceptualRoughness = SmoothnessToPerceptualRoughness(smoothness);
    half roughness = PerceptualRoughnessToRoughness(perceptualRoughness);

#if UNITY_BRDF_GGX
    // 如果使用 GGX 模型计算 NDF(法线分布函数),同时结合近似的 Visibility 与 Fresnel
    // 参考 Siggraph 2015 移动图形课程 “Optimizing PBR for Mobile”
    float a = roughness;
    float a2 = a * a;

    // 计算 modified GGX 分布项:
    // d = nh^2 * (a2 - 1) + 1.00001
    // 注意这里加入 1.00001 以避免除零问题
    float d = nh * nh * (a2 - 1.f) + 1.00001f;
#ifdef UNITY_COLORSPACE_GAMMA
    // 在 Gamma 颜色空间下采用更严格的近似(优化 Gamma 渲染模式下的效果)
    // specularTerm = a / (max(sqrt(0.1), lh) * (1.5 + roughness) * d)
    float specularTerm = a / (max(0.32f, lh) * (1.5f + roughness) * d);
#else
    // 在线性空间下的计算
    // specularTerm = a2 / (max(0.1, lh^2) * (roughness + 0.5) * (d*d) * 4)
    float specularTerm = a2 / (max(0.1f, lh * lh) * (roughness + 0.5f) * (d * d) * 4);
#endif

    // 针对移动平台,防止 FP16 溢出:在移动平台上减去一个极小值
#ifdef SHADER_API_MOBILE
    specularTerm = specularTerm - 1e-4f;
#endif

#else
    // Legacy(旧版实现)
    // 计算 specularPower,利用感知粗糙度转换为镜面高光指数
    half specularPower = PerceptualRoughnessToSpecPower(perceptualRoughness);
    // 修改版 Visibility 函数:
    // 原始公式 ((n+1)*N·H^n) / (8*π * L·H^3) 没有考虑粗糙度,会在低视角下产生过亮效果
    // 这里使用 Modified Kelemen Visibility 近似:计算一个近似倒数项 invV 和 invF
    half invV = lh * lh * smoothness + perceptualRoughness * perceptualRoughness; // 近似计算 Visibility
    half invF = lh;

    // specularTerm = ((specularPower + 1) * pow(nh, specularPower)) / (8 * invV * invF + 1e-4)
    half specularTerm = ((specularPower + 1) * pow(nh, specularPower)) / (8 * invV * invF + 1e-4h);

#ifdef UNITY_COLORSPACE_GAMMA
    // 在 Gamma 空间下,对 specularTerm 做平方根处理以调整亮度
    specularTerm = sqrt(max(1e-4f, specularTerm));
#endif

#endif

// 对于移动平台,clamp 防止 FP16 溢出
#if defined(SHADER_API_MOBILE)
    specularTerm = clamp(specularTerm, 0.0, 100.0);
#endif
#if defined(_SPECULARHIGHLIGHTS_OFF)
    // 如果禁用了 specular highlights,则 specularTerm 置为 0
    specularTerm = 0.0;
#endif

// ------------------- 计算其他项 -------------------

// 计算 surfaceReduction,用于调整间接光照中的 specular 部分
// 公式中,1 - x^3 近似于 1/(x^4+1)^(1/2.2)(适用于 [0,1] 区间)
// 在 Gamma 空间下取常数 0.28,否则取 0.6-0.08*perceptualRoughness 作为系数
#ifdef UNITY_COLORSPACE_GAMMA
    half surfaceReduction = 0.28;
#else
    half surfaceReduction = (0.6 - 0.08 * perceptualRoughness);
#endif

	// 根据粗糙度和感知粗糙度计算最终的 surfaceReduction
	surfaceReduction = 1.0 - roughness * perceptualRoughness * surfaceReduction;
	
	// 计算 grazing term(边缘高光项),用于模拟低视角下额外的高光反射
	half grazingTerm = saturate(smoothness + (1 - oneMinusReflectivity));
	
	// ------------------- 综合计算最终颜色 -------------------
	
	// 直接光照部分:diffColor + specularTerm * specColor,乘以光源颜色和 nl(N·L)
	half3 color = (diffColor + specularTerm * specColor) * light.color * nl
	                // 加上间接漫反射光照贡献
	                + gi.diffuse * diffColor
	                // 加上间接镜面反射贡献:
	                // 利用 FresnelLerpFast 函数在 specColor 与 grazingTerm 之间插值(依据 nv),再乘以 gi.specular 和 surfaceReduction
	                + surfaceReduction * gi.specular * FresnelLerpFast(specColor, grazingTerm, nv);
	
	return half4(color, 1); // 返回最终颜色,alpha 固定为 1
}

1.SmoothnessToPerceptualRoughness

float SmoothnessToPerceptualRoughness(float smoothness)
{
    
    
    return (1 - smoothness);
}

2.PerceptualRoughnessToRoughness

float PerceptualRoughnessToRoughness(float perceptualRoughness)
{
    
    
    return perceptualRoughness * perceptualRoughness;
}

2.FresnelLerpFast

// approximage Schlick with ^4 instead of ^5
inline half3 FresnelLerpFast (half3 F0, half3 F90, half cosA)
{
    
    
    half t = Pow4 (1 - cosA);
    return lerp (F0, F90, t);
}

四、BRDF1_Unity_PBS

// 主物理基BRDF实现
// 基于Disney工作并以Torrance-Sparrow微面模型为基础
// 公式:
//   BRDF = kD / π + kS * (D * V * F) / 4
//   I = BRDF * (N · L)
// 
// * NDF(法线分布函数)可根据 UNITY_BRDF_GGX 选择:
//    a) 归一化 BlinnPhong
//    b) GGX
// * 可见性项采用Smith模型
// * Fresnel采用Schlick近似
half4 BRDF1_Unity_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness,
    float3 normal, float3 viewDir,
    UnityLight light, UnityIndirect gi)
{
    
    
    // 将smoothness转换为感知粗糙度
    float perceptualRoughness = SmoothnessToPerceptualRoughness(smoothness);
    // 计算半向量 H = normalize(light.dir + viewDir)
    float3 halfDir = Unity_SafeNormalize(float3(light.dir) + viewDir);

    // 处理 N·V(法线与视线的点积)负值问题
    // 理论上对于可见像素 NdotV 不应该为负,但透视投影和法线贴图可能会导致负值
    // 如果为负,应调整法线使其面向摄像机,以免产生异常,但这会增加少量ALU开销
    // 可通过宏 UNITY_HANDLE_CORRECTLY_NEGATIVE_NDOTV 控制是否进行校正,默认禁用(0)
    #define UNITY_HANDLE_CORRECTLY_NEGATIVE_NDOTV 0

#if UNITY_HANDLE_CORRECTLY_NEGATIVE_NDOTV
    // 根据 dot(normal, viewDir) 计算偏移量,当小于0时将法线向视线方向平移
    half shiftAmount = dot(normal, viewDir);
    normal = shiftAmount < 0.0f ? normal + viewDir * (-shiftAmount + 1e-5f) : normal;
    // 注意:理论上应对normal重新归一化,但为了节省ALU开销,此处略去
    float nv = saturate(dot(normal, viewDir)); // TODO: 可能不需要saturate
#else
    // 简单采用绝对值处理,虽然不完全正确但能抑制伪影
    half nv = abs(dot(normal, viewDir));
#endif

    // 计算法线与光源方向的点积 N·L,饱和到 [0,1]
    float nl = saturate(dot(normal, light.dir));
    // 计算法线与半向量的点积 N·H,饱和到 [0,1]
    float nh = saturate(dot(normal, halfDir));

    // 计算光源方向与视线之间的点积 L·V,饱和到 [0,1]
    half lv = saturate(dot(light.dir, viewDir));
    // 计算光源方向与半向量之间的点积 L·H,饱和到 [0,1]
    half lh = saturate(dot(light.dir, halfDir));

    // ----------------- 漫反射项 -----------------
    // 使用DisneyDiffuse函数计算漫反射项,传入 nv, nl, lh 和感知粗糙度
    // 最后乘以 nl 模拟光照强度随光线入射角衰减
    half diffuseTerm = DisneyDiffuse(nv, nl, lh, perceptualRoughness) * nl;

    // ----------------- 镜面反射项 -----------------
    // 注意:理论上应将 diffuseTerm 除以π,但为了与Legacy Shader一致并考虑非重要光源的处理,采用乘法
    // 将感知粗糙度转换为实际粗糙度
    float roughness = PerceptualRoughnessToRoughness(perceptualRoughness);
#if UNITY_BRDF_GGX
    // 对于GGX分支:为了防止粗糙度为0导致无镜面反射,取 roughness 的最小值0.002
    roughness = max(roughness, 0.002);
    // 计算可见性项 V 使用 SmithJointGGXVisibilityTerm,输入 N·L、N·V和粗糙度
    float V = SmithJointGGXVisibilityTerm(nl, nv, roughness);
    // 计算法线分布函数 D 使用 GGXTerm,输入 N·H 和粗糙度
    float D = GGXTerm(nh, roughness);
#else
    // Legacy 分支:采用SmithBeckmannVisibilityTerm和归一化 BlinnPhong NDF计算
    half V = SmithBeckmannVisibilityTerm(nl, nv, roughness);
    half D = NDFBlinnPhongNormalizedTerm(nh, PerceptualRoughnessToSpecPower(perceptualRoughness));
#endif

    // 根据 Torrance-Sparrow 模型,镜面反射项 = V * D * π
    float specularTerm = V * D * UNITY_PI;

#   ifdef UNITY_COLORSPACE_GAMMA
        // 如果处于Gamma空间下,对 specularTerm 取平方根以调整亮度
        specularTerm = sqrt(max(1e-4h, specularTerm));
#   endif

    // 为防止在Metal平台上 specularTerm * nl 出现 NaN,取最大值确保合理性
    specularTerm = max(0, specularTerm * nl);
#if defined(_SPECULARHIGHLIGHTS_OFF)
    // 如果禁用了 specular highlights,则将 specularTerm 置为0
    specularTerm = 0.0;
#endif

    // ----------------- 表面缩减因子 -----------------
    // surfaceReduction = 1 / (roughness^2 + 1)
    // 在 Gamma 空间下,用 1 - 0.28 * roughness * perceptualRoughness 作为近似
#   ifdef UNITY_COLORSPACE_GAMMA
        half surfaceReduction = 1.0 - 0.28 * roughness * perceptualRoughness;      // 近似 1-0.28*x^3
#   else
        half surfaceReduction = 1.0 / (roughness * roughness + 1.0);                 // 使数值在 [0.5,1] 范围内渐变
#   endif

    // 为了获得真正的Lambert光照,需要能够完全消除 specular 部分
    // 如果 specColor 全为0,则 specularTerm 乘以0
    specularTerm *= any(specColor) ? 1.0 : 0.0;

    // ----------------- 边缘高光项 -----------------
    // grazingTerm 用于模拟在低视角(接近边缘)下镜面反射的增强效果
    half grazingTerm = saturate(smoothness + (1 - oneMinusReflectivity));

    // ----------------- 综合颜色计算 -----------------
    // 最终颜色由三部分构成:
    // 1. 漫反射部分:diffColor 乘以 (间接漫反射 gi.diffuse + 主光 diffuseTerm * light.color)
    // 2. 镜面反射部分:specularTerm 乘以 light.color 和经过 FresnelTerm 调整后的 specColor
    // 3. 间接镜面反射部分:surfaceReduction 乘以 gi.specular 和经过 FresnelLerp 插值的 specColor(基于 grazingTerm 和 nv)
    half3 color = diffColor * (gi.diffuse + light.color * diffuseTerm)
                   + specularTerm * light.color * FresnelTerm(specColor, lh)
                   + surfaceReduction * gi.specular * FresnelLerp(specColor, grazingTerm, nv);

    // 返回最终颜色,alpha 固定为1(完全不透明)
    return half4(color, 1);
}

1.SmoothnessToPerceptualRoughness

float SmoothnessToPerceptualRoughness(float smoothness)
{
    
    
    return (1 - smoothness);
}

2.DisneyDiffuse

// 注意:Disney Diffuse 的结果必须在外部乘以 (diffuseAlbedo / π)
// 这个函数仅计算用于漫反射的散射系数
half DisneyDiffuse(half NdotV, half NdotL, half LdotH, half perceptualRoughness)
{
    
    
    // 计算 fd90,代表当视角或光线与法线呈90度时的散射因子
    // fd90 = 0.5 + 2 * (LdotH)^2 * perceptualRoughness
    // 这里 LdotH 越大(光与半向量接近),fd90 越大;同时粗糙度越大,fd90 越高
    half fd90 = 0.5 + 2 * LdotH * LdotH * perceptualRoughness;

    // 下面使用 Schlick 近似计算 Fresnel 散射项
    // 对于光线方向(light scatter):
    // 计算公式:1 + (fd90 - 1) * Pow5(1 - NdotL)
    // 当 NdotL 接近 1 时,Pow5(1 - NdotL) 约等于 0,lightScatter 接近 1
    // 当 NdotL 接近 0 时,Pow5(1 - NdotL) 接近 1,lightScatter 接近 fd90
    half lightScatter = (1 + (fd90 - 1) * Pow5(1 - NdotL));

    // 对于视线方向(view scatter):
    // 计算公式:1 + (fd90 - 1) * Pow5(1 - NdotV)
    // 同理,当 NdotV 较大时,viewScatter 约等于 1;当 NdotV 较小时,viewScatter 约等于 fd90
    half viewScatter = (1 + (fd90 - 1) * Pow5(1 - NdotV));

    // 最终返回的散射系数为两个方向散射项的乘积
    // 这个结果反映了漫反射部分对光照散射的综合响应
    return lightScatter * viewScatter;
}

3.PerceptualRoughnessToRoughness

float PerceptualRoughnessToRoughness(float perceptualRoughness)
{
    
    
    return perceptualRoughness * perceptualRoughness;
}

4.SmithJointGGXVisibilityTerm

// 参考文献: http://jcgt.org/published/0003/02/03/paper.pdf
inline float SmithJointGGXVisibilityTerm(float NdotL, float NdotV, float roughness)
{
    
    
#if 0
    // 原始公式:
    // lambda_v = (-1 + sqrt(a2 * (1 - NdotL2) / NdotL2 + 1)) * 0.5f;
    // lambda_l = (-1 + sqrt(a2 * (1 - NdotV2) / NdotV2 + 1)) * 0.5f;
    // G = 1 / (1 + lambda_v + lambda_l);

    // 重新排序代码以提高效率
    half a = roughness;  // 粗糙度参数
    half a2 = a * a;     // 粗糙度平方

    // 计算视角方向和光源方向的几何阴影因子
    half lambdaV = NdotL * sqrt((-NdotV * a2 + NdotV) * NdotV + a2);
    half lambdaL = NdotV * sqrt((-NdotL * a2 + NdotL) * NdotL + a2);

    // 可见性项简化为:(2.0f * NdotL * NdotV) / ((4.0f * NdotL * NdotV) * (lambda_v + lambda_l + 1e-5f));
    // 返回值,注意这里的epsilon是为了避免除零错误,由于不在移动设备上运行,所以可以使用较小的epsilon值
    return 0.5f / (lambdaV + lambdaL + 1e-5f);  
#else
    // 上述公式的近似形式(简化了sqrt计算,虽然不完全数学准确但足够接近)
    float a = roughness;  // 粗糙度参数
    // 近似计算视角方向的几何阴影因子
    float lambdaV = NdotL * (NdotV * (1 - a) + a);
    // 近似计算光源方向的几何阴影因子
    float lambdaL = NdotV * (NdotL * (1 - a) + a);

    // 根据不同的Shader API选择合适的epsilon值
#if defined(SHADER_API_SWITCH)
    // 如果是Switch平台,则使用UNITY_HALF_MIN作为epsilon值
    return 0.5f / (lambdaV + lambdaL + UNITY_HALF_MIN);
#else
    // 其他平台使用1e-5f作为epsilon值,防止除零错误
    return 0.5f / (lambdaV + lambdaL + 1e-5f);
#endif

#endif
}

5.GGXTerm

inline float GGXTerm(float NdotH, float roughness)
{
    
    
    // 粗糙度的平方
    float a2 = roughness * roughness;

    // 计算GGX分布函数中的分母部分
    // 这里使用了两个乘加运算(MAD: Multiply-Add)
    // d = (NdotH * a2 - NdotH) * NdotH + 1.0f;
    // 其中NdotH是法线与半角向量的点积,a2是粗糙度的平方
    float d = (NdotH * a2 - NdotH) * NdotH + 1.0f;

    // 返回GGX分布函数的结果
    // UNITY_INV_PI是一个预定义的常数,等于1/π,用于归一化
    // 分母加上一个很小的值(epsilon),防止除零错误
    // 注意:此函数不适合在移动设备上运行,因此这里的epsilon值比half类型能表示的最小值还要小
    return UNITY_INV_PI * a2 / (d * d + 1e-7f);
}

6.SmithBeckmannVisibilityTerm

// 基于 Smith-Schlick 模型推导出的用于 Beckmann 分布的可见性项
inline half SmithBeckmannVisibilityTerm(half NdotL, half NdotV, half roughness)
{
    
    
    // 常数 c = sqrt(2 / Pi),用于后续计算
    // 这个常数是从 Beckmann 分布推导出来的,用于调整粗糙度参数
    half c = 0.797884560802865h; // 精确值为 sqrt(2 / Pi)

    // 计算 k,k 是一个调整后的粗糙度参数
    // 使用 c 对原始粗糙度进行缩放,使得其适应 Beckmann 分布
    half k = roughness * c;

    // 调用 SmithVisibilityTerm 函数计算可见性项,并乘以 0.25f
    // 这里的 0.25f 是因为 Smith 可见性项通常需要除以 4 来归一化
    return SmithVisibilityTerm(NdotL, NdotV, k) * 0.25f;
}

7.SmithVisibilityTerm

// 通用的 Smith-Schlick 可见性项
inline half SmithVisibilityTerm(half NdotL, half NdotV, half k)
{
    
    
    // 计算视角方向的几何阴影因子 gL
    // 公式: gL = NdotL * (1 - k) + k
    // 其中 NdotL 是法线与光源方向的点积,k 是一个调整参数,通常与粗糙度相关
    half gL = NdotL * (1 - k) + k;

    // 计算光源方向的几何阴影因子 gV
    // 公式: gV = NdotV * (1 - k) + k
    // 其中 NdotV 是法线与视角方向的点积,k 同样是一个调整参数
    half gV = NdotV * (1 - k) + k;

    // 返回可见性项的倒数,加上一个小的 epsilon 值以防止除零错误
    // 这里的 epsilon 值较小,因为此函数不适合在移动设备上运行
    return 1.0 / (gL * gV + 1e-5f);
}

8.NDFBlinnPhongNormalizedTerm

// 使用归一化的 Blinn-Phong 作为法线分布函数 (NDF)
// 在微表面模型中的使用:spec = D * G * F
// 参考文献: https://dl.dropboxusercontent.com/u/55891920/papers/mm_brdf.pdf 中的公式 19
inline half NDFBlinnPhongNormalizedTerm(half NdotH, half n)
{
    
    
    // 归一化因子 norm = (n + 2) / (2 * Pi)
    // 这个因子确保 Blinn-Phong 分布函数在整个半球上的积分等于 1
    half normTerm = (n + 2.0) * (0.5 / UNITY_PI);

    // 计算 Blinn-Phong 高光项,其中 NdotH 是法线与半角向量的点积,n 是光泽度参数
    half specTerm = pow(NdotH, n);

    // 返回归一化的高光项
    return specTerm * normTerm;
}

9.PerceptualRoughnessToSpecPower

// 将感知粗糙度转换为光泽度参数
inline half PerceptualRoughnessToSpecPower(half perceptualRoughness)
{
    
    
    // 调用 PerceptualRoughnessToRoughness 函数,将感知粗糙度转换为实际学术上的粗糙度 m
    // 感知粗糙度是为了在用户界面中提供更直观的控制而设计的,而实际粗糙度 m 是用于计算的
    half m = PerceptualRoughnessToRoughness(perceptualRoughness);   // m 是实际的学术粗糙度

    // 计算粗糙度的平方,并确保其最小值为 1e-4f 以避免数值问题
    half sq = max(1e-4f, m * m);

    // 根据公式 (2.0 / sq) - 2.0 计算光泽度参数 n
    // 参考文献: https://dl.dropboxusercontent.com/u/55891920/papers/mm_brdf.pdf
    half n = (2.0 / sq) - 2.0;

    // 确保光泽度参数 n 的最小值为 1e-4f,以防止 pow(0,0) 的情况发生
    // 当粗糙度为 1.0 且 NdotH 为零时,可能会出现这种情况
    n = max(n, 1e-4f);

    return n;
}

10.FresnelTerm

inline half3 FresnelTerm (half3 F0, half cosA)
{
    
    
    half t = Pow5 (1 - cosA);   // 按照 Schlick 插值方法
    return F0 + (1-F0) * t;
}

11.FresnelLerp

inline half3 FresnelLerp (half3 F0, half3 F90, half cosA)
{
    
    
    half t = Pow5 (1 - cosA);   // 按照 Schlick 插值方法
    return lerp (F0, F90, t);
}