版权

cesuolidec4

https://blog.csdn.net/xiewenzhao123/article/details/54600191

引言

现实世界的光照是极其复杂的,而且会受到诸多因素的影响,这是以目前我们所拥有的处理能力无法模拟的。因此OpenGL的光照仅仅使用了简化的模型并基于对现实的估计来进行模拟,这样处理起来会更容易一些,而且看起来也差不多一样。这些光照模型都是基于我们对光的物理特性的理解。其中一个模型被称为冯氏光照模型(Phong Lighting Model)。冯氏光照模型的主要结构由3个元素组成:环境(Ambient)、漫反射(Diffuse)和镜面(Specular)光照。这些光照元素看起来像下面这样:

先看phong光照模型中一段顶点着色器的代码

#version 330 core
out vec4 FragColor;in vec3 Normal;  //法相向量
in vec3 FragPos; //uniform vec3 lightPos;
uniform vec3 viewPos;
uniform vec3 lightColor;
uniform vec3 objectColor;void main()
{//ambient环境光float ambientStrength = 0.1f;   //环境光照强度vec3 ambient = ambientStrength * lightColor;//diffuse漫反射光vec3 norm =    normalize(Normal);vec3 lightDir = normalize(lightPos - FragPos); //光的方向向量是光的位置向量与片段的位置向量之间的向量差float diff = max(dot(norm, lightDir), 0.0f);vec3 diffuse = diff * lightColor;//specular镜面反射光float specularStrength = 0.9;vec3 viewDir = normalize(viewPos - FragPos);vec3 reflectDir = reflect(-lightDir, norm);float spec = pow(max(dot(viewDir, reflectDir), 0.0f), 32);vec3 specular = specularStrength * spec * lightColor;vec3 result = (ambient + diffuse + specular) * objectColor;FragColor = vec4(result, 1.0f);}

OpenGL光照模型总结

一、 冯氏光照
所谓的冯氏光照分为三大部分:环境光照、漫反射光照以及镜面反射光照。对于同一个物体来讲,当有多个光源发出光线照亮该物体时,都可以将其划分成上述三种情况分别进行计算,最后将三部分组合即为其中一个光源照射物体后得到的最终结果,再将每个光源的照射结果进行叠加即为复杂照射环境下的最终结果。
1、 环境光照:
这是冯氏光照中最简单的光照,只需要用环境光向量乘上物体本身的颜色即可,要注意的是环境光往往比较弱,所以环境光向量要设置的比较小。

vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));

解释:
light.ambient为环境光线,material.diffuse为漫反射光照贴图(因为大部分情况下环境光贴图与漫反射贴图相同),TexCoords为纹理坐标,texture函数会在纹理坐标内生成一些插值坐标,用来采样纹理图片其他地方的颜色。
2、 漫反射光照:

如图可知,计算漫反射光照最重要的就是计算光线向量与法向量的夹角角度。法向量可以通过叉乘获得。而光线向量要通过光源位置向量与片段位置向量相减获得。

vec3 lightDir = normalize(light.position - fragPos);
float diff = max(dot(normal, lightDir), 0.0);
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));

解释:
LightDir为光线向量,normalize是向量标准化函数,将向量变为长度为1的单位向量。Diff为法向量与光线向量夹角,注意该夹角不能小于0,normal是法向量我们一般可以通过数据输入得到。
重要注意点:
当模型发生不均匀缩放是会破坏法向量导致光线扭曲。
解决方法:使用正规矩阵(模型矩阵左上角的逆矩阵的转置矩阵)
得到如下顶点着色器代码:


3、 镜面反射光照

计算该光照需要以下几个数据:光源位置,片段位置,法向量,观察者位置。
光源位置和片段位置能够求出光线向量,通过光线向量与法向量能够计算出反射光线向量,观察者位置和片段位置能够计算出观察者向量,观察者向量与反射光线向量能够求出角度,该角度即为参数spec,用来计算镜面反射光线向量。

vec3 lightDir = normalize(light.position - fragPos);
vec3 viewDir = normalize(viewPos - FragPos);
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));

viewDir为观察者向量。
reflectDir是反射光线向量,reflect是求反射向量的函数其中第一个参数是入射光线,它必须是从光源出发,所以lightDir要取反。
Material.shininess是发光值一个物体的发光值越高,反射光的能力越强,散射得越少,高光点越小。往往设置为32。
4、 光照结果:

vec3 result = ambient + diffuse + specular;
color = vec4(result, 1.0f);

二、 投光物
1、 定向光
当一个光源很远的时候,来自光源的每条光线接近于平行。这看起来就像所有的光线来自于同一个方向,无论物体和观察者在哪儿。当一个光源被设置为无限远时,它被称为定向光(Directional Light),因为所有的光线都有着同一个方向;它会独立于光源的位置。

struct DirLight
{vec3 direction;//定向光的光线向量(定向光的光线向量不发生改变)vec3 ambient;//环境光向量vec3 diffuse;//漫反射光线向量vec3 specular;//镜面反射光线向量
};
uniform DirLight dirLight;
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir)
{vec3 lightDir = normalize(-light.direction);// 计算漫反射系数float diff = max(dot(normal, lightDir), 0.0);// 计算镜面反射系数vec3 reflectDir = reflect(-lightDir, normal);float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);// 计算各光照分量向量vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));return (ambient + diffuse + specular);
}

2、 点光源
点光是一个在时间里有位置的光源,它向所有方向发光,光线随距离增加逐渐变暗。想象灯泡和火炬作为投光物,它们可以扮演点光的角色。

重要特性——衰减:
随着光线穿越距离的变远使得亮度也相应地减少的现象,通常称之为衰减(Attenuation)。
衰减方程:

在这里d代表片段到光源的距离。为了计算衰减值,我们定义3个(可配置)项:常数项Kc,一次项Kl和二次项Kq。

struct PointLight
{vec3 position;//点光源位置float constant;//衰减公式常数项float linear;//衰减公式一次项系数float quadratic;//衰减公式二次项系数vec3 ambient;//环境光向量vec3 diffuse;//漫反射向量vec3 specular;//镜面反射向量
};
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{vec3 lightDir = normalize(light.position - fragPos);// 计算漫反射系数float diff = max(dot(normal, lightDir), 0.0);// 计算镜面反射系数vec3 reflectDir = reflect(-lightDir, normal);float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);// 计算衰减系数float distance = length(light.position - fragPos);float attenuation = 1.0f / (light.constant + light.linear * distance +light.quadratic * (distance * distance));    // 计算各个光照分量向量vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));//各个光照分量乘上衰减系数ambient *= attenuation;diffuse *= attenuation;specular *= attenuation;return (ambient + diffuse + specular);
}

3、 聚光光源
聚光是一种位于环境中某处的光源,它不是向所有方向照射,而是只朝某个方向照射。结果是只有一个聚光照射方向的确定半径内的物体才会被照亮,其他的都保持黑暗。聚光的好例子是路灯或手电筒。

• LightDir:从片段指向光源的向量。
• SpotDir:聚光所指向的方向。
• Phiϕ:定义聚光半径的切光角。每个落在这个角度之外的,聚光都不会照亮。
• Thetaθ:LightDir向量和SpotDir向量之间的角度。θ值应该比Φ值小,这样才会在聚光内。
重要特性——平滑/软化边缘
为创建聚光的平滑边,我们希望去模拟的聚光有一个内圆锥和外圆锥。我们可以把内圆锥设置为前面部分定义的圆锥,我们希望外圆锥从内边到外边逐步的变暗。
为创建外圆锥,我们简单定义另一个余弦值,它代表聚光的方向向量和外圆锥的向量(等于它的半径)的角度。然后,如果片段在内圆锥和外圆锥之间,就会给它计算出一个0.0到1.0之间的亮度。如果片段在内圆锥以内这个亮度就等于1.0,如果在外面就是0.0。

这里ϵ是内部圆锥(ϕ)和外部圆锥(γ)(epsilon = phi - gamma)的差。结果I的值是聚光在当前片段的亮度。

struct SpotLight
{vec3 position;//光源位置vec3 direction;//光的方向,即上图的SpotDirfloat cutOff;//内圆锥切角float outerCutOff;//外圆锥切角float constant;float linear;float quadratic;vec3 ambient;vec3 diffuse;vec3 specular;
};
vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{vec3 lightDir = normalize(light.position - fragPos);// 计算漫反射系数float diff = max(dot(normal, lightDir), 0.0);// 镜面反射系数vec3 reflectDir = reflect(-lightDir, normal);float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);// 光线衰减float distance = length(light.position - fragPos);float attenuation = 1.0f / (light.constant + light.linear * distance + light.quadratic * (distance * distance));    // 计算聚光系数float theta = dot(lightDir, normalize(-light.direction)); float epsilon = light.cutOff - light.outerCutOff;float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0);// 计算各个光照分量答案vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
//给每个光照分量乘上衰减系数和聚光系数ambient *= attenuation * intensity;diffuse *= attenuation * intensity;specular *= attenuation * intensity;return (ambient + diffuse + specular);
}

