OpenGL 基础光照详解
1. 光照
显示世界中,光照环境往往是相对复杂的。因为假设太阳作为世界的唯一光源,那么太阳光照在物体A上A将阳光进行反射后,A又做为一个新的光源共同作用于另一个物体B。所以于B来讲光源是复杂的。然而这只是其中一个因素,受制于天气、温度等其他情况我们需要考虑的因素更多。在OpenGL中我们仅考虑一些简单的模型,对现实情况进行一个近似的模拟。这一节中我们主要介绍一下冯氏光照模型。冯氏光照模型中主要有3个分量构成:

- 环境光照(Ambient Lighting):即使在黑暗的情况下,世界上通常也仍然有一些光亮(月亮、远处的光),所以物体几乎永远不会是完全黑暗的。为了模拟这个,我们会使用一个环境光照常量,它永远会给物体一些颜色。
- 漫反射光照(Diffuse Lighting):模拟光源对物体的方向性影响(Directional Impact)。它是冯氏光照模型中视觉上最显著的分量。物体的某一部分越是正对着光源,它就会越亮。
- 镜面光照(Specular Lighting):模拟有光泽物体上面出现的亮点。镜面光照的颜色相比于物体的颜色会更倾向于光的颜色。
接下来我们将逐一讲解这三个分量。
2. 环境光照
上文中提到了,真实世界中的光源情况是复杂的。如果对系统中的每一个光源都进行考虑,这种算法叫做全局照明算法。但是这种算法既开销高昂又极其复杂。我们使用环境光照来简化这个概念,即使用一个很小的常量光照颜色,叠加到实际光照颜色中。

我们看到此处直接用环境颜色与物体颜色相乘。这是因为当前阶段我们还没有添加光源。
3. 漫反射光照
当光线照射在平面上时会发生镜面反射,这是一个光学常识。但事实上真实世界中的物体表面一般都不是完全的平面而是凹凸不平的(微观上的凹凸不平),此时将发生漫反射。漫反射的结果就是,你不仅可以从光源的镜像角度可以观察到物体,从各个角度你都可以观察得到。不过漫反射的强度区域光源射入平面的夹角有关。通过实验,我们观察到当射入角度与平面法线夹角越小时,漫反射强度越强。
我们要在GL中模拟漫反射效果。从上述的叙述中我们知道,漫反射的关键因素在于法线夹角。那么也就是说,我们在GL中需要两点,第一个是法向量,第二个是光线射入的角度。
法向量是相对于平面而言的,于点是没有意义的。而同一点在不同平面中对应的法向量也是不同的。这就决定了我们需要改造我们的顶点数据,传一组法向量给顶点着色器,此外绘制VAO时也不能以EBO绘制,而应该以VBO绘制。
虽然对灯的着色器使用不能完全利用的顶点数据看起来不是那么高效,但这些顶点数据已经从箱子对象载入后开始就储存在GPU的内存里了,所以我们并不需要储存新数据到GPU内存中。这实际上比给灯专门分配一个新的VBO更高效了。
4. 计算漫反射光照
顶点数据中我们传入了法向量,我们还需要一个角度。顶点着色器中我们是可以拿到顶点数据的,这时只要我们拿到光源的位置即可以计算出光的方向了,标准化以后就是方向向量了。光源的位置一般是相对固定的,我们可以用uniform变量在外界对着色器进行赋值。记得将光源坐标和顶点坐标都转化为世界坐标哦,或者保证光源坐标、顶点坐标和法向量在同一坐标系统内也可。法向量和方向向量都记得要标准化,这样他们点乘的结果才是两个向量的夹角余弦值。

最后,如果夹角余弦值小于零,我们认为这时无意义的,他将造成我们的漫反射分量为负值,而真实世界中,仅可能是无影响,而不会是负影响。所以我们小于0时我们取0。
片段着色器中的代码大概是这个样子的:


