基础光照

一: 环境光照(Ambient Lighting)

  1. 即使在黑暗的情况下,世界上通常也仍然有一些光亮(月亮、远处的光),所以物体几乎永远不会是完全黑暗的。为了模拟这个,我们会使用一个环境光照常量,它永远会给物体一些颜色。
  2. 我们使用一个很小的常量(光照)颜色,添加到物体片段的最终颜色中,这样子的话即便场景中没有直接的光源也能看起来存在有一些发散的光。
  3. 把环境光照添加到场景里非常简单。我们用光的颜色乘以一个很小的常量环境因子,再乘以物体的颜色,然后将最终结果作为片段的颜色:

二: 漫反射光照(Diffuse Lighting)

  1. 模拟光源对物体的方向性影响(Directional Impact)。它是冯氏光照模型中视觉上最显著的分量。物体的某一部分越是正对着光源,它就会越亮。

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

  3. 不同片段朝向光源的方向的不同,这些片段被照亮的情况也不同。

  4. 法向量:垂直于顶点表面的(单位)向量。由于顶点本身并没有表面(它只是空间中一个独立的点),我们利用它周围的顶点来计算出这个顶点的表面。我们能够使用一个小技巧,使用叉乘对立方体所有的顶点计算法向量
    4.1 由于我们向顶点数组添加了额外的数据,所以我们应该更新光照的顶点着色器,然后更新顶点属性指针
    4.2 注:灯使用同样的顶点数组作为它的顶点数据,然而灯的着色器并没有使用新添加的法向量。我们不需要更新灯的着色器或者是属性的配置,但是我们必须至少修改一下顶点属性指针来适应新的顶点数组的大小:
    4.3 所有光照的计算都是在片段着色器里进行,所以我们需要将法向量由顶点着色器传递到片段着色器

  5. 现在对每个顶点都有了法向量,但是我们仍然需要光源的位置向量和片段的位置向量。
    5.1 由于光源的位置是一个静态变量,我们可以简单地在片段着色器中把它声明为uniform:lightPos
    5.2 我们会在世界空间中进行所有的光照计算,因此我们需要一个在世界空间中的顶点位置。我们可以通过把顶点位置属性乘以模型矩阵(不是观察和投影矩阵)来把它变换到世界空间坐标。这个在顶点着色器中很容易完成,所以我们声明一个输出变量,并计算它的世界空间坐标。

    out vec3 FragPos;  
    out vec3 Normal;
    void main()
    {
    gl_Position = projection * view * model * vec4(aPos, 1.0);
    FragPos = vec3(model * vec4(aPos, 1.0));//`这里的FragPos也就是片段在世界坐标中的位置。
    Normal = aNormal;
    } 
    

    5.3 我们已经把法向量从顶点着色器传到了片段着色器。可是,目前片段着色器里的计算都是在世界空间坐标中进行的。所以,我们应该把法向量也转换为世界空间坐标,但是这不是简单地把它乘以一个模型矩阵就能搞定的。
    5.4 法向量只是一个方向向量,不能表达空间中的特定位置。同时,法向量没有齐次坐标(顶点位置中的w分量)。这意味着,位移不应该影响到法向量。因此,如果我们打算把法向量乘以一个模型矩阵,我们就要从矩阵中移除位移部分,只选用模型矩阵左上角3×3的矩阵(注意,我们也可以把法向量的w分量设置为0,再乘以4×4矩阵;这同样可以移除位移)。对于法向量,我们只希望对它实施缩放和旋转变换。
    5.5 其次,如果模型矩阵执行了不等比缩放,顶点的改变会导致法向量不再垂直于表面了。因此,我们不能用这样的模型矩阵来变换法向量。
    5.6 修复不等比缩放的诀窍是使用一个为法向量专门定制的模型矩阵。这个矩阵称之为法线矩阵(Normal Matrix):模型矩阵左上角的逆矩阵的转置矩阵,它使用了一些线性代数的操作来移除对法向量错误缩放的影响。
    5.7 在顶点着色器中,我们可以使用inverse和transpose函数自己生成这个法线矩阵,这两个函数对所有类型矩阵都有效。注意我们还要把被处理过的矩阵强制转换为3×3矩阵,来保证它失去了位移属性以及能够乘以vec3的法向量。

    Normal = mat3(transpose(inverse(model))) * aNormal; 
    
  6. 在片段着色器中添加漫反射光照计算
    6.1 计算光源和片段位置之间的方向向量。前面提到,光的方向向量是光源位置向量与片段位置向量之间的向量差。并且进行标准化。

    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(lightPos - FragPos); 
    

    这里的方向差的计算和摄像机方向向量的计算方法相似,这里的方向是片段指向光源
    6.2 我们对norm和lightDir向量进行点乘,计算光源对当前片段实际的漫发射影响。结果值再乘以光的颜色,得到漫反射分量。两个向量之间的角度越大,漫反射分量就会越小。

三: 镜面光照(Specular Lighting)

  1. 模拟有光泽物体上面出现的亮点。镜面光照的颜色相比于物体的颜色会更倾向于光的颜色。
  2. 和漫反射光照一样,镜面光照也是依据光的方向向量和物体的法向量来决定的,但是它也依赖于观察方向,例如玩家是从什么方向看着这个片段的。
  3. 我们通过反射法向量周围光的方向来计算反射向量。然后我们计算反射向量和视线方向的角度差,如果夹角越小,那么镜面光的影响就会越大。它的作用效果就是,当我们去看光被物体所反射的那个方向的时候,我们会看到一个高光。
  4. 观察向量是镜面光照附加的一个变量,我们可以使用观察者世界空间位置和片段的位置来计算它。之后,我们计算镜面光强度,用它乘以光源的颜色,再将它加上环境光和漫反射分量。
  5. 为了得到观察者的世界空间坐标,我们简单地使用摄像机对象的位置坐标代替(它当然就是观察者)。所以我们把另一个uniform添加到片段着色器,把相应的摄像机位置坐标传给片段着色器:
  6. 计算高光强度
    6.1 首先,我们定义一个镜面强度(Specular Intensity)变量,给镜面高光一个中等亮度颜色,让它不要产生过度的影响。
    6.2 下一步,我们计算视线方向向量,和对应的沿着法线轴的反射向量:
    vec3 viewDir = normalize(viewPos - FragPos);//观察方向向量
    vec3 reflectDir = reflect(-lightDir, norm); //光的反射向量 
    
    6.3 需要注意的是我们对lightDir向量进行了取反。reflect函数要求第一个向量是从光源指向片段位置的向量,但是lightDir当前正好相反,是从片段指向光源(由先前我们计算lightDir向量时,减法的顺序决定)。为了保证我们得到正确的reflect向量,我们通过对lightDir向量取反来获得相反的方向。第二个参数要求是一个法向量,所以我们提供的是已标准化的norm向量。
    6.4 剩下要做的是计算镜面分量。我们先计算视线方向与反射方向的点乘(并确保它不是负值),然后取它的32次幂。这个32是高光的反光度(Shininess)。一个物体的反光度越高,反射光的能力越强,散射得越少,高光点就会越小。
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
    vec3 specular = specularStrength * spec * lightColor; 
    

四: 最后一件事:最终结果:

vec3 result = (ambient + diffuse + specular) * objectColor;
FragColor = vec4(result, 1.0); 
posted @ 2019-08-12 09:20  Garrett_Wale  阅读(279)  评论(0编辑  收藏  举报