Linux OpenGL 实践篇-6 光照

Posted xin_l12

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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

以上是关于Linux OpenGL 实践篇-6 光照的主要内容,如果未能解决你的问题,请参考以下文章

OpenGL中的光照问题

OpenGL:光照模型视图投影变换

问题理解 OpenGL 中的光照

OpenGL ES着色语言-光照效果之散射光

OpenGL 光照

OpenGL ES着色语言-光照效果之散射光