转载自:https://blog.csdn.net/cordova/article/details/53097212

背景

之前已经学习了三个基本的光照模型(环境光,漫射光和镜面反射光),这三种模型都是基于平行光的。平行光只是通过一个向量来表示,没有光源起点,因此它不会随着距离的增大而衰减(实际上没有起点根本无法定义光源和某个物体的距离)。现在我们再来看点光源类型,它有光源起点而且有衰减效果,距离光源越远光线越弱。点光源的经典例子是灯泡,灯泡在屋子里可能效果不明显,但是拿到室外就会明显看出它的衰减效果了。注意之前平行光的方向是恒定的,但点光源光线的方向是变化的,四处扩散。点光源想各个方向均匀照射,因此点光源的方向要通过计算物体到点光源之间的向量得到,这就是为什么要定义点光源的起点而不是它的方向。

点光源光线慢慢变淡的的想象叫做‘衰减’。真实光线的衰减是按照平方反比定律的,也就是说光线的强度和离光源的距离的平方成反比。数学原理如下图中的公式:

但3D图形中这个公式计算的结果看上去效果并不好。例如:当距离很近时,光的强度接近无穷大了。另外,开发者除了通过设置光的起始强度外无法控制点光源的亮度,这样就太受限制了,因此我们添加了几个新的因素到公式中使对其的控制更加灵活:

我们在分母上添加了三个光衰减的参数因子,一个常量参数,一个线性参数和一个指数参数。当将常量参数和线性参数设置为零且指数参数设置为1时,就和实际的物理公式是对应的了,也就是这个特殊情况下在物理上是准确的。当设置常量因子参数为1时,调节另外两个参数整体上就有比较好的衰减变化效果了。常量参数的设置是要保证当距离为0时光照强度达到最大(这个要在程序内进行配置),然后随着距离的增大光照强度要慢慢减弱,因分母在慢慢变大。控制好线性参数因子和指数参数因子的变化,就可以实现想要的衰减效果,线性参数主要用于实现缓慢的衰减效果而指数因子可以控制光强度的迅速衰减。

现在总结计算点光源需要的步骤:

  • 计算和平行光一样的环境光;
  • 计算一个从像素点(世界空间中的)到点光源的向量作为光线的方向。利用这个光线方向就可以计算和平行光一样的漫射光以及镜面反射光了;
  • 计算像素点到点光源的距离用来计算最终的光线强度衰减值;
  • 将三种光叠加在一起,计算得到最终的点光源颜色,通过点光源的衰减性三种光看上去也可以被分离开了。

源代码详解

(lighting_technique.h:24)

struct BaseLight
{Vector3f Color;float AmbientIntensity;float DiffuseIntensity;
};
.
.
.
struct PointLight : public BaseLight
{Vector3f Position;struct{float Constant;float Linear;float Exp;} Attenuation;
}

平行光虽然和点光源不一样,但它们仍然有很多共同之处,它们共同的部分都放到了BaseLight结构体中,而点光源和平行光的结构体则继承自BaseLight。平行光额外添加了方向属性到它的类中,而点光源则添加了世界坐标系中的位置变量和那三个衰减参数因子。

(lighting_technique.h:81)

void SetPointLights(unsigned int NumLights, const PointLight* pLights);

这个教程除了展示如何实现点光源,还展示怎样使用多光源。通常只存在一个平行光光源,也就是太阳光,另外可能还会有一些点光源(屋子里的灯泡,地牢里的火把等等)。这个函数参数有一个点光源数据结构的数组和数组的长度,使用结构体的值来更新shader。

(lighting_technique.h:103)

struct {GLuint Color;GLuint AmbientIntensity;GLuint DiffuseIntensity;GLuint Position;struct{GLuint Constant;GLuint Linear;GLuint Exp;} Atten;
} m_pointLightsLocation[MAX_POINT_LIGHTS];