还有一件事,之前说过,要保证光源坐标、顶点坐标和法向量在同一坐标系统内。一般情况下我们会将他们转化为世界坐标,因为这更符合我们的直觉。我们要把法向量也转换到世界空间内。顶点数据中,你所写的法向量直觉上应该是局部坐标,所以要经过模型矩阵转换。此外,模型矩阵中我们也可能对物体进行非比例变化,这将导致我们的法向量发现也发生改变。所以在把法向量从局部坐标转化至世界坐标时,我们要经过法线矩阵的转换。我们可以通过逆矩阵和转置矩阵来变化模型矩阵。所以顶点着色器中我们的代码大概是这个样子的:

其中inverse()是求逆矩阵函数,transpose是求转置矩阵函数。
5. 镜面光照
那么,最后我们只要再将镜面反射叠加进去就好了。
跟漫反射的原理差不多,我们看到物体的颜色跟物体表面镜面反射的光线与我们观察的视角的夹角有关。当夹角越小时,那么镜面光的影响越大。与我们的效果就是,我们将看到一个高光。
那么我们还是需要两个方向向量,第一个是镜面光的方向向量,第二个是观察角度的方向向量。
镜面光的方向我们可以根据光源方向及法向量通过反射计算出来。观察方向则是我们摄像机的位置与观察点所构成的向量。
所以我们的两个向量大概是这个样子的:

我们看到,观察方向需要我们传入一个摄像机位置,我们需要在渲染循环中将摄像机的世界坐标传给片段着色器。
计算反射光时,我们使用的是reflect函数。这个函数需要的是传入一个从光源指向平面的向量以及平面的法向量。这里之所以我们传入-lightDir是因为我们之前计算的lightDir是从平面指向光源的。
这里我们最好给一个镜面强度系数,以免当光源垂直照向平面时,我们的镜面光分量过于明亮。
两个向量获取后我们就可以计算他们的夹角来计算我们的镜面光分量了。

首先我们看镜面系数的计算方式,点乘求夹角余弦值后取大于0的值,然后调用pow函数。pow是取这个系数的指定次幂。我们指定了32,代表我们指定了他的反光度是32。一个物体的反光度越高,反射光的能力越强,散射得越少,高光点就会越小。在下面的图片里,你会看到不同反光度的视觉效果影响:

至此我们计算出镜面系数,用光源颜色乘镜面系数乘镜面强度系数后即可获得镜面分量。
最后的最后,我们将环境光照、漫反射光照及镜面光照进行叠加后,与物体颜色相乘,即可获得物体在冯氏光照光源影响下所展示的颜色了。

在光照着色器的早期,开发者曾经在顶点着色器中实现冯氏光照模型。在顶点着色器中做光照的优势是,相比片段来说,顶点要少得多,因此会更高效,所以(开销大的)光照计算频率会更低。然而,顶点着色器中的最终颜色值是仅仅只是那个顶点的颜色值,片段的颜色值是由插值光照颜色所得来的。结果就是这种光照看起来不会非常真实,除非使用了大量顶点。

