OpenGL 圖形庫的使用(三十二)—— 高級光照之高級光照Advanced Lighting

版本記錄

版本號 時間
V1.0 2018.01.18

前言

OpenGL 圖形庫項目中一直也沒用過稠腊,最近也想學(xué)著使用這個圖形庫躁染,感覺還是很有意思,也就自然想著好好的總結(jié)一下架忌,希望對大家能有所幫助吞彤。下面內(nèi)容來自歡迎來到OpenGL的世界
1. OpenGL 圖形庫使用(一) —— 概念基礎(chǔ)
2. OpenGL 圖形庫使用(二) —— 渲染模式叹放、對象饰恕、擴展和狀態(tài)機
3. OpenGL 圖形庫使用(三) —— 著色器、數(shù)據(jù)類型與輸入輸出
4. OpenGL 圖形庫使用(四) —— Uniform及更多屬性
5. OpenGL 圖形庫使用(五) —— 紋理
6. OpenGL 圖形庫使用(六) —— 變換
7. OpenGL 圖形庫的使用(七)—— 坐標(biāo)系統(tǒng)之五種不同的坐標(biāo)系統(tǒng)(一)
8. OpenGL 圖形庫的使用(八)—— 坐標(biāo)系統(tǒng)之3D效果(二)
9. OpenGL 圖形庫的使用(九)—— 攝像機(一)
10. OpenGL 圖形庫的使用(十)—— 攝像機(二)
11. OpenGL 圖形庫的使用(十一)—— 光照之顏色
12. OpenGL 圖形庫的使用(十二)—— 光照之基礎(chǔ)光照
13. OpenGL 圖形庫的使用(十三)—— 光照之材質(zhì)
14. OpenGL 圖形庫的使用(十四)—— 光照之光照貼圖
15. OpenGL 圖形庫的使用(十五)—— 光照之投光物
16. OpenGL 圖形庫的使用(十六)—— 光照之多光源
17. OpenGL 圖形庫的使用(十七)—— 光照之復(fù)習(xí)總結(jié)
18. OpenGL 圖形庫的使用(十八)—— 模型加載之Assimp
19. OpenGL 圖形庫的使用(十九)—— 模型加載之網(wǎng)格
20. OpenGL 圖形庫的使用(二十)—— 模型加載之模型
21. OpenGL 圖形庫的使用(二十一)—— 高級OpenGL之深度測試
22. OpenGL 圖形庫的使用(二十二)—— 高級OpenGL之模板測試Stencil testing
23. OpenGL 圖形庫的使用(二十三)—— 高級OpenGL之混合Blending
24. OpenGL 圖形庫的使用(二十四)—— 高級OpenGL之面剔除Face culling
25. OpenGL 圖形庫的使用(二十五)—— 高級OpenGL之幀緩沖Framebuffers
26. OpenGL 圖形庫的使用(二十六)—— 高級OpenGL之立方體貼圖Cubemaps
27. OpenGL 圖形庫的使用(二十七)—— 高級OpenGL之高級數(shù)據(jù)Advanced Data
28. OpenGL 圖形庫的使用(二十八)—— 高級OpenGL之高級GLSL Advanced GLSL
29. OpenGL 圖形庫的使用(二十九)—— 高級OpenGL之幾何著色器Geometry Shader
30. OpenGL 圖形庫的使用(三十)—— 高級OpenGL之實例化Instancing
31. OpenGL 圖形庫的使用(三十一)—— 高級OpenGL之抗鋸齒Anti Aliasing

高級光照

在光照教程中井仰,我們簡單的介紹了Phong光照模型埋嵌,它給我們的場景帶來的基本的現(xiàn)實感。Phong模型看起來還不錯糕档,但本章我們把重點放在一些細(xì)微差別上莉恼。


Blinn-Phong

Phong光照很棒拌喉,而且性能較高速那,但是它的鏡面反射在某些條件下會失效,特別是當(dāng)發(fā)光值屬性低的時候尿背,對應(yīng)一個非常大的粗糙的鏡面區(qū)域端仰。下面的圖片展示了,當(dāng)我們使用鏡面的發(fā)光值為1.0時田藐,一個帶紋理地板的效果:

你可以看到荔烧,鏡面區(qū)域邊緣迅速減弱并截止。出現(xiàn)這個問題的原因是在視線向量和反射向量的角度不允許大于90度汽久。如果大于90度的話鹤竭,點乘的結(jié)果就會是負(fù)數(shù),鏡面的貢獻(xiàn)成分就會變成0景醇。你可能會想臀稚,這不是一個問題,因為大于90度時我們不應(yīng)看到任何光三痰,對吧吧寺?

