按照冯氏光照模型的说法,任何光线都被分割成环境光照、漫反射光照和镜面光照三部分,这三部分分别与物体产生作用叠加后得到我们看到的颜色。但是实际上在现实中有许多种不同的光源(Light Source),比如太阳、蜡烛、手电筒、灯泡等等,仔细观察可以发现这些光源发射光线的方式是不同的,概括起来有三种:

(三张图片来自百度图片搜索)
  • 方向光(Directional Light):又称定向光、平行光,这种光源发射出来的光线是平行光线,每一条光线的方向是相同的,太阳光可以看成是方向光;
  • 点光源(Point Light):一个点光源朝四周发射出放射状的光线,灯泡、蜡烛等就是这种光源;
  • 聚光灯(Spot Light):光源发射出一个圆锥体光,圆锥体外部没有光线,内部光线强度分布表现为靠近圆锥边缘的光强会逐渐变小,手电筒、舞台投光灯等就是这种光源

当然这三种光源不能概括现实中所有的光源,还有更加复杂的,比如灯管就不包括在上述三种灯源中(可以用一排点光源模拟)。话虽如此,这三种光源还是能代表大多数真实光源的,如果用过unity,应该就知道它也提供了这三种光源类型,并且只有这三种才支持实时渲染。

所以我们也用OpenGL去模拟这三种光源的光照效果,并且实现片段着色器的编写。


方向光

方向光(平行光)的特点就是光的照射方向不变,并且光照强度不随着空间变化而衰减。这是因为我们默认把太阳光当成最重要的平行光,而太阳距离地球很远,在地球上的一点点距离变化导致的衰减可以忽略不计,但若是人工制造的平行光(比如用凸透镜+点光源制造的平行光)就必须要考虑衰减了。为了方便起见,渲染时就不考虑衰减了。

方向光需要考虑的参数包括:

  • 光照方向 d i r e c t i o n direction direction ;
  • 三个光照分量 a m b i e n t ambient ambient, d i f f u s e diffuse diffuse, s p e c u l a r specular specular ;

有了这几个参数,按照冯氏光照模型的方法计算三个光照分量的值,然后累加即可。

点光源

点光源的特点是光线呈放射状,并且光照强度随着距离的增大而衰减(Attenuation)。这里的衰减是两方面的:一方面点光源是球面波,按照能量守恒,光强会按 1 R 2 \frac{1}{R^2} R21​衰减,原理就是反比于球体的表面积;另一方面光与介质发生作用而导致能量形式转变,光能变成了热能等等,所以会衰减。假设待渲染的某个片段和光源的距离为 d d d ,那么该片段处的光照衰减值近似为:
L u m i n o s i t y = 1.0 Q u a d r a t i c ∗ d 2 + L i n e a r ∗ d + C o n s t a n t Luminosity=\frac{1.0}{Quadratic*d^2+Linear*d+Constant} Luminosity=Quadratic∗d2+Linear∗d+Constant1.0​
式中的三个参数 Q u a d r a t i c , L i n e a r , C o n s t a n t {Quadratic,Linear,Constant} Quadratic,Linear,Constant 的取值可以参考这里,一般来说,常数项可以保持为 1.0 1.0 1.0不变,而一次项和二次项的系数随着覆盖距离的增大而减小,因为越小的系数在越大的距离才会衰减到一定小的值。

点光源需要考虑的参数包括:

  • 光源位置 p o s i t i o n position position;
  • 三个衰减系数 Q u a d r a t i c , L i n e a r , C o n s t a n t {Quadratic,Linear,Constant} Quadratic,Linear,Constant
  • 三个光照分量 a m b i e n t ambient ambient, d i f f u s e diffuse diffuse, s p e c u l a r specular specular ;

因此我们计算完三个光照分量的值之后,还需要乘上 L u m i n o s i t y Luminosity Luminosity ,才得到最终的结果。

聚光灯

