OpenGL 投光物详解
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 投光物详解的更多相关文章
- # OpenGL常用函数详解(持续更新)
OpenGL常用函数详解(持续更新) 初始化 void glutInit(int* argc,char** argv)初始化GULT库,对应main函数的两个参数 void gultInitWindo ...
- OpenGL一些函数详解(二)
OpenGL ES顶点数据绘制技巧 在OpenGL中,绘制一个长方体,需要将每个顶点的坐标放在一个数组中.保存坐标时有一些技巧(由于字母下标不好表示,因此将下标表示为单引号,如A1将在后文中表示为A' ...
- OpenGL ES入门详解
http://blog.csdn.net/wangyuchun_799/article/details/7736928 1.决定你要支持的OpenGL ES的版本.目前,OpenGL ES包含1.1 ...
- SAI 绘画一个卡通动漫人物详解
本教程介绍使用SAI 绘画一个卡通漫画人物教程 ,教程很详细,动起你的小手一起来试试吧! 软件下载:http://www.dongmansoft.com/xiazai.html
- OpenGL的glTranslatef平移变换函数详解
OpenGL的glTranslatef平移变换函数详解 glTranslated()和glTranslatef()这两个函数是定义一个平移矩阵,该矩阵与当前矩阵相乘,使后续的图形进行平移变换. 我们先 ...
- OpenGL的glRotatef旋转变换函数详解
OpenGL的glRotatef旋转变换函数详解 先看一下函数定义:void glRotatef(GLfloat angle, GLfloat x, GLfloat y, GLflo ...
- Qt的Graphics-View框架和OpenGL结合详解
Qt的Graphics-View框架和OpenGL结合详解 演示程序下载地址:这里 程序源代码下载地址:这里 这是一篇纯技术文,介绍了这一个月来我抽时间研究的成果. Qt中有一个非常炫的例子:Boxe ...
- OpenGL ES一些函数详解(一)
glLoadIdentity和glMultMatrix glLoadIdentity的作用是将当前模型视图矩阵转换为单位矩阵(行数和列数相同的矩阵,并且矩阵的左上角至右下角的连线上的元素都为1,其 ...
- 【OpenGL】详解第一个OpenGL程序
写在前面 OpenGL能做的事情太多了!很多程序也看起来很复杂.很多人感觉OpenGL晦涩难懂,原因大多是被OpenGL里面各种语句搞得头大,一会gen一下,一会bind一下,一会又active一下. ...
- OpenGL ES: (4) EGL API详解 (转)
上一节我们初步学习了 OpenGL ES.EGL.GLSL 的相关概念,了解了它们的功能,以及它们之间的关联.我们知道了 EGL 是绘制 API(比如 OpenGL ES)与 底层平台窗口系统之间的接 ...
随机推荐
- EF6连接oracle
最近项目用到oracle,一直使用sql server,ef很方便连接mssql,但是连接oracle的方法网上很多文章,尝试很多次终于搞定,dbfirst或者codefirst也是可以的. 安装OD ...
- KVM (Centos7)使用macvtap网卡的后续 -- 宿主机创建macvtap网卡,并配置ip
因为使用虚拟机上的 macvtap 网卡与宿主机器上的网卡无法直接通信,所以需要在宿主机上也创建 macvtap 网卡,将 ip 迁移过去. 因为未能找到配置文件的设置方法,所以使用脚本来配置,并将脚 ...
- zanePerfor中一套简单通用的Node前后端Token登录机制和github授权登录方式
HI!,你好,我是zane,zanePerfor是一款我开发的一个前端性能监控平台,现在支持web浏览器端和微信小程序端. 我定义为一款完整,高性能,高可用的前端性能监控系统,这是未来会达到的目的,现 ...
- MySQL5.5+配置主从同步并结合ThinkPHP5设置分布式数据库
前言: 本文章是在同处局域网内的两台windows电脑,且MySQL是5.5以上版本下进行的一主多从同步配置,并且使用的是集成环境工具PHPStudy为例.最后就是ThinkPHP5的分布式的连接,读 ...
- css使用背景灵活展示雪碧图
雪碧图是把各种小图标集合在一起的png图片,通过background-position来展示雪碧图中不同位置的小图标,比如以下图片,在项目中要用到的小图标很多,如果每一个图标都作为一个png或者jpg ...
- 工具—批量备案信息查询并生成fofa查询语句
描述: 1.可以输入一个或多个公司名或域名或备案号,得到备案信息(备案公司名,备案公司网站url,备案号,域名类型,审核时间) 2.读取生成的信息并转为fofa语句,方便了指定目标的信息收集速度 工具 ...
- 一文了解 history 和 react-router 的实现原理
我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品.我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值. 本文作者:霜序 前言 在前一篇文章中,我们详细的说了 react-r ...
- cs50ai1
cs50ai1-------Knowledge cs50ai1-------Knowledge 基础知识 课后题目 代码实践 学习链接 总结 基础知识 对我们来说,一些基本的logic是自然而然的,我 ...
- pycurl库使用详解
要使用pycurl库 要初始化一个curl对象 c = pycurl.Curl() 设置选项 c.setopt
- Vue源码学习(四):<templete>渲染第三步,将ast语法树转换为渲染函数
好家伙, Vue源码学习(三):<templete>渲染第二步,创建ast语法树, 在上一篇,我们已经成功将 我们的模板 转换为ast语法树 接下来我们继续进行操作 1.方法封装 由于 ...