三、 完整的顶点着色器和片段着色器
1、 Vertexshader:

#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normal;
layout (location = 2) in vec2 texCoords;out vec3 Normal;
out vec3 FragPos;
out vec2 TexCoords;uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;void main()
{gl_Position = projection * view *  model * vec4(position, 1.0f);FragPos = vec3(model * vec4(position, 1.0f));Normal = mat3(transpose(inverse(model))) * normal;  TexCoords = texCoords;
}

2、 Fragmentshader

#version 330 core
struct Material {sampler2D diffuse;sampler2D specular;float shininess;
}; struct DirLight {vec3 direction;vec3 ambient;vec3 diffuse;vec3 specular;
};struct PointLight {vec3 position;float constant;float linear;float quadratic;vec3 ambient;vec3 diffuse;vec3 specular;
};struct SpotLight{vec3 position;vec3 direction;float cutOff;float outerCutOff;float constant;float linear;float quadratic;vec3 ambient;vec3 diffuse;vec3 specular;
};#define NR_POINT_LIGHTS 4in vec3 FragPos;
in vec3 Normal;
in vec2 TexCoords;out vec4 color;uniform vec3 viewPos;
uniform DirLight dirLight;
uniform PointLight pointLights[NR_POINT_LIGHTS];
uniform SpotLight spotLight;
uniform Material material;// Function prototypes
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir);
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir);
vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir);void main()
{    // Propertiesvec3 norm = normalize(Normal);vec3 viewDir = normalize(viewPos - FragPos);// == ======================================// Our lighting is set up in 3 phases: directional, point lights and an optional flashlight// For each phase, a calculate function is defined that calculates the corresponding color// per lamp. In the main() function we take all the calculated colors and sum them up for// this fragment's final color.// == ======================================// Phase 1: Directional lightingvec3 result = CalcDirLight(dirLight, norm, viewDir);// Phase 2: Point lightsfor(int i = 0; i < NR_POINT_LIGHTS; i++)result += CalcPointLight(pointLights[i], norm, FragPos, viewDir);    // Phase 3: Spot lightresult += CalcSpotLight(spotLight, norm, FragPos, viewDir);    color = vec4(result, 1.0);
}// Calculates the color when using a directional light.
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir)
{vec3 lightDir = normalize(-light.direction);// Diffuse shadingfloat diff = max(dot(normal, lightDir), 0.0);// Specular shadingvec3 reflectDir = reflect(-lightDir, normal);float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);// Combine resultsvec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));return (ambient + diffuse + specular);
}// Calculates the color when using a point light.
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{vec3 lightDir = normalize(light.position - fragPos);// Diffuse shadingfloat diff = max(dot(normal, lightDir), 0.0);// Specular shadingvec3 reflectDir = reflect(-lightDir, normal);float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);// Attenuationfloat distance = length(light.position - fragPos);float attenuation = 1.0f / (light.constant + light.linear * distance + light.quadratic * (distance * distance));    // Combine resultsvec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));ambient *= attenuation;diffuse *= attenuation;specular *= attenuation;return (ambient + diffuse + specular);
}vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{vec3 lightDir = normalize(light.position - fragPos);// Diffuse shadingfloat diff = max(dot(normal, lightDir), 0.0);// Specular shadingvec3 reflectDir = reflect(-lightDir, normal);float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);// Attenuationfloat distance = length(light.position - fragPos);float attenuation = 1.0f / (light.constant + light.linear * distance + light.quadratic * (distance * distance));    // Spotlight intensityfloat theta = dot(lightDir, normalize(-light.direction)); float epsilon = light.cutOff - light.outerCutOff;float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0);// Combine resultsvec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));ambient *= attenuation * intensity;diffuse *= attenuation * intensity;specular *= attenuation * intensity;return (ambient + diffuse + specular);
}

举个栗子,程序

运行效果

工程源码

参考

cesuolidec4

openGL-phong光照模型很好的总结

learnopengl CN

