自定义 OpenGL 引擎 PBR 结果不符合预期

问题描述 投票:0回答:1

我已经遵循了 PBR 的 LearnOpenGL 教程并获得了附加的输出,虽然看起来不错,但我注意到我在 sketchfab 或 UE5 中看到的结果更加真实,因此我试图了解我的实现是否已关闭,或者是否只是存在那里发挥了我的定制引擎所缺乏的更多魔力。

我的结果与 sketchfab 结果

我尝试使用 renderdoc 进行调试,一切似乎都按预期通过,对我来说最明显的区别是 UE 版本更加“金属”,但似乎我按预期传递了金属纹理。

我的引擎目前不支持发射材料,但除了 HUD 之外,输出似乎是相同的。

我使用了这个模型:https://sketchfab.com/3d-models/battle-damaging-sci-fi-helmet-pbr-b81008d513954189a063ff901f7abfe4

vec3 fresnelSchlick(float cosTheta, vec3 F0)
{
    return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
}

vec3 fresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness)
{
    return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
}   

float geometrySchlickGGX(float NdotV, float roughness)
{
    float r = (roughness + 1.0);
    float k = (r*r) / 8.0;

    float num   = NdotV;
    float denom = NdotV * (1.0 - k) + k;
    
    return num / denom;
}

float geometrySmith(vec3 N, vec3 V, vec3 L, float k)
{
    float NdotV = max(0.0, dot(N, V));
    float NdotL = max(0.0, dot(N, L));
    return geometrySchlickGGX(NdotV, k) * geometrySchlickGGX(NdotL, k);
}

float distributionGGX(vec3 N, vec3 H, float a)
{
    float a2 = a * a;
    float NdotH = max(0.0, dot(N, H));
    float NdotH2 = NdotH * NdotH;

    float nom = a2;
    float denom = NdotH2 * (a2 - 1.0) + 1.0;
    denom = denom * denom * PI;

    return nom / denom;
}

struct Surface
{
    vec3 fragPos;
    vec3 V;
    vec3 N;
    vec3 H;
    vec3 L;
    vec3 F0;
    vec3 albedo;
    float metallic;
    float roughness;
    
};

vec3 calculateBRDF(Surface s)
{
    // Calculate BRDF
    // Calculate Fresnel Schlick 
    vec3 F = fresnelSchlick(max(0.0, dot(s.H, s.V)), s.F0);

    vec3 ks = F;
    vec3 kd = 1.0 - ks;
    kd *= 1.0 - s.metallic;

    // Calculate NDF
    float NDF = distributionGGX(s.N, s.H, s.roughness);

    // Calculate Schlick-GGX
    float G = geometrySmith(s.N, s.V, s.L, s.roughness);

    vec3 numerator = NDF * G * F;
    float denominator = 4.0 * max(0.0, dot(s.N, s.V)) * max(0.0, dot(s.N, s.L)) + 0.0001;
    vec3 specular = numerator / denominator;

    return (specular + kd * s.albedo / PI);
}

vec3 PointLightRadiance(PointLight pLight, Surface s)
{
    s.L = normalize(pLight.position.rgb - s.fragPos); // from frag pos to light pos
    s.H = normalize(s.V + s.L);

    // Calculate Li
    float distance = length(s.L);
    float attenuation = 1.0 / (distance * distance);
    vec3 radiance = pLight.color.rgb * attenuation;

    // Calculate cosTheta
    float cosTheta = max(0.0, dot(s.N, s.L));

    return calculateBRDF(s) * radiance * cosTheta;
}

vec3 DirLightRadiance(DirLight dLight, Surface s)
{
    s.L = normalize(-dLight.direction.xyz);
    s.H = normalize(s.V + s.L);

    // Calculate Li
    vec3 radiance = dLight.color.rgb;

    // Calculate cosTheta
    float cosTheta = max(0.0, dot(s.N, s.L));

    return calculateBRDF(s) * radiance * cosTheta;
}

