OpenGL学习笔记(九)

OpenGL 中级篇(五)

(一)光照贴图

在着色器中使用漫反射贴图和纹理教程介绍的一样。这次把纹理以sampler2D类型储存在Material结构体中。使用diffuse贴图替代早期定义的vec3类型的diffuse颜色。
在这里插入图片描述
改为如下:
在这里插入图片描述
注意:也移除了amibient材质颜色向量,因为ambient颜色绝大多数情况等于diffuse颜色,所以不需要分别去储存它,当然也可以继续保留ambient的vec3向量,但整个物体仍只能拥有一个环境光颜色。如果想要对不同片段有不同的环境光值,需要对环境光值单独使用另外一个纹理。

修改片段着色器

接下来只需要从纹理中采样片段的漫反射颜色值即可:在片段着色器中将会再次用到纹理坐标。
同时不要忘记把ambient材质的颜色设置为diffuse材质的颜色:
在这里插入图片描述
在这里插入图片描述
为了让它工作,需要用纹理坐标更新顶点数据,把它们作为顶点属性传递到片段着色器,把纹理加载并绑定到合适的纹理单元。

更新顶点数据

顶点数据现在包括了顶点位置,法线向量和纹理坐标,每个立方体的顶点都有这些属性。
在这里插入图片描述

更新顶点着色器

更新顶点着色器来接受纹理坐标作为顶点属性,然后发送到片段着色器:
在这里插入图片描述
要保证更新的顶点属性指针,不仅是VAO匹配新的顶点数据,也要把箱子图片加载为纹理。在绘制箱子之前,希望首选纹理单元被赋为material.diffuse这个uniform采样器,并绑定箱子的纹理到这个纹理单元:
在这里插入图片描述

最终效果

在这里插入图片描述

(二)镜面贴图

镜面贴图和其他纹理一样,所以代码和漫反射贴图的代码也相似。确保合理的加载了图片,生成一个纹理对象。由于在同样的片段着色器中使用另一个纹理采样器,必须为镜面贴图使用一个不同的纹理单元,在渲染前把它绑定到合适的纹理单元
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

最终效果

在这里插入图片描述

(三)投光物

目前使用的所有光照都来自于一个单独的光源,这是空间中的一个点。它的效果不错,但是在真实世界,有多种类型的光,它们每个表现都不同。将光投射(Cast)到物体的光源叫做投光物(Light Caster)
按光源划分,投光物可分为三种:

  • 定向光(directional light)

  • 点光源(point light)

  • 聚光(Spotlight)

定向光

当一个光源很远时,来自光源的每条光线接近于平行。看起来像所有的光线来自于同一个方向,无论物体和观察者在哪儿。当一个光源被设置为无限远时,它被称为定向光(Directional Light),因为所有的光线都有着同一个方向;它会独立于光源的位置。
在这里插入图片描述
着色器计算保持大致相同的要求,这次直接使用光的方向向量来代替用lightDir向量和position向量的计算:
在这里插入图片描述
注意:首先对light.direction向量取反。目前使用的光照计算需求一个从片段至光源的光线方向,但人们更习惯定义定向光为一个从光源出发的全局方向。所以需要对全局光照方向向量取反来改变它的方向,它现在是一个指向光源的方向向量了。而且,记得对向量进行标准化。

最终的lightDir向量将和以前一样用在漫反射和镜面光计算中。
为了清晰地观察到一个定向光对所有物体都有同样的影响,在实验场景先定义10个不同的箱子位置,为每个箱子生成不同的模型矩阵,每个模型矩阵包含相应的本地到世界变换:
在这里插入图片描述
同时,不要忘记定义光源的方向:
(注意,我们把方向定义为:从光源处发出的方向)
在这里插入图片描述

最终效果

看起来像天空上有一个光源,把光抛到物体上。且可以看到diffuse和specular元素都对该光源进行反射了

在这里插入图片描述

点光源

除了定向光之外我们也需要一些分散在场景中的点光源(Point Light)。点光源是处于世界中某一个位置的光源,它会朝着所有方向发光,但光线会随着距离逐渐衰减。想象作为投光物的灯泡和火把,它们都是点光源。
在这里插入图片描述

衰减

随着光线穿越距离的变远使得亮度也相应地减少的现象,称之为衰减(Attenuation)。
下面这个公式根据片段距光源的距离计算了衰减值,之后会将它乘以光的强度向量:
在这里插入图片描述
d代表片段到光源的距离。为计算衰减值,定义3个(可配置)项:常数项Kc,一次项Kl和二次项Kq

  • 常数项通常是1.0,作用:保证分母永远不会比1小,因为它可以利用一定的距离增加亮度,这个结果不会影响到所寻找的。
  • 一次项用于与距离值相乘,这会以线性的方式减少亮度。
  • 二次项会与距离的平方相乘,让光源以二次递减的方式减少强度。二次项在距离比较小的时候影响会比一次项小很多,但当距离值比较大的时候它就会比一次项更大了。

