经典光照模型

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

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

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

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

    在实际开发项目中,会遇到很多定时任务的工作.比如:定时导出某些数据.定时发送消息或邮件给用户.定时备份什么类型的文件等等 一般可以写个定时器,来完成相应的需求,在node.js中自已实现也非常容易,接 ...

  2. 使用domain模块捕获异步回调中的异常

    和其他服务器端语言相比,貌似node.js 对于异常捕捉确实非常困难. 首先你会想到try/catch ,但是在使用过程中我们会发现并没有真正将错误控制在try/catch 语句中. 为什么? 答案是 ...

  3. 利用国外服务器搭建ss

    wget --no-check-certificate  https://raw.githubusercontent.com/teddysun/shadowsocks_install/master/s ...

  4. Algorithm --> 全排列

    1.算法简述 简单地说:全排列就是从第一个数字起每个数分别与它后面的数字交换. E.g:E = (a , b , c),则 prem(E)= a.perm(b,c)+ b.perm(a,c)+ c.p ...

  5. 求逆序对[树状数组] jdoj

    求逆序对 题目大意:给你一个序列,求逆序对个数. 注释:n<=$10^5$. 此题显然可以跑暴力.想枚举1到n,再求在i的后缀中有多少比i小的,统计答案即可.这显然是$n^2$的.这...显然过 ...

  6. 爬虫(requests)

    requests库包含两个对象:Response和Requests  Response对象属性:    r.status_code    HTTP请求的返回状态,200表示成功 r.text     ...

  7. C语言使用指针变量指向字符串,对字符串进行处理后再将指针移向开头为什么不能输出?(使用Dev-c++进行编译)

    # include <stdio.h> # include <stdlib.h> int main() { char *point_1="aaaaaabbbbbbzz ...

  8. 第1次作业:no blog no fun

    1.先回答老师的问题 第一部分:结缘计算机       读了进入2012 -- 回顾我走过的编程之路后,我试着回顾了我的编程生涯的开始.我最原始的记忆就是老爸教我用电脑玩连连看,那时候的显示器应该是C ...

  9. 个人作业Week3-案例分析

    DeadLine:2017.10.13 23:00 声明:本作业以邹欣老师博客 http://www.cnblogs.com/xinz/archive/2012/03/26/2417699.html ...

  10. 敏捷冲刺每日报告一(Java-Team)

    第一天报告(10.25  周三) 团队:Java-Team 成员: 章辉宇(284) 吴政楠(286) 陈阳(PM:288) 韩华颂(142) 胡志权(143) github地址:https://gi ...