Linux OpenGL 实践篇-6 光照
经典光照模型
经典光照模型通过单独计算光源成分得到综合光照效果,然后添加到物体表面特定点,这些成分包括:环境光、漫反射光、镜面光。
环境光:是指不是来特定方向的光,在经典光照模型中基本是个常量。
漫反射光:是散射在各个方向上均匀的表面特定光源。物体表面通过光照照亮,即使这个表面没有将光源直接反射到你的眼睛中。漫反射与眼睛的方向没有关系,但与光源的方向有关,当表面直接面向光源的时候会表现的亮一些,而倾斜的时候则暗一些,因为在现实中倾斜的表面接受的光要少一些。在经典光照模型中,我们使用表面的法向量来计算漫放射光照,同时,反射光的颜色也依赖表面的颜色。
镜面光:是表面反射的高亮光。现实中一个高度抛光的金属球能反射一个尖锐的反射光,而一个磨砂的表面则会反射一个更大,而且相对暗一点的反射光,而一个布球则没有反射高光。这个特定阶段的效果强度称为光泽度(shininess)。在经典光照模型中,我们通过计算光源经过物体表面反射后的与眼睛反方向的角度来衡量。这个计算我们需要视线的方向,表面法线,光源的方向。
在经典光照模型中中最常用的一种模型称为冯氏光照模型。
首先我们要介绍第一种光源:方向光。如果一个光源足够的远,那么我们可以认为它发射的光线到物体的表面都是一个方向,这样的光即为方向光。下面我们使用方向光和冯氏光照模型实现一个第一个光照效果。
冯氏光照模型(Phong Lighting Model)

环境光
环境光通常我们给与一个常量表示。
uniform vec4 ambient;
in vec4 vertexColor;
out vec4 color;
void main()
{
vec4 scatteredLight = ambient;
color = min(scatteredLight * vertexColor,vec4(1.0));
}
漫反射光
在现实中表面相对光源的倾斜角度不同,表面的亮度也不同,所以我们可以通过表面的法向量与光源方向的反方向角度计算光的强度,具体可使用向量的点积(余弦值)来计算。
vec3 lightDirection = normalize(lightPos - fragPos);
float diffuse = max(0.0,dot(normal,lightDirection));
其中max是防止负数出现,引发不正确的行为。
镜面光
镜面光其实可理解为大量平行的光线通过表面反射进入眼睛后产生的高亮想象。所以在经典光照模型中我们可使用光源反射后与眼睛反方向的一致性来模拟这种情况,具体的做法是通过光源方向和法向量计算反射向量,然后使用放射向量与眼睛反方向的点积(余弦值)来衡量。
vec3 lightDirection = normalize(lightPos - fragPos);
vec3 reflectDir = reflect(-lightDirection,normal);
float specular = max(0.0,dot(viewDir,reflectDir));
把上述三个结果合并到一起,则最后的片元颜色:
#version core uniform vec3 ambient;
uniform vec3 lightColor;
uniform vec3 lightPos;
uniform vec3 viewPos;
uniform float shininess;
uniform float strength; in vec3 normal;
in vec3 fragPos;
out vec4 color; void main()
{
vec3 lightDirection = normalize(lightPos - fragPos);
vec3 viewDir = normalize(viewPos - fragPos);
vec3 reflectDir = reflect(-lightDirection,normal);
float diffuse = max(0.0,dot(normal,lightDirection));
float specular = max(0.0,dot(viewDir,reflectDir)); if(diffuse == 0.0)
{
specular = 0.0;
}
else
{
specular = pow(specular,shininess);
} vec3 scatteredLight = ambient + lightColor * diffuse;
vec3 reflectedLight = lightColor * specular * strength; vec3 rgb = min(scatteredLight * vec3(0.5f,,) + reflectedLight,vec3(1.0));
color = vec4(rgb,1.0);
}