为了支持多个点光源,shader需要包含一个和点光源结构体(只在GLSL中)内容一样的结构体数组。主要有两种方法来更新shader中的结构体数组:

  • 可以获取每个数组元素中每个结构字段的位置(例如,一个数组如果有五个结构体,每个结构体四个字段,那就需要20个‘位置一致变量’),然后单独设置每个元素中每个字段的值。

  • 也可以只获取数组第一个元素每个字段的位置,然后用一个GL函数来保存元素中每个字段的属性类型。例如,数组元素也就是一个结构体的第一个字段是一个float变量,第二个是一个integer变量,就可以在一次回调中使用一个float数组遍历设置数组中每个结构体第一个字段的值,然后在第二次回调中使用一个int数组来设置每个结构体的第二个值。

第一种方法由于要维护大量的位置一致变量因此很浪费资源,但是会更加灵活,因为你可以通过位置一致变量访问更新数组中的任何一个元素,不需要像第二种方法那样先要转换输入的数据。

第二种方法不需要管理那么多的位置一致变量,但是如果想要同时更新数组中的几个元素的话,同时用户传入的又是一个结果体数组(像SetPointLights()),你就要先将这个结构体数组转换成多个字段的数组结构,因为结构体中每个位置的字段数据都要使用一个同类型的数组来更新。当使用结构体数组时,在数组中两个连续元素(结构体)中的同一个字段之间存在内存间隔(被其他字段间隔开了,我们是想要同一个字段的连续字段数组),需要将它们收集到它们自己的同类型数组中。本教程中,我们将使用第一种方法。最好两个都实现一下,看你觉得哪一个方法更好用。

MAX_POINT_LIGHTS是一个常量,用于限制可以使用的点光源的最大数量,并且必须和着色器中的相应值同步一致。默认值为2,当你增加应用中光的数量,随着光源的增加会发现性能越来越差。这个问题可以使用一种称为“延迟着色”的技术来优化解决,这个后面再探讨。

(lighting.fs:46)

vec4 CalcLightInternal(BaseLight Light, vec3 LightDirection, vec3 Normal)
{vec4 AmbientColor = vec4(Light.Color, 1.0f) * Light.AmbientIntensity;float DiffuseFactor = dot(Normal, -LightDirection);vec4 DiffuseColor = vec4(0, 0, 0, 0);vec4 SpecularColor = vec4(0, 0, 0, 0);if (DiffuseFactor > 0) {DiffuseColor = vec4(Light.Color * Light.DiffuseIntensity * DiffuseFactor, 1.0f);vec3 VertexToEye = normalize(gEyeWorldPos - WorldPos0);vec3 LightReflect = normalize(reflect(LightDirection, Normal));float SpecularFactor = dot(VertexToEye, LightReflect);if (SpecularFactor > 0) {SpecularFactor = pow(SpecularFactor, gSpecularPower);SpecularColor = vec4(Light.Color * gMatSpecularIntensity * SpecularFactor, 1.0f);}}return (AmbientColor + DiffuseColor + SpecularColor);
}

这里在平行光和点光源之间实现很多着色器代码的共享就不算什么新技术了。大多数算法是相同的。不同的是,我们只需要考虑点光源的衰减因素。 此外,针对平行光,光的方向是由应用提供的,而对点光源,需要计算每个像素的光的方向。

上面的函数封装了两种光类型之间的共用部分。 BaseLight结构体包含光强度和颜色。LightDirection是额外单独提供的,原因上面刚刚已经提到。 另外还提供了顶点法线,因为我们在进入片段着色器时要对其进行一次单位化处理,然后在每次调用此函数时使用它。

(lighting.fs:70)

vec4 CalcDirectionalLight(vec3 Normal)
{return CalcLightInternal(gDirectionalLight.Base, gDirectionalLight.Direction, Normal);
}

有了公共的封装函数,定义函数简单的包装调用一下就可以计算出平行光了,参数多数来自全局变量。

(lighting.fs:75)

vec4 CalcPointLight(int Index, vec3 Normal)
{vec3 LightDirection = WorldPos0 - gPointLights[Index].Position;float Distance = length(LightDirection);LightDirection = normalize(LightDirection);vec4 Color = CalcLightInternal(gPointLights[Index].Base, LightDirection, Normal);float Attenuation = gPointLights[Index].Atten.Constant +gPointLights[Index].Atten.Linear * Distance +gPointLights[Index].Atten.Exp * Distance * Distance;return Color / Attenuation;
}

