# 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
1
2
3
4
5
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
1
2
3
4
5
6
7
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

F_Schlick
1
2
3
4
5
6
7
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)(𝑣⋅ℎ)

F_Fresnel
1
2
3
4
5
6
7
8
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)}

IBL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
//重要性采样蒙德卡洛积分
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}

GetLocalLightAttenuation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 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;
}