聚光灯的特点除了包括点光源的所有特点之外,还把光照限制在了一个圆锥体内部,因此聚光灯不可避免的有一个光照方向。在圆锥体的内部的同一圆切面中,光照强度的分布也不是均匀的,体现为偏离中心比较多的光照强度会变小。因此我们给聚光灯定义内圆锥面和外圆锥面,在同一个圆形切面中,内圆锥面内部的光强相等,内圆锥面与外圆锥面之间的光强线性递减到 0 。

定义内圆锥半角(圆锥半角就是母线的夹角)为 α \alpha α,外圆锥半角为 β \beta β,被照射位置和光源的连线与轴的夹角为 θ \theta θ ,那么新的一个光强系数满足:
I n t e n s i t y = c o s θ − c o s β c o s α − c o s β Intensity=\frac{cos\theta-cos\beta}{cos\alpha-cos\beta} Intensity=cosα−cosβcosθ−cosβ​
把 I n t e n s i t y Intensity Intensity **约束(Clamp)**在 [ 0 , 1 ] [0,1] [0,1]之间,就得到了想要的结果。

聚光灯需要考虑的参数包括:

  • 光照方向 d i r e c t i o n direction direction ;
  • 光源位置 p o s i t i o n position position;
  • 内外圆锥半角的余弦值 i n n e r C o s , o u t e r C o s innerCos,outerCos innerCos,outerCos;
  • 三个衰减系数 Q u a d r a t i c , L i n e a r , C o n s t a n t {Quadratic,Linear,Constant} Quadratic,Linear,Constant
  • 三个光照分量 a m b i e n t ambient ambient, d i f f u s e diffuse diffuse, s p e c u l a r specular specular ;

在计算完三个光照分量的值之后,乘上 L u m i n o s i t y ∗ I n t e n s i t y Luminosity*Intensity Luminosity∗Intensity,就得到最终的结果。


片段着色器代码

在OpenGL中编写片段着色器的GLSL代码,把每种光源需要的参数封装成结构体,每种光源计算得到RGB的过程封装成函数,然后可以任意定义任意个数量的光源,然后累加起来光照强度得到最终的颜色输出。具体的代码已经注释得很详细:

#version 430 core// 从前面传下来的数据
in vec3 fragNormal;     // 片段法向量(非单位向量)
in vec3 FragPos;        // 片段位置
in vec2 TexCoords;      // 纹理采样坐标// 片段着色器的颜色输出
out vec4 FragColor;// 定义物体材质,因为这里用了图片纹理做光照贴图,因此就可以用采样器直接获得对应坐标的RGB
// 也可以直接定义纯色的RGB向量
struct Material
{sampler2D diffuse;  // 漫反射纹理sampler2D specular; // 镜面反射纹理float shininess;    // 反光度:越高,反光能力越强,散射的越少,高光点越小
};
uniform Material material;  // 物体材质
uniform vec3 viewPos;       // 观察者位置// 各个光源类型的定义,以及对应求解反射光的函数定义
// 函数参数的normal、viewDir必须是单位向量//----------------- 方向光 ------------------
struct DirLight
{vec3 direction;     // 光照方向// 三个光照分量vec3 ambient;vec3 diffuse;vec3 specular;
};vec3 calDirLight(DirLight light, vec3 normal, vec3 viewDir)
{vec3 lightDir = normalize(-light.direction);// 环境光照vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));// 漫反射光照float diff = max(dot(normal, lightDir), 0.0);vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));// 镜面光照vec3 reflectDir = reflect(-lightDir, normal);float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);   // 材质中【反光度】决定的系数vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));// 合并return (ambient + diffuse + specular);
}
//-------------------------------------------//----------------- 点光源 ------------------
struct PointLight
{vec3 position;  // 光源位置// 三个衰减系数float constant;float linear;float quadratic;// 三个光照分量vec3 ambient;vec3 diffuse;vec3 specular;
};vec3 calPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{vec3 lightDir = normalize(light.position - fragPos);// 环境光照vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));// 漫反射光照float diff = max(dot(normal, lightDir), 0.0);vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));// 镜面光照vec3 reflectDir = reflect(-lightDir, normal);float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);   // 材质中【反光度】决定的系数vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));// 合并,考虑光强衰减float distance = length(light.position - fragPos);float luminosity = 1.0 / (light.constant + light.linear * distance + light.quadratic * distance * distance);   // 光强衰减系数return (ambient + diffuse + specular) * luminosity;
}
//-------------------------------------------//----------------- 聚光灯 ------------------
struct SpotLight
{vec3 direction;     // 光照方向vec3 position;      // 光源位置float innerCos;     // 内圆锥半角余弦值float outerCos;     // 外圆锥半角余弦值// 三个衰减系数float constant;float linear;float quadratic;// 三个光照分量vec3 ambient;vec3 diffuse;vec3 specular;
};vec3 calSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{vec3 lightDir = normalize(light.position - fragPos);// 环境光照vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));// 漫反射光照float diff = max(dot(normal, lightDir), 0.0);vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));// 镜面光照vec3 reflectDir = reflect(-lightDir, normal);float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);   // 材质中【反光度】决定的系数vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));// 考虑光强衰减float distance = length(light.position - fragPos);float luminosity = 1.0 / (light.constant + light.linear * distance + light.quadratic * distance * distance);   // 光强衰减系数// 从这里开始加入聚光灯的判断float theta = dot(lightDir, normalize(-light.direction));float epsilon = light.innerCos - light.outerCos;float intensity = clamp((theta - light.outerCos) / epsilon, 0.0, 1.0);    // 聚光灯决定的光强系数return (ambient + diffuse + specular) * luminosity * intensity;
}
//-------------------------------------------// !!!!! 前面部分定义了一些到目前为止必需的东西,而后面可以根据自己的需要进行编写 !!!!!
// 比如后面,我定义了一个方向光、一个聚光灯和四个点光源uniform DirLight dirLight;
#define NR_POINT_LIGHTS 4
uniform PointLight pointLights[NR_POINT_LIGHTS];
uniform SpotLight spotLight;uniform int isSpotLight;    // 用这个控制聚光灯的亮灭void main()
{vec3 norm = normalize(fragNormal);              // 法向量的单位向量vec3 viewDir = normalize(viewPos - FragPos);    // 观察方向的单位向量vec3 result = vec3(0.0f, 0.0f, 0.0f);// 后面直接调用函数就可以很方便地计算出每种光照的影响result += calDirLight(dirLight, norm, viewDir);for(int i = 0; i < NR_POINT_LIGHTS; i++)result += calPointLight(pointLights[i], norm, FragPos, viewDir);if(isSpotLight == 1)result += calSpotLight(spotLight, norm, FragPos, viewDir);FragColor = vec4(result, 1.0f);
}

Shader的参数比较多,一定小心注意要把所有用到的uniform都赋好值。

