OpenGL中phong光照模型详解

版权

cesuolidec4

https://blog.csdn.net/xiewenzhao123/article/details/54600191

引言

        现实世界的光照是极其复杂的,而且会受到诸多因素的影响,这是以目前我们所拥有的处理能力无法模拟的。因此OpenGL的光照仅仅使用了简化的模型并基于对现实的估计来进行模拟,这样处理起来会更容易一些,而且看起来也差不多一样。这些光照模型都是基于我们对光的物理特性的理解。其中一个模型被称为冯氏光照模型(Phong Lighting Model)。冯氏光照模型的主要结构由3个元素组成:环境(Ambient)、漫反射(Diffuse)和镜面(Specular)光照。这些光照元素看起来像下面这样:

OpenGL中phong光照模型详解_第1张图片

             先看phong光照模型中一段顶点着色器的代码

#version 330 core
out vec4 FragColor;

in vec3 Normal;  //法相向量
in vec3 FragPos; //

uniform vec3 lightPos;
uniform vec3 viewPos;
uniform vec3 lightColor;
uniform vec3 objectColor;

void main()
{
	//ambient环境光
	float ambientStrength = 0.1f;   //环境光照强度
	vec3 ambient = ambientStrength * lightColor;

	//diffuse漫反射光
	vec3 norm =	normalize(Normal);
	vec3 lightDir = normalize(lightPos - FragPos); //光的方向向量是光的位置向量与片段的位置向量之间的向量差
	float diff = max(dot(norm, lightDir), 0.0f);
	vec3 diffuse = diff * lightColor;

	//specular镜面反射光
	float specularStrength = 0.9;
	vec3 viewDir = normalize(viewPos - FragPos);
	vec3 reflectDir = reflect(-lightDir, norm);
	float spec = pow(max(dot(viewDir, reflectDir), 0.0f), 32);
	vec3 specular = specularStrength * spec * lightColor;

	vec3 result = (ambient + diffuse + specular) * objectColor;
	FragColor = vec4(result, 1.0f);

}

OpenGL光照模型总结

一、 冯氏光照
所谓的冯氏光照分为三大部分:环境光照、漫反射光照以及镜面反射光照。对于同一个物体来讲,当有多个光源发出光线照亮该物体时,都可以将其划分成上述三种情况分别进行计算,最后将三部分组合即为其中一个光源照射物体后得到的最终结果,再将每个光源的照射结果进行叠加即为复杂照射环境下的最终结果。
1、 环境光照:
这是冯氏光照中最简单的光照,只需要用环境光向量乘上物体本身的颜色即可,要注意的是环境光往往比较弱,所以环境光向量要设置的比较小。

vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));

解释:
light.ambient为环境光线,material.diffuse为漫反射光照贴图(因为大部分情况下环境光贴图与漫反射贴图相同),TexCoords为纹理坐标,texture函数会在纹理坐标内生成一些插值坐标,用来采样纹理图片其他地方的颜色。
2、 漫反射光照:
这里写图片描述
如图可知,计算漫反射光照最重要的就是计算光线向量与法向量的夹角角度。法向量可以通过叉乘获得。而光线向量要通过光源位置向量与片段位置向量相减获得。

vec3 lightDir = normalize(light.position - fragPos);
float diff = max(dot(normal, lightDir), 0.0);
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));

解释:
LightDir为光线向量,normalize是向量标准化函数,将向量变为长度为1的单位向量。Diff为法向量与光线向量夹角,注意该夹角不能小于0,normal是法向量我们一般可以通过数据输入得到。
重要注意点:
当模型发生不均匀缩放是会破坏法向量导致光线扭曲。
解决方法:使用正规矩阵(模型矩阵左上角的逆矩阵的转置矩阵)
得到如下顶点着色器代码:


3、 镜面反射光照
这里写图片描述
计算该光照需要以下几个数据:光源位置,片段位置,法向量,观察者位置。
光源位置和片段位置能够求出光线向量,通过光线向量与法向量能够计算出反射光线向量,观察者位置和片段位置能够计算出观察者向量,观察者向量与反射光线向量能够求出角度,该角度即为参数spec,用来计算镜面反射光线向量。