由于二次项的存在,光线会在大部分时候以线性的方式衰退,直到距离变得足够大,让二次项超过一次项,光的强度会以更快的速度下降。这样的结果就是,光在近距离时亮度很高,但随着距离变远亮度迅速降低,最后会以更慢的速度减少亮度。下面这张图显示了在100的距离内衰减的效果:
在这里插入图片描述
正确的值设置由很多因素决定:

  • 环境
  • 光覆盖的距离范围
  • 光的类型等

在大多数情况下,这都是经验的问题,以及适量的调整。下面这个表格显示了模拟一个(大概)真实的,覆盖特定半径(距离)的光源时,这些项可能取的一些值。第一列指定的是在给定的三项时光所能覆盖的距离。这些值是大多数光源很好的起始点,它们由Ogre3D的Wiki所提供:
在这里插入图片描述
可以看到常数项Kc基本都为1.0。一次项Kl为了覆盖更远的距离通常很小,二次项Kq就更小了。
在真实环境中,32到100的距离对大多数的光源都足够了。

实现光照

为实现衰减,在着色器中会需要三个额外数值:公式的常量、一次项和二次项。最好把它们储存在之前定义的Light结构体中。
在这里插入图片描述
然后,将在OpenGL中设置这些项:希望光源能够覆盖50的距离,所以会使用表格中对应的常数项、一次项和二次项:
在这里插入图片描述
在片段着色器中实现衰减很直接:根据公式简单的计算衰减值,再乘以ambient、diffuse和specular元素。
公式中仍需要计算片段距光源的距离。可以通过获取片段和光源之间的向量差,并获取结果向量的长度作为距离项。可以使用GLSL内建的length函数来完成这一点:
在这里插入图片描述
然后,在光照计算中,通过把衰减值乘以ambient、diffuse和specular颜色,包含这个衰减值。
注意:可以把ambient元素留着不变,这样amient光照就不会随着距离减少,但是如果使用多余1个的光源,所有的ambient元素会开始叠加,因此这种情况,希望ambient光照也衰减。
在这里插入图片描述

最终效果

可以看到现在只有最近处的箱子的前面被照亮。后面的箱子一点都没被照亮,因为它们距离光源太远了
在这里插入图片描述

聚光

要讨论的最后一种类型的光是聚光(Spotlight)。聚光是位于环境中某个位置的光源,它只朝一个特定方向而不是所有方向照射光线。这样的结果就是只有在聚光方向的特定半径内的物体才会被照亮,其它的物体都会保持黑暗。聚光很好的例子就是路灯或手电筒。
在这里插入图片描述
要讨论的最后一种类型的光是聚光(Spotlight)。聚光是位于环境中某个位置的光源,它只朝一个特定方向而不是所有方向照射光线。这样的结果就是只有在聚光方向的特定半径内的物体才会被照亮,其它的物体都会保持黑暗。聚光很好的例子就是路灯或手电筒。
OpenGL中聚光是用一个世界空间位置,一个方向和一个指定聚光半径的切光角来表示(切光角指定了聚光的半径)。计算的每个片段,如果片段在聚光的切光方向之间(就是在圆锥体内),就会把片段照亮。
在这里插入图片描述

  • LightDir:从片段指向光源的向量。
  • SpotDir:聚光所指向的方向。
  • Phiϕ:定义聚光半径的切光角。每个落在这个角度之外的,聚光都不会照亮。
  • Thetaθ:LightDir向量和SpotDir向量之间的角度。θθ值应该比ΦΦ值小,这样才会在聚光内。

所以要做的是,计算LightDir向量和SpotDir向量的点乘,然后将结果与切光角ϕ对比。

手电筒

手电筒(Flashlight)是一个坐落在观察者位置的聚光,通常瞄准玩家透视图的前面。基本上说,一个手电筒是一个普通的聚光,但是根据玩家的位置和方向持续的更新它的位置和方向。
所以需要为片段着色器提供的值,是聚光的位置向量(来计算光的方向坐标),聚光的方向向量和切光角。可以把这些值储存在Light结构体中:
在这里插入图片描述
下面把这些值传给着色器:
在这里插入图片描述
可以看到,为切光角设置了一个角度,但根据该角度计算余弦值,把结果传给片段着色器。原因是在片段着色器中,计算LightDir和SpotDir向量的点乘,点乘返回余弦值,不是角度,所以不能直接把一个角度和余弦值对比。为了获得这个角度,先计算给定切光角的余弦值,然后把结果传递给片段着色器。由于每个角度都被表示为余弦了,可以直接对比它们,而不用进行其他操作。
现在剩下要做的是计算θ值,用它和ϕ值对比,来决定是否在聚光的内部:
在这里插入图片描述
首先计算lightDir和取反的direction向量的点乘(因为想要向量指向光源,而不是从光源作为指向出发点)。确保对所有相关向量进行了标准化处理。
注意:可能奇怪为什么if条件中使用 > 符号而不是 < 符号。为了在聚光以内,theta不是应该比光的切光值更小吗?这没错,但是不要忘了,角度值是以余弦值来表示的,一个0度的角表示为1.0的余弦值,当一个角是90度的时候被表示为0.0的余弦值,如下图所示:
在这里插入图片描述
余弦越是接近1.0,角度就越小。这就解释了为什么θ需要比切光值更大了。切光值当前被设置为12.5度的余弦,它等于0.9978,所以θ的余弦值在0.9979和1.0之间,片段会在聚光内,被照亮。

