1. 平行光
    因为所有的光线都是平行的,所以物体与光源的相对位置是不重要的,因为对场景中每一个物体光的方向都是一致的。由于光的位置向量保持一致,场景中每个物体的光照计算将会是类似的。

我们可以定义一个光线方向向量而不是位置向量来模拟一个定向光。着色器的计算基本保持不变,但这次我们将直接使用光的direction向量而不是通过position来计算lightDir向量。

struct Light {// vec3 position; // 使用定向光就不再需要了vec3 direction;vec3 ambient;vec3 diffuse;vec3 specular;
};
...
void main()
{vec3 lightDir = normalize(-light.direction);...
}

注意我们首先对light.direction向量取反。我们目前使用的光照计算需求一个从片段至光源的光线方向,但人们更习惯定义定向光为一个从光源出发的全局方向。所以我们需要对全局光照方向向量取反来改变它的方向,它现在是一个指向光源的方向向量了。而且,记得对向量进行标准化,假设输入向量为一个单位向量是很不明智的。

最终的lightDir向量将和以前一样用在漫反射和镜面光计算中。

为了清楚地展示定向光对多个物体具有相同的影响,我们将会再次使用坐标系统章节最后的那个箱子派对的场景。如果你错过了派对,我们先定义了十个不同的箱子位置,并对每个箱子都生成了一个不同的模型矩阵,每个模型矩阵都包含了对应的局部-世界坐标变换:

for(unsigned int i = 0; i < 10; i++)
{glm::mat4 model;model = glm::translate(model, cubePositions[i]);float angle = 20.0f * i;model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));lightingShader.setMat4("model", model);glDrawArrays(GL_TRIANGLES, 0, 36);
}

同时,不要忘记定义光源的方向(注意我们将方向定义为从光源出发的方向,你可以很容易看到光的方向朝下)。

lightingShader.setVec3(“light.direction”, -0.2f, -1.0f, -0.3f);
我们一直将光的位置和位置向量定义为vec3,但一些人会喜欢将所有的向量都定义为vec4。当我们将位置向量定义为一个vec4时,很重要的一点是要将w分量设置为1.0,这样变换和投影才能正确应用。然而,当我们定义一个方向向量为vec4的时候,我们不想让位移有任何的效果(因为它仅仅代表的是方向),所以我们将w分量设置为0.0。

方向向量就会像这样来表示:vec4(0.2f, 1.0f, 0.3f, 0.0f)。这也可以作为一个快速检测光照类型的工具:你可以检测w分量是否等于1.0,来检测它是否是光的位置向量;w分量等于0.0,则它是光的方向向量,这样就能根据这个来调整光照计算了:

if(lightVector.w == 0.0) // 注意浮点数据类型的误差// 执行定向光照计算
else if(lightVector.w == 1.0)

// 根据光源的位置做光照计算(与上一节一样)
你知道吗:这正是旧OpenGL(固定函数式)决定光源是定向光还是位置光源(Positional Light Source)的方法,并根据它来调整光照。
2. 点光
随着光线传播距离的增长逐渐削减光的强度通常叫做衰减(Attenuation)。随距离减少光强度的一种方式是使用一个线性方程。这样的方程能够随着距离的增长线性地减少光的强度,从而让远处的物体更暗。然而,这样的线性方程通常会看起来比较假。在现实世界中,灯在近处通常会非常亮,但随着距离的增加光源的亮度一开始会下降非常快,但在远处时剩余的光强度就会下降的非常缓慢了。所以,我们需要一个不同的公式来减少光的强度。

幸运的是一些聪明的人已经帮我们解决了这个问题。下面这个公式根据片段距光源的距离计算了衰减值,之后我们会将它乘以光的强度向量:

Fatt=1.0Kc+Kl∗d+Kq∗d2
在这里d
代表了片段距光源的距离。接下来为了计算衰减值,我们定义3个(可配置的)项:常数项Kc
、一次项Kl
和二次项Kq