计算点光比定向光要复杂一点。每个点光源的配置都要调用这个函数,因此它将光的索引作为参数,在全局点光源数组中找到对应的点光源。它根据光源位置(由应用程序在世界空间中提供)和由顶点着色器传递过来的顶点世界空间位置来计算光源方向向量。使用内置函数length()计算从点光源到每个像素的距离。 一旦我们有了这个距离,就可以对光的方向向量进行单位化处理。注意,CalcLightInternal()是需要一个单位化的光方向向量的,平行光的单位化由LightingTechnique类来负责。 我们使用CalcInternalLight()函数获得颜色值,并使用我们之前得到的距离来计算光的衰减。最终点光源的颜色是通过将颜色和衰减值相除计算得到的。

(lighting.fs:89)

void main()
{vec3 Normal = normalize(Normal0);vec4 TotalLight = CalcDirectionalLight(Normal);for (int i = 0 ; i < gNumPointLights ; i++) {TotalLight += CalcPointLight(i, Normal);}FragColor = texture2D(gSampler, TexCoord0.xy) * TotalLight;
}

有了前面的基础,片段着色器方面就变得非常简单了。简单地将顶点法线单位化,然后将所有类型光的效果叠加在一起,结果再乘以采样的颜色,就得到最终的像素颜色了。

(lighting_technique.cpp:279)

void LightingTechnique::SetPointLights(unsigned int NumLights, const PointLight* pLights)
{glUniform1i(m_numPointLightsLocation, NumLights);for (unsigned int i = 0 ; i < NumLights ; i++) {glUniform3f(m_pointLightsLocation[i].Color, pLights[i].Color.x, pLights[i].Color.y, pLights[i].Color.z);glUniform1f(m_pointLightsLocation[i].AmbientIntensity, pLights[i].AmbientIntensity);glUniform1f(m_pointLightsLocation[i].DiffuseIntensity, pLights[i].DiffuseIntensity);glUniform3f(m_pointLightsLocation[i].Position, pLights[i].Position.x, pLights[i].Position.y, pLights[i].Position.z);glUniform1f(m_pointLightsLocation[i].Atten.Constant, pLights[i].Attenuation.Constant);glUniform1f(m_pointLightsLocation[i].Atten.Linear, pLights[i].Attenuation.Linear);glUniform1f(m_pointLightsLocation[i].Atten.Exp, pLights[i].Attenuation.Exp);}
}

此函数通过迭代遍历数组元素并依次传递每个元素的属性值,然后使用点光源的值更新着色器。 这是前面所说的“方法1”。

本教程的Demo显示两个点光源在一个场景区域中互相追逐。一个光源基于余弦函数,而另一个光源基于正弦函数。该场景区域是由两个三角形组成的非常简单的四边形平面,法线是一个垂直的向量。

