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

先看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);
}
舉個栗子,程式
運作效果
工程源碼
參考