OpenGL 圖形庫的使用(三十八)—— 高級光照之HDR

版本記錄

版本號 時間
V1.0 2018.01.19

前言

OpenGL 圖形庫項目中一直也沒用過,最近也想學著使用這個圖形庫,感覺還是很有意思,也就自然想著好好的總結一下,希望對大家能有所幫助。下面內容來自歡迎來到OpenGL的世界
1. OpenGL 圖形庫使用(一) —— 概念基礎
2. OpenGL 圖形庫使用(二) —— 渲染模式、對象、擴展和狀態機
3. OpenGL 圖形庫使用(三) —— 著色器、數據類型與輸入輸出
4. OpenGL 圖形庫使用(四) —— Uniform及更多屬性
5. OpenGL 圖形庫使用(五) —— 紋理
6. OpenGL 圖形庫使用(六) —— 變換
7. OpenGL 圖形庫的使用(七)—— 坐標系統之五種不同的坐標系統(一)
8. OpenGL 圖形庫的使用(八)—— 坐標系統之3D效果(二)
9. OpenGL 圖形庫的使用(九)—— 攝像機(一)
10. OpenGL 圖形庫的使用(十)—— 攝像機(二)
11. OpenGL 圖形庫的使用(十一)—— 光照之顏色
12. OpenGL 圖形庫的使用(十二)—— 光照之基礎光照
13. OpenGL 圖形庫的使用(十三)—— 光照之材質
14. OpenGL 圖形庫的使用(十四)—— 光照之光照貼圖
15. OpenGL 圖形庫的使用(十五)—— 光照之投光物
16. OpenGL 圖形庫的使用(十六)—— 光照之多光源
17. OpenGL 圖形庫的使用(十七)—— 光照之復習總結
18. OpenGL 圖形庫的使用(十八)—— 模型加載之Assimp
19. OpenGL 圖形庫的使用(十九)—— 模型加載之網格
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之高級數據Advanced Data
28. OpenGL 圖形庫的使用(二十八)—— 高級OpenGL之高級GLSL Advanced GLSL
29. OpenGL 圖形庫的使用(二十九)—— 高級OpenGL之幾何著色器Geometry Shader
30. OpenGL 圖形庫的使用(三十)—— 高級OpenGL之實例化Instancing
31. OpenGL 圖形庫的使用(三十一)—— 高級OpenGL之抗鋸齒Anti Aliasing
32. OpenGL 圖形庫的使用(三十二)—— 高級光照之高級光照Advanced Lighting
33. OpenGL 圖形庫的使用(三十三)—— 高級光照之Gamma校正Gamma Correction
34. OpenGL 圖形庫的使用(三十四)—— 高級光照之陰影 - 陰影映射Shadow Mapping
35. OpenGL 圖形庫的使用(三十五)—— 高級光照之陰影 - 點陰影Point Shadows
36. OpenGL 圖形庫的使用(三十六)—— 高級光照之法線貼圖Normal Mapping
37. OpenGL 圖形庫的使用(三十七)—— 高級光照之視差貼圖Parallax Mapping

HDR

一般來說,當存儲在幀緩沖(Framebuffer)中時,亮度和顏色的值是默認被限制在0.0到1.0之間的。這個看起來無辜的語句使我們一直將亮度與顏色的值設置在這個范圍內,嘗試著與場景契合。這樣是能夠運行的,也能給出還不錯的效果。但是如果我們遇上了一個特定的區域,其中有多個亮光源使這些數值總和超過了1.0,又會發生什么呢?答案是這些片段中超過1.0的亮度或者顏色值會被約束在1.0,從而導致場景混成一片,難以分辨:

這是由于大量片段的顏色值都非常接近1.0,在很大一個區域內每一個亮的片段都有相同的白色。這損失了很多的細節,使場景看起來非常假。

解決這個問題的一個方案是減小光源的強度從而保證場景內沒有一個片段亮于1.0。然而這并不是一個好的方案,因為你需要使用不切實際的光照參數。一個更好的方案是讓顏色暫時超過1.0,然后將其轉換至0.0到1.0的區間內,從而防止損失細節。

顯示器被限制為只能顯示值為0.0到1.0間的顏色,但是在光照方程中卻沒有這個限制。通過使片段的顏色超過1.0,我們有了一個更大的顏色范圍,這也被稱作HDR(High Dynamic Range, 高動態范圍)。有了HDR,亮的東西可以變得非常亮,暗的東西可以變得非常暗,而且充滿細節。

