OpenGL学习笔记《八》光照

  在理解opengl中的光照之前,先理解一下颜色。在我们现实生活中,我们看到的物体颜色,是物体反射的颜色,即没有被物体吸收掉的部分。opengl利用这个,在系统中模拟的颜色,就是定义一个表示对象的颜色向量A,再顶一个一个表示光源的颜色向量B,然后在片段着色器中将两个向量相乘得到表示物体的颜色。因此大致可以理解为,opengl中最终要渲染出的颜色,其实是表示对象的颜色乘以某个影响系数,得出来的。

  因为现实生活中的光照非常的复杂,早期计算设备条件不足的情况下不能做很复杂的计算,得出的光照效果比较粗糙。近年来随着计算设备能力的提升,开始出现光照追踪技术,尽可能的模拟出现实生活中的光照效果。

  opengl中采用的光照模型,叫做冯氏光照模型(Phong lighting model),由三个分量组成:环境光(ambient),漫散射光(diffuse),镜面反射光(specular),三个分量在opengl中分别使用一个vec3向量表示,vec3三个分量x,y,z分别表示r,g,b。单独一个分量对最终渲染的影响,就是用表示分量颜色的向量去乘以对象颜色的向量,得到的结果就是opengl系统模拟的对象在受光照影响下的效果。而冯氏光照模型,就是将三个分量的效果组合起来,得到最终的影响效果。三种模式效果如下:

 

 

环境光(ambient)

  在现实生活中光照的来源有很多,如别的物体反射过来的,因此可能在我们并没有直接看到光源的情况下,我们也能看到眼前的某些物体(即这些物体有反射光线过来)。冯氏光照模型中的环境光分量就是用来模拟这种效果,我们在这里简单的模拟,即在光源颜色的基础上乘以一个常量系数,得到的就是环境光颜色,再将这个颜色乘以对象颜色,得到模拟出来的效果,在这里假定这个常量为0.1,调整片元着色器代码:

void main()
{
    float ambientStrength = 0.1;
    vec3 ambient = ambientStrength * lightColor;

    vec3 result = ambient * objectColor;
    FragColor = vec4(result, 1.0);
}  

得到的效果如下:

 

 

 通过调整常量的值,我们可以模拟出在不同的光照强度下,对象的不同表现。

漫散射光(diffuse)

  在现实生活中,以灯泡为光源,我们拿一个物体放到离灯泡不同的位置,物体表现出来的颜色效果会有不同,如果离的近、与灯泡垂直了,那么物体表面就会更亮,如果离的远那么就会相对暗点。冯氏光照模型中的漫散射光分量模拟的就是这种效果,以下图为例:

 

 

 我们提到的与灯泡垂直,离灯泡远,其实可以理解为上图中θ角度的变化,θ角度越小,即接近于垂直,那么此时物体表现的就会越亮,反之则暗,参考上文中提到的环境光系数的影响,在这里我们也可以模拟一个系数,来达到效果。从数学角度出发,两个单位向量的点乘,结果等于两个单位向量的模乘以两个向量夹角的余弦值,0-90度范围内,夹角越小,值越大。因此在这里我们可以根据这个特性,来模拟漫散射效果。

  根据光源位置,顶点坐标,我们可以得到表示光源方向的环境向量,然后我们再引入法线向量这个概念(垂直于表面的向量),用两个向量相乘得到的值,乘以光源的颜色就可以得到模拟漫反射的光源颜色,再将这个颜色乘以对象的颜色,就可以模拟出漫反射影响下的对象表现效果。

  两个向量相减,可以得到一个表示方向的向量,在这里我们是用顶点的位置减去光源的位置,得到一个从顶点位置指向光源的单位向量,而法线向量我们可以根据对象的形状直接得到,调整片元着色器代码:

void main(){

    vec3 norm = normalize(oNormal);
    vec3 lightDir = normalize(u_lightPos - oFragPos);
    float diff = max(dot(norm, lightDir), 0.0f);
    vec3 diffuse = diff * u_lightColor;

    float ambientStrength = 0.1f;
    vec3 ambient = ambientStrength * u_lightColor;
    vec3 result = (ambient + diffuse) * u_objectColor;

    FragColor = vec4(result, 1.0f);
}

  因为我们提到了是用单位向量进行计算,所以保险起见对于我们要使用到的向量,都调用normalize处理一下。最终得到的效果:

 

 

 光源的位置,是白色矩形的位置,从上图就可以看到与光源夹角越小的就更亮,否则就更暗。

