title: BRDF 方程
date: 2022-04-07 12:00
count: true
math: true
tags:


# BRDF 方程

Untitled

小数字 0 为观察视觉,i 为入射角,Li 为 radiance
Untitled

这里的 “kd” 是入射光线中被折射部分的能量所占的比率,而 “ks” 是被反射部分的比率。

Untitled

Untitled

法线分布函数 D 项

Untitled

在现代引擎中流行和常用的 NDF 为 GGX (Trowbridge-Reitz) 法线分布函数。D (h) 来描述组成表面一点 的所有微表面的法线分布概率。则可以这样理解:向 NDF 输入一个朝向 h, 这个 h 向量为法线 dot 半向量(view direction 加 light direction),NDF 会返回朝向 是 h 的微表面数占微表面总数的比例,a 为 roughness, 但在 ue 里 a2 是 pow4 (roughness)

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

# G 项(几何函数)

Untitled

N dot L 和 N dot V 要 Max 0,
这里的 k 基于几何函数是针对直接光照还是针对 IBL 光照的重映射 (Remapping)
在 ue 中用的是 [Heitz 2014, "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs"]

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

Untitled

# F 项(Fresnel)

使用的是 Schlick 近似法
Untitled

而 ue 采用了

FSchlick(h,v,F0)=F0+(1F0)(1(hv))5F_{Schlick}(h, v, F_0) = F_0 + (1 - F_0) ( 1 - (h \cdot v))^5

float3 F_Schlick( float3 SpecularColor, float VoH )
{
    float Fc = Pow5( 1 - VoH );                    // 1 sub, 3 mul
    return saturate( 50.0 * SpecularColor.g ) * Fc + (1 - Fc) * SpecularColor;
}


不过,UE 并没有完全使用以上公式,而是为了效率采用球面高斯(Spherical Gaussian)近似法代替了 Pow 运算:

(𝑣,h)=𝐹0+(1𝐹0)2(5.55473(𝑣h)6.98316)(𝑣h)(𝑣,ℎ)=𝐹0+(1−𝐹0)2(−5.55473(𝑣⋅ℎ)−6.98316)(𝑣⋅ℎ)

float3 F_Fresnel( float3 SpecularColor, float VoH )
{
    float3 SpecularColorSqrt = sqrt( clamp( float3(0, 0, 0), float3(0.99, 0.99, 0.99), SpecularColor ) );
    float3 n = ( 1 + SpecularColorSqrt ) / ( 1 - SpecularColorSqrt );
    float3 g = sqrt( n*n + VoH*VoH - 1 );
    return 0.5 * Square( (g - VoH) / (g + VoH) ) * ( 1 + Square( ((g+VoH)*VoH - 1) / ((g-VoH)*VoH + 1) ) );
}

各向异性 Li 光强度

Ks 高光反射率

h half vector :L add V

Untitled

# IBL

UE 的 IBL 将光照积分用黎曼和方法来近似模拟,光照函数结合了蒙特卡洛和重要性采样,公式如下:

HLi(l)f(l,v)cosθldl1Nk=1NLi(lk)f(lk,v)cosθlkp(lk,v)\int \limits_H L_i(l)f(l,v)\cos\theta_ldl \approx \frac{1}{N} \sum_{k=1}^N \cfrac{L_i(l_k)f(l_k,v)\cos\theta_{l_k}}{p(l_k,v)}

//重要性采样蒙德卡洛积分
float4 ImportanceSampleGGX( float2 E, float a2 )
{
    float Phi = 2 * PI * E.x;
    float CosTheta = sqrt( (1 - E.y) / ( 1 + (a2 - 1) * E.y ) );
    float SinTheta = sqrt( 1 - CosTheta * CosTheta );

    float3 H;
    H.x = SinTheta * cos( Phi );
    H.y = SinTheta * sin( Phi );
    H.z = CosTheta;
    
    float d = ( CosTheta * a2 - CosTheta ) * CosTheta + 1;
    float D = a2 / ( PI*d*d );
    float PDF = D * CosTheta;

    return float4( H, PDF );
}

float3 SpecularIBL( uint2 Random, float3 SpecularColor, float Roughness, float3 N, float3 V )
{
    float3 SpecularLighting = 0;

    const uint NumSamples = 32;
    for( uint i = 0; i < NumSamples; i++ )
    {
        float2 E = Hammersley( i, NumSamples, Random );
        float3 H = TangentToWorld( ImportanceSampleGGX( E, Pow4(Roughness) ).xyz, N );
        float3 L = 2 * dot( V, H ) * H - V;

        float NoV = saturate( dot( N, V ) );
        float NoL = saturate( dot( N, L ) );
        float NoH = saturate( dot( N, H ) );
        float VoH = saturate( dot( V, H ) );
        
        if( NoL > 0 )
        {
            float3 SampleColor = AmbientCubemap.SampleLevel( AmbientCubemapSampler, L, 0 ).rgb;

            float Vis = Vis_SmithJointApprox( Pow4(Roughness), NoV, NoL );
            float Fc = pow( 1 - VoH, 5 );
            float3 F = (1 - Fc) * SpecularColor + Fc;

            // Incident light = SampleColor * NoL
            // Microfacet specular = D*G*F / (4*NoL*NoV) = D*Vis*F
            // pdf = D * NoH / (4 * VoH)
            SpecularLighting += SampleColor * F * ( NoL * Vis * (4 * VoH / NoH) );
        }
    }

    return SpecularLighting / NumSamples;
}

对于光源的距离衰减函数,UE 采用了如下的物理近似:

Falloff=saturate(1(distance/lightRadius)4)2distance2+1\text{Falloff} = \cfrac{\text{saturate}(1-(\text{distance}/\text{lightRadius})^4)^2}{\text{distance}^2+1}

// Engine\Shaders\Private\DeferredLightingCommon.ush

float GetLocalLightAttenuation(float3 WorldPosition, FDeferredLightData LightData, inout float3 ToLight, inout float3 L)
{
    ToLight = LightData.Position - WorldPosition;
        
    float DistanceSqr = dot( ToLight, ToLight );
    L = ToLight * rsqrt( DistanceSqr );

    float LightMask;
    if (LightData.bInverseSquared)
    {
        LightMask = Square( saturate( 1 - Square( DistanceSqr * Square(LightData.InvRadius) ) ) );
    }
    
    (......)

    return LightMask;
}