OpenGL学习笔记(八)

OpenGL 中级篇(四)

光照模型

OpenGL的光线模型只是接近了自然世界中光线的工作方式。这些光照模型都是基于我们对光的物理特性的理解。
其中一个模型被称为冯氏光照模型(Phong Lighting Model)。冯氏光照模型的主要结构由3个元素组成:环境(Ambient)、漫反射(Diffuse)和镜面(Specular)光照

  • 环境光:没有特定的方向,被它照射的表面会均匀的反射
  • 漫反射:来自特定的方向,被它照射的表面会均匀的反射
  • 镜面反射光:来自特定的方向,它在表面的反射也有特定的方向
    在这里插入图片描述

创建一个光照场景

创建一个表示灯(光源)的立方体,所以为灯创建一个新的VAO。
在这里插入图片描述
只需要再定义一个片段着色器就行了:
在这里插入图片描述
光源色和物体颜色的反射运算:

color = vec4(lightColor * objectColor, 1.0f);

把光源的颜色与物体的颜色相乘,所得到的就是这个物体所反射该光源的颜色(也就是感知到的颜色)。
接下来把物体的颜色设置为珊瑚红并把光源设置为白色:
在这里插入图片描述
需要为灯创建另外一套着色器程序,保证它能够在其他光照着色器变化时保持不变。顶点着色器和绘制箱子的一样,而片段着色器保证了灯的颜色一直是亮的,通过定义一个常量的白色实现:
在这里插入图片描述
使用灯立方体目的:直观看到光源在场景中的具体位置。
为了显示灯的效果,我们将表示光源的立方体绘制在与光源同样的位置。
声明一个全局vec3变量来表示光源在场景的世界空间坐标中的位置:

glm::vec3 lightPos(1.2f, 1.0f, 2.0f);

然后把灯平移到该位置,对它进行缩放,让它不那么明显:
在这里插入图片描述

最终效果

在这里插入图片描述

环境光照

将学习使用一种简化的全局照明模型,叫做环境光照(Ambient Lighting)。使用一个(数值)很小的常量(光)颜色添加进物体片段(Fragment,指当前讨论的光线在物体上的照射点)的最终颜色里,这看起来就像即使没有直射光源也始终存在着一些发散的光。

把环境光照添加到实验场景里:用光的颜色乘以一个(数值)很小常量环境因子,再乘以物体的颜色,然后使用它作为片段的颜色:
在这里插入图片描述

漫反射光照

环境光本身不提供最明显的光照效果,但是漫反射光照(Diffuse Lighting)会对物体产生显著的视觉影响。漫反射光使物体上与光线排布越近的片段越能从光源处获得更多的亮度。
在这里插入图片描述
图左上方有一个光源,发出的光线落在物体的一个片段上。我们需要测量这个光线与它所接触片段之间的角度。如果光线垂直于物体表面,这束光对物体的影响会最大化(更亮)。
为了测量光线和片段的角度,使用法向量(Normal Vector) 来辅助测量,法向量垂直于片段表面(这里以黄色箭头表示) ,两个向量之间的角度就能够根据点乘计算出来。
点乘返回一个标量,可以用它计算光线对片段颜色的影响,基于不同片段所朝向光源的方向的不同,这些片段被照亮的情况也不同。
注意,这里使用的是单位向量(长度是1的向量)取得两个向量夹角余弦值,所以需要确保所有的向量都被标准化,否则点乘的返回值就不仅是余弦值了,而是余弦值的倍数

法向量的计算

最简单的做法是求出这3个顶点构成的三角形的面法向量,并将面法向量作为个顶点的法向量。
当用三角形片元逼近表示曲面时,将面片法向量作为构成该面片的顶点法向量不可能产生很平滑的效果。
一种更好的求取顶点法向量的方法是计算法向量的均值。
为了求出顶点v的顶点法向量vn ,我们需要求出共享顶点v的所有三角形的面法向量。则vn可以有这些面法向量取均值得到。
假定有3个三角形单元共享顶点v,其面法向量分别为n0, n1, n2,则vn的计算方法为:vn =1 / 3(n0 + n1 + n2)
在生成曲面时,通常令顶点法线和相邻平面的法线保持等角,如下图所示,这样进行渲染时,会在平面接缝处产生一种平滑过渡的效果。
在这里插入图片描述
使用叉乘计算可以为立方体所有的顶点计算出法向量,但是由于3D立方体不是一个复杂的形状,所以可以简单的把法线数据手工添加到顶点数据中(部分顶点数据和法向量列举如下)。
在这里插入图片描述