镜面反射光(specular)

  在现实生活中,我们也有这样的体会,我们从不同的角度去观察物体,看到的效果也会不一样。在冯氏光照模型中,使用镜面反射光来模拟这个效果,如下图所示:

 

 

 在这里使用到的是光源向量基于法线对称后,与表示视线方向向量的夹角,来模拟镜面反射的效果,用两个向量点乘后得到的值,再乘以光源颜色,得到表示镜面反射光分量的颜色向量,再去乘以对象的颜色,得到模拟出来的效果。调整后的片元着色器代码:

void main(){
    float ambientStrength = 0.1f;
    vec3 ambient = ambientStrength * u_lightColor;

    vec3 norm = normalize(oNormal);
    vec3 lightDir = normalize(u_lightPos - oFragPos);
    float diff = max(dot(norm, lightDir), 0.0f);
    vec3 diffuse = diff * u_lightColor;

    float specularStrength = 0.5f;
    vec3 viewDir = normalize(u_viewPos - oFragPos);
    vec3 reflectDir = reflect(-lightDir, norm);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0f), 32);
    vec3 specular = specularStrength * spec * u_lightColor;

    vec3 result = (ambient + diffuse + specular) * u_objectColor;
    FragColor = vec4(result, 1.0f);
}

我们使用GLSL内置的reflect函数计算反射后的光源方向,然后计算与视线方向单位向量的夹角值,最后我们取这个值的32次方值,取的次方值越高,在表现上就越像聚焦的效果,如下图:

 

 最终我们将三个分量的值相加,再去乘以对象的颜色,就得到了冯氏光照模型下模拟出来的效果。

材质(Material)Lighting map投光物(Light caster)

  现实生活中,光照的效果非常复杂,比如一组物体,离光源的远近,会影响物体的亮度;木头和铁块表现出来的效果不同;以聚光灯为光源照射一组物体,中心区域的物体表现的会亮一点,周围区域的会表现的暗一点。我们上面提到的冯氏光照模型其实也可以模拟出这种效果,我们可以看到上面的片元着色器代码中,环境光、漫散射光、镜面反射光都有乘以一个常量,如果我们动态的去调整这个常量,就可以得到不同的效果。

  如,我们模拟一个离光源远近,物体表现不同的效果,我们可以乘以一个衰减系数,这个衰减系数的计算有一个公式:

\begin{equation} F_{att} = \frac{1.0}{K_c + K_l * d + K_q * d^2} \end{equation}

  其中,分子部分为常量1,保证最终得到的系数值需要小于1,分母由三个部分组成,一个常量项,一个线性项,一个二次项部分;d则表示距离光源的远近,用来模拟下图的系数值:

 

   即随着距离的增加,系数值变小,到一定距离之后这个系数值基本维持在一个比较小的范围。调整后的片元着色器代码:

// attenuation
    float distance = length(u_light.position - oFragPos);
    float attenuation = 1.0 / (u_light.constant + u_light.linear * distance + u_light.quadratic * (distance * distance));

    // ambient
    vec3 ambient = u_light.ambient * vec3(texture(u_material.diffuse, oTexCoords));
    ambient *= attenuation;

    // diffuse
    vec3 norm = normalize(oNormal);
    vec3 lightDir = normalize(u_light.position - oFragPos);
    float diff = max(dot(norm, lightDir), 0.0f);
    vec3 diffuse = u_light.diffuse * (diff * vec3(texture(u_material.diffuse, oTexCoords)));
    diffuse *= attenuation;

    // specular
    vec3 viewDir = normalize(u_viewPos - oFragPos);
    vec3 reflectDir = reflect(-lightDir, norm);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0f), u_material.shininess);
    vec3 specular = u_light.specular * (spec * vec3(texture(u_material.specular, oTexCoords)));
    specular *= attenuation;

    vec3 result = (ambient + diffuse + specular);
    FragColor = vec4(result, 1.0f);

得到的效果如下:

 

 

  以上就是简单的理解一下opengl中的光照。学习的网站上通过一个章节来讲解,主要就是围绕对冯氏光照模型的三个分量,乘以不同的系数值,来达到模拟现实生活中不同的效果。

posted @ 2019-10-25 16:59  Le Ciel  阅读(663)  评论(0编辑  收藏  举报