学习OpenGL:光源的种类和实现相关推荐

  1. 学习opengl入门

    当然,这些只是我7天来业余时间的学习,我觉得这个网址不错,大家如果也想学习opengl,并且具有一定的C语言C++基础,入门课程推荐大家去学习这个网址http://www.cnblogs.com/cr ...

  2. 从零开始学习OpenGL ES之五 – 材质

    从零开始学习OpenGL ES之五 – 材质 作者: iPhoneGeek 爱疯极客 09-Jan-10 iPhone Development 浏览次数: 411 |  评论 ↓ Tweet Shar ...

  3. 学习opengl官方指南 01 opengl介绍

    申明:翻译的不好,勿喷,轻喷. 本章的目标 1. 可以知道opengl一些常见的专有名词 2. 识别不同级别的渲染复杂度 3. 理解opengl的命令语法 4. 知道opengl管段渲染的系列操作 5 ...

  4. 【我的OpenGL学习进阶之旅】【持续更新】关于学习OpenGL的一些资料

    目录 一.相关书籍 OpenGL 方面 C方面 NDK 线性代数 二.相关博客 2.0 一些比较官方的链接 2.1 OpenGL着色器语言相关 2.2 [[yfan]](https://segment ...

  5. WhyGL:一套学习OpenGL的框架,及翻写Nehe的OpenGL教程

    最近在重学OpenGL,之所以说重学是因为上次接触OpenGL还是在学校里,工作之后就一直在搞D3D,一转眼已经毕业6年了.OpenGL这门手艺早就完全荒废了,现在只能是重学.学习程序最有效的办法是动 ...

  6. OpenGL光源位置

    一.OpenGL光源简介 OpenGL提供了多种形式的光源,如点光源.平行光源和聚光灯光源等.所有光源都使用 glLight*接口来设置光源属性,其中包括 glLight{if} 和 glLight{ ...

  7. 从显示一张图片开始学习OpenGL ES

    前言 网上很多介绍OpenGL ES的文章,但由于OpenGL ES内容太多,所以这些文章难免过于臃肿杂乱,很难抓住重点,对于初学者来说最后还是云里雾里.很多人(包括笔者本人)开始深入了解OpenGL ...

  8. LED光源的种类与LED光源与灯具的定义介绍

    下面为大家介绍LED光源的种类与LED光源与灯具的区别,也就是定义.LED光源的种类有很多种,LED光源表示的是LED照明行业.LED是由发光二极管简称LED.是由Ga.As.P.N(镓.砷.磷.氮) ...

  9. 在 Lazarus 中学习 OpenGL

    在 Lazarus 中学习 OpenGL 教学网站 https://learnopengl-cn.github.io/ API 查询 https://docs.gl/ 创建窗口 Lazarus 可以在 ...

  10. 通过GLUT库学习OpenGL

    GLUT(The OpenGL Utility Toolkit),OpenGL实用工具箱,是一个与窗口系统无关的OpenGL编程工具箱. 最早由Mark Robins编写.它实现了一个简单的窗口应用程 ...

最新文章

  1. 京东API网关实践之路!
  2. 数开头的成语有哪些_艺术留学文书申请过程中应避开哪些雷区?ACG艺术留学
  3. SuperMap webGIS 简易提示框示例
  4. c++17(2)-枚举类enum class
  5. WinXP下 扫雷程序逆向分析 --扫雷辅助(一)
  6. CorelDraw技巧|设计师要了解数位板怎么用
  7. [html] 可替换元素和不可替换元素有什么不同的特点?
  8. 黑苹果简单驱动 MultiBeast用法基础篇
  9. Centos8装Wine笔记
  10. Mybatis(1)----------简介及第一开发程序
  11. 机器学习入门到进阶十本好书推荐
  12. JVM - 工欲善其事必先利其器之虚拟机工具(上)
  13. 阿里云服务器深度学习环境从0配置(Ubuntu16.04+cuda8.0+cudnn6.0+tensorflow1.4+Anaconda3+opencv2+tensorlayer1.7.4)
  14. 安卓开发招聘!免费Android高级工程师学习资源,2年以上经验必看
  15. chrome、Firefox、IE浏览器和驱动下载地址
  16. FS2222可调过压过流芯片IC,40V耐压过压保护可调OVP可调OCP
  17. DateEdit仅显示和选择年份
  18. JS键盘对应Code
  19. OS Review3 并发进程
  20. 2019.9.21-冒泡排序代码

热门文章

  1. python grid函数_详解numpy中的meshgrid函数用法
  2. Linux 开机启动的三种方法
  3. 易查分应用大全:查询统计功能使用说明
  4. 全量手机号各号段数量
  5. HADOOP常用指令
  6. echart 实现多柱状图+多折线图
  7. Python小程序(8)--空气质量指数计算+爬虫获取空气质量指数+数据分析
  8. c51汇编语言冒泡排序,汇编实现冒泡排序的方法示例
  9. 成都迎来史诗级架构运维峰会,三大女神降临,送海量技术图书(8折优惠仅剩一周)...
  10. Windows启动docker客户端报错:Hardware assisted virtualization and enabled in the BIOS