常数项通常保持为1.0,它的主要作用是保证分母永远不会比1小,否则的话在某些距离上它反而会增加强度,这肯定不是我们想要的效果。
一次项会与距离值相乘,以线性的方式减少强度。
二次项会与距离的平方相乘,让光源以二次递减的方式减少强度。二次项在距离比较小的时候影响会比一次项小很多,但当距离值比较大的时候它就会比一次项更大了。
由于二次项的存在,光线会在大部分时候以线性的方式衰退,直到距离变得足够大,让二次项超过一次项,光的强度会以更快的速度下降。这样的结果就是,光在近距离时亮度很高,但随着距离变远亮度迅速降低,最后会以更慢的速度减少亮度。下面这张图显示了在100的距离内衰减的效果:

你可以看到光在近距离的时候有着最高的强度,但随着距离增长,它的强度明显减弱,并缓慢地在距离大约100的时候强度接近0。这正是我们想要的。

选择正确的值
但是,该对这三个项设置什么值呢?正确地设定它们的值取决于很多因素:环境、希望光覆盖的距离、光的类型等。在大多数情况下,这都是经验的问题,以及适量的调整。下面这个表格显示了模拟一个(大概)真实的,覆盖特定半径(距离)的光源时,这些项可能取的一些值。第一列指定的是在给定的三项时光所能覆盖的距离。这些值是大多数光源很好的起始点,它们由Ogre3D的Wiki所提供:

距离 常数项 一次项 二次项
7 1.0 0.7 1.8
13 1.0 0.35 0.44
20 1.0 0.22 0.20
32 1.0 0.14 0.07
50 1.0 0.09 0.032
65 1.0 0.07 0.017
100 1.0 0.045 0.0075
160 1.0 0.027 0.0028
200 1.0 0.022 0.0019
325 1.0 0.014 0.0007
600 1.0 0.007 0.0002
3250 1.0 0.0014 0.000007
你可以看到,常数项Kc
在所有的情况下都是1.0。一次项Kl
为了覆盖更远的距离通常都很小,二次项Kq
甚至更小。尝试对这些值进行实验,看看它们在你的实现中有什么效果。在我们的环境中,32到100的距离对大多数的光源都足够了。

实现衰减
为了实现衰减,在片段着色器中我们还需要三个额外的值:也就是公式中的常数项、一次项和二次项。它们最好储存在之前定义的Light结构体中。注意我们使用上一节中计算lightDir的方法,而不是上面定向光部分的。

struct Light {vec3 position;  vec3 ambient;vec3 diffuse;vec3 specular;float constant;float linear;float quadratic;
};

然后我们将在OpenGL中设置这些项:我们希望光源能够覆盖50的距离,所以我们会使用表格中对应的常数项、一次项和二次项:

lightingShader.setFloat("light.constant",  1.0f);
lightingShader.setFloat("light.linear",    0.09f);
lightingShader.setFloat("light.quadratic", 0.032f);

在片段着色器中实现衰减还是比较直接的:我们根据公式计算衰减值,之后再分别乘以环境光、漫反射和镜面光分量。

我们仍需要公式中距光源的距离,还记得我们是怎么计算一个向量的长度的吗?我们可以通过获取片段和光源之间的向量差,并获取结果向量的长度作为距离项。我们可以使用GLSL内建的length函数来完成这一点:

float distance    = length(light.position - FragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));

接下来,我们将包含这个衰减值到光照计算中,将它分别乘以环境光、漫反射和镜面光颜色。

我们可以将环境光分量保持不变,让环境光照不会随着距离减少,但是如果我们使用多于一个的光源,所有的环境光分量将会开始叠加,所以在这种情况下我们也希望衰减环境光照。简单实验一下,看看什么才能在你的环境中效果最好。

ambient  *= attenuation;
diffuse  *= attenuation;
specular *= attenuation;
  1. 聚光

OpenGL中聚光是用一个世界空间位置、一个方向和一个切光角(Cutoff Angle)来表示的,切光角指定了聚光的半径(译注:是圆锥的半径不是距光源距离那个半径)。对于每个片段,我们会计算片段是否位于聚光的切光方向之间(也就是在锥形内),如果是的话,我们就会相应地照亮片段。下面这张图会让你明白聚光是如何工作的:

LightDir:从片段指向光源的向量。
SpotDir:聚光所指向的方向。
Phiϕ
:指定了聚光半径的切光角。落在这个角度之外的物体都不会被这个聚光所照亮。
Thetaθ
:LightDir向量和SpotDir向量之间的夹角。在聚光内部的话θ
值应该比ϕ
值小。
所以我们要做的就是计算LightDir向量和SpotDir向量之间的点积(还记得它会返回两个单位向量夹角的余弦值吗?),并将它与切光角ϕ
值对比。你现在应该了解聚光究竟是什么了,下面我们将以手电筒的形式创建一个聚光。

手电筒
手电筒(Flashlight)是一个位于观察者位置的聚光,通常它都会瞄准玩家视角的正前方。基本上说,手电筒就是普通的聚光,但它的位置和方向会随着玩家的位置和朝向不断更新。

所以,在片段着色器中我们需要的值有聚光的位置向量(来计算光的方向向量)、聚光的方向向量和一个切光角。我们可以将它们储存在Light结构体中:

struct Light {vec3  position;vec3  direction;float cutOff;...
};

接下来我们将合适的值传到着色器中:

lightingShader.setVec3("light.position",  camera.Position);
lightingShader.setVec3("light.direction", camera.Front);
lightingShader.setFloat("light.cutOff",   glm::cos(glm::radians(12.5f)));

你可以看到,我们并没有给切光角设置一个角度值,反而是用角度值计算了一个余弦值,将余弦结果传递到片段着色器中。这样做的原因是在片段着色器中,我们会计算LightDir和SpotDir向量的点积,这个点积返回的将是一个余弦值而不是角度值,所以我们不能直接使用角度值和余弦值进行比较。为了获取角度值我们需要计算点积结果的反余弦,这是一个开销很大的计算。所以为了节约一点性能开销,我们将会计算切光角对应的余弦值,并将它的结果传入片段着色器中。由于这两个角度现在都由余弦角来表示了,我们可以直接对它们进行比较而不用进行任何开销高昂的计算。

接下来就是计算θ
值,并将它和切光角ϕ
对比,来决定是否在聚光的内部:

float theta = dot(lightDir, normalize(-light.direction));if(theta > light.cutOff)
{       // 执行光照计算
}
else  // 否则,使用环境光,让场景在聚光之外时不至于完全黑暗color = vec4(light.ambient * vec3(texture(material.diffuse, TexCoords)), 1.0);

我们首先计算了lightDir和取反的direction向量(取反的是因为我们想让向量指向光源而不是从光源出发)之间的点积。记住要对所有的相关向量标准化。

你可能奇怪为什么在if条件中使用的是 > 符号而不是 < 符号。theta不应该比光的切光角更小才是在聚光内部吗?这并没有错,但不要忘记角度值现在都由余弦值来表示的。一个0度的角度表示的是1.0的余弦值,而一个90度的角度表示的是0.0的余弦值,你可以在下图中看到:

你现在可以看到,余弦值越接近1.0,它的角度就越小。这也就解释了为什么theta要比切光值更大了。切光值目前设置为12.5的余弦,约等于0.9978,所以在0.9979到1.0内的theta值才能保证片段在聚光内,从而被照亮。

