天天看點

OpenGL中phong光照模型詳解

版權

​​cesuolidec4​​

​​article/details/54600191​​

引言

        現實世界的光照是極其複雜的,而且會受到諸多因素的影響,這是以目前我們所擁有的處理能力無法模拟的。是以OpenGL的光照僅僅使用了簡化的模型并基于對現實的估計來進行模拟,這樣處理起來會更容易一些,而且看起來也差不多一樣。這些光照模型都是基于我們對光的實體特性的了解。其中一個模型被稱為馮氏光照模型(Phong Lighting Model)。馮氏光照模型的主要結構由3個元素組成:環境(Ambient)、漫反射(Diffuse)和鏡面(Specular)光照。這些光照元素看起來像下面這樣:

OpenGL中phong光照模型詳解

             先看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、 漫反射光照:

OpenGL中phong光照模型詳解

如圖可知,計算漫反射光照最重要的就是計算光線向量與法向量的夾角角度。法向量可以通過叉乘獲得。而光線向量要通過光源位置向量與片段位置向量相減獲得。

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、 鏡面反射光照

OpenGL中phong光照模型詳解

計算該光照需要以下幾個資料:光源位置,片段位置,法向量,觀察者位置。

光源位置和片段位置能夠求出光線向量,通過光線向量與法向量能夠計算出反射光線向量,觀察者位置和片段位置能夠計算出觀察者向量,觀察者向量與反射光線向量能夠求出角度,該角度即為參數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),因為所有的光線都有着同一個方向;它會獨立于光源的位置。

OpenGL中phong光照模型詳解
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、 點光源

點光是一個在時間裡有位置的光源,它向所有方向發光,光線随距離增加逐漸變暗。想象燈泡和火炬作為投光物,它們可以扮演點光的角色。

OpenGL中phong光照模型詳解

重要特性——衰減:

随着光線穿越距離的變遠使得亮度也相應地減少的現象,通常稱之為衰減(Attenuation)。

衰減方程:

OpenGL中phong光照模型詳解

在這裡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、 聚光光源

聚光是一種位于環境中某處的光源,它不是向所有方向照射,而是隻朝某個方向照射。結果是隻有一個聚光照射方向的确定半徑内的物體才會被照亮,其他的都保持黑暗。聚光的好例子是路燈或手電筒。

OpenGL中phong光照模型詳解

• LightDir:從片段指向光源的向量。

• SpotDir:聚光所指向的方向。

• Phiϕ:定義聚光半徑的切光角。每個落在這個角度之外的,聚光都不會照亮。

• Thetaθ:LightDir向量和SpotDir向量之間的角度。θ值應該比Φ值小,這樣才會在聚光内。

重要特性——平滑/軟化邊緣

為建立聚光的平滑邊,我們希望去模拟的聚光有一個内圓錐和外圓錐。我們可以把内圓錐設定為前面部分定義的圓錐,我們希望外圓錐從内邊到外邊逐漸的變暗。

為建立外圓錐,我們簡單定義另一個餘弦值,它代表聚光的方向向量和外圓錐的向量(等于它的半徑)的角度。然後,如果片段在内圓錐和外圓錐之間,就會給它計算出一個0.0到1.0之間的亮度。如果片段在内圓錐以内這個亮度就等于1.0,如果在外面就是0.0。

OpenGL中phong光照模型詳解

這裡ϵ是内部圓錐(ϕ)和外部圓錐(γ)(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光照模型詳解

​​工程源碼​​ 

參考

繼續閱讀