Unity 内置Standard Shader UNITY_BRDF_PBS函数分析 (二)

四、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;
}

公式如下

Disney Diffuse模型通过粗糙度和视角/光源方向的散射增强项计算漫反射反射率:
f diffuse = diffColor π ⋅ ScatteringFactor f_{\text{diffuse}} = \frac{\text{diffColor}}{\pi} \cdot \text{ScatteringFactor} fdiffuse=πdiffColorScatteringFactor
其中:

  • ScatteringFactor(散射系数):
    ScatteringFactor = lightScatter ∗ viewScatter  = [ 1 + ( F D 90 − 1 ) ⋅ ( 1 − N ⋅ L ) 5 ] ⋅ [ 1 + ( F D 90 − 1 ) ⋅ ( 1 − N ⋅ V ) 5 ] \text{ScatteringFactor} = \text{lightScatter} * \text{viewScatter } = \left[1 + (F_{D90} - 1) \cdot (1 - N \cdot L)^5\right] \cdot \left[1 + (F_{D90} - 1) \cdot (1 - N \cdot V)^5\right] ScatteringFactor=lightScatterviewScatter =[1+(FD901)(1NL)5][1+(FD901)(1NV)5]
  • F D 90 F_{D90} FD90(散射因子在90度视角时的值):
    F D 90 = 0.5 + 2 ⋅ ( L ⋅ H ) 2 ⋅ perceptualRoughness F_{D90} = 0.5 + 2 \cdot (L \cdot H)^2 \cdot \text{perceptualRoughness} FD90=0.5+2(LH)2perceptualRoughness
    • L ⋅ H L \cdot H LH:光源方向 L L L 与半角向量 H H H 的夹角余弦( H = L + V ∣ L + V ∣ H = \frac{L + V}{|L + V|} H=L+VL+V)。
    • perceptualRoughness = 1 − smoothness \text{perceptualRoughness} = 1 - \text{smoothness} perceptualRoughness=1smoothness(光滑度转换为感知粗糙度)。

下图来自Unity shader 入门精要
在这里插入图片描述

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
}
1.Smith-Joint GGX 原始公式:

λ V = ( − 1 + a 2 ⋅ ( 1 − ( N ⋅ L ) 2 ) ( N ⋅ L ) 2 + 1 ) ⋅ 1 2 \lambda_V = \left( -1 + \sqrt{ \frac{a2 \cdot \left(1 - (N \cdot L)^2\right)}{(N \cdot L)^2} + 1 } \right) \cdot \frac{1}{2} λV=(1+(NL)2a2(1(NL)2)+1 )21

λ L = ( − 1 + a 2 ⋅ ( 1 − ( N ⋅ V ) 2 ) ( N ⋅ V ) 2 + 1 ) ⋅ 1 2 \lambda_L = \left( -1 + \sqrt{ \frac{a2 \cdot \left(1 - (N \cdot V)^2\right)}{(N \cdot V)^2} + 1 } \right) \cdot \frac{1}{2} λL=(1+(NV)2a2(1(NV)2)+1 )21

G = 1 1 + λ V + λ L G = \frac{1}{1 + \lambda_V + \lambda_L} G=1+λV+λL1


其中:

  • a 2 a2 a2 表示粗糙度平方( α 2 \alpha^2 α2)。
  • N d o t L NdotL NdotL N d o t V NdotV NdotV 分别表示法线与光源、视角方向的点积(即 cos ⁡ θ L \cos\theta_L cosθL cos ⁡ θ V \cos\theta_V cosθV)。
2.简化后的表达式(代码中给出的中间步骤):

λ V = N ⋅ L ⋅ [ ( 1 − α 2 ) ( N ⋅ V ) 2 + α 2 ] , λ L = N ⋅ V ⋅ [ ( 1 − α 2 ) ( N ⋅ L ) 2 + α 2 ] . \begin{aligned} \lambda_V &= N \cdot L \cdot \sqrt{ \left[ (1 - \alpha^2) (N \cdot V)^2 + \alpha^2 \right] }, \\ \lambda_L &= N \cdot V \cdot \sqrt{ \left[ (1 - \alpha^2) (N \cdot L)^2 + \alpha^2 \right] }. \end{aligned} λVλL=NL[(1α2)(NV)2+α2] ,=NV[(1α2)(NL)2+α2] .