修改顶点着色器

因为向顶点数组添加了额外的数据,所以应该更新光照的顶点着色器:
在这里插入图片描述
现在已经向每个顶点添加了一个法向量,已经更新了顶点着色器,接着,更新顶点属性指针(Vertex Attibute Pointer)
注意:发光物使用同样的顶点数组作为它的顶点数据,然而发光物的着色器没有使用新添加的法向量。不更新发光物的着色器或者属性配置,但必须修改顶点属性指针来适应新的顶点数组的大小:
在这里插入图片描述
所有光照的计算需要在片段着色器里进行,所以需要把法向量由顶点着色器传递到片段着色器:
在这里插入图片描述
接着在片段着色器中定义相应的输入值:in vec3 Normal;
每个顶点现在都有了法向量,但是我们仍然需要光的位置向量和片段的位置向量。由于光的位置是一个静态变量,我们可以简单的在片段着色器中把它声明为uniform
uniform vec3 lightPos;
然后在渲染循环中(渲染循环的外面也可以,因为它不会改变)更新uniform。我们使用在前面声明的lightPos向量作为光源位置:
lightingShader.setVec3(“lightPos”, lightPos);
在这里插入图片描述

修改片元着色器

现在,所有需要的变量都设置好了,可以在片段着色器中开始光照的计算了。
首先计算光源和片段位置之间的方向向量。前面提到,光的方向向量是光的位置向量与片段的位置向量之间的向量差。且为确保所有相关向量最后都转换为单位向量,需要把法线和方向向量都进行归一化:
在这里插入图片描述
下一步,对norm和lightDir向量进行点乘,来计算光对当前片段的实际的散射影响。结果值再乘以光的颜色,得到散射因子。两个向量之间的角度越大,散射因子就会越小:
在这里插入图片描述
注意:如果两个向量之间的角度大于90度,点乘的结果就会变成负数,这样会导致散射因子变为负数。为此,使用max函数返回两个参数之间较大的参数,从而保证散射因子不会变成负数。负数的颜色是没有实际定义的,所以最好避免它。
既然有了一个环境光照颜色和一个散射光颜色,把它们相加,然后把结果乘以物体的颜色,来获得片段最后的输出颜色。
在这里插入图片描述

最终效果

在这里插入图片描述

镜面光照

镜面光照(Specular Lighting) 同样依据光的方向向量和物体的法向量,但是它也会依据观察方向
在这里插入图片描述
通过反射法向量周围光的方向计算反射向量。然后计算反射向量和视线方向的角度,如果之间的角度越小,那么镜面光的影响就会越大。它的作用效果就是,当去看光被物体所反射的那个方向的时候,会看到一个高光。
在这里插入图片描述
观察向量是镜面光照的一个附加变量,可以使用 观察者世界空间位置(Viewer’s World Space Position) 和片段的位置来计算。之后,计算镜面光亮度,用它乘以光的颜色,再将它加上环境光和漫反射分量。
为了得到观察者的世界空间坐标,简单地使用摄像机对象的位置坐标代替(它就是观察者)。所以把另一个uniform添加到片段着色器,把相应的摄像机位置坐标传给片段着色器:
在这里插入图片描述
已经获得所有需要的变量,可以计算高光亮度了。首先,定义一个镜面强度(Specular Intensity)变量specularStrength,给镜面高光一个中等亮度颜色,这样不会产生过度的影响。

float specularStrength = 0.5f;

如果把它设置为1.0f,会得到一个对于珊瑚色立方体来说过度明亮的镜面亮度因子。后面会讨论所有这些光照亮度的合理设置,以及它们是如何影响物体的。
下一步,计算视线方向向量,和对应的沿着法线轴的反射向量:
在这里插入图片描述
注意:此处使用了lightDir向量的相反数。
reflect()要求的第一个是从光源指向片段位置的向量,但是lightDir当前是从片段指向光源的向量(由先前计算lightDir向量时,(减数和被减数)减法的顺序决定)。为了保证得到正确的reflect坐标,通过lightDir向量的相反数获得它的方向的反向。第二个参数要求是一个法向量,所以提供已标准化的norm向量。
剩下要做的是计算镜面亮度分量:

float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
vec3 specular = specularStrength * spec * lightColor;

先计算视线方向与反射方向的点乘(确保它不是负值),然后取它的32次幂。这个32是高光的反光度(Shininess)。一个物体的反光度越高,反射光的能力越强,散射得越少,高光点越小。
不希望镜面成分过于显眼,所以把指数设置为32。
会看到不同发光值对视觉(效果)的影响:
在这里插入图片描述
剩下的最后一件事情是把它添加到环境光颜色和散射光颜色里,然后再乘以物体颜色:
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

最终效果

在这里插入图片描述

材质

把一个镜面高光元素添加到这三个颜色里,这是我们需要的所有材质属性:
在这里插入图片描述
在片段着色器中,创建一个结构体(Struct),来储存物体的材质属性。也可以把它们储存为独立的uniform值,但是作为一个结构体来储存可以更有条理。
在结构体中,为每个冯氏光照模型的元素都定义一个颜色向量。

  • ambient材质向量定义了在环境光照下这个物体反射的是什么颜色;通常这是和物体颜色相同的颜色。
  • diffuse材质向量定义了在漫反射光照下物体的颜色。漫反射颜色被设置为(和环境光照一样)我们需要的物体颜色。
  • specular材质向量设置的是物体受到的镜面光照的影响的颜色(或者可能是反射一个物体特定的镜面高光颜色)。
  • shininess影响镜面高光的散射 / 半径。
    这四个元素定义了一个物体的材质,通过它们能够模拟很多真实世界的材质。
    下面的图片展示了几种真实世界材质对我们的立方体的影响:
    在这里插入图片描述
    从上图可以看到,正确地指定一个物体的材质属性,似乎就是改变物体的相关属性的比例。效果显然很引人注目,但是对于大多数真实效果,最终需要更加复杂的形状,而不单单是一个立方体。
    为一个物体赋予一款正确的材质是非常困难的,这需要大量实验和丰富的经验,所以由于错误的设置材质而毁了物体的画面质量是件经常发生的事。
    为了正确赋予物体材质属性,试试在着色器中实现一个材质系统。
    在片段着色器中创建的uniform材质结构体储存所有材质元素,可以从uniform变量material取得它们,实现改变光照计算来顺应新的材质属性:
    在这里插入图片描述
    现在获得所有材质结构体的属性,通过材质颜色的帮助,计算结果输出的颜色。物体的每个材质属性乘以它们对应光照元素。
    通过设置适当的uniform,可以在应用中设置物体的材质。如果希望填充这个结构体,就仍然必须设置结构体中的各个元素的uniform值,但是这次带有结构体名字作为前缀:
    在这里插入图片描述

最终效果

在这里插入图片描述

光的属性

通过使用一个强度值改变环境和镜面强度的方式解决了这个问题。这次做的系统相同,但是这次为每个光照元素指定了强度向量。如果想象lightColor是vec3(1.0),代码看起来像是这样:

vec3 ambient = vec3(1.0f) * material.ambient;
vec3 diffuse = vec3(1.0f) * (diff * material.diffuse);
vec3 specular = vec3(1.0f) * (spec * material.specular);

所以物体的每个材质属性返回了每个光照元素的全强度。vec3(1.0)值可以各自独立的影响各个光源。现在物体的ambient元素完全地展示了立方体的颜色,可是环境元素不应该对最终颜色有这么大的影响,所以要设置光的ambient亮度为小一点的值,限制环境色:

vec3 result = vec3(0.1f) * material.ambient;

可以用同样的方式影响光源diffuse和specular的强度。所以,可以创建一些光的属性来各自独立地影响每个光照元素。为光的属性创建一个结构体,与材质结构体相似:
在这里插入图片描述
一个光源的ambient、diffuse和specular光都有不同的亮度。环境光通常设置为一个比较低的亮度,因为不希望环境色太过显眼。光源的diffuse元素通常设置为光所具有的颜色;经常是一个明亮的白色。specular元素通常被设置为vec3(1.0f)类型的全强度发光。要记住的是同样把光的位置添加到结构体中。
在这里插入图片描述
然后,在应用里设置光的亮度:
在这里插入图片描述

最终效果

在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值