openGL平行光、点光、聚光相关推荐

  1. Opengl-光照-基本光照-投光物-多光源(现实世界的光可不只有太阳也并不只有一个)

    前言 相信大家看过各种发光的道具,手电筒?看到过吧?灯泡看到过吧?除了太阳生活中还有各种灯红酒绿的地方(说错了)等着你去看啊 各种光源 平行光-太阳或者很远处的光都可以叫做平行光 平行光的光的方向是一 ...

  2. Three.js-灯光与阴影

    目录 1.常见的光源类型 1.1 环境光(AmbientLight) 1.1.1 构造函数 1.1.2 属性 1.1.3 方法 1.1.4 环境光效果 1.2 平行光(DriectionalLight ...

  3. d3d与OpenGL的博弈

    d3d与OpenGL的博弈   来自:http://thatax.blog.163.com/blog/static/20892680200871494531969/ 随着OOXML与ODF的竞争为世人 ...

  4. kanziopengl杂谈

    最近有幸总结了一下kanzi中opengl的东西,实际比较重点的也就是shader部分,对于其他部分,这里也会简单的说,但是具体的功能作用就要自己下来慢慢研究了 一:引言 本文章主要介绍kanzi和o ...

  5. 3dmax体积雾渲染不出来_【扮家家云渲染效果图】3Dmax体积光制作丛林光束|干货教程...

    首先打开场景文件 场景中创建了一些树木组成了森林的效果.首先要为场景创建灯光. 单击创建,选择灯光,将类型切换为标准.接着单击目标平行光. 在场景中拖拽进行创建,创建一盏目标平行光, 然后单击修改,勾 ...

  6. DirectX11 With Windows SDK--07 添加光照与常用几何模型、光栅化状态

    DirectX11 With Windows SDK--07 添加光照与常用几何模型.光栅化状态 原文:DirectX11 With Windows SDK--07 添加光照与常用几何模型.光栅化状态 ...

  7. C4D-学习笔记-4-渲染(ProRender渲染介绍)

    HDR天空贴图旋转 选中材质,坐标,就可以旋转了. 平行光/无限光 可以通过旋转来调整平行光的角度. 平行光没有投影 解决办法,左上角选项,投影,打开 渲染器差异 标准:标准渲染器 物理:更真实的渲染 ...

  8. 【GLSL教程】(六)逐顶点的光照

    引言 在OpenGL中有三种类型的光:方向光(directional).点光(point).聚光(spotlight).本教程将从方向光讲起,首先我们将使用GLSL来模仿OpenGL中的光. 我们将向 ...

  9. u3d011 秘密行动_学习记录

    目录 1.网格碰撞器 2.灯光 3.声音AudioSource 3.人物移动 4. z轴越障碍相机跟随 5.自动寻路NavMeshAgent 6.简单计时器 7.画线LineRenderer 1.网格 ...

最新文章

  1. unity, monoDevelop ide 代码提示不起作用的解决方法
  2. Windows中的system函数
  3. SSL/TLS协议信息泄露漏洞(CVE-2016-2183)【原理扫描】远程桌面 3389 Windows 2016
  4. Python的GUI框架PySide
  5. Linux Dynamic Shared Library LD Linker
  6. python 如何封装成so_python打包成so文件
  7. Python轻量级WEB框架web.py之操作数据库
  8. centos 修改root密码_Vultr 修改 Root 密码的方法
  9. RTI_DDS线程模型
  10. visio一分二的箭头_visio双箭头怎么画? visio2013绘制双箭头直线的教程
  11. tensorflow代码翻译成pytorch代码 -详细教程+案例
  12. C#实现Socket
  13. springboot 与rabbitmq集成+生产者投递确认+消费者手动确认+TTL+死信队列+延时队列
  14. Java基础学习(11)---Java注解和反射
  15. 开源源码商城系统盘点
  16. 怎么提高代码质量?-来自Google的研发经验总结
  17. iOS 应用退到管理后台 左上角图片未更新(或不显示)
  18. msn space 决定搬家了
  19. Low-light images enhancement/暗光/低光/微光增强系列:Attention-guided Low-light Image Enhancement(详解)
  20. CVE-2022-32991

热门文章

  1. 高德vue-amap使用(一)标记点位获取地址及经纬度
  2. 春晚小宫女唐奕霖 网友封为最美的年轻董事长
  3. Excel冻结多行多列
  4. python pip 豆瓣镜像
  5. 给猜字游戏增加难度设置
  6. 韩老师坦克大战2.0版本
  7. FPGA控制DDR读写(AXI4总线接口)
  8. Apache的性能解读
  9. casperjs ajax请求,CasperJs中的sendAJAX数据参数
  10. 苹果xr配置_定了!苹果发布会9月11日