点光源
点光源模拟现实中的点灯、路灯等光源。点光源与平行光的区别有两点:
1.平行光只有一个方向,而点光源为一个点向四面八方发射光线,所以我们不能再用一个lightdirection来表示光源的方向;
2.物体表面接受的光源随着距离的减少而减少。
这个减少我们可理解为衰减,衰减与物体与光源的距离的平方成比例,但通常这样衰减衰减非常快,除非你把周围的散射光再考虑进来,或者使用其他的方式添加其他所有的的物理作用的完整模型添加到光源。经典光照模型中,环境光帮助没有完整模型的光照填补缺口,然后在一些地方使用线性衰减来填充。所以,最后我们将使用一个包含:常量、线性、距离的二次函数作为系统的衰减模型;
#version core uniform vec3 ambient;
uniform vec3 lightColor;
uniform vec3 lightPos;
uniform vec3 viewPos;
uniform float shininess;
uniform float strength; uniform float constantAttenuation;
uniform float linearAttenuation;
uniform float quadraticAttenuation; in vec3 normal;
in vec3 fragPos;
out vec4 color; void main()
{
vec3 norm = normalize(normal);
vec3 lightDirection = (lightPos - fragPos);
float lightDis = length(lightDirection);
lightDirection = lightDirection / lightDis; //判断当前片元接受光照的强度
float attenuation = 1.0 /
(constantAttenuation +
linearAttenuation * lightDis +
quadraticAttenuation * lightDis * lightDis); vec3 viewDir = normalize(viewPos - fragPos);
//vec3 halfVec = normalize(lightDirection + viewDir)
vec3 reflectDir = reflect(-lightDirection,norm); float diffuse = max(0.0,dot(norm,lightDirection));
float specular = max(0.0,dot(viewDir,reflectDir));
if(diffuse == 0.0)
{
specular = 0.0;
}
else
{
specular = pow(specular,shininess);
} vec3 scatteredLight = ambient + lightColor * diffuse * attenuation;
vec3 reflectedLight = lightColor * specular * strength * attenuation; vec3 rgb = min(scatteredLight * vec3(0.5f,,) + reflectedLight,vec3(1.0));
color = vec4(rgb,1.0);
}
效果如图:

聚光灯
在舞台和电影中,聚光灯投影一个强大的光束来照亮一个明确的区域。在OpenGL中,我们可以使用电光源然后加一个圆锥体限制模拟某一个方向的聚光灯。圆锥体的定义我们可以再次考虑余弦值(点积),即定义一个聚光灯的方向,然后控制一个角度,在这个角度范围内的才有光线,而对应到余弦值就是大于某个值,如0.99(为什么是大于?考虑余弦值的变化)。同时我们也可以增大角度的余弦值来锐化(或者钝化)光源的的光锥范围来将亮度提升更高。这样当它接近截止的边缘时,允许控制光源的衰减成都。
#version core uniform vec3 ambient;
uniform vec3 lightColor;
uniform vec3 lightPos;
uniform vec3 viewPos;
uniform float shininess;
uniform float strength; uniform float constantAttenuation;
uniform float linearAttenuation;
uniform float quadraticAttenuation; uniform vec3 coneDir;
uniform float spotCosCutoff;
uniform float spotExponent; in vec3 normal;
in vec3 fragPos;
out vec4 color; void main()
{
vec3 norm = normalize(normal);
vec3 lightDirection = (lightPos - fragPos);
float lightDis = length(lightDirection);
lightDirection = lightDirection / lightDis; //判断当前片元接受光照的强度
float attenuation = 1.0 /
(constantAttenuation +
linearAttenuation * lightDis +
quadraticAttenuation * lightDis * lightDis); vec3 viewDir = normalize(viewPos - fragPos);
float spotCos = dot(lightDirection,-coneDir);
if(spotCos < spotCosCutoff)
{
attenuation = 0.0;
}
else
{
attenuation *= pow(spotCos,spotExponent);
} vec3 reflectDir = reflect(lightDirection,norm);
float diffuse = max(0.0,dot(norm,lightDirection));
float specular = max(0.0,dot(viewDir,reflectDir)); if(diffuse == 0.0)
{
specular = 0.0;
}
else
{
specular = pow(specular,shininess);
} vec3 scatteredLight = ambient + lightColor * diffuse * attenuation;
vec3 reflectedLight = lightColor * specular * strength * attenuation; vec3 rgb = min(scatteredLight * vec3(0.5f,,) + reflectedLight,vec3(1.0));
color = vec4(rgb,1.0);
}
效果如下:

多光源
在场景中我们可能需要不止一个光源,我们可以在着色器中定义多个光源,光源类型包括上述的:方向光、点光源、聚光灯。我们可以使用glsl中的结构体来定义光源和材质,即把材质和光源的属性封装到一个结构体中,然后通过访问结构体的属性来使用数据。glsl的结构体类似c语言的结构体,使用struct关键字声明,之后我们就可以像使用内置类型一样使用这个结构体。如果我们声明了一个uniform的结构体对象,那么我们怎么在opengl代码中给它传值了?方法就是通过“结构体变量.属性”的方式来获取索引,接下来就和普通的uniform对象赋值一样了。比如我们有一个叫Material的结构体和Material类型的uniform对象mat,这个结构体有一个字段是vec3 ambient,我们可以使用glGetUniformLocation(program,"mat.ambient")获取索引;然后使用glUnifrom3f传值。如果是数组对象,则跟c语言一样使用下标获取数组中的对象。
#version core
struct Material {
vec3 ambient;
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;
};
uniform vec3 viewPos;
uniform Material material;
uniform DirLight dirLight;
#define NR_POINT_LIGHTS 4
uniform PointLight pointLights[NR_POINT_LIGHTS];
in vec3 fragPos;
in vec2 texCoords;
in vec3 normal;
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);
out vec4 fragColor;
void main()
{
vec3 viewDir = normalize(viewPos - fragPos);
vec3 result = CalcDirLight(dirLight,normal,viewDir);
for(int i=;i <NR_POINT_LIGHTS ; i++)
{
result+= CalcPointLight(pointLights[i],normal,fragPos,viewDir);
}
fragColor = vec4(result,1.0);//texture(material.diffuse,texCoords);
}
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),),material.shininess);
vec3 ambient = light.ambient * vec3(texture(material.diffuse,texCoords));
vec3 diffuse = light.diffuse * diff * vec3(texture(material.specular,texCoords));
vec3 specular = light.specular * spec * vec3(texture(material.specular, texCoords));
return (ambient + diffuse + 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(reflectDir, viewDir),0.0),material.shininess);
float dis = length(lightDir);
float attenuation = 1.0 / (light.constant + light.linear * dis + light.quadratic * dis * dis);
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);
}
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(reflectDir,viewDir),0.0),material.shininess);
float dis = length(lightDir);
float attenuation = 1.0 / (light.constant + light.linear * dis + light.quadratic * dis * dis);
float spotCos = dot(lightDir,normalize(-lightDir));
float epsilon = light.cutOff - light.outerCutOff;
float intensity = clamp((spotCos - 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 speclar = light.specular * spec * vec3(texture(material.specular, texCoords));
ambient *= attenuation * intensity;
diffuse *= attenuation * intensity;
specular *= attenuation * intensity;
return (ambient + diffuse + specular);
}
效果如图:

源代码:https://github.com/xin-lover/opengl-learn/tree/master/chapter-8-multiple_lighting
Linux OpenGL 实践篇-6 光照的更多相关文章
- Linux OpenGL 实践篇-5 纹理
纹理 在之前的实践中,我们所渲染的物体的表面颜色都是纯色或者根据顶点位置计算出的一个颜色,这种方式在表现物体细节方面是比较吃资源的,因为我们每增加一个细节,我们就需要定义更多的顶点及其属性.所以美术人 ...
- Linux OpenGL 实践篇-4 坐标系统
OpenGL中顶点经过顶点着色器后会变为标准设备坐标系.标准设备坐标系的各坐标的取值范围是[-1,1],超过这个范围的点将会被剔除.而这个变换的过程可描述为顶点在几个坐标系统的变换,这几个坐标系统为: ...
- Linux OpenGL 实践篇-3 绘制三角形
本次实践是绘制两个三角形,重点理解顶点数组对象和OpenGL缓存的使用. 顶点数组对象 顶点数组对象负责管理一组顶点属性,顶点属性包括位置.法线.纹理坐标等. OpenGL缓存 OpenGL缓存实质上 ...
- Linux OpenGL 实践篇-2 创建一个窗口
OpenGL 作为一个图形接口,并没有包含窗口的相关内容,但OpenGL使用必须依赖窗口,即必须在窗口中绘制.这就要求我们必须了解一种窗口系统,但不同的操作系统提供的创建窗口的API都不相同,如果我们 ...
- Linux OpenGL 实践篇-1 OpenGL环境搭建
本次实践所使用环境为CentOS 7. 参考:http://www.xuebuyuan.com/1472808.html OpenGL开发环境搭建: 1.opengl库安装 opengl库使用mesa ...
- Linux OpenGL 实践篇-16 文本绘制
文本绘制 本文主要射击Freetype的入门理解和在OpenGL中实现文字的渲染. freetype freetype的官网,本文大部分内容参考https://www.freetype.org/fre ...
- Linux OpenGL 实践篇-15-图像数据操作
OpenGL图像数据操作 之前的实践中,我们在着色器中的输入输出都是比较固定的.比如在顶点或片元着色器中,顶点属性的输入和帧缓存的颜色值:虽然我们可以通过纹理或者纹理缓存对象(TBO)来读取任意的内存 ...
- Linux OpenGL 实践篇-14-多实例渲染
多实例渲染 OpenGL的多实例渲染是一种连续执行多条相同的渲染命令的方法,并且每条命令产生的结果都有轻微的差异,通常用于渲染大量的几何物体. 设想一个场景,比如太空,我们需要渲染数以万记的星球,如果 ...
- Linux OpenGL 实践篇-13-geometryshader
几何着色器 几何着色器是位于图元装配和片元着色器之前的一个着色器阶段,是一个可选阶段.它的输入是一个图元的完整的顶点信息,通常来自于顶点着色器,但如果细分计算着色器启用的话,那输入则是细分计算着色器的 ...
随机推荐
- 自动修改博客CSS样式用的代码
<script type="text/javascript" src="https://code.jquery.com/jquery-3.1.1.min.js&qu ...
- 制作Linux登录欢迎界面
1.登录提示语: 将提示语写入/etc/motd 文件 _ooOoo_ o8888888o 88" . "88 (| -_- |) O\ = /O ____/`---'\____ ...
- 【动态规划】滚动数组的求解(C++)
虽然接触动态规划算法已经有一段时间,给一个01背包问题,能够做到一个表格简单粗暴下去,然后求得结果,但心里总觉得对这个算法理解十分不到位,抱着对算法的热爱,网上很多大牛的算法思维实在让我佩服的五体投地 ...
- localhost访问不了的解决方法
c:\windows\system32\drivers\etc\hosts 用记事本打开,加入一行 127.0.0.1 localhost
- Java的HelloWorld程序
Java的HelloWorld程序 1.新建文本文档,编写HelloWorld程序,最后保存时记得保存成.java格式 2.在D盘新建一个HelloJava文件夹用于保存java程序 3.使用WIN+ ...
- mongodb 集群分片
分片 在Mongodb里面存在另一种集群,就是分片技术,可以满足MongoDB数据量大量增长的需求 当MongoDB存储海量的数据时,一台机器可能不足以存储数据,也可能不足以提供可接受的读写吞吐量,这 ...
- iOS开发UIKit框架-可视化编程-XIB
1. Interface Builder 可视化编程 1> 概述 GUI : 图形用户界面(Graphical User Interface, 简称GUI, 又称图形化界面) 是指采用图形方式显 ...
- 20145237《Java程序设计》实验报告一
实验一 Java开发环境的熟悉(Windows + Eclipse) 实验内容 1.使用JDK编译.运行简单的Java程序: 2.使用Eclipse 编辑.编译.运行.调试Java程序. 实验要求 1 ...
- 开始使用HTML5和CSS3验证表单
使用HTML5和CSS3验证表单 客户端验证是网页客户端程序最常用的功能之一,我们之前使用了各种各样的js库来进行表单的验证.HTML5其实早已为我们提供了表单验证的功能.至于为啥没有流行起来估计是兼 ...
- 故障公告:IIS应用程序池停止工作造成博客站点无法访问
非常抱歉,今天凌晨博客站点负载均衡中所有3台服务器的IIS应用程序池突然停止工作,造成 1:20-7:45 左右博客站点无法正常访问,由此给您带来很大的麻烦,请您谅解. 服务器操作系统是 Window ...