G = 0.5 λ V + λ L + ϵ , G = \frac{0.5}{\lambda_V + \lambda_L + \epsilon}, G=λV+λL+ϵ0.5,
其中 ϵ \epsilon ϵ 是防止除零的小常数(如 1 × 1 0 − 5 1 \times 10^{-5} 1×105)。

3.近似公式(实际使用的高效实现)

代码中实际使用的近似公式通过简化平方根运算,以提高计算效率,同时保持视觉效果的接近:

λ V ≈ N ⋅ L ⋅ ( ( N ⋅ V ) ⋅ ( 1 − α ) + α ) , λ L ≈ N ⋅ V ⋅ ( ( N ⋅ L ) ⋅ ( 1 − α ) + α ) . \begin{aligned} \lambda_V &\approx N \cdot L \cdot \left( (N \cdot V) \cdot (1 - \alpha) + \alpha \right), \\ \lambda_L &\approx N \cdot V \cdot \left( (N \cdot L) \cdot (1 - \alpha) + \alpha \right). \end{aligned} λVλLNL((NV)(1α)+α),NV((NL)(1α)+α).

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);
}
数学表达式

D G G X ( N ⋅ H , α ) = α 2 π ( ( α 2 − 1 ) ( N ⋅ H ) 2 + 1 ) 2 D_{GGX}( \mathbf{N} \cdot \mathbf{H}, \alpha) = \frac{\alpha^2}{\pi \left( (\alpha^2 - 1)(\mathbf{N} \cdot \mathbf{H})^2 + 1 \right)^2} DGGX(NH,α)=π((α21)(NH)2+1)2α2

其中:

  • N \mathbf{N} N 是表面法线,
  • H \mathbf{H} H 是半角向量(即视线方向和光源方向的中间方向),
  • α \alpha α 是粗糙度参数,等于代码中的 roughness 的平方 (a2),
  • N ⋅ H \mathbf{N} \cdot \mathbf{H} NH 表示法线与半角向量的点积,对应于代码中的 NdotH
  • ϵ \epsilon ϵ (在这里是 1e-7f)是一个小值,被加到分母上以避免除零错误。

下图来自Unity shader 入门精要
在这里插入图片描述
图中少了一个括号

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;
}

// 通用的 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);
}
数学表达式

G S c h l i c k ( N ⋅ L , N ⋅ V , k ) = 1 G L ( N ⋅ L , k ) × G V ( N ⋅ V , k ) G_{Schlick}( \mathbf{N} \cdot \mathbf{L}, \mathbf{N} \cdot \mathbf{V}, k ) = \frac{1}{G_L(\mathbf{N} \cdot \mathbf{L}, k) \times G_V(\mathbf{N} \cdot \mathbf{V}, k)} GSchlick(NL,NV,k)=GL(NL,k)×GV(NV,k)1

其中:

  • N \mathbf{N} N 是表面法线,
  • L \mathbf{L} L 是光源方向,
  • V \mathbf{V} V 是视角方向,
  • N ⋅ L \mathbf{N} \cdot \mathbf{L} NL 表示法线与光源方向的点积,对应于代码中的 NdotL
  • N ⋅ V \mathbf{N} \cdot \mathbf{V} NV 表示法线与视角方向的点积,对应于代码中的 NdotV
  • k k k 是一个调整参数,通常与粗糙度相关联,
  • G L G_L GL G V G_V GV 分别是从视角和光源方向考虑的几何阴影因子。

具体到每个几何阴影因子 G L G_L GL G V G_V GV,它们按照Schlick的近似公式定义如下:

G L = N ⋅ L ( N ⋅ L ) ( 1 − k ) + k G_L = \frac{\mathbf{N} \cdot \mathbf{L}}{(\mathbf{N} \cdot \mathbf{L})(1-k) + k} GL=(NL)(1k)+kNL
G V = N ⋅ V ( N ⋅ V ) ( 1 − k ) + k G_V = \frac{\mathbf{N} \cdot \mathbf{V}}{(\mathbf{N} \cdot \mathbf{V})(1-k) + k} GV=(NV)(1k)+kNV

近似公式(实际使用的高效实现)

g L = ( N ⋅ L ) × ( 1 − k ) + k gL = (\mathbf{N} \cdot \mathbf{L}) \times (1 - k) + k gL=(NL)×(1k)+k
g V = ( N ⋅ V ) × ( 1 − k ) + k gV = (\mathbf{N} \cdot \mathbf{V}) \times (1 - k) + k gV=(NV)×(1k)+k

最终的可见性项 G S c h l i c k G_{Schlick} GSchlick 通过以下方式计算:

G S c h l i c k = 1 g L × g V + ϵ G_{Schlick} = \frac{1}{gL \times gV + \epsilon} GSchlick=gL×gV+ϵ1

这里加上了一个很小的值 ϵ \epsilon ϵ (在代码中是 1e-5f)以防止除零错误。

综合表达式

S m i t h B e c k m a n n V i s i b i l i t y T e r m ( N ⋅ L , N ⋅ V , r o u g h n e s s ) = 0.25 ( ( N ⋅ L ) × ( 1 − k ) + k ) × ( ( N ⋅ V ) × ( 1 − k ) + k ) + 1 e − 5 SmithBeckmannVisibilityTerm(\mathbf{N} \cdot \mathbf{L}, \mathbf{N} \cdot \mathbf{V}, roughness) = \frac{0.25}{\left( (\mathbf{N} \cdot \mathbf{L}) \times (1 - k) + k \right) \times \left( (\mathbf{N} \cdot \mathbf{V}) \times (1 - k) + k \right) + 1e-5} SmithBeckmannVisibilityTerm(NL,NV,roughness)=((NL)×(1k)+k)×((NV)×(1k)+k)+1e50.25
其中
k = r o u g h n e s s × 2 π k = roughness \times \sqrt{\frac{2}{\pi}} k=roughness×π2

ϵ 以防止除零错误 \epsilon以防止除零错误 ϵ以防止除零错误

下图来自Unity shader 入门精要

在这里插入图片描述
可以发现K值不一样,我用的是Unity2021.3.23f1

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;
}
数学表达式

归一化的Blinn-Phong NDF可以表示为:

D B l i n n − P h o n g ( N ⋅ H , n ) = n + 2 2 π ( N ⋅ H ) n D_{Blinn-Phong}( \mathbf{N} \cdot \mathbf{H}, n ) = \frac{n + 2}{2\pi} (\mathbf{N} \cdot \mathbf{H})^n DBlinnPhong(NH,n)=2πn+2(NH)n

其中:

  • N \mathbf{N} N 是表面法线,
  • H \mathbf{H} H 是半角向量(即视线方向和光源方向的中间方向),
  • N ⋅ H \mathbf{N} \cdot \mathbf{H} NH 表示法线与半角向量的点积,对应于代码中的 NdotH
  • n n n 是光泽度参数,控制高光的锐利度,
  • n + 2 2 π \frac{n + 2}{2\pi} 2πn+2 是归一化因子,确保分布函数在整个半球上的积分等于1。

下图来自Unity shader 入门精要
在这里插入图片描述

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;
}
数学表达式

n = max ⁡ ( 2 max ⁡ ( 1 e − 4 , m 2 ) − 2 , 1 e − 4 ) n = \max\left(\frac{2}{\max(1e-4, m^2)} - 2, 1e-4\right) n=max(max(1e4,m2)22,1e4)

其中:
m = p e r c e p t u a l R o u g h n e s s 2 m = perceptualRoughness^2 m=perceptualRoughness2
这里 m m m 是实际粗糙度,而 perceptualRoughness 是用户界面中提供的感知粗糙度值。

10.FresnelTerm

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

