一、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_PBS,BRDF2_Unity_PBS,BRDF3_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);
}