GGX

 https://www.jianshu.com/p/9e6253897e39


Decima의 대략적인 구현도 UE4의 방식을 따르는데, 선택한 광원의 위치가 더 이상 반사광에 가장 가까운 지점이 아니라 N⋅H를 가장 크게 할 수 있는 지점(N이 가까울수록, H는 GGX 하이라이트 분포에 있거나, R이 L에 가까울수록 조명이 BRDF에 더 많이 기여하므로 Disney BRDF Explorer에서 GGX 분포를 직접 확인할 수 있습니다.)


Decima는 다음과 같은 방법으로 빛의 방향을 선택합니다.


L = \sqrt{1 - s^2}*L_c+s*T

B, L_c, T를 좌표계로 하고 이 원판에서 N⋅H가 최대점에 도달하도록 phiφ를 찾는다.

L=\sqrt{1 - s^2}L_c+s*(\cos{\phi} * T + \sin{\phi} * B)

tan{\frac{\phi}{2}}는 보편적으로 , \cos{\phi}\phix=tan{\frac{\phi}{2}}으로 변환됩니다. 

그리고 (분수에 근 부호가 포함되어 있으므로 이것을 선택하고 직접(N \cdot H)^2에대한GGX적NDF계산을 한다)

f(x)=gx4+hx3+ix2+jx2+kax4+bx3+cx2+dx2+e,x=tan2ϕ


x_0=0,f(x_0)=Karis's (N\cdot H)^2

함수의 근은  뉴턴 반복으로 찾을 수 있으므로 는 여러 반복 후에 근 값으로 수렴할 수 있습니다.  이떄  f(x)x=f(x에 대해 뉴턴 iteration을 수행하고
f(x_1)


float GetNoHSquared(float radiusTan, float NdotL, float NdotV, float VdotL)
{
// See presentation for more deatil
}
void EvaluateNdotH(float radius, float roughness, float lightCenterDistance, float NdotL,float NdotV, float LdotV, inout float NdotH)
{
float radiusTan = max(0.001, radius / lightCenterDistance - roughness * 0.25);
NdotH = sqrt(GetNoHSquared(radiusTan, NdotL, NdotV, LdotV));
}
void EvaluateNormalizationFactor(float roughness, float LdotH, float radius)
{
// Decima: Still in flux
float roughnessSquaredLdotH = roughness * roughness * (LdotH + 0.001);
normalization = roughnessSquaredLdotH / (roughnessSquaredLdotH + 0.25 * radius * (2.0 * roughness + radius));
}

여기서 사용시 GGX에서 제공하는 계산식에 각별히 주의가 필요합니다 변경된 NdotH를 제외하고 NdotL에서 L과 H는 NdotV, VdotL, LdotH 모두 초기 L=normalize(point-hitPoint)L=normalize (point-hitPoint ) 방향, Point가 Normal에 수직이면 실패하지만 여전히 더 나은 결과를 얻을 수 있습니다.







\int_H L_i(l)f(l,v) cos\theta_ldl \approx \frac{1}{N}\sum_{k=1}^N\frac{L_i(l_k)f(l_k, v)cos\theta_{l_k}}{p(l_k,v)} ~~~~~~(6)


//https://agraphicsguy.wordpress.com/2015/11/01/sampling-microfacet-brdf/
float3 ImportanceSampleGGX( float2 Xi, float Roughness, float3 N )
{
    float a = Roughness * Roughness;
    float Phi = 2 * PI * Xi.x;
    float CosTheta = sqrt( (1 - Xi.y) / ( 1 + (a*a - 1) * Xi.y ) );
    float SinTheta = sqrt( 1 - CosTheta * CosTheta );
    float3 H;
    H.x = SinTheta * cos( Phi );
    H.y = SinTheta * sin( Phi );
    H.z = CosTheta;
    float3 UpVector = abs(N.z) < 0.999 ? float3(0,0,1) : float3(1,0,0);
    float3 TangentX = normalize( cross( UpVector, N ) );
    float3 TangentY = cross( N, TangentX );
    // Tangent to world space
    return TangentX * H.x + TangentY * H.y + N * H.z;
}

float3 SpecularIBL( float3 SpecularColor , float Roughness, float3 N, float3 V )
{
    float3 SpecularLighting = 0;
    const uint NumSamples = 1024;
    for( uint i = 0; i < NumSamples; i++ )
    {
        float2 Xi = Hammersley( i, NumSamples );
        float3 H = ImportanceSampleGGX( Xi, Roughness, 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 = EnvMap.SampleLevel( EnvMapSampler , L, 0 ).rgb;
            float G = G_Smith( 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)
            // pdf = D * NoH / (4 * VoH)
            SpecularLighting += SampleColor * F * G * VoH / (NoH * NoV);
        }
    }
    return SpecularLighting / NumSamples;
}

댓글

이 블로그의 인기 게시물

About AActor!!! "UObject" has no member "BeginPlay"

UNREAL Android build information

C++ 생성자 위임 (delegating constructor)