vec3 lightDir = normalize(light.position - fragPos);
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));

viewDir为观察者向量。
reflectDir是反射光线向量,reflect是求反射向量的函数其中第一个参数是入射光线,它必须是从光源出发,所以lightDir要取反。
Material.shininess是发光值一个物体的发光值越高,反射光的能力越强,散射得越少,高光点越小。往往设置为32。
4、 光照结果:

vec3 result = ambient + diffuse + specular;
color = vec4(result, 1.0f);

二、 投光物
1、 定向光
当一个光源很远的时候,来自光源的每条光线接近于平行。这看起来就像所有的光线来自于同一个方向,无论物体和观察者在哪儿。当一个光源被设置为无限远时,它被称为定向光(Directional Light),因为所有的光线都有着同一个方向;它会独立于光源的位置。
这里写图片描述

struct DirLight 
{
    vec3 direction;//定向光的光线向量(定向光的光线向量不发生改变)
    vec3 ambient;//环境光向量
    vec3 diffuse;//漫反射光线向量
    vec3 specular;//镜面反射光线向量
};
uniform DirLight dirLight;
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.0), material.shininess);
    // 计算各光照分量向量
    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));
    return (ambient + diffuse + specular);
}

2、 点光源
点光是一个在时间里有位置的光源,它向所有方向发光,光线随距离增加逐渐变暗。想象灯泡和火炬作为投光物,它们可以扮演点光的角色。
这里写图片描述
重要特性——衰减:
随着光线穿越距离的变远使得亮度也相应地减少的现象,通常称之为衰减(Attenuation)。
衰减方程:
这里写图片描述
在这里d代表片段到光源的距离。为了计算衰减值,我们定义3个(可配置)项:常数项Kc,一次项Kl和二次项Kq。

struct PointLight 
{
    vec3 position;//点光源位置
    float constant;//衰减公式常数项
    float linear;//衰减公式一次项系数
    float quadratic;//衰减公式二次项系数
    vec3 ambient;//环境光向量
    vec3 diffuse;//漫反射向量
    vec3 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(viewDir, reflectDir), 0.0), material.shininess);
    // 计算衰减系数
    float distance = length(light.position - fragPos);
    float attenuation = 1.0f / (light.constant + light.linear * distance +light.quadratic * (distance * distance));    
    // 计算各个光照分量向量
    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);
}

3、 聚光光源
聚光是一种位于环境中某处的光源,它不是向所有方向照射,而是只朝某个方向照射。结果是只有一个聚光照射方向的确定半径内的物体才会被照亮,其他的都保持黑暗。聚光的好例子是路灯或手电筒。
这里写图片描述
• LightDir:从片段指向光源的向量。
• SpotDir:聚光所指向的方向。
• Phiϕ:定义聚光半径的切光角。每个落在这个角度之外的,聚光都不会照亮。
• Thetaθ:LightDir向量和SpotDir向量之间的角度。θ值应该比Φ值小,这样才会在聚光内。
重要特性——平滑/软化边缘
为创建聚光的平滑边,我们希望去模拟的聚光有一个内圆锥和外圆锥。我们可以把内圆锥设置为前面部分定义的圆锥,我们希望外圆锥从内边到外边逐步的变暗。
为创建外圆锥,我们简单定义另一个余弦值,它代表聚光的方向向量和外圆锥的向量(等于它的半径)的角度。然后,如果片段在内圆锥和外圆锥之间,就会给它计算出一个0.0到1.0之间的亮度。如果片段在内圆锥以内这个亮度就等于1.0,如果在外面就是0.0。
这里写图片描述
这里ϵ是内部圆锥(ϕ)和外部圆锥(γ)(epsilon = phi - gamma)的差。结果I的值是聚光在当前片段的亮度。

struct SpotLight
{
    vec3 position;//光源位置
    vec3 direction;//光的方向,即上图的SpotDir
    float cutOff;//内圆锥切角
    float outerCutOff;//外圆锥切角

    float constant;
    float linear;
    float quadratic;

