從0開始的OpenGL學習(十五)-多光源

本文主要解決一個問題:

如何在場景中實現多個光源?

引言

在之前的文章中,我們學了很多OpenGL中的光照知識,包括馮氏著色、材質、光照貼圖以及不同類型的光源模型等等。本文中,我們要把這些知識都組合起來,在場景中創造6個光源。我們要創造1個的方向光,4個點光源以及1個聚光燈(手電筒),然后看看整個場景會是什么樣子。

封裝光源操作

為了使用多個光源,我們將會把光照計算的操作封裝進GLSL函數中。如果你是一個新手,可能覺得這不是必要的操作。如果你有一些經驗,將代碼封裝成函數是一件自然而然的事情,這樣做不僅結構清晰,而且易于使用。

我們已經學了很多GLSL的語法,但是封裝函數還沒有學到。不過不用擔心,GLSL中的函數和C中的函數很相似,都需要一個函數名,一個返回值,在調用之前需要聲明等等。對于三種不同的光源模型,我們定義了3個不同的函數,分別是:CalcDirLight,CalcPointLight和CalcSpotLight。

想想在一個場景中,很多的光源照射到同一個物體上時,物體會呈現出什么樣子?多種光的效果會疊加起來,呈現出一種混合的狀態,我們試著來總結一個流程:

  1. 一個顏色向量表示片元的輸出顏色
  2. 計算每個光源對輸出顏色的影響,將所有的結果相加。
  3. 將所有結果的和傳遞給片元顏色作為最終結果

用偽代碼表示這個過程就是這樣:

void main(){
  vec3 output = vec3(0,0);
  output += 計算方向光的函數;
  for (int i = 0; i < 點光源數量; ++i)
    output += 計算點光源的函數;
  output += 計算聚光燈的函數;
  FragColor = vec4(output, 1.0);
}

在實現的過程,實際的代碼可能與這個不同,不必拘泥于這個代碼形式,思路是這樣就不會有問題。接下來,我們來定義一些計算不同光源對片元顏色產生影響的函數。

方向光

函數形式非常簡單,只需根據輸入的參數計算方向光對當前片元顏色的影響并返回結果就行了。不過首先,我們要來定義一個方向光源的結構體。

//方向光源
struct DirLight{
    vec3 direction;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};
uniform DirLight dirLight;

之后,將這個方向光源作為參數傳遞到計算光照的函數中:

vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir);

可以看到,這個函數需要一個DirLight的對象,法線參數,以及觀察方向。如果你非常熟悉之前的代碼,那么實現這個函數對你來說就輕而易舉。

vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir) {
    vec3 lightDir = normalize(-light.direction);
    //環境光
    vec3 ambient = light.ambient * vec3 (texture(material.diffuse, TexCoords));

    //漫反射
    float diff = max(dot(normal, lightDir), 0.0);
    vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));

    //鏡面高光
    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));

    return (ambient + diffuse + specular);
}

基本上都是復制粘貼之前的代碼,然后將代碼整理一下的結果。

點光源

和方向光一樣,我們先要定義一個點光源的結構,然后創建4個點光源。不同的是,我們采用數組的方式來創建4個點光源。具體實現如下:

//點光源
struct PointLight{
    vec3 position;

    float constant;
    float linear;
    float quadratic;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};
#define NR_POINT_LIGHTS 4
uniform PointLight pointLight[NR_POINT_LIGHTS];

如你所見,定義數組的語法也和C類似,筆者覺得會C語言真是太幸運了。當然,我們可以把所有的數據放到一個光源結構中,這樣所有的光源都能使用同一個結構。但筆者更傾向于定義不同的結構,這樣更簡潔,擴展性更好,占用的空間也更少。

計算光照的原型如下:

vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir); 

實現的方式也和之前的代碼一樣,我們復制粘貼過來,然后做些修改:

//計算點光源的影響
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir){
    //環境光
    vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));

    //漫反射光
    vec3 norm = normalize(normal);
    vec3 lightDir = normalize(light.position - FragPos);  
        
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));

    //鏡面高光
    vec3 reflectDir = reflect(-lightDir, norm);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
    vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));

    //衰減
    float distance = length(light.position - FragPos);
    float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
    ambient *= attenuation;
    diffuse *= attenuation;
    specular *= attenuation;

    return ambient + diffuse + specular;
}