根据Schlick的近似方法,菲涅耳项 F ( θ ) F(\theta) F(θ) 可以表示为:

F ( θ ) = F 0 + ( 1 − F 0 ) ( 1 − cos ⁡ θ ) 5 F(\theta) = F_0 + (1 - F_0)(1 - \cos\theta)^5 F(θ)=F0+(1F0)(1cosθ)5

其中:

  • F 0 F_0 F0 是法线入射时(即当入射角 (\theta) 为0度时)的反射率,
  • cos ⁡ θ \cos\theta cosθ 是视角方向和表面法线之间的点积(即 cosA 在代码中),
  • ( 1 − cos ⁡ θ ) 5 (1 - \cos\theta)^5 (1cosθ)5 是Schlick近似的核心部分,用来模拟随着视角变化的反射率变化。

下图来自Unity shader 入门精要

在这里插入图片描述

11.FresnelLerp

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

根据Schlick的近似方法,菲涅耳项 F ( θ ) F(\theta) F(θ) 可以表示为:

F ( θ ) = F 0 + ( F 90 − F 0 ) ( 1 − cos ⁡ θ ) 5 F(\theta) = F_0 + (F_{90} - F_0)(1 - \cos\theta)^5 F(θ)=F0+(F90F0)(1cosθ)5

其中:

  • F 0 F_0 F0 是法线入射时(即当入射角 (\theta) 为0度时)的反射率,
  • F 90 F_{90} F90 是当入射角接近90度时的反射率,
  • cos ⁡ θ \cos\theta cosθ 是视角方向和表面法线之间的点积(即 cosA 在代码中),
  • ( 1 − cos ⁡ θ ) 5 (1 - \cos\theta)^5 (1cosθ)5 是Schlick近似的核心部分,用来模拟随着视角变化的反射率变化。

在代码实现中,使用了线性插值(lerp)来简化这一过程。线性插值的公式为:

lerp ( A , B , t ) = A + t ( B − A ) \text{lerp}(A, B, t) = A + t(B - A) lerp(A,B,t)=A+t(BA)

因此,结合Schlick近似与线性插值,可以得到:

F ( θ ) = lerp ( F 0 , F 90 , t ) = F 0 + t ( F 90 − F 0 ) F(\theta) = \text{lerp}(F_0, F_{90}, t) = F_0 + t(F_{90} - F_0) F(θ)=lerp(F0,F90,t)=F0+t(F90F0)

其中 :
t = ( 1 − cos ⁡ θ ) 5 t = (1 - \cos\theta)^5 t=(1cosθ)5

12.总结

BRDF1_Unity_PBS 的实现基于 Disney 工作,并以 Torrance-Sparrow 微面模型为基础,使用了多种不同的组件来计算最终的 BRDF(双向反射分布函数)。

1.核心公式

BRDF 主要由漫反射项和镜面反射项组成:

B R D F = k D π + k S ⋅ D ⋅ V ⋅ F 4 BRDF = \frac{kD}{\pi} + kS \cdot \frac{D \cdot V \cdot F}{4} BRDF=πkD+kS4DVF

其中:

  • k D kD kD 是漫反射颜色,
  • k S kS kS 是镜面反射颜色,
  • D D D 是法线分布函数(NDF),
  • V V V 是几何可见性函数,
  • F F F 是菲涅尔项。
2. 漫反射项

漫反射项通过 DisneyDiffuse 函数计算,考虑了视角方向、光源方向、半角向量与感知粗糙度之间的关系:

d i f f u s e T e r m = D i s n e y D i f f u s e ( N d o t V , N d o t L , L d o t H , p e r c e p t u a l R o u g h n e s s ) × N d o t L diffuseTerm = DisneyDiffuse(NdotV, NdotL, LdotH, perceptualRoughness) \times NdotL diffuseTerm=DisneyDiffuse(NdotV,NdotL,LdotH,perceptualRoughness)×NdotL

这里,DisneyDiffuse 的具体形式如下:

f d 90 = 0.5 + 2 ⋅ ( L d o t H ) 2 ⋅ p e r c e p t u a l R o u g h n e s s fd90 = 0.5 + 2 \cdot (LdotH)^2 \cdot perceptualRoughness fd90=0.5+2(LdotH)2perceptualRoughness
l i g h t S c a t t e r = ( 1 + ( f d 90 − 1 ) ⋅ ( 1 − N d o t L ) 5 ) lightScatter = (1 + (fd90 - 1) \cdot (1 - NdotL)^5) lightScatter=(1+(fd901)(1NdotL)5)
v i e w S c a t t e r = ( 1 + ( f d 90 − 1 ) ⋅ ( 1 − N d o t V ) 5 ) viewScatter = (1 + (fd90 - 1) \cdot (1 - NdotV)^5) viewScatter=(1+(fd901)(1NdotV)5)
d i f f u s e T e r m = l i g h t S c a t t e r ⋅ v i e w S c a t t e r × N d o t L diffuseTerm = lightScatter \cdot viewScatter \times NdotL diffuseTerm=lightScatterviewScatter×NdotL

3. 镜面反射项

镜面反射项依赖于三个主要组件:法线分布函数NDF( D D D),几何可见性函数( V V V ),和菲涅尔项( F F F)。

GGX 分支

当选择 GGX 作为 NDF 时:

r o u g h n e s s = m a x ( r o u g h n e s s , 0.002 ) roughness = max(roughness, 0.002) roughness=max(roughness,0.002)
V = S m i t h J o i n t G G X V i s i b i l i t y T e r m ( N d o t L , N d o t V , r o u g h n e s s ) V = SmithJointGGXVisibilityTerm(NdotL, NdotV, roughness) V=SmithJointGGXVisibilityTerm(NdotL,NdotV,roughness)
D = G G X T e r m ( N d o t H , r o u g h n e s s ) D = GGXTerm(NdotH, roughness) D=GGXTerm(NdotH,roughness)

s p e c u l a r T e r m = V ⋅ D ⋅ π specularTerm = V \cdot D \cdot \pi specularTerm=VDπ

其中,SmithJointGGXVisibilityTermGGXTerm 分别为:

S m i t h J o i n t G G X V i s i b i l i t y T e r m = 0.5 l a m b d a V + l a m b d a L + 1 e − 5 f SmithJointGGXVisibilityTerm = \frac{0.5}{lambdaV + lambdaL + 1e-5f} SmithJointGGXVisibilityTerm=lambdaV+lambdaL+1e5f0.5
G G X T e r m = r o u g h n e s s 2 π ⋅ ( ( N d o t H ⋅ r o u g h n e s s 2 − N d o t H ) ⋅ N d o t H + 1 ) 2 + 1 e − 7 f GGXTerm = \frac{roughness^2}{\pi \cdot ((NdotH \cdot roughness^2 - NdotH) \cdot NdotH + 1)^2 + 1e-7f} GGXTerm=π((NdotHroughness2NdotH)NdotH+1)2+1e7froughness2

Beckmann 分支

如果选择了 Beckmann 分布,则使用 SmithBeckmannVisibilityTermNDFBlinnPhongNormalizedTerm

k = r o u g h n e s s ⋅ 2 π k = roughness \cdot \sqrt{\frac{2}{\pi}} k=roughnessπ2
V = S m i t h V i s i b i l i t y T e r m ( N d o t L , N d o t V , k ) ⋅ 0.25 V = SmithVisibilityTerm(NdotL, NdotV, k) \cdot 0.25 V=SmithVisibilityTerm(NdotL,NdotV,k)0.25
D = n + 2 2 π ⋅ ( N d o t H ) n D = \frac{n + 2}{2\pi} \cdot (NdotH)^n D=2πn+2(NdotH)n

其中,SmithVisibilityTerm 如下:

