zoukankan      html  css  js  c++  java
  • PBR.光照

    光源辐射率: 
        辐射率(radiance)表示光源在给定立体角ω下的辐射通量(或光源发射的能量)。
        那么假设立体角ω无限小时,辐射率就表示单束光线(或说某个单一方向)的辐射通量。
    点光源:point light,在所有方向都有相同的亮度,辐射强度(radiant intensity)等于其发射出来的所有方向的辐射通量(radiant flux)。
        对于场景中的一个点p,只会有一束光直接照射到点p,其他 光线的辐射率都为0。
        辐射强度:点光源无论从任何角度看,点光源都有相同的辐射强度,所以可以简单地使用其辐射通量表示辐射强度,也就是一个RGB常亮。
        辐射率:需要考虑点p的位置,距离衰减,法线角度衰减。
        vec3  lightColor  = vec3(23.47, 21.31, 20.79);
        vec3  wi          = normalize(lightPos - fragPos);
        float cosTheta    = max(dot(N, Wi), 0.0);
        float attenuation = calculateAttenuation(fragPos, lightPos);
        float radiance    = lightColor * attenuation * cosTheta;
        基本和普通diffuse漫反射光照一样。
        前提:假设点光源无限小,如果有体积,点光源会有一个以上的入射光线辐射率不为0。
    方向光:directional light,辐射率拥有恒定的入射方向,而且不会有衰减。
    聚光灯:spotlight,没有恒定的辐射强度,而是会根据照射方向有所不同。
     
    直接光照:
        辐照度=所有光源的辐射率,所以直接光照的计算非常简单,只需要逐个光源计算辐射率,然后加在一起。接着根据BRDF和光源的入射角来缩放该辐射率。
        这个算法也是符合反射率方程(The reflectance equation)的积分运算的。
        vec3 Lo = vec3(0.0);
        for(int i = 0; i < 4; ++i)
        {
            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;
            [...]  
        由于我们在线性空间内计算光照,我们使用在物理上更为准确的平方倒数(inverse-square law)作为衰减因子。
        对于每一个光源都需要计算完整的BRDF项:
        Ks = F,也就是菲涅尔系数,表示光线给反射的百分比:
        vec3 fresnelSchlick(float cosTheta, vec3 F0)
        {
            return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
        }
        Fo表示0度入射角的反射(surface relfection at zero incidence),也就是垂直看向材质时的反光率。Fo会因材质的不同而不同,而且金属的反光还会带有颜色。大多数非金属材质取0.04都能取得视觉上物理可信的效果。对于金属材质则直接取albedo贴图的颜色值。
        vec3 F0 = vec3(0.04);
        F0      = mix(F0, albedo, metallic);
        vec3 F  = fresnelSchlick(max(dot(H, V), 0.0), F0);
        D和G计算的实现:
    float DistributionGGX(vec3 N, vec3 H, float roughness)
        {
            float a      = roughness*roughness;
            float a2     = a*a;
            float NdotH  = max(dot(N, H), 0.0);
            float NdotH2 = NdotH*NdotH;
    
            float nom   = a2;
            float denom = (NdotH2 * (a2 - 1.0) + 1.0);
            denom = PI * denom * denom;
    
            return nom / denom;
        }
    
        float GeometrySchlickGGX(float NdotV, float roughness)
        {
            float r = (roughness + 1.0);
            float k = (r*r) / 8.0;
    
            float nom   = NdotV;
            float denom = NdotV * (1.0 - k) + k;
    
            return nom / denom;
        }
        float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness)
        {
            float NdotV = max(dot(N, V), 0.0);
            float NdotL = max(dot(N, L), 0.0);
            float ggx2  = GeometrySchlickGGX(NdotV, roughness);
            float ggx1  = GeometrySchlickGGX(NdotL, roughness);
    
            return ggx1 * ggx2;
        }
        根据Disney公司的观察和Epic采用经验,在D和G的计算公式中使用roughness * roughness来进行计算会有更正确的光照效果
        float NDF = DistributionGGX(N, H, roughness);       
        float G   = GeometrySmith(N, V, L, roughness);
        Cook-Torrance BRDF反射高光部分:
        vec3 nominator    = NDF * G * F;
        float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.001;
        vec3 specular     = nominator / denominator;
        额外加了一个0.001是为了防止除0。
        菲涅尔方程求出了Ks,那么Kd=1-Ks,但同时考虑到金属没有折射光线,也就是没有漫反射,所以金属的Kd=0,代码如下:
        vec3 kS = F;
        vec3 kD = vec3(1.0) - kS;
    
        kD *= 1.0 - metallic;   
        最终的反射率:
        const float PI = 3.14159265359;
    
        float NdotL = max(dot(N, L), 0.0);        
        Lo += (kD * albedo / PI + specular) * radiance * NdotL;
    }   
        可以看到specular没有再乘一次Ks,是因为DFG里面的F实际上就是Ks。
        再加一个环境光项(ao贴图):
        vec3 ambient = vec3(0.03) * albedo * ao;
        vec3 color   = ambient + Lo;  
     
    IBL环境光照:
        需要计算积分,因为光线会在任何一个方向入射到物体表面。
     
    线性空间和HDR渲染:
        我们之前所有的运算都必须在线性空间(linear space)进行,所以我们需要在shader的最后做伽马校正(gamma correct)。
        线性空间:PBR要求所有的输入都是线性的,不然计算的结果会不正确。
        我们希望输入的光照都尽可能接近真实,那么计算出的辐射率范围可能会非常大,也就是说Lo是一个HDR的值(大于1.0)。但最终的颜色输出范围是LDR的,所以在gamma correct之前,我们会先通过色调映射(tone or exposure map)将HDR值映射到LDR的值。
        color = color / (color + vec3(1.0));
        color = pow(color, vec3(1.0/2.2)); 
        这里我们使用了Reinhard方法来进行色调映射,我们没有使用framebuffer或post-processing的方式来进行色调映射,所以我们直接在shader的最后添加了这两步。
        PBR pipeline需要考虑线性空间和HDR的因素,不然可能渲染出来的画面会丢失细节,并且视觉上不正确看起来也不好看。
     
    完整的直接光照PBR shader:
        #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 fresnelSchlick(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 numerator    = NDF * G * F;
                float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0);
                vec3 specular     = numerator / max(denominator, 0.001);  
                    
                // add to outgoing radiance Lo
                float NdotL = max(dot(N, L), 0.0);                
                Lo += (kD * albedo / PI + specular) * 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);
        }  
     
    带贴图的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     = getNormalFromNormalMap();
            float metallic  = texture(metallicMap, TexCoords).r;
            float roughness = texture(roughnessMap, TexCoords).r;
            float ao        = texture(aoMap, TexCoords).r;
            [...]
        }
        美术一般在输出albedo贴图时都会使用sRGB空间,所以需要在计算光照之前手动转到线性空间。ao贴图也一样。不过metallic和roughness多数都会直接使用线性空间来保存。
     
  • 相关阅读:
    Opensource .NET and Mono REST Web Services framework.NET Community Content on InfoQ Servicestack
    动态软件框架开发模型图
    数据库镜像
    DDD:策略模式如何结合动态表达式
    CentOS下j2ee环境搭建
    C#代码段编辑/编译工具
    .NET平台下不借助Office实现Word、Powerpoint等文件的解析(完)
    .NET Attribute(特性)的作用与用法
    tornado开发输入输出,数据库操作,内置模板,综合示例
    基于事件的异步编程模式(EMP)
  • 原文地址:https://www.cnblogs.com/sifenkesi/p/11924857.html
Copyright © 2011-2022 走看看