美文网首页
OpenGL 光照基础

OpenGL 光照基础

作者: 瀚_ | 来源:发表于2019-07-07 20:29 被阅读0次

    颜色与光照的关系

    • 我们看到的物体的颜色,实际是光照射物体后发射的光进入眼睛后感受到的颜色,而不是物体实际材料的颜色。
    • 光照射到物体上,一部分会被物体吸收,一部分被发射进入眼睛,我们看到的颜色就是反射后进入我们眼睛的颜色。
    • 颜色吸收和反射的过程可以表示为:LightIntensity * ObjectColor = ReflectColor.
    • 计算为:(R, G, B) * (X, Y, Z) = (XR, YG, ZB)

    Phong Reflection Model

    • Phong Reflection Model是经典的光照模型,它计算光照为:环境光 + 漫反射光 + 镜面光

    环境光

    • 环境光是场景中光源给定或者全局给定的一个光照常量。
    • 它一般很小,主要是为了模拟计时场景中没有光照时,也不是全部黑屏的效果。
    • 环境光 = 光源的环境光颜色 * 物体的环境光材质颜色
    ```
    varying vec3 objectColor;
    void main() {
        // 环境光成分,至少有10%的光照到物体的所有面
        float = ambientStrength = 0.1f;
        // 环境光颜色
        vec3 ambient = ambientStrength * lightColor * objectColor;
        gl_FragColor = vec4(ambient, 1.0); 
    }
    ```
    

    漫反射光

    • 漫反射光强度与光线入射方向和物体表面的法向量之间的夹角θ相关。


      漫反射.png
    • 需要计算的两个向量为:

      • 光源和顶点位置之间的向量L.
      • 法向量N 可以通过顶点属性指定,但顶点经过模型视图变换后需要重新计算。
    • 在世界坐标系中,计算L时,光源lightPos是在世界坐标系中指定的位置,直接使用即可。

      • 顶点位置需要变换到世界坐标系中,利用模型(Model)矩阵进行变换:FragPos = vec3(model * vec4(position, 1.0)).

      • 计算N时,不能直接使用Model * normal来换取变换后的法向量,需要使用:Normal = mat3(transpose(inverse(model))) * normal.

      • 其中inverse()为求矩阵的逆,transpose()求矩阵的转置。

    • 漫反射颜色 = 光源的漫反射颜色 * 物体的漫反射材质颜色 * DiffuseFactor,其中 DiffuseFactor = max(0, dot(N,L))

      顶点着色器代码:

      #version 330
      layout(location = 0) in vec3 position;
      layout(location = 1) in vec2 textCoord;
      layout(location = 2) in vec3 normal;
      
      out vec3 FragPos;
      out vec2 TextCoord;
      out vec3 FragNormal;
      
      uniform mat4 projection;
      uniform mat4 view;
      uniform mat4 model;
      
      void main() {
          gl_Position = projection * view * model * vec4(positon, 1.0);
          FragPos = vec3(model * vec4(position, 1.0));
          TextCoord = textCoord;
          mat3 normalMatrix = mat3(transpose(inverse(model)));
          FragNormal = normalMatrix * normal;
      }
      

      片元着色器代码:

      #version 330
      
      precision mediump float;
      
      uniform vec3 lightPos;  // 光源位置
      uniform vec3 lightColor;
      uniform vec3 objectColor;
      
      in vec3 FragPos;       // 模型变换的顶点位置
      in vec2 TextCoord;
      in vec3 FragNormal;
      
      void main() {
      
          vec3 lightDir = normalize(lightPos - FragPos);
          vec3 normal = normalize(FragNormal);
          float diffuseFactor = max(dot(lightDir, normal), 0.0);
          vec3 diffuse = diffuseFactor * lightColor * objectColor;
          gl_FragColor = vec4(diffuse, 1.0);
      }
      

    镜面反射光

    • 镜面光模拟的是物体表面光滑时反射的高亮光,镜面光反射的通常是光的颜色,而不是物体的颜色。
    • 计算镜面光,需要考虑光源和顶点位置之间的向量L、法向量N、反射方向R、观察者和顶点位置之间的向量V之间的关系。


      镜面光.png
    • 当R和V的夹角θ越小时,人眼观察到的镜面光成分越明显。镜面反射系数定义为:specFactor = cos(θ)^s, 其中s表示镜面高光系数(shininess),它的值一般取2的整数幂,值越大则高光部分越集中。

    • 镜面反射颜色 = 光源的镜面光颜色 * 物体的镜面光材质颜色 * specFactor

      #version 330
      
      precision mediump float;
          
      uniform vec3 lightPos;  // 光源位置
      uniform vec3 lightColor;
      uniform vec3 objectColor;
      uniform vec3 viewPos;
          
      in vec3 FragPos;       // 模型变换的顶点位置
      in vec2 TextCoord;
      in vec3 FragNormal;
      
      void main() {
          // 镜面反射成分
          float specularStrength = 0.5f;
          vec3 reflectDir = normalize(reflect(-lightDir, normal));
          vec3 viewDir = normalize(viewPos - FragPos);
          float specFactor = pow(max(dot(reflectDir, viewDir), 0.0), 32); //32为镜面高光系数
          vec3 specular = specularStrength * specFactor * lightColor * objectColor;
          gl_FragColor = vec4(specular, 1.0); 
      }
      
      

      利用reflect函数计算光的出射方向时,要求入射方向指向物体表面位置,因此这里翻转了lightDir

    材质属性

    • 不同物体对光有不同的反映,要模拟不同物体接受光照后的效果,需要考虑物体的材质属性。

    • 为物体指定材质属性时,可以为物体指定这三个不同成分的光的强度作为材质属性,同时加上高光系数shininess。

    • 同时可以为光源不同成分指定不同的强度。片元着色器的代码为:

      #version 330
      
      in vec3 FragPos;
      in vec2 TextCoord;
      in vec3 FragNormal;
      
      out vec4 color;
      
      // 材质属性结构体
      struct MaterialAttr
      {
          vec3 ambient;   // 环境光
          vec3 diffuse;    // 漫反射光
          vec3 specular;   // 镜面光
          float shininess; //镜面高光系数
      };
      // 光源属性结构体
      struct LightAttr
      {
          vec3 position;
          vec3 ambient;
          vec3 diffuse;
          vec3 specular;
      };
      
      uniform MaterialAttr material;
      uniform LightAttr light;
      uniform vec3 viewPos;
      
      void main()
      {   
          // 环境光成分
          vec3    ambient = light.ambient * material.ambient;
      
          // 漫反射光成分 此时需要光线方向为指向光源
          vec3    lightDir = normalize(light.position - FragPos);
          vec3    normal = normalize(FragNormal);
          float   diffFactor = max(dot(lightDir, normal), 0.0);
          vec3    diffuse = diffFactor * light.diffuse * material.diffuse;
      
          // 镜面反射成分 此时需要光线方向为由光源指出
          float   specularStrength = 0.5f;
          vec3    reflectDir = normalize(reflect(-lightDir, normal));
          vec3    viewDir = normalize(viewPos - FragPos);
          float   specFactor = pow(max(dot(reflectDir, viewDir), 0.0), material.shininess);
          vec3    specular = specFactor * light.specular * material.specular;
      
          vec3    result = ambient + diffuse + specular;
          color   = vec4(result , 1.0f);
      }
      

    光源类型

    方向光源

    • 方向光源的方向几乎是平行,只有一个方向。

    • 不考虑光的衰减,它与光源的具体位置无关,只需要为它指定方向即可。

    • 一般我们指定方向光源的方向时,习惯从光源指向物体,而在计算光照时,又需要从物体指向光源的方向,因此需要做一个翻转。

      // 方向光源属性结构体
      struct DirLightAttr
      {
          vec3 direction; // 方向光源
          vec3 ambient;
          vec3 diffuse;
          vec3 specular;
      };
      
    • 计算光照时,直接使用direction
      vec3 lightDir = normalize(-light.direction)

    点光源

    • 物体与光源的距离d增大时,光照的强度将会减弱
    • 光照强度的衰减系数Fatt与距离d之间的关系为:
      衰减因子.png

    点光源结构体:
    ```
    // 点光源属性结构体
    struct PointLightAttr
    {
    vec3 position;
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;

        float constant; // 衰减常数
        float linear;   // 衰减一次系数
        float quadratic; // 衰减二次系数
    };
    ```
    
    计算光照强度后乘以衰减系数
    
    ```
      // 计算衰减因子
    float distance = length(light.position - FragPos); // 在世界坐标系中计算距离
    float attenuation = 1.0f / (light.constant + light.linear * distance + light.quadratic * distance * distance);
    vec3 result = (ambient + diffuse + specular) * attenuation;
    color   = vec4(result , 1.0f);
    ```
    

    聚光灯光源

    • 聚光灯光源的特点是光只在一个指定的范围内发散。


      聚光灯.png
    • 聚光灯需要指定3个属性:

      • SpotDir 聚光灯的灯轴的方向
      • LightPos 聚光灯的位置
      • Cutoff 聚光灯的张角 即图中的ϕ
    • 计算聚光灯的光照效果时需要计算的量为:

      • lightDir 物体的位置和光源位置之差构成的光照射方向
      • θ是lightDir与SpotDir之间的夹角
        // 聚光灯光源属性结构体
      struct SpotLightAttr
      {
          vec3 position;  // 聚光灯的位置
          vec3 direction; // 聚光灯的spot direction
          float cutoff;   // 聚光灯张角的余弦值
          vec3 ambient;
          vec3 diffuse;
          vec3 specular;
      
          float constant; // 衰减常数
          float linear;   // 衰减一次系数
          float quadratic; // 衰减二次系数
      };
      
      

      片元着色器实现思路:

         void main()
      {   
         // 环境光成分
         vec3 lightDir = normalize(light.position - FragPos);
         // 光线与聚光灯spotDir夹角余弦值
         float theta = dot(lightDir, normalize(-light.direction));
          if(theta > light.cutoff)    
          {
              // 在聚光灯张角范围内 计算漫反射光成分 镜面反射成分 
          }
          else
          {
              // 不在张角范围内时只有环境光成分
          }
      }
      

    参考博客

    OpenGL学习脚印:光源类型和使用多个光源(Light source and multiple lights)
    OpenGL学习脚印: 光照基础(basic lighting)
    OpenGL学习脚印: 光照中材质和lighting maps使用(material and lighting maps)

    相关文章

      网友评论

          本文标题:OpenGL 光照基础

          本文链接:https://www.haomeiwen.com/subject/qapihctx.html