float shadowCalculations(vec4 fragPos)
{
    // perform perspective divide
    vec3 projCoords = fragPos.xyz / fragPos.w;
    
    vec2 texelSize = 1.0 / textureSize(gShadowMap, 0);
    
    projCoords = projCoords * 0.5 + 0.5; 
    
    float borderBias =  max(texelSize.x, texelSize.y) * 2;
    
    if(projCoords.x >= 1.0 - borderBias || projCoords.x <= borderBias ||
        projCoords.y >= 1.0 - borderBias || projCoords.y <= borderBias ||
        projCoords.z >= 1.0 - borderBias || projCoords.z <= borderBias)
        return 0.0;
    
    float shadow = 0;
    float bias = 0.005;
    float currentDepth = projCoords.z;
    
    
    for(int x = -1; x <= 1; ++x)
    {
        for(int y = -1; y <= 1; ++y)
        {
            float pcfDepth = texture(gShadowMap, projCoords.xy + vec2(x, y) * texelSize).r;
            shadow += (currentDepth - bias > pcfDepth) ? 1.0 : 0.0;
        }
    }
    
    shadow /= 9.0;
    
    return shadow;
}

void main() 
{ 
    // retrieve data from G-buffer
    vec3 fragPos = texture(gPosition, TexCoords).rgb;
    vec3 normal = texture(gNormal, TexCoords).rgb;
    vec3 albedo = pow(texture(gAlbedo, TexCoords).rgb, vec3(2.2));
    float metallic = texture(gMRA, TexCoords).r;
    float roughness = texture(gMRA, TexCoords).g;
    float ao = texture(gMRA, TexCoords).b;

    vec4 fragPosInLightSpace = lightSpaceMatrix * vec4(fragPos, 1.f);
    float shadow = shadowCalculations(fragPosInLightSpace);

    vec3 F0 = vec3(0.04); // every dieltctric object has F0 = 0.04
    F0 = mix(F0, albedo, metallic);

    // Reflectance equation
    // L0(P, W0) = integral[ BRDF(P, W0, Wi, roughness) * Li(P, Wi) * cosTheta(n, Wi) * dw ]
    // BRDF = [ DFG / (4 * dot(n, w0) * dot(n, wi)) ] + Kd * albedo / PI
    vec3 N = normalize(normal);
    vec3 V = normalize(cameraPos - fragPos);
    vec3 R = reflect(-V, N);
    vec3 L0 = vec3(0.0);

    Surface s;
    s.fragPos = fragPos;
    s.V = V;
    s.N = N;
    s.F0 = F0;
    s.metallic = metallic;
    s.roughness = roughness;
    s.albedo = albedo;

    for(int i = 0; i < pointLightCount; ++i)
    {
        L0 += PointLightRadiance(pointLights[i], s);
    }

    for(int i = 0; i < dirLightCount; ++i)
    {
        L0 += DirLightRadiance(dirLight[i], s) * (1.0 - shadow);
    }

    // generate Kd to accomodate only for diffuse (exclude specular)
    vec3 F = fresnelSchlickRoughness(max(0.0, dot(N, V)), F0, roughness);
    
    vec3 prefilterColor = textureLod(gPrefilterEnvMap, R, roughness * MAX_REFLECTION_LOD).rgb;
    vec2 envBRDF = texture(gBRDFIntegrationLUT, vec2(max(dot(N, V), 0.0), roughness)).rg;
    vec3 specular = prefilterColor * (envBRDF.x * F + envBRDF.y); 

    vec3 ks = F;
    vec3 kd = 1.0 - ks;

    // ambient diffuse irradiance
    vec3 irradiance = texture(gIrradianceMap, N).rgb;
    vec3 diffuse = irradiance * albedo;
    vec3 ambient = (kd * diffuse + specular) * ao * vec3(1.f);

    // combine results
    vec3 color = L0 + ambient;

    // HDR
    color = color / (color + vec3(1.0));

    // Gamma correction
    color = pow(color, vec3(1.0/2.2));

#ifdef CUSTOM_SHADER
    frag(color);
#endif
    
    FragColor = vec4(color, 1.0);
} 
opengl glsl pbr
1个回答
0
投票

致所有走过这条路的人,

经过一番调查,我的错误实际上根本不在着色器中, 我加载了一个 LDR(低动态范围)纹理作为天空盒,它创建了一个混合辐照度贴图(光的方差较小 -> 生成的纹理中的方差较小),因此修复方法是添加适当的 HDR 支持并加载 HDR 纹理作为天空盒。

结果(仍然不完美,因为 AO 看起来有点奇怪,但肯定看起来更“金属”,这正是我认为缺乏的): 结果

© www.soinside.com 2019 - 2024. All rights reserved.