title: BRDF 方程
date: 2022-04-07 12:00
count: true
math: true
tags:
小数字 0 为观察视觉,i 为入射角,Li 为 radiance
这里的 “kd” 是入射光线中被折射部分的能量所占的比率,而 “ks” 是被反射部分的比率。
法线分布函数 D 项
在现代引擎中流行和常用的 NDF 为 GGX (Trowbridge-Reitz) 法线分布函数。D (h) 来描述组成表面一点 的所有微表面的法线分布概率。则可以这样理解:向 NDF 输入一个朝向 h, 这个 h 向量为法线 dot 半向量(view direction 加 light direction),NDF 会返回朝向 是 h 的微表面数占微表面总数的比例,a 为 roughness, 但在 ue 里 a2 是 pow4 (roughness)
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 | |
} |
N dot L 和 N dot V 要 Max 0,
这里的 k 基于几何函数是针对直接光照还是针对 IBL 光照的重映射 (Remapping)
在 ue 中用的是 [Heitz 2014, "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs"]
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 ); | |
} |
使用的是 Schlick 近似法
而 ue 采用了
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 运算:
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
UE 的 IBL 将光照积分用黎曼和方法来近似模拟,光照函数结合了蒙特卡洛和重要性采样,公式如下:
//重要性采样蒙德卡洛积分
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 采用了如下的物理近似:
// 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;
}