依舊沒什么花頭,就是前面已經實現過的代碼。

聚光燈

這里我們可以偷個懶,因為前一篇文章中,我們最后實現的就是聚光燈,之前又是新增數據結構,沒有改之前的Light結構,這里我們只需要將原有的Light結構換個名字成SpotLight就直接獲得了一個聚光燈的結構。

然后,定義一個聚光燈的處理函數如下:

//計算聚光燈的影響
vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir){
   //環境光
    vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));

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

    //鏡面高光
    vec3 reflectDir = reflect(-lightDir, norm);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
    vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));

    //聚光燈
    float theta = dot(lightDir, normalize(-light.direction));   //計算片元角度的cos值
    float epsilon = light.cutOff - light.outerCutOff;   //計算epsilon的值,用內錐角的cos值減去外錐角的cos值
    float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0);   //根據公式計算光照強度,并限制結果的范圍

    diffuse *= intensity;
    specular *= intensity;

    //衰減
    float distance = length(light.position - FragPos);
    float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
    ambient *= attenuation;
    diffuse *= attenuation;
    specular *= attenuation;

    return ambient + diffuse + specular;
}

修改了一些變量名,比如normal,觀察方向也不需要計算,直接可以使用了,省了不少事。

整合

根據之前分析的結構,將代碼補充完整:

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

    //方向光
    vec3 result = CalcDirLight(dirLight, norm, viewDir);

    //點光源
    for(int i = 0; i < NR_POINT_LIGHTS; ++i)
        result += CalcPointLight(pointLights[i], norm, FragPos, viewDir);

    //聚光燈
    result += CalcSpotLight(spotLight, norm, FragPos, viewDir);
 
    FragColor = vec4(result, 1.0f);
}

在主函數中設置結構體中的元素你已經很熟悉了,那么怎么設置數組中的元素呢?答案很簡單,還是C語言的語法,請看下面的代碼:

lightingShader.setFloat("pointLights[0].constant", 1.0f);

沒錯,像C語言訪問數組那樣訪問一個元素,然后設置其值。

別忘了還有點光源的位置我們沒設置,快來看看我們把這些點光源放在哪里:

glm::vec3 pointLightPositions[] = {
    glm::vec3( 0.7f,  0.2f,  2.0f),
    glm::vec3( 2.3f, -3.3f, -4.0f),
    glm::vec3(-4.0f,  2.0f, -12.0f),
    glm::vec3( 0.0f,  0.0f, -3.0f)
}; 

接下來,我們就要為著色器中的這些元素賦值了。你可能會想,這里面這么多元素,難道要一個一個去賦值嗎,有沒有更好的方法?不過很遺憾,目前來說我們還沒有簡單的賦值方法,只能手動賦值,為了避免手寫的枯燥,筆者在這里把賦值的代碼貼出來:

/*
為光源賦值
*/
// 方向光
lightingShader.setVec3("dirLight.direction", -0.2f, -1.0f, -0.3f);
lightingShader.setVec3("dirLight.ambient", 0.05f, 0.05f, 0.05f);
lightingShader.setVec3("dirLight.diffuse", 0.4f, 0.4f, 0.4f);
lightingShader.setVec3("dirLight.specular", 0.5f, 0.5f, 0.5f);
// 點光源1
lightingShader.setVec3("pointLights[0].position", pointLightPositions[0]);
lightingShader.setVec3("pointLights[0].ambient", 0.05f, 0.05f, 0.05f);
lightingShader.setVec3("pointLights[0].diffuse", 0.8f, 0.8f, 0.8f);
lightingShader.setVec3("pointLights[0].specular", 1.0f, 1.0f, 1.0f);
lightingShader.setFloat("pointLights[0].constant", 1.0f);
lightingShader.setFloat("pointLights[0].linear", 0.09);
lightingShader.setFloat("pointLights[0].quadratic", 0.032);
// 點光源2
lightingShader.setVec3("pointLights[1].position", pointLightPositions[1]);
lightingShader.setVec3("pointLights[1].ambient", 0.05f, 0.05f, 0.05f);
lightingShader.setVec3("pointLights[1].diffuse", 0.8f, 0.8f, 0.8f);
lightingShader.setVec3("pointLights[1].specular", 1.0f, 1.0f, 1.0f);
lightingShader.setFloat("pointLights[1].constant", 1.0f);
lightingShader.setFloat("pointLights[1].linear", 0.09);
lightingShader.setFloat("pointLights[1].quadratic", 0.032);
// 點光源3
lightingShader.setVec3("pointLights[2].position", pointLightPositions[2]);
lightingShader.setVec3("pointLights[2].ambient", 0.05f, 0.05f, 0.05f);
lightingShader.setVec3("pointLights[2].diffuse", 0.8f, 0.8f, 0.8f);
lightingShader.setVec3("pointLights[2].specular", 1.0f, 1.0f, 1.0f);
lightingShader.setFloat("pointLights[2].constant", 1.0f);
lightingShader.setFloat("pointLights[2].linear", 0.09);
lightingShader.setFloat("pointLights[2].quadratic", 0.032);
// 點光源4
lightingShader.setVec3("pointLights[3].position", pointLightPositions[3]);
lightingShader.setVec3("pointLights[3].ambient", 0.05f, 0.05f, 0.05f);
lightingShader.setVec3("pointLights[3].diffuse", 0.8f, 0.8f, 0.8f);
lightingShader.setVec3("pointLights[3].specular", 1.0f, 1.0f, 1.0f);
lightingShader.setFloat("pointLights[3].constant", 1.0f);
lightingShader.setFloat("pointLights[3].linear", 0.09);
lightingShader.setFloat("pointLights[3].quadratic", 0.032);
// 聚光燈
lightingShader.setVec3("spotLight.position", camera.Position);
lightingShader.setVec3("spotLight.direction", camera.Front);
lightingShader.setVec3("spotLight.ambient", 0.0f, 0.0f, 0.0f);
lightingShader.setVec3("spotLight.diffuse", 1.0f, 1.0f, 1.0f);
lightingShader.setVec3("spotLight.specular", 1.0f, 1.0f, 1.0f);
lightingShader.setFloat("spotLight.constant", 1.0f);    lightingShader.setFloat("spotLight.linear", 0.09);
lightingShader.setFloat("spotLight.quadratic", 0.032);
lightingShader.setFloat("spotLight.cutOff", glm::cos(glm::radians(12.5f)));
lightingShader.setFloat("spotLight.outerCutOff", glm::cos(glm::radians(15.0f)));

為光源賦值之后,我們還要把其余的光源也創建出來,代碼也很簡單,和我們之前創建多個盒子的時候沒什么兩樣。

glBindVertexArray(lightVAO);
for (unsigned int i = 0; i < 4; ++i) {
    glm::mat4 model2;
    model2 = glm::translate(model2, pointLightPositions[i]);
    model2 = glm::scale(model2, glm::vec3(0.2f));
    lampShader.setMat4("model", glm::value_ptr(model2));
    glDrawArrays(GL_TRIANGLES, 0, 36);
}

這里只有一點要注意,就是要使用光源VAO之后再繪制光源立方體。

編譯運行,如果沒錯,你看到的場景應該是這樣的:

運行效果

仔細觀察,還有手電筒的效果哦!如果效果不一樣,請下載源碼進行比對。

總結

本文并沒有介紹什么新的知識,只是將已有知識進行整合使用,實現一些效果,你也可以在本文的基礎上改變光源的顏色值,看看場景會變成什么稀奇古怪的樣子,這是一個很有趣的過程。

下一篇
目錄
上一篇

參考資料

www.learnopengl.com(非常好的網站,推薦學習)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,247評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,520評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,362評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,805評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,541評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,896評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,887評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,062評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,608評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,356評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,555評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,077評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,769評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,175評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,489評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,289評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,516評論 2 379

推薦閱讀更多精彩內容