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. KVM 使用 Centos CLoud Image 安装虚拟机

    1 下载镜像 # 资源地址:https://cloud.centos.org/centos/7/images/ wget https://cloud.centos.org/centos/7/image ...

  2. Libvirtd networks -- 为libvirtd 中虚拟机指定ip遇到的问题

    backgroup 为libvirtd 中虚拟机指定ip,操作如下: virsh --connect qemu:///system dumpxml vm01 | grep "mac addr ...

  3. 解决pandas 读取csv文件报错

    使用encoding参数: pd.read_csv(path,sep=",",encoding='utf-16') 注意:该参数之后的编码格式,并不是固定的,需要用记事本打开csv ...

  4. .NET下数据库的负载均衡“经典方案”(大项目必备,建议收藏)

    [前言] 本文讲述的"数据库负载均衡"方案,为市面上最经典(没有之一),由.NET界骨灰级大佬推出.采用该技术方案的大公司,一年省下了几个亿的支出. [正文] 支持.Net Cor ...

  5. 原来TypeScript中的接口和泛型这么好理解

    "接口"和"泛型"是 TypeScript 相比于 JavaScript 新增的内容,都用于定义数据类型 前面两篇文章总结了TypeScript中的 类型注解. ...

  6. go接收alertmanager告警并发送钉钉

    前言 功能:作为 alertmanager 的 webhook receiver,提取需要的数据转发到钉钉群机器人的webhook web框架:gin alertmanager版本:0.24 系统版本 ...

  7. shell分析nginx日志的一些指令

    前言 nginx日志格式默认 shell指令 查看有多少个IP访问: awk '{print $1}' log_file|sort|uniq|wc -l 查看某一个页面被访问的次数: grep &qu ...

  8. VictoriaLogs:一款超低占用的 ElasticSearch 替代方案

    背景 前段时间我们想实现 Pulsar 消息的追踪流程,追踪实现的效果图如下: 实现其实比较简单,其中最重要的就是如何存储消息. 消息的读取我们是通过 Pulsar 自带的 BrokerInterce ...

  9. 【渗透测试】Vulnhub EMPIRE BREAKOUT

    渗透环境 攻击机:   IP: 192.168.149.128(Kali) 靶机:     IP:192.168.149.130 靶机下载地址:https://www.vulnhub.com/entr ...

  10. 一次Python本地cache不当使用导致的内存泄露

    背景 近期一个大版本上线后,Python编写的api主服务使用内存有较明显上升,服务重启后数小时就会触发机器的90%内存占用告警,分析后发现了本地cache不当使用导致的一个内存泄露问题,这里记录一下 ...