OpenGL中phong光照模型详解相关推荐

  1. 图形 2.4 传统经验光照模型详解(PBR光照计算公式介绍)

    参考视频: 图形 2.4 传统经验光照模型详解 GAMES101-现代计算机图形学入门-闫令琪 P15 参考资料: PBR-learnopengl 彻底看懂PBR/BRDF方程-知乎 辐射强度.辐亮度 ...

  2. 图形 2.4 传统经验光照模型详解

    [技术美术百人计划]图形 2.4 传统经验光照模型详解_哔哩哔哩_bilibili 什么是光照模型 当光照射到物体表面时,物体对光会发生反射.透射.吸收.衍射.折射.和干涉,其中被物体吸收的部分转化为 ...

  3. Clipper库中文文档详解

    Clipper库中文文档详解 简介 Clipper Library(以下简称为Clipper库或ClipperLib或Clipper)提供了对线段和多边形的裁剪(Clipping)以及偏置(offse ...

  4. ALSA声卡驱动中的DAPM详解之四:在驱动程序中初始化并注册widget和route

    前几篇文章我们从dapm的数据结构入手,了解了代表音频控件的widget,代表连接路径的route以及用于连接两个widget的path.之前都是一些概念的讲解以及对数据结构中各个字段的说明,从本章开 ...

  5. Asp.net中GridView使用详解(引)【转】

    Asp.net中GridView使用详解(引) GridView无代码分页排序 GridView选中,编辑,取消,删除 GridView正反双向排序 GridView和下拉菜单DropDownList ...

  6. Linux中iptraf命令详解(IP局域网监控工具)

    2019独角兽企业重金招聘Python工程师标准>>> Linux中iptraf命令详解(IP局域网监控工具) 发布时间:2017-12-27 20:46:03   作者:佚名    ...

  7. ArcGIS Engine中的Symbols详解

    转自原文 ArcGIS Engine中的Symbols详解 本文由本人翻译ESRI官方帮助文档.尊重劳动成果,转载请注明来源. Symbols ArcObjects用了三种类型的Symbol(符号样式 ...

  8. js路由在php上面使用,React中路由使用详解

    这次给大家带来React中路由使用详解,React中路由使用的注意事项有哪些,下面就是实战案例,一起来看一下. 路由 通过 URL 映射到对应的功能实现,React 的路由使用要先引入 react-r ...

  9. Linux中etc目录详解

    Linux中etc目录详解 /etc目录 包含很多文件.许多网络配置文件也在/etc 中. /etc/rc   or/etc/rc.d   or/etc/rc*.d   启动.或改变运行级时运行的sc ...

最新文章

  1. 《深入理解Java虚拟机》(第二版)学习1:JVM的内存划分
  2. 梯度下降法 —— 经典的优化方法
  3. FragmentPagerAdapter与FragmentStatePagerAdapter使用详解与区别
  4. 【风控系统】风控中心—京东基于Spark的风控系统架构实践和技术细节
  5. JavaWeb 如何防止表单重复提交 - 使用Token,令牌
  6. Linux shell脚本全面学习
  7. 新产品、新团队、新技术
  8. python字符串与数字转换,python 字符串和整数的转换方法
  9. HTML的表单及框架
  10. Mysql 10位 13位时间戳对比现在时间 时间对比查询
  11. 短信注册验证以及邮箱激活
  12. 骑行318、 2016.7.13
  13. OpenWrt设置修改IP地址
  14. python 重试—retry库的使用和自定义超时retry
  15. 部署本地thinkphp6(iis+php7)
  16. vue 视频上传组件
  17. 电脑狂、理论家、情报员……你是哪种类型的软件工程师?
  18. java热敏打印机_用java在POS热敏打印机上打印PDF或PNG
  19. springboot高校学生宿舍水电费报修考勤管理系统
  20. Radon变换理论介绍与matlab实现--经验交流

热门文章

  1. flv f4v mp4 视频播放器代码
  2. 天琊 V1.0(测试 1125版)
  3. 设计,构建线框图和对Android应用进行原型制作:第1部分
  4. VALSE 4月12日 下午 第一会场 深度学习模型设计 会议记录
  5. spark 集群处理后转单机pyspark 或 pands 数据处理 的方法
  6. mysql sql 列变成横向_mysql怎么更改纵向变横向排列
  7. 印象深刻的bug汇总(持续更新)
  8. Making Sense of all these Crazy Web Service Standards
  9. 苹果手机闹钟声音大小怎么调_偷偷安利5款让手机体验到爆的app,乐趣满满
  10. springmvc(2)处理器设配器和映射器