OpenGL 编程指南(第八版)学习笔记——7 光照与阴影

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wlk1229/article/details/81813529

OpenGL 编程指南学习资料以及我整理的代码下载地址https://pan.baidu.com/s/1bqrcspD

文中提到的代码为下载文件中的“OpenGL编程指南 VS2015代码.zip”文件,代码也可以到github上下载:https://github.com/Kylewlk/OpenGL-Programming-Guide-8th-Edition-Code

7 光照与阴影

光照

光照可以使得3D看上去有凹凸感,看上去更加立体,以下是无光照和有光照绘制3D模型对比:

光照可以分为光照模型和光源两个方面,经典光照模型有:环境光、漫反射光、镜面光;光源类型有:方向光源、点光、聚光源。

 

光照模型

光照模型主要跟四个因素有关,环境光、视角、物体表面的朝向、光源。

环境光:物体的环境光只与环境光有关,与视角、物体表面朝向、光源无关,你可以理解为物体特有的颜色或者物体自己的发的光。

漫反射光:物体的漫反射使得我们在任何方向观看物体其颜色和亮度一样,所以漫反射光与具体的视角无关,但漫反射与物体表面朝向和光源有关,比如物体朝向光源的一面将会更亮。

镜面反射光:镜面反射光与视角、物体朝向、光源都有关,比如物体反射光照向眼睛时,此时看上去最亮。

 

光源

方向光源:对于物体上每个点受到的光照都来自一个方向,且光照强度相同,如太阳光。

点光源:物体受到的光照来自一个点,物体上每个点受到的光照方向都是该点与光点的连线方向,且光照强度随距离进行递减,如灯泡光。

聚光源:聚光有在点光源基础上增加了一个限制,光被限制在一个圆锥内,其他与点光源一致,如手电筒光。

 

光照实现

 

环境光

物体的环境光只与环境光有关,所以只需要将物体本身与环境光相乘即可,具体参见代码“7.2 -1ambient”,结果如下图:

 

漫反射加方向光

漫反射光的强度与物体的朝向和光方向有关,我们用物体表面的法向量与光方向夹角的余弦值来表示这个强度,具体参见代码“7.2 -2LightDirectionDiffuse”,结果如下

 

镜面反射与方向光

以下图片展示了光线经过物体表面发生镜面反射。

图中五根向量都是单位向量,五根向量分别为:

Eye:眼睛看物体的方向

Normal: 物体表面法向量

Light: 为光方向,入射光

Reflect: 反射方向,反射光

HalfVector: 半向量,HalfVector = normalize(Eye - Light)

 

反射光强度为Eye与Reflect夹角的余玄值,即dot(Eye, Reflect)。这样计算需要计算物体每个点的反射向量。实际上dot(Eye, Reflect)与dot(HalfVector, Normal)值相近,为了节省性能,我们通常使用dot(HalfVector, Normal)计算反射光的强度,因为在光方向确定而视角也是确定的所以每个点的HalfVector都是一致的,这样不必计算每个点光的反射向量。

 

高光尖锐指数Shininess:计算完光照强度后,还需要将光照进行一次指数运算,用来控制高光效果,这个值一定程度翻译了物体表面的反射率,以下时不同Shininess值绘制的下过对比,可以很清楚的看见模型中间高光区域的变化。

 

实现具体参见代码“7.2 -3LightDirectionSpecular”,结果如下

 

点光源实现

点光源的实现与方向光类似,但在计算漫反射和镜面反射时光方向会由于物体点的位置不同而变化。另外点光源还需要加上一个随着距离衰减的强度的计算。实现具体参见代码“7.2 -4LightPoint”,结果如下:

点光源位置在上方,下面的正方体离得更远,所以光照强度更低,亮度更低。

 

聚光源实现

聚光源与点光源类似,只是需要增加一个判断点是否被聚光源照射到的判断。首先聚光源是由方向的,在判断物体是否被聚光源照射到时,只需要计算光照的方向与聚光源方向夹角的余玄值,与限制的值对比即可。

实现具体参见代码“7.2 -4LightPoint”,结果如下:

 

阴影

阴影原理

实现阴影的原理有很多种,书中采用深度比较的方式来实现阴影,光源采用的是点光源。

先看下面三张图片,图片上方对于相应工程的名字:

第一张图是将视角转换到点光源位置,然后看整个3D世界坐标,视角函数为:LightView = vmath::lookat(LightPosition, vmath::vec3(0.0f), vmath::vec3(0.0f, 1.0f, 0.0f))最后将这个视角绘制的帧保存到纹理。

第二张图采用的是我们需要的视角进行绘制,绘制时使用没有采用物体本身的颜色,而是采用图(1)作为纹理进行映射的结果。

第三张图是完整的阴影实现,绿色的地面有正方体的阴影。

 

从图(2)和图(3)中可以看出,图(2)中绿色地面上图形就是图(3)中阴影的位置。首先需要我们了解以下图(1)的纹理是如何映射到图(2)的。图(2)映射纹理时采用的纹理坐标,是将绘制点转换到点光源视角下然后投影后的坐标。实际上图(2)绿地上的图形就是图(1)中被正方体遮挡的区域。

 

确定阴影位置:要确定哪些位置是阴影,我只需要确定哪些位置在图(1)中是被遮挡的。我们知道图(2)中绿地上图形就是图(1)中被正方体遮挡的区域,那在程序中怎么确定一个物体是否被遮挡呢?答案是深度值,我们知道开启深度比较后,绘制的最终图形中每个像素都是当前位置深度值最小的,也就是最靠近视角坐标原点的。当深度值比当前像素的深度值更大时则说明该点被遮挡了,那这个点也就处于阴影中。

 

阴影的实现

1. 我需要获取点光源视角下的深度纹理。

这一步比较简单,我们只需要创建一张深度纹理,然后关联到帧缓存中,帧缓存中不需要关联颜色附件因为我们只需要深度纹理。注意:我们绘制图形时使用的时NDC坐标,x, y, z都被限制在[-1.0, 1.0]中,而在深度纹理中,纹理的x,y坐标为[0.0, 1.0],深度值范围为[0.0, 1.0]。

以下是我使用深度值与白色(vec4(1.0, 1.0, 1.0, 1.0))相乘,得到的深度图,深度值越小颜色越黑,具体Shadow-test03-LightDepthTex”:

在读取深度纹理时我们使用sampler2D采样器,读取的深度值在r分量中。

 

2. 绘制图形并计算阴影

计算阴影,只需要进行一次深度比较即可,参见代码“7.4Shadow-test04-NoTextureporj”,在片元着色器中具体如下:

         vec3 shadow = ShadowCoord.xyz/ShadowCoord.w;//齐次空间坐标转换

         float d = texture(DepthTex, shadow.xy).r;//读取深度纹理中的深度值

         float f = 0.0;

         if(shadow.z <= d)//当前片元在Light视图下深度

                       //值小于等于深度纹理中的值,该片元没有被遮挡

         {

                   f = 1.0;

         }

         f=0时将不会计算漫反射和镜面反射,f=1时将会计算漫反射和镜面反射光。

 

对于阴影的比较GLSL提供了投影映射textureProj函数和阴影采样器sampler2DShadow。

textureProj可以将传入的齐次空间坐标进行转换

sampler2DShadow可以自动进行深度比较,需要设置采样器比较函数,设置方法如下:

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);

 

现在片元着色器只需要以下一行代码就可以实现阴影计算,具体参见代码“7.4Shadow”。

float f = textureProj(DepthTex, ShadowCoord);

 

3. 结果如下图

 

猜你喜欢

转载自blog.csdn.net/wlk1229/article/details/81813529