    vec3 ambient;
    vec3 diffuse;
    vec3 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(viewDir, reflectDir), 0.0), material.shininess);
    // 光线衰减
    float distance = length(light.position - fragPos);
    float attenuation = 1.0f / (light.constant + light.linear * distance + light.quadratic * (distance * distance));    
    // 计算聚光系数
    float theta = dot(lightDir, normalize(-light.direction)); 
    float epsilon = light.cutOff - light.outerCutOff;
    float intensity = clamp((theta - 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 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
//给每个光照分量乘上衰减系数和聚光系数
    ambient *= attenuation * intensity;
    diffuse *= attenuation * intensity;
    specular *= attenuation * intensity;
    return (ambient + diffuse + specular);
}

三、 完整的顶点着色器和片段着色器
1、 Vertexshader:

#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normal;
layout (location = 2) in vec2 texCoords;

out vec3 Normal;
out vec3 FragPos;
out vec2 TexCoords;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
    gl_Position = projection * view *  model * vec4(position, 1.0f);
    FragPos = vec3(model * vec4(position, 1.0f));
    Normal = mat3(transpose(inverse(model))) * normal;  
    TexCoords = texCoords;
}

2、 Fragmentshader

#version 330 core
struct Material {
    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;
};

#define NR_POINT_LIGHTS 4

in vec3 FragPos;
in vec3 Normal;
in vec2 TexCoords;

out vec4 color;

uniform vec3 viewPos;
uniform DirLight dirLight;
uniform PointLight pointLights[NR_POINT_LIGHTS];
uniform SpotLight spotLight;
uniform Material material;

// Function prototypes
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);

void main()
{    
    // Properties
    vec3 norm = normalize(Normal);
    vec3 viewDir = normalize(viewPos - FragPos);

    // == ======================================
    // Our lighting is set up in 3 phases: directional, point lights and an optional flashlight
    // For each phase, a calculate function is defined that calculates the corresponding color
    // per lamp. In the main() function we take all the calculated colors and sum them up for
    // this fragment's final color.
    // == ======================================
    // Phase 1: Directional lighting
    vec3 result = CalcDirLight(dirLight, norm, viewDir);
    // Phase 2: Point lights
    for(int i = 0; i < NR_POINT_LIGHTS; i++)
        result += CalcPointLight(pointLights[i], norm, FragPos, viewDir);    
    // Phase 3: Spot light
    result += CalcSpotLight(spotLight, norm, FragPos, viewDir);    

    color = vec4(result, 1.0);
}

// Calculates the color when using a directional light.
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir)
{
    vec3 lightDir = normalize(-light.direction);
    // Diffuse shading
    float diff = max(dot(normal, lightDir), 0.0);
    // Specular shading
    vec3 reflectDir = reflect(-lightDir, normal);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
    // Combine results
    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));
    return (ambient + diffuse + specular);
}

// Calculates the color when using a point light.
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{
    vec3 lightDir = normalize(light.position - fragPos);
    // Diffuse shading
    float diff = max(dot(normal, lightDir), 0.0);
    // Specular shading
    vec3 reflectDir = reflect(-lightDir, normal);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
    // Attenuation
    float distance = length(light.position - fragPos);
    float attenuation = 1.0f / (light.constant + light.linear * distance + light.quadratic * (distance * distance));    
    // Combine results
    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);
    // Diffuse shading
    float diff = max(dot(normal, lightDir), 0.0);
    // Specular shading
    vec3 reflectDir = reflect(-lightDir, normal);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
    // Attenuation
    float distance = length(light.position - fragPos);
    float attenuation = 1.0f / (light.constant + light.linear * distance + light.quadratic * (distance * distance));    
    // Spotlight intensity
    float theta = dot(lightDir, normalize(-light.direction)); 
    float epsilon = light.cutOff - light.outerCutOff;
    float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0);
    // Combine results
    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 * intensity;
    diffuse *= attenuation * intensity;
    specular *= attenuation * intensity;
    return (ambient + diffuse + specular);
}

举个栗子,程序

运行效果

OpenGL中phong光照模型详解_第2张图片

工程源码 

参考

cesuolidec4

openGL-phong光照模型很好的总结

learnopengl CN

你可能感兴趣的:(openGL,openGL-phong光照)