zoukankan      html  css  js  c++  java
  • <

    PBR Opengl 的应用,理想,完美的公式,这公式在BRDF不知道的情况下完全废物:

    所以现在引入一个最简单的BRDF模型:Cook-Torrance BRDF:

     左边是lambertian部分,右边是高光部分。能量守恒部分肯定要满足 diffuse部分+镜面部分 <=1

     

     

    公式中DFG全部为一个函数。或者由几个函数组成。

    D:Normal distribution function,h是half vertor, a其实是roughness,这个公式称作Trowbridge-Reitz GGX:

    一般代码表示这个D的返回值都是ggx_tr(),输入3个参数,法线,半向量,roughness

    G:Geometry function,经过实际测试,就是灯光照亮微平面的遮蔽效果。类似阴影,但是又不是阴影。

     

    其中k比较特殊,在IBL中和灯光直接照明意义不一样。

     

     正确的G还要考虑灯光向量进去。所以Smith's method:

    F: 菲尼尔方程用Fresnel-Schlick近似法:F0 是indices of refraction or IOR

     

    合并到一块:

     

    分开积分相加:

     

    可以看到分为Kd(diffuse部分) Ks(specular reflection部分)。

    开局给你一个球:DEBUG In houdini

    1,一般灯光GGX VIS:

    vector lgtpos = chv("lgtpos");
    float rough = chf("rough");
    vector wi = normalize(lgtpos - @P);
    vector wo = normalize(campos - @P);
    vector h = normalize(wi+wo);
    
    float lambert = max(dot(@N, wi), 0);
    
    
    float D_GGX_TR(vector N;vector H;float a)
    {
        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;
    }
    
    vector rough_tex = texture(chs("image"),@uv.x, @uv.y);
    vector diff = texture(chs("diff"),@uv.x, @uv.y);
    
    
    float ggx = D_GGX_TR(@N,h,rough_tex.x );
    
    
    
    @Cd = diff  + ggx*0.5 ;
    View Code

    Roughness:0.106

    Roughness:0.315

     

    完全给你一个球:

     

    Normal distribution function +  geometry function 

    vector campos = chv("campos");
    vector lgtpos = chv("lgtpos");
    float rough = chf("rough");
    vector wi = normalize(lgtpos - @P);
    vector wo = normalize(campos - @P);
    vector h = normalize(wi+wo);
    
    float lambert = max(dot(@N, wi), 0);
    
    
    float D_GGX_TR(vector N;vector H;float a)
    {
        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 k)
    {
        float nom   = NdotV;
        float denom = NdotV * (1.0 - k) + k;
    
        return nom / denom;
    }
    
    float GeometrySmith(vector N; vector V;vector L; float k)
    {
        float NdotV = max(dot(N, V), 0.0);
        float NdotL = max(dot(N, L), 0.0);
        float ggx1 = GeometrySchlickGGX(NdotV, k);
        float ggx2 = GeometrySchlickGGX(NdotL, k);
        return ggx1 * ggx2;
    }
    float kdirect(float rough){
        return ( (rough+1) * (rough+1)) / 8.0f ;
    }
    
    vector rough_tex = texture(chs("image"),@uv.x, @uv.y);
    vector diff = texture(chs("diff"),@uv.x, @uv.y);
    
    
    float ggx_tr = D_GGX_TR(@N,h,rough_tex.x );
    float ggx_trbase = D_GGX_TR(@N,h,rough );
    
    float k = kdirect(rough_tex.x);
    float ggx_smith = GeometrySmith(@N, wo, wi, k);
    ggx_smith = max(ggx_smith , 0.0);
    @Cd = diff + ggx_smith * ggx_tr ;
    View Code

     第一幅图是带geometry function,主要给灯光做了带遮挡

     

     Normal distribution function +  geometry function  + fresnel 

    vector campos = chv("campos");
    vector lgtpos = chv("lgtpos");
    float rough = chf("rough");
    vector wi = normalize(lgtpos - @P);
    vector wo = normalize(campos - @P);
    vector h = normalize(wi+wo);
    
    float lambert = max(dot(@N, wi), 0);
    
    
    float D_GGX_TR(vector N;vector H;float a)
    {
        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 k)
    {
        float nom   = NdotV;
        float denom = NdotV * (1.0 - k) + k;
    
        return nom / denom;
    }
    
    float GeometrySmith(vector N; vector V;vector L; float k)
    {
        float NdotV = max(dot(N, V), 0.0);
        float NdotL = max(dot(N, L), 0.0);
        float ggx1 = GeometrySchlickGGX(NdotV, k);
        float ggx2 = GeometrySchlickGGX(NdotL, k);
        return ggx1 * ggx2;
    }
    float kdirect(float rough){
        return  pow(rough,2)  / 2.0f ;
    }
    
    
    vector fresnelSchlick(float cosTheta; vector F0)
    {
        return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
    }
    
    
    vector rough_tex = texture(chs("image"),@uv.x, @uv.y);
    vector diff = texture(chs("diff"),@uv.x, @uv.y);
    
    
    float ggx_tr = D_GGX_TR(@N,h,rough_tex.x );
    float ggx_trbase = D_GGX_TR(@N,h,rough );
    
    float k = kdirect(rough_tex.x);
    float ggx_smith = GeometrySmith(@N, wo, wi, k);
    ggx_smith = max(ggx_smith , 0.0);
    //@Cd = diff + ggx_smith * ggx_tr ;
    
    vector fresnelv =  fresnelSchlick(dot(@N,wo), chf("fresnel" ));
    
    @Cd = diff*.2 + ggx_tr* ggx_smith * fresnelv;
    View Code

     加上n dot wi:

     

    以上是没考虑Cook-Torrance BRDF的分母,除以分母比较简单,但是避免除0,建议+0.00000001f

    2,IBL 照明:

    同样分为两部分,第一部分为direct diffuse lighting。

    首先在Diffuse irradiance渲染部分:

    本来的解决方案如下:

    意思就是在半球范围内蒙特卡洛积分,但是问题是在fragment shader 运算这个非常耗时。蓝色的最后pre-compute预计算,预计算就是你拿到的HDR图片的

    的辐射度,实际上是蒙特卡洛积分,那么如果我先给你这个方向上的HDR Li辐射度呢?

    然后反着看这个方法:

    其中Li是最麻烦的,他是任意Wi,dw上的辐射度。为了实时的获取辐射度核心理念就是将N作为获取能量的方向,但是Li是卷积处理过的环境贴图(但是这个环境贴图一定不是模糊贴图那么随便)。

    // calculate reflectance at normal incidence; if dia-electric (like plastic) use F0 
    // of 0.04 and if it's a metal, use the albedo color as F0 (metallic workflow) 
    vec3 F0 = vec3(0.04); 
    F0 = mix(F0, albedo, metallic);
    
    
    
    vec3 kS = fresnelSchlick(max(dot(N, V), 0.0), F0);
    vec3 kD = 1.0 - kS;
    kD *= 1.0 - metallic;    
    vec3 irradiance = texture(irradianceMap, N).rgb;
    vec3 diffuse = irradiance * albedo;
    vec3 ambient = (kD * diffuse) * ao;
    // vec3 ambient = vec3(0.002);
    
    vec3 color = ambient + Lo;

     

     计算半球内的p辐射度的蒙特卡洛法等于对cubemap求卷积。所以教程也是先是HDR->Framebuffer rendering CubeMap->convoluting this cubemap-> 采样在N

     

     

    上面的漫反射公式用球坐标是等价意义:

    离散后: 注意样本是在phi和theta上分布了N1, N2 个样本 。所以总样本在分母上的话一定N1 * N2

     

     卷积代码:这个是要用framebuffer + colorAttachment ,把结果储存在colorAttachment,在pbr shader 里再samplerCube回来。

    #version 330 core
    layout (location = 0) in vec3 aPos;
    
    out vec3 WorldPos;
    
    uniform mat4 projection;
    uniform mat4 view;
    
    void main()
    {
        WorldPos = aPos;  
        gl_Position =  projection * view * vec4(WorldPos, 1.0);
    }
    #version 330 core
    out vec4 FragColor;
    in vec3 WorldPos;
    
    uniform samplerCube environmentMap;
    
    const float PI = 3.14159265359;
    
    void main()
    {        
        // The world vector acts as the normal of a tangent surface
        // from the origin, aligned to WorldPos. Given this normal, calculate all
        // incoming radiance of the environment. The result of this radiance
        // is the radiance of light coming from -Normal direction, which is what
        // we use in the PBR shader to sample irradiance.
        vec3 N = normalize(WorldPos);
    
        vec3 irradiance = vec3(0.0);   
        
        // tangent space calculation from origin point
        vec3 up    = vec3(0.0, 1.0, 0.0);
        vec3 right = cross(up, N);
        up            = cross(N, right);
           
        float sampleDelta = 0.025;
        float nrSamples = 0.0;
        for(float phi = 0.0; phi < 2.0 * PI; phi += sampleDelta)
        {
            for(float theta = 0.0; theta < 0.5 * PI; theta += sampleDelta)
            {
                // spherical to cartesian (in tangent space)
                vec3 tangentSample = vec3(sin(theta) * cos(phi),  sin(theta) * sin(phi), cos(theta));
                // tangent space to world
                vec3 sampleVec = tangentSample.x * right + tangentSample.y * up + tangentSample.z * N; 
    
                irradiance += texture(environmentMap, sampleVec).rgb * cos(theta) * sin(theta);
                nrSamples++;
            }
        }
        irradiance = PI * irradiance * (1.0 / float(nrSamples));
        
        FragColor = vec4(irradiance, 1.0);

     最后pbr shader代码获取这个就简单了:

    vec3 irradiance = texture(irradianceMap, N).rgb;
    vec3 diffuse = irradiance * albedo;

    在这里如果有2个物体,相互之间有遮挡,这样简单的判断,实际上物体相互之间的遮挡的环境辐射度肯定低,在Raytracing框架却很容易实现,当前作者并没有考虑此问题

    Specular IBL:

     

     这次积分的BRDF 依靠 wi wo n向量,不像diffuse ibl只依靠wi,解决方案就是Epic Games' split sum approximation :

    积分第一个部分是 pre-filtered environment map (预计算/过滤环境贴图),similar to the diffuse irradiance map

    这个和diffuse环境贴图卷积是不一样,要考虑下roughness,因为一定要模拟在microface表面的一个效果:

    第一幅图就是个镜面反射比较简单。也就是在我们的方案中,不卷积环境贴图,roughness为0。

    后面2个高光采样的话有2中方案把采样分布到 Lobe中,参见我以前的文章:ImportanceSampleGGX && "CosWeightedSampleGGX"

     作者相应的卷积公式第一部分代码:

    #version 330 core
    out vec4 FragColor;
    in vec3 localPos;
    
    uniform samplerCube environmentMap;
    uniform float roughness;
    
    const float PI = 3.14159265359;
    
    float RadicalInverse_VdC(uint bits);
    vec2 Hammersley(uint i, uint N);
    vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness);
    
    void main()
    {       
        vec3 N = normalize(localPos);    
        vec3 R = N;
        vec3 V = R;
    
        const uint SAMPLE_COUNT = 1024u;
        float totalWeight = 0.0;   
        vec3 prefilteredColor = vec3(0.0);     
        for(uint i = 0u; i < SAMPLE_COUNT; ++i)
        {
            vec2 Xi = Hammersley(i, SAMPLE_COUNT);
            vec3 H  = ImportanceSampleGGX(Xi, N, roughness);
            vec3 L  = normalize(2.0 * dot(V, H) * H - V);
    
            float NdotL = max(dot(N, L), 0.0);
            if(NdotL > 0.0)
            {
                prefilteredColor += texture(environmentMap, L).rgb * NdotL;
                totalWeight      += NdotL;
            }
        }
        prefilteredColor = prefilteredColor / totalWeight;
    
        FragColor = vec4(prefilteredColor, 1.0);
    }  
    View Code

    其中:如上图我画的向量解释:

    vec3 L  = normalize(2.0 * dot(V, H) * H - V);

    V向量是红色(在卷积贴图中认为的由点位置到摄像机向量),H向量是蓝色(GGX重要性采样向量),L向量为黑色(反射向量)。可以试着想下 在卷积环境球表面的情况。

    为了更加简化这个过程,教程中使用的是Cube Map,我想如果换成HDR Image -> 卷积 - >HDR Sphere。那岂不是更简单:

    所以一切的方案首先从houdini实践正确性。而且顺便测试了离线的HDR sphere这种方式,而不是用cube map的这种傻逼方式(在这里不管任何性能):

    所以我的思路实现方法:给你一个图片,先把他卷积了,在houdini卷积就是直接用comp系统(opengl对应framebuffer color attachment)

    但是houdini comp里首先得把uv坐标转换成一个球坐标。这里公式来自于PBRT

     

     

     第一幅图是把uv转sphere的坐标:

    snippt代码:

    x = sin(theta) * cos(phi);
    y = sin(theta) * sin(phi);
    z = cos(theta);

    后面的球坐标xyz要调换下,我对了下单位球位置的顶点颜色,这个是正确的。

    第二个comp节点是卷积:

     snippt代码:

    float roughness = rough;    
    
    float VanDerCorpus(int n; int base)
    {
        float invBase = 1.0f / float(base);
        float denom   = 1.0f;
        float result  = 0.0f;
    
        for(int i = 0; i < 32; ++i)
        {
            if(n > 0)
            {
                denom   = float(n) % 2.0f;
                result += denom * invBase;
                invBase = invBase / 2.0f;
                n       = int(float(n) / 2.0f);
            }
        }
    
        return result;
    }
    // ----------------------------------------------------------------------------
    
    // ----------------------------------------------------------------------------
    vector2 HammersleyNoBitOps(int i; int N)
    {
        return set(float(i)/float(N), VanDerCorpus(i, 2));
    }
    
    
    vector ImportanceSampleGGX(vector2 Xi;vector N;float roughness)
    {
        float a = roughness*roughness;
    
        float phi = 2.0 * PI * Xi.x;
        float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y));
        float sinTheta = sqrt(1.0 - cosTheta*cosTheta);
    
        // from spherical coordinates to cartesian coordinates
        vector H;
        H.x = cos(phi) * sinTheta;
        H.y = sin(phi) * sinTheta;
        H.z = cosTheta;
    
        // from tangent-space vector to world-space sample vector
        vector up        = abs(N.z) < 0.999 ? set(0.0, 0.0, 1.0) : set(1.0, 0.0, 0.0);
        vector tangent   = normalize(cross(up, N));
        vector bitangent = cross(N, tangent);
    
        vector sampleVec = tangent * H.x + bitangent * H.y + N * H.z;
        return normalize(sampleVec);
    }
    
    
    
    vector sampleSphere(vector dir; string img){
        float phi = atan2(dir.x, dir.z);
        float theta = acos(dir.y);
        float u = phi / (2.000 *PI );
        float v = 1 - theta / PI;
        //u = clamp(u,0,1);
        //v = clamp(v,0,1);
        vector imgL = texture(img,u,v);
        return imgL;
    }
    
    
    int SAMPLE_COUNT = count;
    vector N = normalize(localPos);    
    vector R = N;
    
    vector V = R;
    float totalWeight = 0.0;   
    vector prefilteredColor = 0.0;  
    
    for(int i = 0; i < SAMPLE_COUNT; ++i)
    {
        int testi = i;
        vector2 xy = HammersleyNoBitOps(testi,SAMPLE_COUNT);
        
        vector H  =  ImportanceSampleGGX(xy, N, roughness*roughness);
        vector L  = normalize(2.0 * dot(V, H) * H - V);
    
        float NdotL = max(dot(N, L), 0.0);
        if(NdotL > 0.0)
        {
            prefilteredColor += sampleSphere(L,map) * NdotL;
            totalWeight      += NdotL;
        }
        
    }
    
    
    prefilteredColor = prefilteredColor / totalWeight;
    
    color = prefilteredColor;
    ImportanceSampleGGX

    HDR原图:

    卷积后结果: samplers:100, roughness:0.7

    对应的结果:

     

    roughness:0.1 sampler=100

    REF:

    https://wiki.jmonkeyengine.org/jme3/advanced/pbr_part3.html#thanks-epic-games

    https://learnopengl.com/#!PBR/Theory

    http://www.codinglabs.net/article_physically_based_rendering_cook_torrance.aspx

    http://www.codinglabs.net/article_physically_based_rendering.aspx

  • 相关阅读:
    来电科技-自助租借充电宝
    一次使用NodeJS实现网页爬虫记
    八爪鱼采集器
    杭州市职称系统
    zz
    有道智选-网易效果推广
    Ubuntu10.04下载并编译Android4.3源代码
    poj 1654 Area 多边形面积
    Android利用Looper在子线程中改变UI
    Notepad 快捷键 大全
  • 原文地址:https://www.cnblogs.com/gearslogy/p/12705204.html
Copyright © 2011-2022 走看看