天天看點

Linux OpenGL 實踐篇-6 光照

經典光照模型

經典光照模型通過單獨計算光源成分得到綜合光照效果,然後添加到物體表面特定點,這些成分包括:環境光、漫反射光、鏡面光。

環境光:是指不是來特定方向的光,在經典光照模型中基本是個常量。

漫反射光:是散射在各個方向上均勻的表面特定光源。物體表面通過光照照亮,即使這個表面沒有将光源直接反射到你的眼睛中。漫反射與眼睛的方向沒有關系,但與光源的方向有關,當表面直接面向光源的時候會表現的亮一些,而傾斜的時候則暗一些,因為在現實中傾斜的表面接受的光要少一些。在經典光照模型中,我們使用表面的法向量來計算漫放射光照,同時,反射光的顔色也依賴表面的顔色。

鏡面光:是表面反射的高亮光。現實中一個高度抛光的金屬球能反射一個尖銳的反射光,而一個磨砂的表面則會反射一個更大,而且相對暗一點的反射光,而一個布球則沒有反射高光。這個特定階段的效果強度稱為光澤度(shininess)。在經典光照模型中,我們通過計算光源經過物體表面反射後的與眼睛反方向的角度來衡量。這個計算我們需要視線的方向,表面法線,光源的方向。

馮氏光照模型(Phong Lighting Model)

在經典光照模型中中最常用的一種模型稱為馮氏光照模型。

首先我們要介紹第一種光源:方向光。如果一個光源足夠的遠,那麼我們可以認為它發射的光線到物體的表面都是一個方向,這樣的光即為方向光。下面我們使用方向光和馮氏光照模型實作一個第一個光照效果。

Linux OpenGL 實踐篇-6 光照

環境光

環境光通常我們給與一個常量表示。

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);
}      
Linux OpenGL 實踐篇-6 光照

點光源

點光源模拟現實中的點燈、路燈等光源。點光源與平行光的差別有兩點:

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);
}
                                                                                           

效果如圖:

Linux OpenGL 實踐篇-6 光照

聚光燈

 在舞台和電影中,聚光燈投影一個強大的光束來照亮一個明确的區域。在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);
 }
                                                                                         

                                                                                           

效果如下:

Linux OpenGL 實踐篇-6 光照

多光源

在場景中我們可能需要不止一個光源,我們可以在着色器中定義多個光源,光源類型包括上述的:方向光、點光源、聚光燈。我們可以使用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);
}
                                                       
Linux OpenGL 實踐篇-6 光照

 源代碼:https://github.com/xin-lover/opengl-learn/tree/master/chapter-8-multiple_lighting

繼續閱讀