OpenGL|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|OpenGL 光照基础
    文章图片
    漫反射.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 330precision 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|OpenGL 光照基础
    文章图片
    镜面光.png
  • 当R和V的夹角θ越小时,人眼观察到的镜面光成分越明显。镜面反射系数定义为:specFactor = cos(θ)^s, 其中s表示镜面高光系数(shininess),它的值一般取2的整数幂,值越大则高光部分越集中。
  • 镜面反射颜色 = 光源的镜面光颜色 * 物体的镜面光材质颜色 * specFactor
    #version 330precision 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 330in 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() { // 环境光成分 vec3ambient = light.ambient * material.ambient; // 漫反射光成分 此时需要光线方向为指向光源 vec3lightDir = normalize(light.position - FragPos); vec3normal = normalize(FragNormal); floatdiffFactor = max(dot(lightDir, normal), 0.0); vec3diffuse = diffFactor * light.diffuse * material.diffuse; // 镜面反射成分 此时需要光线方向为由光源指出 floatspecularStrength = 0.5f; vec3reflectDir = normalize(reflect(-lightDir, normal)); vec3viewDir = normalize(viewPos - FragPos); floatspecFactor = pow(max(dot(reflectDir, viewDir), 0.0), material.shininess); vec3specular = specFactor * light.specular * material.specular; vec3result = 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之间的关系为:
    OpenGL|OpenGL 光照基础
    文章图片
    衰减因子.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|OpenGL 光照基础
    文章图片
    聚光灯.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|OpenGL 光照基础】OpenGL学习脚印:光源类型和使用多个光源(Light source and multiple lights)
OpenGL学习脚印: 光照基础(basic lighting)
OpenGL学习脚印: 光照中材质和lighting maps使用(material and lighting maps)

    推荐阅读