在顶点着色器中实现的冯氏光照模型叫做Gouraud着色(Gouraud Shading),而不是冯氏着色(Phong Shading)。记住,由于插值,这种光照看起来有点逊色。冯氏着色能产生更平滑的光照效果。
OpenGL 基础光照详解的更多相关文章
- I2C 基础原理详解
今天来学习下I2C通信~ I2C(Inter-Intergrated Circuit)指的是 IC(Intergrated Circuit)之间的(Inter) 通信方式.如上图所以有很多的周边设备都 ...
- python 3.x 爬虫基础---Urllib详解
python 3.x 爬虫基础 python 3.x 爬虫基础---http headers详解 python 3.x 爬虫基础---Urllib详解 前言 爬虫也了解了一段时间了希望在半个月的时间内 ...
- RabbitMQ基础知识详解
什么是MQ? MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法.MQ是消费-生产者模型的一个典型的代表,一端往消息队列中不断写入消息,而另一端则可以读取队列中 ...
- Nmap扫描教程之基础扫描详解
Nmap扫描教程之基础扫描详解 Nmap扫描基础扫描 当用户对Nmap工具了解后,即可使用该工具实施扫描.通过上一章的介绍,用户可知Nmap工具可以分别对主机.端口.版本.操作系统等实施扫描.但是,在 ...
- jmeter 基础功能详解
jmeter 基础功能详解 thread group:包含一组线程,每个线程独立地执行测试计划. sampler:采样器,有多种不同的sample实现,用来发起各种请求,如http请求,jdbc请求, ...
- hadoop基础-SequenceFile详解
hadoop基础-SequenceFile详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.SequenceFile简介 1>.什么是SequenceFile 序列文件 ...
- Cisco路由技术基础知识详解
第一部分 请写出568A的线序(接触网络第一天就应该会的,只要你掐过,想都能想出来) .网卡MAC地址长度是( )个二进制位(16进制与2进制的换算关系,只是换种方式问,不用你拿笔去算) A.12 ...
- RabbitMQ,Apache的ActiveMQ,阿里RocketMQ,Kafka,ZeroMQ,MetaMQ,Redis也可实现消息队列,RabbitMQ的应用场景以及基本原理介绍,RabbitMQ基础知识详解,RabbitMQ布曙
消息队列及常见消息队列介绍 2017-10-10 09:35操作系统/客户端/人脸识别 一.消息队列(MQ)概述 消息队列(Message Queue),是分布式系统中重要的组件,其通用的使用场景可以 ...
- # OpenGL常用函数详解(持续更新)
OpenGL常用函数详解(持续更新) 初始化 void glutInit(int* argc,char** argv)初始化GULT库,对应main函数的两个参数 void gultInitWindo ...
- 经典Spring入门基础教程详解
经典Spring入门基础教程详解 https://pan.baidu.com/s/1c016cI#list/path=%2Fsharelink2319398594-201713320584085%2F ...
随机推荐
- 代码随想录算法训练营第一天| LeetCode 704. 二分查找、LeetCode 27. 移除元素
704. 二分查找 题目链接:https://leetcode.cn/problems/binary-search/ 视频链接:https://www.bilibili.c ...
- Go中 net/http 使用
转载请注明出处: net/http是Go语言标准库中的一个包,提供了实现HTTP客户端和服务器的功能.它使得编写基于HTTP协议的Web应用程序变得简单和方便. net/http包的主要用途包括: 实 ...
- React: 动态添加样式
问题背景 在软件开发过程中,经常会出现动态添加style或className,比如:同一个表格组件在A处调用,需要固定前四列数据, B处调用则不用,那这时候,动态添加元素就派上了用场. 解决方案 如下 ...
- QPushButton按钮的使用
1 import sys 2 from PyQt5.QtCore import * 3 from PyQt5.QtGui import * 4 from PyQt5.QtWidgets import ...
- Unity Shader编辑器工具类ShaderUtil 常用函数和用法
Unity Shader编辑器工具类ShaderUtil 常用函数和用法 Unity的Shader编辑器工具类ShaderUtil提供了一系列函数,用于编译.导入和管理着色器.本文将介绍ShaderU ...
- Pandas 使用教程 JSON
目录 JSON 转换为 CSV 简单 JSON 从 URL 中读取 JSON 数据: 字典转化为 DataFrame 数据 内嵌的 JSON 数据 复杂 JSON Pandas 可以很方便的处理 JS ...
- OpenLDAP 自助修改密码系统
一.创建docker-compose文件 root@xx:~# mkdir self-service-password root@xx:~# cd self-service-password root ...
- Asp-Net-Core开发笔记:FrameworkDependent搭配docker部署
前言 之前我写过一篇使用 docker 部署 AspNetCore 应用的文章,这种方式搭配 CICD 非常方便, build 之后 push 到私有的 dockerhub ,在生产服务器上 pull ...
- Confluence的Excel插件Elements Spreadsheet安装
背景 Confluence是现在广泛使用的团队协作文档系统.虽然自身带了一些表格编辑功能,但表格的整体功能较弱,比如不能通过Excel文件进行导入导出,表格在复制到Excel时格式会比较奇怪等等.对于 ...
- Python 列表操作指南3
示例,将新列表中的所有值设置为 'hello': newlist = ['hello' for x in fruits] 表达式还可以包含条件,不像筛选器那样,而是作为操纵结果的一种方式: 示例,返回 ...