g L = N d o t L ⋅ ( 1 − k ) + k gL = NdotL \cdot (1 - k) + k gL=NdotL(1k)+k
g V = N d o t V ⋅ ( 1 − k ) + k gV = NdotV \cdot (1 - k) + k gV=NdotV(1k)+k
S m i t h V i s i b i l i t y T e r m = 1 g L ⋅ g V + 1 e − 5 f SmithVisibilityTerm = \frac{1}{gL \cdot gV + 1e-5f} SmithVisibilityTerm=gLgV+1e5f1

4. 菲涅尔项

对于菲涅尔项,使用 Schlick 近似:

F = F 0 + ( 1 − F 0 ) ⋅ ( 1 − c o s A ) 5 F = F0 + (1 - F0) \cdot (1 - cosA)^5 F=F0+(1F0)(1cosA)5

对于间接镜面反射部分,使用 FresnelLerp

F = l e r p ( F 0 , F 90 , ( 1 − c o s A ) 5 ) F = lerp(F0, F90, (1 - cosA)^5) F=lerp(F0,F90,(1cosA)5)

5.综合颜色计算

最终的颜色由以下三部分构成:

  1. 漫反射部分: d i f f C o l o r × ( g i . d i f f u s e + l i g h t . c o l o r × d i f f u s e T e r m ) diffColor \times (gi.diffuse + light.color \times diffuseTerm) diffColor×(gi.diffuse+light.color×diffuseTerm)
  2. 镜面反射部分: s p e c u l a r T e r m × l i g h t . c o l o r × F r e s n e l T e r m ( s p e c C o l o r , l h ) specularTerm \times light.color \times FresnelTerm(specColor, lh) specularTerm×light.color×FresnelTerm(specColor,lh)
  3. 间接镜面反射部分: s u r f a c e R e d u c t i o n × g i . s p e c u l a r × F r e s n e l L e r p ( s p e c C o l o r , g r a z i n g T e r m , n v ) surfaceReduction \times gi.specular \times FresnelLerp(specColor, grazingTerm, nv) surfaceReduction×gi.specular×FresnelLerp(specColor,grazingTerm,nv)

通过这些步骤,BRDF1_Unity_PBS 实现了一个物理基础的渲染模型,支持不同的微表面分布和可见性模型,提供了灵活且真实的材质表现。

智慧消防安全与应急管理是现代城市安全管理的重要组成部分,随着城市化进程的加速,传统消防安全管理面临着诸多挑战,如消防安全责任制度落实不到位、消防设施日常管理不足、消防警力不足等。这些问题不仅制约了消防安全管理水平的提升,也给城市的安全运行带来了潜在风险。然而,物联网和智慧城市技术的快速发展为解决这些问题提供了新的思路和方法。智慧消防作为物联网和智慧城市技术结合的创新产物,正在成为社会消防安全管理的新趋势。 智慧消防的核心在于通过技术创新实现消防安全管理的智能化和自动化。其主要应用包括物联网消防安全监管平台、城市消防远程监控系统、智慧消防平台等,这些系统利用先进的技术手段,如GPS、GSM、GIS等,实现了对消防设施的实时监控、智能巡检和精准定位。例如,单兵定位方案通过信标点定位和微惯导加蓝牙辅助定位技术,能够精确掌握消防人员的位置信息,从而提高救援效率和安全性。智慧消防不仅提升了消防设施的管理质量,还优化了社会消防安全管理资源的配置,降低了管理成本。此外,智慧消防的应用还弥补了传统消防安全管理中数据处理方式落后、值班制度执行不彻底等问题,赋予了建筑消防设施智能化、自动化的能力。 尽管智慧消防技术在社会消防安全管理工作中的应用已经展现出巨大的潜力和优势,但目前仍处于实践探索阶段。相关职能部门和研究企业需要加大研究开发力度,进一步完善系统的功能与实效性。智慧消防的发展既面临风险,也充满机遇。当前,社会消防安全管理工作中仍存在制度执行不彻底、消防设施日常维护不到位等问题,而智慧消防理念与技术的应用可以有效弥补这些弊端,提高消防安全管理的自动化与智能化水平。随着智慧城市理念的不断发展和实践,智慧消防将成为推动社会消防安全管理工作与城市化进程同步发展的关键力量。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值