最终效果

观察到在聚光内的片段才会被照亮,但看起来有点不自然

  • 原因:聚光有了一个硬边。片段着色器一旦到达了聚光的圆锥边缘,它就立刻黑了下来,却没有任何平滑减弱的过度。一个真实的聚光的光会在它的边界处平滑减弱的。

在这里插入图片描述

平滑/软边缘化

为了创建一种看起来边缘平滑的聚光,需要模拟聚光有一个内圆锥(Inner Cone)和一个外圆锥(Outer Cone)。可以将内圆锥设置为上一部分中的那个圆锥,但也需要一个外圆锥,来让光从内圆锥逐渐减暗,直到外圆锥的边界。
为了创建一个外圆锥,只需要再定义一个余弦值来代表聚光方向向量和外圆锥向量(等于它的半径)的夹角。然后,如果一个片段处于内外圆锥之间,将会给它计算出一个0.0到1.0之间的强度值。如果片段在内圆锥之内,它的强度就是1.0,如果在外圆锥之外强度值就是0.0。
在这里插入图片描述
在这里插入图片描述
由于现在有了一个亮度值,当在聚光外的时候是个负的,当在内部圆锥以内大于1。如果适当地把这个值固定,在片段着色器中就再不需要if - else了,可以简单地用计算出的亮度值乘以光的元素:
在这里插入图片描述
注意:使用了clamp(),它把第一个参数固定在0.0和1.0之间。这保证了亮度值不会超出[0, 1]以外。
把outerCutOff值添加到了Light结构体,并在应用中设置了它的uniform值。

最终效果

实现聚光边缘软化,内部切光角12.5f,外部切光角是17.5f
在这里插入图片描述

(四)多光源

在前面的课程已经学习了许多关于OpenGL 光照的知识,其中包括冯氏照明模型(Phong shading)、光照材质(Materials)、光照图(Lighting maps)以及各种投光物(Light casters)。本部分将结合上述所学的知识,创建一个包含六个光源的场景。将模拟一个类似阳光的平行光(Directional light)和4个定点光(Point lights)以及一个手电筒(Flashlight)。
当在场景中使用多个光源时,通常使用以下方法:需要有一个单独的颜色向量代表片段的输出颜色。对于每一个光源,它对片段的贡献颜色将会加到片段的输出颜色向量上。所以场景中的每个光源都会计算它们各自对片段的影响,并结合为一个最终的输出颜色。下面是使用这种方式进行多光源运算的一般结构:
在这里插入图片描述
要在片段着色器中定义一个函数用来计算平行光在对应的照射点上的光照颜色,这个函数需要几个参数并返回一个计算平行光照结果的颜色。

平行光

首先需要设置一系列用于表示平行光的变量,可以将这些变量定义在一个叫做DirLight的结构体中,并定义一个这个结构体类型的uniform变量。
在这里插入图片描述在这里插入图片描述

点光源

和计算平行光一样,同样需要定义一个函数用于计算点光源同样我们定义一个包含点光源所需属性的结构体:
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
每一个光源的运算结果都添加到了输出颜色上,输出颜色包含了此场景中的所有光源的影响。
设置平行光结构体的uniform值和之前所讲过的方式一样,需要了解的是如何设置场景中PointLight结构体的uniforms变量数组。设置uniform变量数组和设置单个uniform变量值是相似的,只需要用一个合适的下标就能够检索到数组中想要的uniform变量了。
在这里插入图片描述
同时还需要为每个光源设置它们的位置。这里,定义一个glm::vec3类的数组来包含这些点光源的坐标:
在这里插入图片描述
同时还需要根据这些光源的位置在场景中绘制4个表示光源的立方体,这样的工作在之前的课程中已经做过了。

最终效果

将点光源和平形光合在一起产生的效果
在这里插入图片描述
上图的光源是使用默认属性的效果,若尝试对光源属性做出修改尝试的话,会出现很多有意思的画面。使用最简单的光照属性的改变我们就足已创建有趣的视觉效果:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_42050609/article/details/125159166