• 【Overload游戏引擎细节分析】PBR材质Shader


    PBR基于物理的渲染可以实现更加真实的效果,其Shader值得分析一下。但PBR需要较多的基础知识,不适合不会OpenGL的朋友。

    一、PBR理论

    PBR指基于物理的渲染,其理论较多,需要的基础知识也较多,我在这就不再写一遍了,具体可以参看:
    LearnOpenGL PBR理论-英文 或者 LearnOpenGL PBR理论-中文

    Overload也提供了这种材料,借助贴图可以实现非常真实的材质效果。下面这个例子的贴图来自LearnOpenGL,大家可以自己去下载。
    在这里插入图片描述

    二、PBR Shader分析

    顶点着色器
    #shader vertex
    #version 430 core
    
    layout (location = 0) in vec3 geo_Pos;
    layout (location = 1) in vec2 geo_TexCoords;
    layout (location = 2) in vec3 geo_Normal;
    layout (location = 3) in vec3 geo_Tangent;
    layout (location = 4) in vec3 geo_Bitangent;
    
    /* Global information sent by the engine */
    layout (std140) uniform EngineUBO
    {
        mat4    ubo_Model;
        mat4    ubo_View;
        mat4    ubo_Projection;
        vec3    ubo_ViewPos;
        float   ubo_Time;
    };
    
    /* Information passed to the fragment shader */
    out VS_OUT
    {
        vec3        FragPos;
        vec3        Normal;
        vec2        TexCoords;
        mat3        TBN;
        flat vec3   TangentViewPos;
        vec3        TangentFragPos;
    } vs_out;
    
    void main()
    {
        vs_out.TBN = mat3
        (
            normalize(vec3(ubo_Model * vec4(geo_Tangent,   0.0))),
            normalize(vec3(ubo_Model * vec4(geo_Bitangent, 0.0))),
            normalize(vec3(ubo_Model * vec4(geo_Normal,    0.0)))
        );
    
        mat3 TBNi = transpose(vs_out.TBN);
    
        vs_out.FragPos          = vec3(ubo_Model * vec4(geo_Pos, 1.0));
        vs_out.Normal           = normalize(mat3(transpose(inverse(ubo_Model))) * geo_Normal);
        vs_out.TexCoords        = geo_TexCoords;
        vs_out.TangentViewPos   = TBNi * ubo_ViewPos;
        vs_out.TangentFragPos   = TBNi * vs_out.FragPos;
    
        gl_Position = ubo_Projection * ubo_View * vec4(vs_out.FragPos, 1.0);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    顶点着色器基本与standard材质一致,这里就不再分析了,具体可看standard材质Shader

    片元着色器:
    #shader fragment
    #version 430 core
    
    /** 模型视图矩阵、摄像机位置,使用UBO传入 */
    /* Global information sent by the engine */
    layout (std140) uniform EngineUBO
    {
        mat4    ubo_Model;
        mat4    ubo_View;
        mat4    ubo_Projection;
        vec3    ubo_ViewPos;
        float   ubo_Time;
    };
    
    /* 顶点着色器的输出 */
    /* Information passed from the fragment shader */
    in VS_OUT
    {
        vec3        FragPos;
        vec3        Normal;
        vec2        TexCoords;
        mat3        TBN;
        flat vec3   TangentViewPos;
        vec3        TangentFragPos;
    } fs_in;
    
    /* 光源数据用SSBO传入 */
    /* Light information sent by the engine */
    layout(std430, binding = 0) buffer LightSSBO
    {
        mat4 ssbo_Lights[];
    };
    
    out vec4 FRAGMENT_COLOR;
    
    uniform sampler2D   u_AlbedoMap; // 反照率贴图
    uniform sampler2D   u_MetallicMap; // 金属度贴图
    uniform sampler2D   u_RoughnessMap; // 粗糙度贴图
    uniform sampler2D   u_AmbientOcclusionMap; // 环境光遮蔽贴图
    uniform sampler2D   u_NormalMap; // 法线贴图
    uniform vec4        u_Albedo                = vec4(1.0); // 反照率系数,控制反照率贴图的权重
    uniform vec2        u_TextureTiling         = vec2(1.0, 1.0);
    uniform vec2        u_TextureOffset         = vec2(0.0, 0.0);
    uniform bool        u_EnableNormalMapping   = false;  // 是否使用法线贴图
    uniform float       u_HeightScale           = 0.0;
    uniform float       u_Metallic              = 1.0; // 金属度
    uniform float       u_Roughness             = 1.0; // 粗糙度
    
    const float PI = 3.14159265359;
    
    // 计算法向分布函数D,使用Trowbridge-Reitz GGX  
    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 num   = a2;
        float denom = (NdotH2 * (a2 - 1.0) + 1.0);
        denom = PI * denom * denom;
    	
        return num / denom;
    }
    
    
    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;
    }
    
    // Smith’s method
    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;
    }
    
    // 菲涅尔项,使用Fresnel-Schlick方程
    vec3 fresnelSchlick(float cosTheta, vec3 F0)
    {
        return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
    }
    
    /* 将32位数字变成RGBA颜色 */
    vec3 UnPack(float p_Target)
    {
        return vec3
        (
            // CPU传入的数据是0-255,转换成0-1.0
            float((uint(p_Target) >> 24) & 0xff)    * 0.003921568627451,
            float((uint(p_Target) >> 16) & 0xff)    * 0.003921568627451,
            float((uint(p_Target) >> 8) & 0xff)     * 0.003921568627451
        );
    }
    
    bool PointInAABB(vec3 p_Point, vec3 p_AabbCenter, vec3 p_AabbHalfSize)
    {
        return
        (
            p_Point.x > p_AabbCenter.x - p_AabbHalfSize.x && p_Point.x < p_AabbCenter.x + p_AabbHalfSize.x &&
            p_Point.y > p_AabbCenter.y - p_AabbHalfSize.y && p_Point.y < p_AabbCenter.y + p_AabbHalfSize.y &&
            p_Point.z > p_AabbCenter.z - p_AabbHalfSize.z && p_Point.z < p_AabbCenter.z + p_AabbHalfSize.z
        );
    }
    
    /*光照衰减系数,LearnOpenGL中有具体公式*/
    float LuminosityFromAttenuation(mat4 p_Light)
    {
        const vec3  lightPosition   = p_Light[0].rgb;
        const float constant        = p_Light[0][3];
        const float linear          = p_Light[1][3];
        const float quadratic       = p_Light[2][3];
    
        const float distanceToLight = length(lightPosition - fs_in.FragPos);
        const float attenuation     = (constant + linear * distanceToLight + quadratic * (distanceToLight * distanceToLight));
        return 1.0 / attenuation;
    }
    
    /* 盒状环境光 */
    vec3 CalcAmbientBoxLight(mat4 p_Light)
    {
        const vec3  lightPosition   = p_Light[0].rgb;
        const vec3  lightColor      = UnPack(p_Light[2][0]);
        const float intensity       = p_Light[3][3];
        const vec3  size            = vec3(p_Light[0][3], p_Light[1][3], p_Light[2][3]);
    
        return PointInAABB(fs_in.FragPos, lightPosition, size) ? lightColor * intensity : vec3(0.0);
    }
    
    /* 球状环境光 */
    vec3 CalcAmbientSphereLight(mat4 p_Light)
    {
        const vec3  lightPosition   = p_Light[0].rgb;
        const vec3  lightColor      = UnPack(p_Light[2][0]);
        const float intensity       = p_Light[3][3];
        const float radius          = p_Light[0][3];
    
        return distance(lightPosition, fs_in.FragPos) <= radius ? lightColor * intensity : vec3(0.0);
    }
    
    void main()
    {
        vec2 texCoords = u_TextureOffset + vec2(mod(fs_in.TexCoords.x * u_TextureTiling.x, 1), mod(fs_in.TexCoords.y * u_TextureTiling.y, 1));
    
        vec4 albedoRGBA     = texture(u_AlbedoMap, texCoords) * u_Albedo; // Albedo反照率贴图数据
        vec3 albedo         = pow(albedoRGBA.rgb, vec3(2.2)); // 这种反照率处理方式与LearOpenGL一致
        float metallic      = texture(u_MetallicMap, texCoords).r * u_Metallic; // 金属度
        float roughness     = texture(u_RoughnessMap, texCoords).r * u_Roughness; // 粗糙度
        float ao            = texture(u_AmbientOcclusionMap, texCoords).r; // 环境光遮蔽AO
        vec3 normal;
    
        if (u_EnableNormalMapping) // 是否使用法线贴图
        {
            normal = texture(u_NormalMap, texCoords).rgb; // 法线贴图的原始值
            normal = normalize(normal * 2.0 - 1.0);   // 法线贴图矢量坐标范围变成-1到1
            normal = normalize(fs_in.TBN * normal);   // 变换到全局坐标系下
        }
        else
        {
            normal = normalize(fs_in.Normal); // 使用顶点着色器输出的法线
        }
    
        vec3 N = normalize(normal); 
        vec3 V = normalize(ubo_ViewPos - fs_in.FragPos); // 计算视线方向
    
        vec3 F0 = vec3(0.04); 
        F0 = mix(F0, albedo, metallic); // 插值方式得到平面的基础反射率F0
    	           
        // reflectance equation
        vec3 Lo = vec3(0.0);
        vec3 ambientSum = vec3(0.0); // 环境光结果
    
        for (int i = 0; i < ssbo_Lights.length(); ++i) 
        {
            // 两种环境光灯光
            if (int(ssbo_Lights[i][3][0]) == 3)
            {
                ambientSum += CalcAmbientBoxLight(ssbo_Lights[i]);
            }
            else if (int(ssbo_Lights[i][3][0]) == 4)
            {
                ambientSum += CalcAmbientSphereLight(ssbo_Lights[i]);
            }
            else
            {
                // calculate per-light radiance
                // 光源方向
                vec3 L = int(ssbo_Lights[i][3][0]) == 1 ? -ssbo_Lights[i][1].rgb : normalize(ssbo_Lights[i][0].rgb - fs_in.FragPos);
                vec3 H = normalize(V + L);// 半程向量
                float distance    = length(ssbo_Lights[i][0].rgb - fs_in.FragPos);
                float lightCoeff = 0.0; // 最终到片元处的光强系数 
    
                switch(int(ssbo_Lights[i][3][0]))
                {
                    case 0:
                        lightCoeff = LuminosityFromAttenuation(ssbo_Lights[i]) * ssbo_Lights[i][3][3]; // 点光源要考虑随距离衰减
                        break;
    
                    case 1:
                        lightCoeff = ssbo_Lights[i][3][3]; // 方向光无衰减
                        break;
    
                    // 聚光灯的计算
                    case 2:
                        const vec3  lightForward    = ssbo_Lights[i][1].rgb;
                        const float cutOff          = cos(radians(ssbo_Lights[i][3][1]));
                        const float outerCutOff     = cos(radians(ssbo_Lights[i][3][1] + ssbo_Lights[i][3][2]));
    
                        const vec3  lightDirection  = normalize(ssbo_Lights[i][0].rgb - fs_in.FragPos);
                        const float luminosity      = LuminosityFromAttenuation(ssbo_Lights[i]);
    
                        /* Calculate the spot intensity */
                        const float theta           = dot(lightDirection, normalize(-lightForward)); 
                        const float epsilon         = cutOff - outerCutOff;
                        const float spotIntensity   = clamp((theta - outerCutOff) / epsilon, 0.0, 1.0);
    
                        lightCoeff = luminosity * spotIntensity * ssbo_Lights[i][3][3];
                        break;
                }
    
                vec3 radiance = UnPack(ssbo_Lights[i][2][0]) * lightCoeff;
                
                // 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 = ambientSum * albedo * ao;// 环境光最终贡献
        vec3 color = ambient + Lo; // 环境光与cook-torrance模型累加
    	
        // HDR色调映射
        color = color / (color + vec3(1.0));
        // gamma 矫正
        color = pow(color, vec3(1.0/2.2));  
       
        FRAGMENT_COLOR = vec4(color, albedoRGBA.a); // alpha使用反照率贴图
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262

    Fragment Shader大体分为三部分:

    1. 从贴图中获取反照率、金属度、粗糙度、法线数据
    2. 计算灯光光照,环境光灯光只影响环境光;方向光、聚光灯、点光源会影响光强lightCoeff,最终的光照使用cook-torrance模型进行计算,公式可以参考LearnOpenGL
    3. 最后进行环境光与PBR模型结果进行叠加,并进行色调映射与gamma矫正,这里使用的公式在LearnOpenGL中都有的

    总结:
    这个PBR Shader整体上与LearnOpenGL中的理论一致,看完LearnOpenGL之后再看这个Shader就比较简单了。

    完结总结:

    写的这里,这个专栏暂时告一段落了,主要分析了Overload的Render模块,其他的包括UI、物理引擎、音频等模块没有涉及。Overload是一个Demo性质的游戏引擎,其渲染涉只涉及最基础的渲染方式,是对OpenGL简单封装,远远满足不了实际游戏开发需求,只能作为渲染引擎入门。
    另外,这个专栏的文章只聚焦一些细节,对应架构涉及很少,因为本人发现架构方面的文章参考性不大,一旦一个软件定型架构方面的改动很困难,读了软件架构的文章也很难在工作中用上。故单纯只介绍一个技术点反而可能拿来直接使用。最后希望能对大家有所帮助!

  • 相关阅读:
    java毕业设计基于Bootstrap的家具商城系统设计mybatis+源码+调试部署+系统+数据库+lw
    Oracle数据库 on duplicate key update功能
    43.227.196.1服务器出现自动死机情况,会是什么问题呢?
    重点来了,具有优质脂肪的坚果居然是减肥的好帮手!
    Mac系统,webots和pycharm联合仿真,配置问题解决方案!
    阿里云的应用型负载均衡,网络型负载均衡,传统型负载均衡
    BadUsb程序大全-值得收藏
    深度优先搜索和广度优先搜索
    Linux下的环境变量
    LeetCode第 85 场双周赛总结
  • 原文地址:https://blog.csdn.net/loveoobaby/article/details/133955742