1. 投光物

继续上一节的流程,到目前为止,我们介绍的都是点光源。但是现实世界中,光源的类型却要相对复杂一些。大概会有这么几种形式:定向光点光源聚光等等。

 2. 定向光

当一个光源处于很远的地方时,来自光源的每条光线就会近似于互相平行。这点很好理解,生活中我们的太阳光,就可以近似看做是平行光。定向光的特点是,光线的防线与光源的位置没有关系,他们的光线方向都是相同的。

在之前的例子中,我们都要通过光源的位置和物体上的顶点来计算光线射入的角度,从而计算漫反射分量和镜面光分量。如果是定向光,我们这直接可以拿到光线的射入角度。

这部分内容十分简单,只要将我们之前传入的用来计算光源方向的光源位置替换为光源方向直接使用即可。不过有一点需要注意的是:

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

这里在原教程中存在这样一个扩展:

我们一直将光的位置和位置向量定义为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)的方法,并根据它来调整光照。

私以为,之所以要求w分量等于1是因为在坐标系统这一章中,我们了解到,w分量是用来做透视效果的。而平行光是不具备透视效果的,所以我们将其w分量设置为1。

3. 点光源

点光源,就是用来描述向所有方向发光,但光线会随着距离逐渐衰减的光源。生活中,火把、灯泡等都可以看做是点光源。

之前的章节中,我们所模拟的光源都是一个简化的点光源,因为它不具备衰减的特性,这一节中,我们将着重完成光线的衰减这一部分。

4. 衰减

随着光线传播距离的增长逐渐削弱光的强度通常叫做衰减。现实世界中,等在近处通常会非常亮,但随着距离的增加光源的亮度一开始会下降非常快,但在远处时剩余的光强度就会下降的非常缓慢了。这里有一个公式,可以用它来就算光的衰减值:

这里d代表顶点至光源的距离,另外三个分别是系数。可以看到是一个二次函数的倒数,所以他的图像大概会是这个样子的:

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

5. 选择衰减值

那么如何配置这三个系数将是一个重点,影响他们的因素有很多:环境、覆盖距离、光源类型等。

在我们的环境中,32到100的距离对大多数的光源都足够了。

6. 实现衰减

做了上述的准备工作,实际上衰减实现起来比较简单。只要先计算出衰减值后,在三个分量上各自乘以衰减值即可。这里的代码跟上节的定向光不同,而是我们上章中的点光源。实现起来比较简单,这里直接放代码:

你可以看到,只有前排的箱子被照亮的,距离最近的箱子是最亮的。后排的箱子一点都没有照亮,因为它们离光源实在是太远了。

7. 聚光

我们来说一下我们将介绍的最后一种类型的光,聚光

聚光是位于环境中某个位置的光源,它只朝一个特定方向而不是所有方向照射光线。这样的结果就是只有在聚光方向的特定半径内的物体才会被照亮,其它的物体都会保持黑暗。聚光很好的例子就是路灯或手电筒。

根据上面的图片我们知道,我们只要确定设置顶点的光线与聚光方向的夹角是否在聚光夹角中即可。

  • LightDir:从片段指向光源的向量。
  • SpotDir:聚光所指向的方向。
  • Phiϕ:指定了聚光半径的切光角。落在这个角度之外的物体都不会被这个聚光所照亮。
  • Thetaθ:LightDir向量和SpotDir向量之间的夹角。在聚光内部的话θ值应该比ϕ值小。

所以我们要做的就是计算LightDir向量和SpotDir向量之间的点积,它会返回两个单位向量夹角的余弦值,并将它与切光角ϕ值对比。你现在应该了解聚光究竟是什么了,下面我们将以手电筒的形式创建一个聚光。

8. 手电筒

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

这里思路很简单,之前我们封装过摄像机类,在摄像机类中,我们的Front向量会随着玩家的位置和朝向不断的更新,他正是我们所需要的聚光方向。

我们改造我们的Light结构体:

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

我们看到这里我们传入的是我们内圆锥的角度余弦值,这是因为如果传角度的话,在片段着色器中我们是根据光照方向和顶点到光源的方向做点乘求向量的夹角余弦值的,所以我们直接将cutOff也转变为夹角余弦值来减少片段着色器中的计算量。

这时候,我们的片段着色器中大概是这个样子的:

这时,我们看大超出内圆锥的部分已经是灰暗的一片了,手电筒效果基本已经完成。但是我们还可以进一步完善它,一个真实的聚光将会在边缘处逐渐减少亮度。(当然这取决于你希望俊光边缘的展现方式)

9. 平滑、软化边缘

为了创建逐渐变暗的效果,我们还要指定一个外圆锥,然后让光线的镜面光分量和漫反射分量的作用强度在这之间做插值运算即可。思路很简单,我们直接看下代码:

其中clamp函数是释义如下:

float clamp(a,b,c),将a转换为介于[b,c]之间的值。

现在你已经有了一个完美的手电筒了:

OpenGL 投光物详解的更多相关文章

  1. # OpenGL常用函数详解(持续更新)

    OpenGL常用函数详解(持续更新) 初始化 void glutInit(int* argc,char** argv)初始化GULT库,对应main函数的两个参数 void gultInitWindo ...

  2. OpenGL一些函数详解(二)

    OpenGL ES顶点数据绘制技巧 在OpenGL中,绘制一个长方体,需要将每个顶点的坐标放在一个数组中.保存坐标时有一些技巧(由于字母下标不好表示,因此将下标表示为单引号,如A1将在后文中表示为A' ...

  3. OpenGL ES入门详解

     http://blog.csdn.net/wangyuchun_799/article/details/7736928 1.决定你要支持的OpenGL ES的版本.目前,OpenGL ES包含1.1 ...

  4. SAI 绘画一个卡通动漫人物详解

    本教程介绍使用SAI 绘画一个卡通漫画人物教程 ,教程很详细,动起你的小手一起来试试吧! 软件下载:http://www.dongmansoft.com/xiazai.html

  5. OpenGL的glTranslatef平移变换函数详解

    OpenGL的glTranslatef平移变换函数详解 glTranslated()和glTranslatef()这两个函数是定义一个平移矩阵,该矩阵与当前矩阵相乘,使后续的图形进行平移变换. 我们先 ...

  6. OpenGL的glRotatef旋转变换函数详解

    OpenGL的glRotatef旋转变换函数详解 先看一下函数定义:void glRotatef(GLfloat angle,  GLfloat x,     GLfloat y,     GLflo ...

  7. Qt的Graphics-View框架和OpenGL结合详解

    Qt的Graphics-View框架和OpenGL结合详解 演示程序下载地址:这里 程序源代码下载地址:这里 这是一篇纯技术文,介绍了这一个月来我抽时间研究的成果. Qt中有一个非常炫的例子:Boxe ...

  8. OpenGL ES一些函数详解(一)

    glLoadIdentity和glMultMatrix   glLoadIdentity的作用是将当前模型视图矩阵转换为单位矩阵(行数和列数相同的矩阵,并且矩阵的左上角至右下角的连线上的元素都为1,其 ...

  9. 【OpenGL】详解第一个OpenGL程序

    写在前面 OpenGL能做的事情太多了!很多程序也看起来很复杂.很多人感觉OpenGL晦涩难懂,原因大多是被OpenGL里面各种语句搞得头大,一会gen一下,一会bind一下,一会又active一下. ...

  10. OpenGL ES: (4) EGL API详解 (转)

    上一节我们初步学习了 OpenGL ES.EGL.GLSL 的相关概念,了解了它们的功能,以及它们之间的关联.我们知道了 EGL 是绘制 API(比如 OpenGL ES)与 底层平台窗口系统之间的接 ...

随机推荐

  1. CSP-S复习列表

    DP:序列,区间,背包,多维,状压,树型 优化:滚动,单调性,树状数组 数据结构:栈,队,链,deque,priority_queue,vector,set,map 树状数组,分块思想 前缀和,差分思 ...

  2. 虚拟化学习:vps和云计算的区别

    1 vps 可以由虚拟机实现,但并不是虚拟机; 虚拟机是云计算的核心,但虚拟机也不等于云主机. 2 vps是面向单台服务器的虚拟化技术,服务器挂了,其上的vps也跟着挂了. 3 云计算是面向服务器集群 ...

  3. 解决AccessDatabaseEngine.exe 32位64位安装失败问题

    cmd下执行 你的路径\AccessDatabaseEngine.exe /quiet 转载于:https://www.cnblogs.com/64mb/p/10844676.html

  4. 【Azure K8S | AKS】在AKS集群中创建 PVC(PersistentVolumeClaim)和 PV(PersistentVolume) 示例

    问题描述 在AKS集群中创建 PVC(PersistentVolumeClaim)和 PV(PersistentVolume) 示例 问题解答 在Azure Kubernetes Service(AK ...

  5. 基于Go编写一个可视化Navicat本地密码解析器

    前提 开发小组在测试环境基于docker构建和迁移一个MySQL8.x实例,过程中大意没有记录对应的用户密码,然后发现某开发同事本地Navicat记录了根用户,于是搜索是否能够反解析Navicat中的 ...

  6. SpringBoot3集成Kafka

    目录 一.简介 二.环境搭建 1.Kafka部署 2.Kafka测试 3.可视化工具 三.工程搭建 1.工程结构 2.依赖管理 3.配置文件 四.基础用法 1.消息生产 2.消息消费 五.参考源码 标 ...

  7. #Powerbi 1分钟学会,RANK函数,多字段排名函数.

    一:思维导图&数据源示例 1.1思维导图 1.2示例数据源 二:参数构成 三:案例度量值 基础度量值 总销量 = CALCULATE(SUM('数据源'[销量])) 总销售额 = CALCUL ...

  8. C语言下载minGW地址

    https://sourceforge.net/projects/mingw-w64/files/ 下载红框内即可

  9. WPF MVVM之点滴分享

    (第五点:绑定源有修改) 我并不打算长篇累牍的介绍什么是MVVM.我尽量简洁的介绍,并把自己的经验分享给大家. 一.关于MVVM M:Model,数据模型(后台存储数据的类) V:View,视图(大部 ...

  10. Understanding UML in seconds

    UML 是一种分析设计语言,也就是一种建模语言. UML结构解析 UML其结构主要包括以下几个部分: 视图(View) 多个图形组成的集合: 图(Diagram) 图的种类有13种图,但常用的也就两种 ...