錯了窜管,這只適用于漫散射部分,當(dāng)法線和光源之間的角度大于90度時意味著光源在被照亮表面的下方稚机,這樣光的散射成分就會是0.0幕帆。然而,對于鏡面光照赖条,我們不會測量光源和法線之間的角度失乾,而是測量視線和反射方向向量之間的∥痴В看看下面的兩幅圖:

現(xiàn)在看來問題就很明顯了仗扬。左側(cè)圖片顯示Phong反射的θ小于90度的情況。我們可以看到右側(cè)圖片視線和反射之間的角θ大于90度蕾额,這樣鏡面反射成分將會被消除早芭。通常這也不是問題,因為視線方向距離反射方向很遠(yuǎn)诅蝶,但如果我們使用一個數(shù)值較低的發(fā)光值參數(shù)的話退个,鏡面半徑就會足夠大,以至于能夠貢獻(xiàn)一些鏡面反射的成份了调炬。在例子中语盈,我們在角度大于90度時消除了這個貢獻(xiàn)(如第一個圖片所示)。

1977年James F. Blinn引入了Blinn-Phong著色缰泡,它擴展了我們目前所使用的Phong著色刀荒。Blinn-Phong模型很大程度上和Phong是相似的,不過它稍微改進(jìn)了Phong模型棘钞,使之能夠克服我們所討論到的問題缠借。它放棄使用反射向量,而是基于我們現(xiàn)在所說的一個叫做半程向量(halfway vector)的向量宜猜,這是個單位向量泼返,它在視線方向和光線方向的中間。半程向量和表面法線向量越接近姨拥,鏡面反射成份就越大绅喉。

當(dāng)視線方向恰好與(想象中的)反射向量對齊時,半程向量就與法線向量重合叫乌。這樣觀察者的視線越接近原本的反射方向柴罐,鏡面反射的高光就會越強。

這里憨奸,你可以看到無論觀察者往哪里看革屠,半程向量和表面法線之間的夾角永遠(yuǎn)都不會超過90度(當(dāng)然除了光源遠(yuǎn)遠(yuǎn)低于表面的情況)。這樣會產(chǎn)生和Phong反射稍稍不同的結(jié)果,但這時看起來會更加可信屠阻,特別是發(fā)光值參數(shù)比較低的時候红省。Blinn-Phong著色模型也正是早期OpenGL固定函數(shù)輸送管道(fixed function pipeline)所使用的著色模型。

得到半程向量很容易国觉,我們將光的方向向量和視線向量相加吧恃,然后將結(jié)果歸一化(normalize)

翻譯成GLSL代碼如下:

vec3 lightDir = normalize(lightPos - FragPos);
vec3 viewDir = normalize(viewPos - FragPos);
vec3 halfwayDir = normalize(lightDir + viewDir);

實際的鏡面反射的計算麻诀,就成為計算表面法線和半程向量的點乘痕寓,并對其結(jié)果進(jìn)行約束(大于或等于0),然后獲取它們之間角度的余弦蝇闭,再添加上發(fā)光值參數(shù):

float spec = pow(max(dot(normal, halfwayDir), 0.0), shininess);
vec3 specular = lightColor * spec;

除了我們剛剛討論的呻率,Blinn-Phong沒有更多的內(nèi)容了。Blinn-Phong和Phong的鏡面反射唯一不同之處在于呻引,現(xiàn)在我們要測量法線和半程向量之間的角度礼仗,而半程向量是視線方向和反射向量之間的夾角。

Blinn-Phong著色的一個附加好處是逻悠,它比Phong著色性能更高元践,因為我們不必計算更加復(fù)雜的反射向量了。

引入了半程向量來計算鏡面反射后童谒,我們再也不會遇到Phong著色的驟然截止問題了单旁。下圖展示了兩種不同方式下發(fā)光值指數(shù)為0.5時鏡面區(qū)域的不同效果:

Phong和Blinn-Phong著色之間另一個細(xì)微差別是,半程向量和表面法線之間的角度經(jīng)常會比視線和反射向量之間的夾角更小饥伊。結(jié)果就是象浑,為了獲得和Phong著色相似的效果,必須把發(fā)光值參數(shù)設(shè)置的大一點琅豆。通常的經(jīng)驗是將其設(shè)置為Phong著色的發(fā)光值參數(shù)的2至4倍愉豺。

下圖是Phong指數(shù)為8.0和Blinn-Phong指數(shù)為32的時候,兩種specular反射模型的對比:

你可以看到Blinn-Phong的鏡面反射成分要比Phong銳利一些趋距。這通常需要使用一點小技巧才能獲得之前你所看到的Phong著色的效果粒氧,但Blinn-Phong著色的效果比默認(rèn)的Phong著色通常更加真實一些。

這里我們用到了一個簡單像素著色器节腐,它可以在普通Phong反射和Blinn-Phong反射之間進(jìn)行切換:

void main()
{
    [...]
    float spec = 0.0;
    if(blinn)
    {
        vec3 halfwayDir = normalize(lightDir + viewDir);  
        spec = pow(max(dot(normal, halfwayDir), 0.0), 16.0);
    }
    else
    {
        vec3 reflectDir = reflect(-lightDir, normal);
        spec = pow(max(dot(viewDir, reflectDir), 0.0), 8.0);
    }

你可以在這里找到這個簡單的demo的源碼以及頂點片段著色器。按下b鍵摘盆,這個demo就會從Phong切換到Blinn-Phong光照翼雀,反之亦然。

// Std. Includes
#include <string>

// GLEW
#define GLEW_STATIC
#include <GL/glew.h>

// GLFW
#include <GLFW/glfw3.h>

// GL includes
#include <learnopengl/shader.h>
#include <learnopengl/camera.h>

// GLM Mathemtics
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

// Other Libs
#include <SOIL.h>

// Properties
const GLuint SCR_WIDTH = 800, SCR_HEIGHT = 600;

// Function prototypes
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void Do_Movement();
GLuint loadTexture(GLchar* path);

// Camera
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));

// Delta
GLfloat deltaTime = 0.0f;
GLfloat lastFrame = 0.0f;

// Options
GLboolean blinn = false;

// The MAIN function, from here we start our application and run our Game loop
int main()
{
    // Init GLFW
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", nullptr, nullptr); // Windowed
    glfwMakeContextCurrent(window);

    // Set the required callback functions
    glfwSetKeyCallback(window, key_callback);
    glfwSetCursorPosCallback(window, mouse_callback);
    glfwSetScrollCallback(window, scroll_callback);

    // Options
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

    // Initialize GLEW to setup the OpenGL Function pointers
    glewExperimental = GL_TRUE;
    glewInit();

    // Define the viewport dimensions
    glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);

    // Setup some OpenGL options
    glEnable(GL_DEPTH_TEST);
    // glDepthFunc(GL_ALWAYS); // Set to always pass the depth test (same effect as glDisable(GL_DEPTH_TEST))

    // Setup and compile our shaders
    Shader shader("advanced_lighting.vs", "advanced_lighting.frag");

    GLfloat planeVertices[] = {
        // Positions          // Normals         // Texture Coords
         8.0f, -0.5f,  8.0f,  0.0f, 1.0f, 0.0f,  5.0f, 0.0f,
        -8.0f, -0.5f,  8.0f,  0.0f, 1.0f, 0.0f,  0.0f, 0.0f,
        -8.0f, -0.5f, -8.0f,  0.0f, 1.0f, 0.0f,  0.0f, 5.0f,

         8.0f, -0.5f,  8.0f,  0.0f, 1.0f, 0.0f,  5.0f, 0.0f,
        -8.0f, -0.5f, -8.0f,  0.0f, 1.0f, 0.0f,  0.0f, 5.0f,
         8.0f, -0.5f, -8.0f,  0.0f, 1.0f, 0.0f,  5.0f, 5.0f
    };
    // Setup plane VAO
    GLuint planeVAO, planeVBO;
    glGenVertexArrays(1, &planeVAO);
    glGenBuffers(1, &planeVBO);
    glBindVertexArray(planeVAO);
    glBindBuffer(GL_ARRAY_BUFFER, planeVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(planeVertices), &planeVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
    glEnableVertexAttribArray(2);
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));
    glBindVertexArray(0);

    // Light source
    glm::vec3 lightPos(0.0f, 0.0f, 0.0f);

    // Load textures
    GLuint floorTexture = loadTexture("../../../resources/textures/wood.png");

    // Game loop
    while(!glfwWindowShouldClose(window))
    {
        // Set frame time
        GLfloat currentFrame = glfwGetTime();
        deltaTime = currentFrame - lastFrame;
        lastFrame = currentFrame;

        // Check and call events
        glfwPollEvents();
        Do_Movement();

        // Clear the colorbuffer
        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // Draw objects
        shader.Use(); 
        glm::mat4 view = camera.GetViewMatrix();
        glm::mat4 projection = glm::perspective(camera.Zoom, (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "view"), 1, GL_FALSE, glm::value_ptr(view));
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
        // Set light uniforms
        glUniform3fv(glGetUniformLocation(shader.Program, "lightPos"), 1, &lightPos[0]);
        glUniform3fv(glGetUniformLocation(shader.Program, "viewPos"), 1, &camera.Position[0]);
        glUniform1i(glGetUniformLocation(shader.Program, "blinn"), blinn);
        // Floor
        glBindVertexArray(planeVAO);
        glBindTexture(GL_TEXTURE_2D, floorTexture);
        glDrawArrays(GL_TRIANGLES, 0, 6);
        glBindVertexArray(0);               

        std::cout << (blinn ? "true" : "false") << std::endl;

        // Swap the buffers
        glfwSwapBuffers(window);
    }

    glfwTerminate();
    return 0;
}