HDR原本只是被運用在攝影上,攝影師對同一個場景采取不同曝光拍多張照片,捕捉大范圍的色彩值。這些圖片被合成為HDR圖片,從而綜合不同的曝光等級使得大范圍的細節可見。看下面這個例子,左邊這張圖片在被光照亮的區域充滿細節,但是在黑暗的區域就什么都看不見了;但是右邊這張圖的高曝光卻可以讓之前看不出來的黑暗區域顯現出來。

這與我們眼睛工作的原理非常相似,也是HDR渲染的基礎。當光線很弱的啥時候,人眼會自動調整從而使過暗和過亮的部分變得更清晰,就像人眼有一個能自動根據場景亮度調整的自動曝光滑塊。

HDR渲染和其很相似,我們允許用更大范圍的顏色值渲染從而獲取大范圍的黑暗與明亮的場景細節,最后將所有HDR值轉換成在[0.0, 1.0]范圍的LDR(Low Dynamic Range,低動態范圍)。轉換HDR值到LDR值得過程叫做色調映射(Tone Mapping),現在現存有很多的色調映射算法,這些算法致力于在轉換過程中保留盡可能多的HDR細節。這些色調映射算法經常會包含一個選擇性傾向黑暗或者明亮區域的參數。

在實時渲染中,HDR不僅允許我們超過LDR的范圍[0.0, 1.0]與保留更多的細節,同時還讓我們能夠根據光源的真實強度指定它的強度。比如太陽有比閃光燈之類的東西更高的強度,那么我們為什么不這樣子設置呢?(比如說設置一個10.0的漫亮度) 這允許我們用更現實的光照參數恰當地配置一個場景的光照,而這在LDR渲染中是不能實現的,因為他們會被上限約束在1.0。

因為顯示器只能顯示在0.0到1.0范圍之內的顏色,我們肯定要做一些轉換從而使得當前的HDR顏色值符合顯示器的范圍。簡單地取平均值重新轉換這些顏色值并不能很好的解決這個問題,因為明亮的地方會顯得更加顯著。我們能做的是用一個不同的方程與/或曲線來轉換這些HDR值到LDR值,從而給我們對于場景的亮度完全掌控,這就是之前說的色調變換,也是HDR渲染的最終步驟。


浮點幀緩沖

在實現HDR渲染之前,我們首先需要一些防止顏色值在每一個片段著色器運行后被限制約束的方法。當幀緩沖使用了一個標準化的定點格式(像GL_RGB)為其顏色緩沖的內部格式,OpenGL會在將這些值存入幀緩沖前自動將其約束到0.0到1.0之間。這一操作對大部分幀緩沖格式都是成立的,除了專門用來存放被拓展范圍值的浮點格式。

當一個幀緩沖的顏色緩沖的內部格式被設定成了GL_RGB16F, GL_RGBA16F, GL_RGB32F或者GL_RGBA32F時,這些幀緩沖被叫做浮點幀緩沖(Floating Point Framebuffer),浮點幀緩沖可以存儲超過0.0到1.0范圍的浮點值,所以非常適合HDR渲染。

想要創建一個浮點幀緩沖,我們只需要改變顏色緩沖的內部格式參數就行了(注意GL_FLOAT參數):

glBindTexture(GL_TEXTURE_2D, colorBuffer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_FLOAT, NULL);

默認的幀緩沖默認一個顏色分量只占用8位(bits)。當使用一個使用32位每顏色分量的浮點幀緩沖時(使用GL_RGB32F 或者GL_RGBA32F),我們需要四倍的內存來存儲這些顏色。所以除非你需要一個非常高的精確度,32位不是必須的,使用GLRGB16F就足夠了。

有了一個帶有浮點顏色緩沖的幀緩沖,我們可以放心渲染場景到這個幀緩沖中。在這個教程的例子當中,我們先渲染一個光照的場景到浮點幀緩沖中,之后再在一個鋪屏四邊形(Screen-filling Quad)上應用這個幀緩沖的顏色緩沖,代碼會是這樣子:

glBindFramebuffer(GL_FRAMEBUFFER, hdrFBO);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  
    // [...] 渲染(光照的)場景
glBindFramebuffer(GL_FRAMEBUFFER, 0);

// 現在使用一個不同的著色器將HDR顏色緩沖渲染至2D鋪屏四邊形上
hdrShader.Use();
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, hdrColorBufferTexture);
RenderQuad();

這里場景的顏色值存在一個可以包含任意顏色值的浮點顏色緩沖中,值可能是超過1.0的。這個簡單的演示中,場景被創建為一個被拉伸的立方體通道和四個點光源,其中一個非常亮的在隧道的盡頭:

std::vector<glm::vec3> lightColors;
lightColors.push_back(glm::vec3(200.0f, 200.0f, 200.0f));
lightColors.push_back(glm::vec3(0.1f, 0.0f, 0.0f));
lightColors.push_back(glm::vec3(0.0f, 0.0f, 0.2f));
lightColors.push_back(glm::vec3(0.0f, 0.1f, 0.0f));  

渲染至浮點幀緩沖和渲染至一個普通的幀緩沖是一樣的。新的東西就是這個的hdrShader的片段著色器,用來渲染最終擁有浮點顏色緩沖紋理的2D四邊形。我們來定義一個簡單的直通片段著色器(Pass-through Fragment Shader)

#version 330 core
out vec4 color;
in vec2 TexCoords;

uniform sampler2D hdrBuffer;

void main()
{             
    vec3 hdrColor = texture(hdrBuffer, TexCoords).rgb;
    color = vec4(hdrColor, 1.0);
}  

這里我們直接采樣了浮點顏色緩沖并將其作為片段著色器的輸出。然而,這個2D四邊形的輸出是被直接渲染到默認的幀緩沖中,導致所有片段著色器的輸出值被約束在0.0到1.0間,盡管我們已經有了一些存在浮點顏色紋理的值超過了1.0。

很明顯,在隧道盡頭的強光的值被約束在1.0,因為一大塊區域都是白色的,過程中超過1.0的地方損失了所有細節。因為我們直接轉換HDR值到LDR值,這就像我們根本就沒有應用HDR一樣。為了修復這個問題我們需要做的是無損轉化所有浮點顏色值回0.0-1.0范圍中。我們需要應用到色調映射。


色調映射

色調映射(Tone Mapping)是一個損失很小的轉換浮點顏色值至我們所需的LDR[0.0, 1.0]范圍內的過程,通常會伴有特定的風格的色平衡(Stylistic Color Balance)。

最簡單的色調映射算法是Reinhard色調映射,它涉及到分散整個HDR顏色值到LDR顏色值上,所有的值都有對應。Reinhard色調映射算法平均得將所有亮度值分散到LDR上。我們將Reinhard色調映射應用到之前的片段著色器上,并且為了更好的測量加上一個Gamma校正過濾(包括SRGB紋理的使用):

void main()
{             
    const float gamma = 2.2;
    vec3 hdrColor = texture(hdrBuffer, TexCoords).rgb;

    // Reinhard色調映射
    vec3 mapped = hdrColor / (hdrColor + vec3(1.0));
    // Gamma校正
    mapped = pow(mapped, vec3(1.0 / gamma));

    color = vec4(mapped, 1.0);
}   

有了Reinhard色調映射的應用,我們不再會在場景明亮的地方損失細節。當然,這個算法是傾向明亮的區域的,暗的區域會不那么精細也不那么有區分度。

現在你可以看到在隧道的盡頭木頭紋理變得可見了。用了這個非常簡單地色調映射算法,我們可以合適的看到存在浮點幀緩沖中整個范圍的HDR值,給我們對于無損場景光照精確的控制。

另一個有趣的色調映射應用是曝光(Exposure)參數的使用。你可能還記得之前我們在介紹里講到的,HDR圖片包含在不同曝光等級的細節。如果我們有一個場景要展現日夜交替,我們當然會在白天使用低曝光,在夜間使用高曝光,就像人眼調節方式一樣。有了這個曝光參數,我們可以去設置可以同時在白天和夜晚不同光照條件工作的光照參數,我們只需要調整曝光參數就行了。

一個簡單的曝光色調映射算法會像這樣:

uniform float exposure;

void main()
{             
    const float gamma = 2.2;
    vec3 hdrColor = texture(hdrBuffer, TexCoords).rgb;

    // 曝光色調映射
    vec3 mapped = vec3(1.0) - exp(-hdrColor * exposure);
    // Gamma校正 
    mapped = pow(mapped, vec3(1.0 / gamma));

    color = vec4(mapped, 1.0);
}  

在這里我們將exposure定義為默認為1.0的uniform,從而允許我們更加精確設定我們是要注重黑暗還是明亮的區域的HDR顏色值。舉例來說,高曝光值會使隧道的黑暗部分顯示更多的細節,然而低曝光值會顯著減少黑暗區域的細節,但允許我們看到更多明亮區域的細節。下面這組圖片展示了在不同曝光值下的通道:

這個圖片清晰地展示了HDR渲染的優點。通過改變曝光等級,我們可以看見場景的很多細節,而這些細節可能在LDR渲染中都被丟失了。比如說隧道盡頭,在正常曝光下木頭結構隱約可見,但用低曝光木頭的花紋就可以清晰看見了。對于近處的木頭花紋來說,在高曝光下會能更好的看見。

你可以在這里找到這個演示的源碼和HDR的頂點片段著色器。

// 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);
void RenderScene(Shader &shader);
void RenderCube();
void RenderQuad();

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

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

// Options
GLboolean hdr = true; // Change with 'Space'
GLfloat exposure = 1.0f; // Change with Q and E

// Global variables
GLuint woodTexture;

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

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

    // Light sources
    // - Positions
    std::vector<glm::vec3> lightPositions;
    lightPositions.push_back(glm::vec3(0.0f, 0.0f, 49.5f)); // back light
    lightPositions.push_back(glm::vec3(-1.4f, -1.9f, 9.0f));
    lightPositions.push_back(glm::vec3(0.0f, -1.8f, 4.0f));
    lightPositions.push_back(glm::vec3(0.8f, -1.7f, 6.0f));
    // - Colors
    std::vector<glm::vec3> lightColors;
    lightColors.push_back(glm::vec3(200.0f, 200.0f, 200.0f));
    lightColors.push_back(glm::vec3(0.1f, 0.0f, 0.0f));
    lightColors.push_back(glm::vec3(0.0f, 0.0f, 0.2f));
    lightColors.push_back(glm::vec3(0.0f, 0.1f, 0.0f));

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

    // Set up floating point framebuffer to render scene to
    GLuint hdrFBO;
    glGenFramebuffers(1, &hdrFBO);
    // - Create floating point color buffer
    GLuint colorBuffer;
    glGenTextures(1, &colorBuffer);
    glBindTexture(GL_TEXTURE_2D, colorBuffer);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    // - Create depth buffer (renderbuffer)
    GLuint rboDepth;
    glGenRenderbuffers(1, &rboDepth);
    glBindRenderbuffer(GL_RENDERBUFFER, rboDepth);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, SCR_WIDTH, SCR_HEIGHT);
    // - Attach buffers
    glBindFramebuffer(GL_FRAMEBUFFER, hdrFBO);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorBuffer, 0);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rboDepth);
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
        std::cout << "Framebuffer not complete!" << std::endl;
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    glClearColor(0.1f, 0.1f, 0.1f, 1.0f);

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

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

        // 1. Render scene into floating point framebuffer
        glBindFramebuffer(GL_FRAMEBUFFER, hdrFBO);
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
            glm::mat4 projection = glm::perspective(camera.Zoom, (GLfloat)SCR_WIDTH / (GLfloat)SCR_HEIGHT, 0.1f, 100.0f);
            glm::mat4 view       = camera.GetViewMatrix();
            glm::mat4 model;
            shader.Use();
            glUniformMatrix4fv(glGetUniformLocation(shader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
            glUniformMatrix4fv(glGetUniformLocation(shader.Program, "view"),       1, GL_FALSE, glm::value_ptr(view));
            glActiveTexture(GL_TEXTURE0);
            glBindTexture(GL_TEXTURE_2D, woodTexture);
            // - set lighting uniforms
            for (GLuint i = 0; i < lightPositions.size(); i++)
            {
                glUniform3fv(glGetUniformLocation(shader.Program, ("lights[" + std::to_string(i) + "].Position").c_str()), 1, &lightPositions[i][0]);
                glUniform3fv(glGetUniformLocation(shader.Program, ("lights[" + std::to_string(i) + "].Color").c_str()), 1, &lightColors[i][0]);
            }
            glUniform3fv(glGetUniformLocation(shader.Program, "viewPos"), 1, &camera.Position[0]);
            // - render tunnel
            model = glm::mat4();
            model = glm::translate(model, glm::vec3(0.0f, 0.0f, 25.0));
            model = glm::scale(model, glm::vec3(5.0f, 5.0f, 55.0f));
            glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
            glUniform1i(glGetUniformLocation(shader.Program, "inverse_normals"), GL_TRUE);
            RenderCube();
        glBindFramebuffer(GL_FRAMEBUFFER, 0);      

        // 2. Now render floating point color buffer to 2D quad and tonemap HDR colors to default framebuffer's (clamped) color range
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);        
        hdrShader.Use();
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, colorBuffer);
        glUniform1i(glGetUniformLocation(hdrShader.Program, "hdr"), hdr);
        glUniform1f(glGetUniformLocation(hdrShader.Program, "exposure"), exposure);
        RenderQuad();       

        std::cout << "exposure: " << exposure << std::endl;

        // Swap the buffers
        glfwSwapBuffers(window);
    }

    glfwTerminate();
    return 0;
}


