OpenGL ES教程——基础光照

210 阅读5分钟

image.png

大家画过素描画吗?

如果想让画面有立体感,要怎么办呢?阴影是关键。

光照,即是opengl的“素描画技巧”,可通过它绘制光的强弱变化,增加立体感,真实感。

1、颜色

可能会有这样的现象:有些物体在阳光下看着是A颜色,但在另一种光线照射下就会变成B颜色。

这是因为物体的颜色取决于两个方面,自己的颜色以及光照的颜色。物体会反射代表自身的颜色,被反射的颜色即是我们感知的颜色。

在opengl中,两个颜色相乘,即是最终的感知颜色:

glm::vec3 lightColor(1.0f, 1.0f, 1.0f); //光照颜色
glm::vec3 toyColor(1.0f, 0.5f, 0.31f); //物体本身颜色
glm::vec3 result = lightColor * toyColor; // = (1.0f, 0.5f, 0.31f); 反射颜色,即我们感知颜色

2、冯氏光照模型

自然的光照非常复杂,冯氏光照模型是其中一种较简单的抽象。

冯氏光照模型的主要结构由3个分量组成:环境(Ambient)、漫反射(Diffuse)和镜面(Specular)光照

image.png

  • 环境光照(Ambient Lighting):即使在黑暗的情况下,世界上通常也仍然有一些光亮(月亮、远处的光),所以物体几乎永远不会是完全黑暗的。为了模拟这个,我们会使用一个环境光照常量,它永远会给物体一些颜色。
  • 漫反射光照(Diffuse Lighting):模拟光源对物体的方向性影响(Directional Impact)。它是冯氏光照模型中视觉上最显著的分量。物体的某一部分越是正对着光源,它就会越亮。
  • 镜面光照(Specular Lighting):模拟有光泽物体上面出现的亮点。镜面光照的颜色相比于物体的颜色会更倾向于光的颜色。

3、环境光照

如何来计算环境光照呢?

我们通过用一个很小的环境因子,来模拟环境光照。

void main() { 
    float ambientStrength = 0.1; 
    vec3 ambient = ambientStrength * lightColor; 
    vec3 result = ambient * objectColor; 
    FragColor = vec4(result, 1.0);
}

这样的效果如下:

image.png

4、漫反射

漫反射光照使物体上与光线方向越接近的片段能从光源处获得更多的亮度

image.png

如上图:离光源点最近的地方应该是光源点的正下方,即角度为0,光线垂直于物体表面时。

当角度越大时(趋近于90度时),光源的影响应该越小。当角度越小时,光源的影响越大,物体更亮。那么怎么获取这个角度呢?

光源的位置是固定的,可知的(本示例中绘制了两个正方体,其中一个是模拟的光源,可用它作光源位置)。另一个垂直于物体表面的向量是什么呢?我们叫法向量,由于体例中是立方体,计算垂直于立方体表面的法向量是较简单的,可以预先算好。那么,如何计算它们之间的角度呢?通过向量的点乘来计算,两个单位向量之间的点乘结果就是它们角度的cos值。

vec3 norm = normalize(normal); //对法向量标准化
vec3 lightDir = normalize(lightPos - fragPos); //光源位置减去物体绘制时的位置,即光照的方向
float diff = max(dot(norm, lightDir), 0.0); //光照方向和法向量做点乘,由于点乘可能有负数,负结果无意义,即最小为0
vec3 diffcuse = diff * lightColor; //影响系统乘以光源颜色,即是最终的漫反射颜色

上述代码中的fragPos值是什么:绘制时物体上片段的位置

我们现在只知道光源的位置,要知道光线的方向,必须还得知道光照的位置,即片段位置。这里通常只需要片段在世界坐标中的位置。mvp三大矩阵当中,只有model矩阵影响物体在世界坐标中的位置,所以通过如下方式计算它:

fragPos = vec3(model * vec4(a_position, 1.0));

5、镜面光照

和漫反射光照一样,镜面光照也决定于光的方向向量和物体的法向量,但是它也决定于观察方向,例如玩家是从什么方向看向这个片段的。镜面光照决定于表面的反射特性。如果我们把物体表面设想为一面镜子,那么镜面光照最强的地方就是我们看到表面上反射光的地方

image.png

从上图可以看出,计算镜面光照比漫反射更复杂一点,它多了一个观察向量,即眼睛的方向。眼睛的方向可以简单认为就是摄像机的位置

float specularStrength = 0.5; //加一个亮度影响因子,平衡效果
vec3 viewDir = normalize(viewPos - fragPos); //计算眼睛方向
vec3 reflectDir = reflect(-lightDir, norm); //reflect函数可计算反射方向
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32.0); //计算视线方向与反射方向的点乘(并确保它不是负值),然后取它的32次幂
vec3 specular = specularStrength * spec * lightColor; //最后的镜面反射效果

有个小细节需要注意:两个向量相减,如w - v,最后的结果为从v指向w。注意前文中的

vec3 lightDir = normalize(lightPos - fragPos)

光照方量指向的方向是光源,但reflect的第一个参数要求为从光源指向片段的向量,所以对lightDir取反。

通过如上三步,最终的效果为:

image.png

源码可参考本人github。