OpenGL着色器程序解析--点光源相关推荐

  1. OpenGL着色器程序解析--纹理贴图

    背景 纹理贴图意思是将任意类型的图片贴在3d模型的一个或者多个面上.图片可以是任意的但通常是一种通用的样式,比如:砖块.植物.荒芜的土地等等,可以提高场景的真实性.比较下面两幅图片:  为了实现纹理贴 ...

  2. OpenGL着色器程序解析--镜面反射光

    转载自:https://blog.csdn.net/cordova/article/details/52996876 背景 我们在计算环境光的时候,光的强度是唯一的影响因素.然后处理漫射光的时候公式中 ...

  3. 《Qt-OpenGL系列编程》课程学习记录(1):相关概念、VAO、VBO、绘制三角形、使用OpenGL原生方式编译链接着色器程序

    大家可以去B站看课程的视频支持一下作者哈: OpenGL,Qt实现:1入门篇(已更完)_哔哩哔哩_bilibili课程相关源码.PPT.安装包,完整课程合集(1:入门篇:2:基础光照:3:模型加载:4 ...

  4. 3D河豚鱼—OpenGL着色器(Shader)和GLSL程序

    3D河豚鱼-OpenGL着色器(Shader)和GLSL程序 效果图 程序代码 #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #el ...

  5. OpenGL着色器基础

    前言: 本文翻译自LearnOpengl经典教程,OpenGL着色器基础介绍的比较通俗易懂,特总结分享一下! 为什么要使用着色器? 我们知道,OpenGL一般使用经典的固定渲染管线来渲染对象,但是随着 ...

  6. OpenGL着色器GLSL

    OpenGL着色器 OpenGL着色器简介 GLSL 数据类型 向量 输入与输出 顶点着色器 片段着色器 Uniform 更多属性 我们自己的着色器类 从文件读取 OpenGL着色器简介 着色器(Sh ...

  7. OpenGL(三)——OpenGL着色器基础

    上一篇我们介绍了OpenGL基础相关的知识:OpenGL图形绘制和OpenGL入门,今天介绍一下OpenGL另一重要的成员----OpenGL着色器. 什么是OpenGL着色器? Open GL ES ...

  8. opengl 着色器

    Opengl 着色器 文章目录 Opengl 着色器 前言 一.GLSL 二.使用步骤 效果 前言 着色器(Shader)是运行在GPU上的小程序.这些小程序为图形渲染管线的某个特定部分而运行.从基本 ...

  9. OpenGL着色器透视变换实例-通过旋转平移调试着色器

    OpenGL 着色器新手样例 带透视变换和旋转平移缩放 OpenGL着色器样例 - 最简单的顶点着色器 + 片元着色器 头文件和宏定义 全局变量部分 读取着色器 从文本中读取着色器代码 初始化着色器 ...

  10. OpenGL 着色器基础

    Instagram,Snapchat,Photoshop. 所有这些应用都是用来做图像处理的.图像处理可以简单到把一张照片转换为灰度图,也可以复杂到是分析一个视频,并在人群中找到某个特定的人.尽管这些 ...

最新文章

  1. 微信小程序红包开发 小程序发红包 开发过程中遇到的坑 微信小程序红包接口的...
  2. 【原】Ubuntu中安装 mercurial - TortoiseHG
  3. 国际会议排名zz(通信、网络类)
  4. 用界面读取图片并且保存图片的方法
  5. webpack原理探究 打包优化
  6. Linux 2.6内核配置说明(Networking网络)
  7. 偏移出来的数据不准_独家解读!京东高可用分布式流数据存储的架构设计
  8. 如何理性客观地看待人工智能热潮
  9. 手把手教你用AI画梵高的《星空》
  10. python3虚拟环境不带任何模块_Python3虚拟环境-不存在的包
  11. 博图在线升级 gsd_升级ing!旺铺不够吸引人?快来学习国际站“吸睛”新玩法...
  12. 记录一次破解移动吉比特光猫H2-2超管密码的过程
  13. linux计划任务详解,Linux计划任务详解
  14. 闪迪u盘量产工具U盘正常显示但是多出几个空分区的解决方案
  15. 基于vue-cli3的vue项目 通过postcss-pxtorem 实现px自动转换成rem
  16. Doxygen 安装使用
  17. 视频采集卡二次开发(天敏SDK2500+openCV)
  18. Oracle 利用 UTL_SMTP 包发送邮件
  19. DSP CCS12.00 芯片:TMS320F28335 TFTLCD显示屏幕的应用
  20. OpenCV教程(转自:浅墨_毛星云博客)

热门文章

  1. 基于氚云平台的应用开发学习(三)
  2. [自我介绍]第一篇博客
  3. 关于idea中的maven索引异常问题(在idea中创建maven项目时,在pom.xml文件中加入依赖,提示出现的很慢)
  4. android开发 解析 b5,Android iconify 使用详解
  5. JDBC 连接mysql数据库出现 client does not support authen…… update mysql client
  6. 谷歌浏览器下载、安装、配置。(保姆级详细教程。)
  7. iOS打包静态库的姿势
  8. win 10计算机服务,win10 怎么打开服务_win10打开系统服务的3种方法
  9. LeetCode罗马数字转整数
  10. ptt评论量子计算机,PTT网友热议Nuguri替补:打野下路状态都拉跨不换,先换上路??...