OpenGL 光照基础

颜色与光照的关系

  • 我们看到的物体的颜色,实际是光照射物体后发射的光进入眼睛后感受到的颜色,而不是物体实际材料的颜色。
  • 光照射到物体上,一部分会被物体吸收,一部分被发射进入眼睛,我们看到的颜色就是反射后进入我们眼睛的颜色。
  • 颜色吸收和反射的过程可以表示为: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); 
}
```

漫反射光

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


    OpenGL 光照基础_第1张图片
    漫反射.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之间的关系。


    OpenGL 光照基础_第2张图片
    镜面光.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);
```

聚光灯光源

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


    OpenGL 光照基础_第3张图片
    聚光灯.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 光照基础)