// This function loads a texture from file. Note: texture loading functions like these are usually 
// managed by a 'Resource Manager' that manages all resources (like textures, models, audio). 
// For learning purposes we'll just define it as a utility function.
GLuint loadTexture(GLchar* path)
{
    // Generate texture ID and load texture data 
    GLuint textureID;
    glGenTextures(1, &textureID);
    int width,height;
    unsigned char* image = SOIL_load_image(path, &width, &height, 0, SOIL_LOAD_RGB);
    // Assign texture to ID
    glBindTexture(GL_TEXTURE_2D, textureID);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
    glGenerateMipmap(GL_TEXTURE_2D);    

    // Parameters
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glBindTexture(GL_TEXTURE_2D, 0);
    SOIL_free_image_data(image);
    return textureID;

}

bool keys[1024];
bool keysPressed[1024];
// Moves/alters the camera positions based on user input
void Do_Movement()
{
    // Camera controls
    if(keys[GLFW_KEY_W])
        camera.ProcessKeyboard(FORWARD, deltaTime);
    if(keys[GLFW_KEY_S])
        camera.ProcessKeyboard(BACKWARD, deltaTime);
    if(keys[GLFW_KEY_A])
        camera.ProcessKeyboard(LEFT, deltaTime);
    if(keys[GLFW_KEY_D])
        camera.ProcessKeyboard(RIGHT, deltaTime);

    if (keys[GLFW_KEY_B] && !keysPressed[GLFW_KEY_B])
    {
        blinn = !blinn;
        keysPressed[GLFW_KEY_B] = true;
    }
}

GLfloat lastX = 400, lastY = 300;
bool firstMouse = true;
// Is called whenever a key is pressed/released via GLFW
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
    if(key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
        glfwSetWindowShouldClose(window, GL_TRUE);

    if (key >= 0 && key <= 1024)
    {
        if (action == GLFW_PRESS)
            keys[key] = true;
        else if (action == GLFW_RELEASE)
        {
            keys[key] = false;
            keysPressed[key] = false;
        }
    }
}

void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
    if(firstMouse)
    {
        lastX = xpos;
        lastY = ypos;
        firstMouse = false;
    }

    GLfloat xoffset = xpos - lastX;
    GLfloat yoffset = lastY - ypos; 
    
    lastX = xpos;
    lastY = ypos;

    camera.ProcessMouseMovement(xoffset, yoffset);
}   

void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
    camera.ProcessMouseScroll(yoffset);
}

后記

本篇已經(jīng)結(jié)束孩擂,下一篇是Gamma校正狼渊。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子狈邑,更是在濱河造成了極大的恐慌城须,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件米苹,死亡現(xiàn)場離奇詭異糕伐,居然都是意外死亡,警方通過查閱死者的電腦和手機蘸嘶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門良瞧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人训唱,你說我怎么就攤上這事褥蚯。” “怎么了况增?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵赞庶,是天一觀的道長。 經(jīng)常有香客問我澳骤,道長尘执,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任宴凉,我火速辦了婚禮誊锭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘弥锄。我一直安慰自己丧靡,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般毡证。 火紅的嫁衣襯著肌膚如雪净蚤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天歹河,我揣著相機與錄音,去河邊找鬼。 笑死卤恳,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的寒矿。 我是一名探鬼主播突琳,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼符相!你這毒婦竟也來了拆融?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎镜豹,沒想到半個月后傲须,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡趟脂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年泰讽,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片散怖。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡菇绵,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出镇眷,到底是詐尸還是另有隱情咬最,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布欠动,位于F島的核電站永乌,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏具伍。R本人自食惡果不足惜翅雏,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望人芽。 院中可真熱鬧望几,春花似錦、人聲如沸萤厅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽惕味。三九已至楼誓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間名挥,已是汗流浹背疟羹。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留禀倔,地道東北人榄融。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像蹋艺,于是被迫代替她去往敵國和親剃袍。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容