基于物理的渲染 – 实现篇(二)

上一篇:基于物理的渲染 – 实现篇(一)

完整的PBR光照着色器

现在唯一剩下的就是将最终的色调映射和伽玛校正的颜色传递给片段着色器的输出通道,我们就拥有了一个PBR直接光照着色器。基于完整性考虑,下面列出完整的main函数:

#version 330 core
out vec4 FragColor;
in vec2 TexCoords;
in vec3 WorldPos;
in vec3 Normal;

// material parameters
uniform vec3 albedo;
uniform float metallic;
uniform float roughness;
uniform float ao;

// lights
uniform vec3 lightPositions[4];
uniform vec3 lightColors[4];

uniform vec3 camPos;

const float PI = 3.14159265359;

float DistributionGGX(vec3 N, vec3 H, float roughness);
float GeometrySchlickGGX(float NdotV, float roughness);
float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness);
vec3 fresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness);

void main()
{       
    vec3 N = normalize(Normal);
    vec3 V = normalize(camPos - WorldPos);

    vec3 F0 = vec3(0.04); 
    F0 = mix(F0, albedo, metallic);

    // reflectance equation
    vec3 Lo = vec3(0.0);
    for(int i = 0; i < 4; ++i) 
    {
        // calculate per-light radiance
        vec3 L = normalize(lightPositions[i] - WorldPos);
        vec3 H = normalize(V + L);
        float distance    = length(lightPositions[i] - WorldPos);
        float attenuation = 1.0 / (distance * distance);
        vec3 radiance     = lightColors[i] * attenuation;        

        // cook-torrance brdf
        float NDF = DistributionGGX(N, H, roughness);        
        float G   = GeometrySmith(N, V, L, roughness);      
        vec3 F    = fresnelSchlick(max(dot(H, V), 0.0), F0);       

        vec3 kS = F;
        vec3 kD = vec3(1.0) - kS;
        kD *= 1.0 - metallic;     

        vec3 nominator    = NDF * G * F;
        float denominator = 4 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.001; 
        vec3 brdf = nominator / denominator;

        // add to outgoing radiance Lo
        float NdotL = max(dot(N, L), 0.0);                
        Lo += (kD * albedo / PI + brdf) * radiance * NdotL; 
    }   

    vec3 ambient = vec3(0.03) * albedo * ao;
    vec3 color = ambient + Lo;

    color = color / (color + vec3(1.0));
    color = pow(color, vec3(1.0/2.2));  

    FragColor = vec4(color, 1.0);
}  

希望在学习了前面教程的反射方程的理论知识之后,这个shader不再会让大家苦恼。使用这个shader,4个点光源照射在金属度和粗糙度不同的球上的效果大概类似这样:


从下往上金属度的值从0.0到1.0,粗糙度从左往右从0.0增加到1.0.你可以通过观察小球之间的区别理解金属度和粗糙度参数的作用。
示例的源码可以从这里找到。

(原文:https://learnopengl.com/#!PBR/Lighting, 翻译:coldkaweh)

使用纹理的PBR

为了实现逐像素的控制材质表面的属性我们必须使用纹理替代单个的材质参数:

[...]
uniform sampler2D albedoMap;
uniform sampler2D normalMap;
uniform sampler2D metallicMap;
uniform sampler2D roughnessMap;
uniform sampler2D aoMap;

void main()
{
    vec3 albedo     = pow(texture(albedoMap, TexCoords).rgb, 2.2);
    vec3 normal     = getNormalFromMap();
    float metallic  = texture(metallicMap, TexCoords).r;
    float roughness = texture(roughnessMap, TexCoords).r;
    float ao        = texture(aoMap, TexCoords).r;
    [...]
}

要注意美工制作的albedo纹理一般都是sRGB空间的,因此我们要先转换到线性空间再进行后面的计算。根据美工不同,AO纹理也许同样需要从sRGB转换到线性空间。

将前面那些小球的材质属性替换成纹理之后,对比以前用的光照算法,PBR有了一个质的提升:


你可以在这里找到带纹理的Demo源码,所有用到的纹理在这里(用了白色的AO贴图)。记住金属表面在直接光照环境中更暗是因为他们没有漫反射。在环境使用环境高光进行光照计算的情况下看起来也是正常的,这个我们在下一个教程里再说。

这里没有其他PBR渲染示例中那样令人惊艳的效果,因为我们还没有加入基于图片的光照(image based lighting)技术。尽管如此,这个shader任然算是一个基于物理的渲染,即使没有IBL你也可以法线光照看起来真实了很多。

原文链接:https://learnopengl.com/#!PBR/Lighting

本文转自:CSDN,译者:coldkaweh,转载此文目的在于传递更多信息,版权归原作者所有。
原文链接:https://blog.csdn.net/coldkaweh/article/details/70209237

推荐阅读