// RenderQuad() Renders a 1x1 quad in NDC, best used for framebuffer color targets
// and post-processing effects.
GLuint quadVAO = 0;
GLuint quadVBO;
void RenderQuad()
{
    if (quadVAO == 0)
    {
        GLfloat quadVertices[] = {
            // Positions        // Texture Coords
            -1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
            -1.0f, -1.0f, 0.0f, 0.0f, 0.0f,
            1.0f, 1.0f, 0.0f, 1.0f, 1.0f,
            1.0f, -1.0f, 0.0f, 1.0f, 0.0f,
        };
        // Setup plane VAO
        glGenVertexArrays(1, &quadVAO);
        glGenBuffers(1, &quadVBO);
        glBindVertexArray(quadVAO);
        glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
        glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, GL_STATIC_DRAW);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);
        glEnableVertexAttribArray(1);
        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
    }
    glBindVertexArray(quadVAO);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    glBindVertexArray(0);
}

// RenderCube() Renders a 1x1 3D cube in NDC.
GLuint cubeVAO = 0;
GLuint cubeVBO = 0;
void RenderCube()
{
    // Initialize (if necessary)
    if (cubeVAO == 0)
    {
        GLfloat vertices[] = {
            // Back face
            -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, // Bottom-left
            0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, // top-right
            0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, // bottom-right         
            0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,  // top-right
            -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,  // bottom-left
            -0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,// top-left
            // Front face
            -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom-left
            0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,  // bottom-right
            0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,  // top-right
            0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // top-right
            -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,  // top-left
            -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,  // bottom-left
            // Left face
            -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-right
            -0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top-left
            -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,  // bottom-left
            -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-left
            -0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f,  // bottom-right
            -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-right
            // Right face
            0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-left
            0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-right
            0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top-right         
            0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,  // bottom-right
            0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,  // top-left
            0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // bottom-left     
            // Bottom face
            -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, // top-right
            0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, // top-left
            0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,// bottom-left
            0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, // bottom-left
            -0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, // bottom-right
            -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, // top-right
            // Top face
            -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,// top-left
            0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom-right
            0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, // top-right     
            0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom-right
            -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,// top-left
            -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f // bottom-left        
        };
        glGenVertexArrays(1, &cubeVAO);
        glGenBuffers(1, &cubeVBO);
        // Fill buffer
        glBindBuffer(GL_ARRAY_BUFFER, cubeVBO);
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
        // Link vertex attributes
        glBindVertexArray(cubeVAO);
        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)));
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glBindVertexArray(0);
    }
    // Render Cube
    glBindVertexArray(cubeVAO);
    glDrawArrays(GL_TRIANGLES, 0, 36);
    glBindVertexArray(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_SRGB, 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_SPACE] && !keysPressed[GLFW_KEY_SPACE])
    {
        hdr = !hdr;
        keysPressed[GLFW_KEY_SPACE] = true;
    }

    // Change parallax height scale
    if (keys[GLFW_KEY_Q])
        exposure -= 0.5 * deltaTime;
    else if (keys[GLFW_KEY_E])
        exposure += 0.5 * deltaTime;
}

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

1. HDR拓展

在這里展示的兩個色調映射算法僅僅是大量(更先進)的色調映射算法中的一小部分,這些算法各有長短.一些色調映射算法傾向于特定的某種顏色/強度,也有一些算法同時顯示低于高曝光顏色從而能夠顯示更加多彩和精細的圖像。也有一些技巧被稱作自動曝光調整(Automatic Exposure Adjustment)或者叫人眼適應(Eye Adaptation)技術,它能夠檢測前一幀場景的亮度并且緩慢調整曝光參數模仿人眼使得場景在黑暗區域逐漸變亮或者在明亮區域逐漸變暗,

HDR渲染的真正優點在龐大和復雜的場景中應用復雜光照算法會被顯示出來,但是出于教學目的創建這樣復雜的演示場景是很困難的,這個教程用的場景是很小的,而且缺乏細節。但是如此簡單的演示也是能夠顯示出HDR渲染的一些優點:在明亮和黑暗區域無細節損失,因為它們可以由色調映射重新獲取;多個光照的疊加不會導致亮度被約束的區域;光照可以被設定為他們原來的亮度而不是被LDR值限定。而且,HDR渲染也使一些有趣的效果更加可行和真實; 其中一個效果叫做泛光(Bloom),我們將在下一節討論他。


附加資源

后記

本篇已結束,下一篇關于泛光。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容