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. 由有序链表构建平衡二叉搜索树-sortedListToBST

    描述 给定一个有序列表,将其转换成为一个平衡搜索二叉树 题不难,不过在讨论中发现此题的中序遍历用法略有不同,感觉有点意思 手写一遍加深印象 暴力解法,链表转数组,额外空间O(N),递归遍历搞定.几分钟 ...

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

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

  3. 从原理聊JVM(四):JVM中的方法调用原理

    1 引言 多态是Java语言极为重要的一个特性,可以说是Java语言动态性的根本,那么线程执行一个方法时到底在内存中经历了什么,JVM又是如何确定方法执行版本的呢? 2 栈帧 JVM中由栈帧存储方法的 ...

  4. 错过这5大AI绘画提示词平台,你会拍大腿!别问,直接收藏!

    如今,AI绘画已经不再是简单的技术展示,而是逐渐转向了商业化的运营. 有的人利用AI生成的图片,再结合ChatGPT产生的文字,然后在平台上发布,这样就可以赚取平台的广告费. 其他一些变现操作参考之前 ...

  5. 论文解读(WDGRL)《Wasserstein Distance Guided Representation Learning for Domain Adaptation》

    Note:[ wechat:Y466551 | 可加勿骚扰,付费咨询 ] 论文信息 论文标题:Wasserstein Distance Guided Representation Learning f ...

  6. 虚拟机问题:VMware Workstation 与 Device/Credential Guard 不兼容。在禁用 Device/Credential Guard 后,可以运行

    解决办法:关闭Hyper-V功能. windows10使用VMware Workstation打开虚拟机时显示 VMware Workstation 与 Hyper-V 不兼容.请先从系统中移除 Hy ...

  7. OA管理系统源码

    介绍 oa管理系统,只有基本功能,可进行二次开发 软件架构 技术框架:Spring+SpringMVC+Mybatis+BootStrap 数据库:MySQL 服务器:JDK7+Tomcat7 安装教 ...

  8. skynet的timer似乎有问题

    skynet.timeout 传进去 number 范围内的数值但是会溢出, 调查发现 skynet.timeout 调用的是 c 的方法: c.intcommand("TIMEOUT&qu ...

  9. 5.2 磁盘CRC32完整性检测

    CRC校验技术是用于检测数据传输或存储过程中是否出现了错误的一种方法,校验算法可以通过计算应用与数据的循环冗余校验(CRC)检验值来检测任何数据损坏.通过运用本校验技术我们可以实现对特定内存区域以及磁 ...

  10. Solution -「九省联考 2018」劈配

    Description Link. 一年一度的综艺节目<中国新代码>又开始了.Zayid 从小就梦想成为一名程序员,他觉得这是一个展示自己的舞台,于是他毫不犹豫地报名了. 轻车熟路的 Za ...