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 330 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,0,0) + reflectedLight,vec3(1.0));
        color = vec4(rgb,1.0);
}

点光源

点光源模拟现实中的点灯、路灯等光源。点光源与平行光的区别有两点:

1.平行光只有一个方向,而点光源为一个点向四面八方发射光线,所以我们不能再用一个lightdirection来表示光源的方向;

2.物体表面接受的光源随着距离的减少而减少。

这个减少我们可理解为衰减,衰减与物体与光源的距离的平方成比例,但通常这样衰减衰减非常快,除非你把周围的散射光再考虑进来,或者使用其他的方式添加其他所有的的物理作用的完整模型添加到光源。经典光照模型中,环境光帮助没有完整模型的光照填补缺口,然后在一些地方使用线性衰减来填充。所以,最后我们将使用一个包含:常量、线性、距离的二次函数作为系统的衰减模型;

#version 330 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,0,0) + reflectedLight,vec3(1.0));
        color = vec4(rgb,1.0);
}
                                                                                     

效果如图:

聚光灯

在舞台和电影中,聚光灯投影一个强大的光束来照亮一个明确的区域。在OpenGL中,我们可以使用电光源然后加一个圆锥体限制模拟某一个方向的聚光灯。圆锥体的定义我们可以再次考虑余弦值(点积),即定义一个聚光灯的方向,然后控制一个角度,在这个角度范围内的才有光线,而对应到余弦值就是大于某个值,如0.99(为什么是大于?考虑余弦值的变化)。同时我们也可以增大角度的余弦值来锐化(或者钝化)光源的的光锥范围来将亮度提升更高。这样当它接近截止的边缘时,允许控制光源的衰减成都。

#version 330 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,0,0) + 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 331 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=0;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),0),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

原文地址:https://www.cnblogs.com/xin-lover/p/8850890.html

时间: 2025-01-11 15:05:12

Linux OpenGL 实践篇-6 光照的相关文章

Linux OpenGL 实践篇-1

本次实践所使用环境为CentOS 7. 参考:http://www.xuebuyuan.com/1472808.html OpenGL开发环境搭建: 1.opengl库安装 opengl库使用mesa库,安装命令: yum intall mesa* mesa库是一个开源的三维计算机图形库,以开源的形式实现了opengl应用程序接口.具体介绍:https://www.mesa3d.org/intro.html. 2.glut安装 下载freeglut,下载地址为: https://github.c

Linux OpenGL 实践篇-3 framebuffer

GLEW说明 GLEW(OpenGL Extension Wrangler) 是OpenGL的另一个辅助库,主要封装了从OpenGL库中获取函数地址的过程,还包含了一些可以跨平台使用的OpenGL编程方法. 本次实践是使用数据缓存绘制两个三角形,重点是缓存的创建和数据输入.数据输入后,根据数据使用方式可分为非基于索引绘制和基于索引绘制,使用的方法分别为glDrawArray和glDrayElements. 首先,明确OpenGL缓存使用步骤: glGenBuffer glBindBuffer g

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

OpenGL 作为一个图形接口,并没有包含窗口的相关内容,但OpenGL使用必须依赖窗口,即必须在窗口中绘制.这就要求我们必须了解一种窗口系统,但不同的操作系统提供的创建窗口的API都不相同,如果我们在学习OpenGL时要去学习一整套的窗口系统,这将带来很多的不便,所以出现了GLUT.GLUT全称OpenGL Utility Toolkit,是一套和窗口系统无关的软件包,为我们提供了窗口创建,用户输入输出处理等功能.优点是:简小,精悍.注意GLUT并不是一个功能特别全面的窗口系统工具包,所以构建

Linux OpenGL 实践篇-9 模型

之前一直渲染箱子,显得有点单调.这一次我们绘制一个用艺术家事先用建模工具创建的模型. 本次实践参考:https://learnopengl-cn.github.io/03%20Model%20Loading/01%20Assimp/ 在之前我们的OpenGL实践中,绘制图形的过程是先定义顶点的位置.法线.纹理坐标(UV)等信息,按一定的规则组织后传给着色器处理,最终绘制到屏幕上.现在使用艺术家构建的模型,绘制的过程并没有变,只不过顶点和使用的贴图信息从原来我们自己定义变为从已构建好的模型中提取,

Linux OpenGL 实践篇-12-ProceduralTexturing

程序式纹理 简单的来说程序式纹理就是用数学公式描述物体表面的纹路 .而实现这个过程的着色器我们称之为程序纹理着色器,通常在这类着色器中我们能使用的输入信息也就是顶点坐标和纹理坐标. 程序式纹理的优点 1.程序式纹理的内存占用比预存纹理要低的多:因为程序式纹理主要是算法的实现,数据都是通过计算产生的: 2.程序生成的纹理没有固定的面积和分辨率,可以随意的应用到不同大小的物体,而不用担心精度不够的问题: 3.程序式纹理可以写入一些算法的关键参数,可以方便的供程序修改从而创建出有趣的效果,而预存的纹理

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

多实例渲染 OpenGL的多实例渲染是一种连续执行多条相同的渲染命令的方法,并且每条命令产生的结果都有轻微的差异,通常用于渲染大量的几何物体. 设想一个场景,比如太空,我们需要渲染数以万记的星球,如果我们使用常规的做法,渲染的过程应该是是:绘制第一个星球glBindVertexArray--glDrawArrays或glDrawElements,然后使用同样的流程绘制其它的星球.但这种方式非常容易达到计算机的性能瓶颈,就算是渲染的物体是最简单的面片,因为在绘制的整个过程中,绘制物体的时间其实非常

OpenGL学习(五) 光照与材质

OpenGL中的光照     环境光:在环境中进行了充分的散射,无法分辨其方向的光. 散射光:来自某个方向. 镜面光:来自一个特定的方向,并且倾向于从表面某个特定的方向反射. 除了以上三种光外,材料可能具有一种发射颜色,它模拟那些源自某个物体的光. 为了实现明暗效果,必须启用光照计算,而且每种光源也必须被启用.对于单个光源,我们可以这样做: glEnable(GL_LIGHTING); glEnable(GL_LIGHT0) 注:一旦光照被启用,glColor*()指定的颜色值将不再使用. 指定

OpenGL中启用光照前的准备——指定法线

我们在使用光源时,除了强度和颜色之外,还需要指定光源的位置和方向,并且这些光源的位置和方向将会极大地影响场景的外观. OpenGL至少支持8种独立的光源.当我们指定一个光源时,便要告诉OpenGL这个光源的位置以及它的照射方向.光源经常向四周照射,但也可以向一个方向照射.无论在哪种情况下,对于我们所绘制的任何物体,来自任何光源的光线(除了纯粹的环境光源之外)都将根据一个角度撞击组成这个物体的多边形的表面.为了计算围绕多边形表面的着色效果,OpenGL必须能够计算光线与多边形表面之间的角度. 设想

OpenGL中的光照与材料

在OpenGL光照模型中,除非一个物体自己会发光,否则它将受到3种不同类型的光的照射,这3种不同类型的光分别是:环境光(ambient).散射光(diffuse)和镜面光(specular).作为现实世界中光照的抽象,这3种类型的光允许我们模拟和控制光照在物体表面上所产生的效果. 环境光(ambient) 环境光并不来自任何特定的方向.由环境光所照射的物体在所有方向的表面都是均匀照亮的.在OpenGL中,这种光照类型实际上模拟了环境中源自所有光源的散光. 散射光(diffuse) OpenGL中