经典光照模型

经典光照模型通过单独计算光源成分得到综合光照效果,然后添加到物体表面特定点,这些成分包括:环境光、漫反射光、镜面光。

环境光:是指不是来特定方向的光,在经典光照模型中基本是个常量。

漫反射光:是散射在各个方向上均匀的表面特定光源。物体表面通过光照照亮,即使这个表面没有将光源直接反射到你的眼睛中。漫反射与眼睛的方向没有关系,但与光源的方向有关,当表面直接面向光源的时候会表现的亮一些,而倾斜的时候则暗一些,因为在现实中倾斜的表面接受的光要少一些。在经典光照模型中,我们使用表面的法向量来计算漫放射光照,同时,反射光的颜色也依赖表面的颜色。

镜面光:是表面反射的高亮光。现实中一个高度抛光的金属球能反射一个尖锐的反射光,而一个磨砂的表面则会反射一个更大,而且相对暗一点的反射光,而一个布球则没有反射高光。这个特定阶段的效果强度称为光泽度(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 光照的更多相关文章

  1. Linux OpenGL 实践篇-5 纹理

    纹理 在之前的实践中,我们所渲染的物体的表面颜色都是纯色或者根据顶点位置计算出的一个颜色,这种方式在表现物体细节方面是比较吃资源的,因为我们每增加一个细节,我们就需要定义更多的顶点及其属性.所以美术人 ...

  2. Linux OpenGL 实践篇-4 坐标系统

    OpenGL中顶点经过顶点着色器后会变为标准设备坐标系.标准设备坐标系的各坐标的取值范围是[-1,1],超过这个范围的点将会被剔除.而这个变换的过程可描述为顶点在几个坐标系统的变换,这几个坐标系统为: ...

  3. Linux OpenGL 实践篇-3 绘制三角形

    本次实践是绘制两个三角形,重点理解顶点数组对象和OpenGL缓存的使用. 顶点数组对象 顶点数组对象负责管理一组顶点属性,顶点属性包括位置.法线.纹理坐标等. OpenGL缓存 OpenGL缓存实质上 ...

  4. Linux OpenGL 实践篇-2 创建一个窗口

    OpenGL 作为一个图形接口,并没有包含窗口的相关内容,但OpenGL使用必须依赖窗口,即必须在窗口中绘制.这就要求我们必须了解一种窗口系统,但不同的操作系统提供的创建窗口的API都不相同,如果我们 ...

  5. Linux OpenGL 实践篇-1 OpenGL环境搭建

    本次实践所使用环境为CentOS 7. 参考:http://www.xuebuyuan.com/1472808.html OpenGL开发环境搭建: 1.opengl库安装 opengl库使用mesa ...

  6. Linux OpenGL 实践篇-16 文本绘制

    文本绘制 本文主要射击Freetype的入门理解和在OpenGL中实现文字的渲染. freetype freetype的官网,本文大部分内容参考https://www.freetype.org/fre ...

  7. Linux OpenGL 实践篇-15-图像数据操作

    OpenGL图像数据操作 之前的实践中,我们在着色器中的输入输出都是比较固定的.比如在顶点或片元着色器中,顶点属性的输入和帧缓存的颜色值:虽然我们可以通过纹理或者纹理缓存对象(TBO)来读取任意的内存 ...

  8. Linux OpenGL 实践篇-14-多实例渲染

    多实例渲染 OpenGL的多实例渲染是一种连续执行多条相同的渲染命令的方法,并且每条命令产生的结果都有轻微的差异,通常用于渲染大量的几何物体. 设想一个场景,比如太空,我们需要渲染数以万记的星球,如果 ...

  9. Linux OpenGL 实践篇-13-geometryshader

    几何着色器 几何着色器是位于图元装配和片元着色器之前的一个着色器阶段,是一个可选阶段.它的输入是一个图元的完整的顶点信息,通常来自于顶点着色器,但如果细分计算着色器启用的话,那输入则是细分计算着色器的 ...

随机推荐

  1. 【BootStrap】 布局组件 I

    BootStrap布局组件 I 除了在原生的HTML基础上进行了外观和类别上的改进,BS还包装了很多组件进库中,设计网页时我们可以方便地调用这些组件.下面来简略地介绍一下各种各样的组件 ■ 字体图标 ...

  2. 大数据 --> ProtoBuf的使用和原理

    ProtoBuf的使用和原理 一.简介 Protobuf是一个灵活的.高效的用于序列化数据的协议.相比较XML和JSON格式,protobuf更小.更快.更便捷.Protobuf是跨语言的,并且自带了 ...

  3. 源码实现 --> atoi函数实现

    atoi函数实现 atoi()函数的功能是将一个字符串转换为一个整型数值. 例如“12345”,转换之后的数值为12345,“-0123”转换之后为-123. #include <stdio.h ...

  4. Oracle查看表空间大小和使用率

    1. 全部表空间的大小select tablespace_name, sum(bytes)/1024/1024 from dba_data_files group by tablespace_name ...

  5. JVM GC算法

    在判断哪些内存需要回收和什么时候回收用到GC 算法,本文主要对GC 算法进行讲解. JVM垃圾判定算法 常见的JVM垃圾判定算法包括:引用技术算法.可达性分析算法. 引用技术算法(Reference ...

  6. Spring-MongoDB 关键类的源码分析

    本文分析的是 spring-data-mongodb-1.9.2.RELEASE.jar 和 mongodb-driver-core-3.2.2.jar. 一.UML Class Diagram 核心 ...

  7. 配置 CSV Data Set Config 来参数化新增客户信息操作

    1.首先根据新增客户信息的http请求,来确定需要参数化的变量,选取符合测试需求且经常变化或未来会变化的变量为需要参数化的变量,如本文中的客户端名称(sys_name).描述(description) ...

  8. alpha-咸鱼冲刺day8

    一,合照 emmmmm.自然还是没有的. 二,项目燃尽图 三,项目进展 正在进行页面整合.然后还有注册跟登陆的功能完善-- 四,问题困难 数据流程大概是搞定了.不过语法不是很熟悉,然后还有各种判定. ...

  9. java利用iTextWorker生成pdf

    使用itext生成pdf,在linux环境下,中文全部失踪,因为itext要在linux下支持中文字体需要引入itext-asian, 并添加一个字体类. public static class Pd ...

  10. JDBC操作数据库的三种方式比较

    JDBC(java Database Connectivity)java数据库连接,是一种用于执行上sql语句的javaAPI,可以为多种关系型数据库提供统一访问接口.我们项目中经常用到的MySQL. ...