GGX
🔥.icon マジでこの分野苦手で手探りでやってます 何も参考にしないでほしい
前提
$ \vec{N}: 法線
$ \vec{V}: ビューベクトル
$ \vec{L}: ライトベクトル
$ \vec{H} = |\vec{V} + \vec{L}|: ハーフベクトル
$ \frac{DGF}{4 (\vec{N} \cdot \vec{L})(\vec{N} \cdot \vec{V})} あるいは、意味変わらんが $ \frac{DGF}{4 (\vec{N} \cdot \vec{\omega_o})(\vec{N} \cdot \vec{\omega_i})}
$ D: 微小面法線分布関数、Normal Distribution Function (NDF) $ G: 幾何減衰項
$ F: フレネル項
BRDF
概要
分布
NDF
$ \alpha: Roughness
$ D = \frac{\alpha^2}{\pi ((\vec{N} \cdot \vec{H})^2 (\alpha^2 - 1) + 1)^2}
code:glsl
float dGGX(float dotNH, float alphaSq) {
float f = dotNH * dotNH * (alphaSq - 1.0) + 1.0;
return alphaSq / (PI * f * f + 1E-4);
}
Filamentの実装はより効率的にしててえらい 🔗 code:glsl
float D_GGX(float NoH, float roughness) {
float a = NoH * roughness;
float k = roughness / (1.0 - NoH * NoH + a * a);
return k * k * (1.0 / PI);
}
幾何減衰項
SmithのShadowing Function
$ \alpha: Roughness
$ G_1(v) = \frac{2(\vec{N} \cdot v)}{\vec{N} \cdot v + \sqrt{\alpha^2 + (1 - \alpha^2)(\vec{N} \cdot v)^2}}
$ G = G_1(\vec{V}) \ G_1(\vec{L})
code:glsl
float g1Smith( float alphaSq, float dotNV ) {
return 2.0 * dotNV / ( dotNV + sqrt( mix( dotNV * dotNV, 1.0, alphaSq ) ) );
}
Schlick-GGX
$ G_1の計算式がひたすらデケエのでこれを小さくしたい人がいるらしい
Schlick-GGXというのが $ \frac{\vec{N} \cdot v}{(\vec{N} \cdot v)(1 - k) + k}
$ kは直接光源とIBLとで別のを使うみたいなことが書いてあった なんじゃそりゃ
直接光源の場合、$ k = \frac{(\alpha + 1)^2}{8}
IBLの場合、$ k = \frac{\alpha^2}{2}
Visibility Function
いわゆるSmith Joint GGXと呼ばれるやつ
$ \alpha: Roughness
$ \Lambda_V = (\vec{N} \cdot \vec{V}) \sqrt{(\vec{N} \cdot \vec{L})^2 (1 - \alpha^2) + \alpha^2}
$ \Lambda_L = (\vec{N} \cdot \vec{L}) \sqrt{(\vec{N} \cdot \vec{V})^2 (1 - \alpha^2) + \alpha^2}
$ V = \frac{0.5}{\Lambda_V + \Lambda_L}
ただし、 $ V = \frac{G}{4 (\vec{N} \cdot \vec{L})(\vec{N} \cdot \vec{V})}
code:glsl
float vGGX(float dotNL, float dotNV, float alphaSq) {
float lambdaV = dotNL * sqrt(mix(dotNV * dotNV, 1.0, alphaSq));
float lambdaL = dotNV * sqrt(mix(dotNL * dotNL, 1.0, alphaSq));
return 0.5 / (lambdaV + lambdaL + 1E-4);
}
フレネル項: Schlick
Schlick
$ F_0: 垂直に光が入射したときの反射率
$ F_{90}: 水平に光が入射したときの反射率
$ F = F_0 + (F_{90} - F_0) (1 - (\vec{V} \cdot \vec{H}))^5
code:glsl
float fSchlick( float f0, float f90, float dotVH ) {
return mix(
f0,
f90,
pow( saturate( 1.0 - dotVH ), 5.0 )
);
}
重点サンプリング
code:glsl
vec3 importanceSampleGGX( vec2 Xi, float roughness, vec3 N ) {
float phi = TAU * Xi.x;
float cosTheta = saturate(
sqrt( ( 1.0 - Xi.y ) / ( 1.0 + ( pow( roughness, 4.0 ) - 1.0 ) * Xi.y ) )
);
float sinTheta = sqrt( 1.0 - cosTheta * cosTheta );
return orthBas( N ) * vec3(
cos( phi ) * sinTheta,
sin( phi ) * sinTheta,
cosTheta
);
}
pdfは$ D (\vec{N} \cdot \vec{H}) J_m = \frac{D (\vec{N} \cdot \vec{H})}{4 (\vec{V} \cdot \vec{H})}
可視法線分布サンプリング
なんじゃそりゃ
$ G項に強い法線分布らしい
$ D_{\omega_i(\omega_m)} = \frac{G_1(\omega_i, \omega_m) (\omega_i \cdot \omega_m) D(\omega_m)}{\omega_i \cdot n}
anisotropy考えずにコードにしてみる
code:glsl
vec3 sampleGGXVNDF( float alphaSq, vec3 V, vec3 N ) {
vec2 Xi = hash2();
// stretch view
float dotNV = max( 1E-3, dot( N, V ) );
vec3 Vt = normalize( mix( N, V / dotNV, alphaSq ) );
// sample point with polar coord
float Vz = dot( Vt, N );
float a = 1.0 / ( 1.0 + Vz );
float r = sqrt( Xi.x );
float phi = ( Xi.y < a ) ? PI * Xi.y / a : PI + ( Xi.y - a ) / ( 1.0 - a ) * PI;
float P1 = r * cos( phi );
float P2 = r * sin( phi ) * ( ( Xi.y < a ) ? 1.0 : Vz );
// compute normal
vec3 H = orthBas( Vt ) * vec3( P1, P2, sqrt( max( 0.0, 1.0 - P1 * P1 - P2 * P2 ) ) );
// unstretch
float dotNH = max( 1E-3, dot( N, H ) );
H = normalize( mix( N, H / dotNH, alphaSq ) );
return H;
}
pdfは$ D_{\omega_i(\omega_m)} J_m = \frac{G_1(\omega_i, \omega_m) (\omega_i \cdot \omega_m) D(\omega_m)}{4 (\omega_i \cdot n) (\omega_o \cdot \omega_m)} = \frac{G_1(\omega_i, \omega_m) D(\omega_m)}{4 (\omega_i \cdot n)}
参考