OpenGL 圖形庫的使用(二十三)—— 高級OpenGL之混合Blending

版本記錄

版本號 時間
V1.0 2018.01.11

前言

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 圖形庫的使用(七)—— 坐標系統(tǒng)之五種不同的坐標系統(tǒng)(一)
8. OpenGL 圖形庫的使用(八)—— 坐標系統(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

混合

OpenGL中,混合(Blending)通常是實現(xiàn)物體透明度(Transparency)的一種技術(shù)。透明就是說一個物體(或者其中的一部分)不是純色(Solid Color)的,它的顏色是物體本身的顏色和它背后其它物體的顏色的不同強度結(jié)合。一個有色玻璃窗是一個透明的物體,玻璃有它自己的顏色,但它最終的顏色還包含了玻璃之后所有物體的顏色。這也是混合這一名字的出處,我們混合(Blend)(不同物體的)多種顏色為一種顏色。所以透明度能讓我們看穿物體。

透明的物體可以是完全透明的(讓所有的顏色穿過),或者是半透明的(它讓顏色通過,同時也會顯示自身的顏色)。一個物體的透明度是通過它顏色的aplha值來決定的。Alpha顏色值是顏色向量的第四個分量,你可能已經(jīng)看到過它很多遍了。在這個教程之前我們都將這個第四個分量設(shè)置為1.0,讓這個物體的透明度為0.0,而當(dāng)alpha值為0.0時物體將會是完全透明的。當(dāng)alpha值為0.5時,物體的顏色有50%是來自物體自身的顏色,50%來自背后物體的顏色。

我們目前一直使用的紋理有三個顏色分量:紅、綠、藍。但一些材質(zhì)會有一個內(nèi)嵌的alpha通道,對每個紋素(Texel)都包含了一個alpha值。這個alpha值精確地告訴我們紋理各個部分的透明度。比如說,下面這個窗戶紋理中的玻璃部分的alpha值為0.25(它在一般情況下是完全的紅色,但由于它有75%的透明度,能讓很大一部分的網(wǎng)站背景顏色穿過,讓它看起來不那么紅了),邊框的alpha值是0.0。

我們很快就會將這個窗戶紋理添加到場景中,但是首先我們需要討論一個更簡單的技術(shù),來實現(xiàn)只有完全透明和完全不透明的紋理的透明度。


丟棄片段

有些圖片并不需要半透明,只需要根據(jù)紋理顏色值,顯示一部分,或者不顯示一部分,沒有中間情況。比如說草,如果想不太費勁地創(chuàng)建草這種東西,你需要將一個草的紋理貼在一個2D四邊形(Quad)上,然后將這個四邊形放到場景中。然而,草的形狀和2D四邊形的形狀并不完全相同,所以你只想顯示草紋理的某些部分,而忽略剩下的部分。

下面這個紋理正是這樣的,它要么是完全不透明的(alpha值為1.0),要么是完全透明的(alpha值為0.0),沒有中間情況。你可以看到,只要不是草的部分,這個圖片顯示的都是網(wǎng)站的背景顏色而不是它本身的顏色。

所以當(dāng)添加像草這樣的植被到場景中時,我們不希望看到草的方形圖像,而是只顯示草的部分,并能看透圖像其余的部分。我們想要丟棄(Discard)顯示紋理中透明部分的片段,不將這些片段存儲到顏色緩沖中。在此之前,我們還要學(xué)習(xí)如何加載一個透明的紋理。

要想加載有alpha值的紋理,我們并不需要改很多東西,stb_image在紋理有alpha通道的時候會自動加載,但我們?nèi)砸诩y理生成過程中告訴OpenGL,我們的紋理現(xiàn)在使用alpha通道了:

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);

同樣,保證你在片段著色器中獲取了紋理的全部4個顏色分量,而不僅僅是RGB分量:

void main()
{
    // FragColor = vec4(vec3(texture(texture1, TexCoords)), 1.0);
    FragColor = texture(texture1, TexCoords);
}

既然我們已經(jīng)知道該如何加載透明的紋理了,是時候?qū)⑺鼛雽崙?zhàn)了,我們將會在深度測試小節(jié)的場景中加入幾棵草。

我們會創(chuàng)建一個vector,向里面添加幾個glm::vec3變量來代表草的位置:

vector<glm::vec3> vegetation;
vegetation.push_back(glm::vec3(-1.5f,  0.0f, -0.48f));
vegetation.push_back(glm::vec3( 1.5f,  0.0f,  0.51f));
vegetation.push_back(glm::vec3( 0.0f,  0.0f,  0.7f));
vegetation.push_back(glm::vec3(-0.3f,  0.0f, -2.3f));
vegetation.push_back(glm::vec3( 0.5f,  0.0f, -0.6f));

每個草都被渲染到了一個四邊形上,貼上草的紋理。這并不能完美地表示3D的草,但這比加載復(fù)雜的模型要快多了。使用一些小技巧,比如在同一個位置加入一些旋轉(zhuǎn)后的草四邊形,你仍然能獲得比較好的結(jié)果的。

因為草的紋理是添加到四邊形對象上的,我們還需要創(chuàng)建另外一個VAO,填充VBO,設(shè)置正確的頂點屬性指針。接下來,在繪制完地板和兩個立方體后,我們將會繪制草:

glBindVertexArray(vegetationVAO);
glBindTexture(GL_TEXTURE_2D, grassTexture);  
for(unsigned int i = 0; i < vegetation.size(); i++) 
{
    model = glm::mat4();
    model = glm::translate(model, vegetation[i]);               
    shader.setMat4("model", model);
    glDrawArrays(GL_TRIANGLES, 0, 6);
}

運行程序你將看到:

出現(xiàn)這種情況是因為OpenGL默認是不知道怎么處理alpha值的,更不知道什么時候應(yīng)該丟棄片段。我們需要自己手動來弄。幸運的是,有了著色器,這還是非常容易的。GLSL給了我們discard命令,一旦被調(diào)用,它就會保證片段不會被進一步處理,所以就不會進入顏色緩沖。有了這個指令,我們就能夠在片段著色器中檢測一個片段的alpha值是否低于某個閾值,如果是的話,則丟棄這個片段,就好像它不存在一樣:

#version 330 core
out vec4 FragColor;

in vec2 TexCoords;

uniform sampler2D texture1;

void main()
{             
    vec4 texColor = texture(texture1, TexCoords);
    if(texColor.a < 0.1)
        discard;
    FragColor = texColor;
}

這里,我們檢測被采樣的紋理顏色的alpha值是否低于0.1的閾值,如果是的話,則丟棄這個片段。片段著色器保證了它只會渲染不是(幾乎)完全透明的片段?,F(xiàn)在它看起來就正常了:

注意,當(dāng)采樣紋理的邊緣的時候,OpenGL會對邊緣的值和紋理下一個重復(fù)的值進行插值(因為我們將它的環(huán)繞方式設(shè)置為了GL_REPEAT。這通常是沒問題的,但是由于我們使用了透明值,紋理圖像的頂部將會與底部邊緣的純色值進行插值。這樣的結(jié)果是一個半透明的有色邊框,你可能會看見它環(huán)繞著你的紋理四邊形。要想避免這個,每當(dāng)你alpha紋理的時候,請將紋理的環(huán)繞方式設(shè)置為:

GL_CLAMP_TO_EDGE:
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

你可以在這里找到源碼。

#include <glad/glad.h>  
#include <GLFW/glfw3.h>  
#include <stb_image.h>  
#include <glm/glm.hpp> 
#include <glm/gtc/matrix_transform.hpp>  
#include <glm/gtc/type_ptr.hpp>  
#include <learnopengl/shader_m.h>
#include <learnopengl/camera.h>  
#include <learnopengl/model.h>
#include <iostream>

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void processInput(GLFWwindow *window);
unsigned int loadTexture(const char *path);

// settings
const unsigned int SCR_WIDTH = 1280;
const unsigned int SCR_HEIGHT = 720;

// camera
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
float lastX = (float)SCR_WIDTH / 2.0;
float lastY = (float)SCR_HEIGHT / 2.0;
bool firstMouse = true;

// timing
float deltaTime = 0.0f;
float lastFrame = 0.0f;

int main()
{
    // glfw: initialize and configure
    // ------------------------------
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

#ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // uncomment this statement to fix compilation on OS X
#endif

    // glfw window creation
    // --------------------
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
    glfwSetCursorPosCallback(window, mouse_callback);
    glfwSetScrollCallback(window, scroll_callback);

    // tell GLFW to capture our mouse
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

    // glad: load all OpenGL function pointers
    // ---------------------------------------
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }
// configure global opengl state
    // -----------------------------
    glEnable(GL_DEPTH_TEST);
// build and compile shaders  
// ------------------------- 
Shader shader("3.1.blending.vs", "3.1.blending.fs");

// set up vertex data (and buffer(s)) and configure vertex attributes
    // ------------------------------------------------------------------
    float cubeVertices[] = {
        // positions          // texture Coords
        -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,
         0.5f, -0.5f, -0.5f,  1.0f, 0.0f,
         0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
         0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,

        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
         0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
         0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
         0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
        -0.5f,  0.5f,  0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,

        -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        -0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
        -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

         0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
         0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
         0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
         0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
         0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
         0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
         0.5f, -0.5f, -0.5f,  1.0f, 1.0f,
         0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
         0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,

        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
         0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
         0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
         0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        -0.5f,  0.5f,  0.5f,  0.0f, 0.0f,
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f
    };
    float planeVertices[] = {
        // positions          // texture Coords 
         5.0f, -0.5f,  5.0f,  2.0f, 0.0f,
        -5.0f, -0.5f,  5.0f,  0.0f, 0.0f,
        -5.0f, -0.5f, -5.0f,  0.0f, 2.0f,

         5.0f, -0.5f,  5.0f,  2.0f, 0.0f,
        -5.0f, -0.5f, -5.0f,  0.0f, 2.0f,
         5.0f, -0.5f, -5.0f,  2.0f, 2.0f
    };
    float transparentVertices[] = {
        // positions         // texture Coords (swapped y coordinates because texture is flipped upside down)
        0.0f,  0.5f,  0.0f,  0.0f,  0.0f,
        0.0f, -0.5f,  0.0f,  0.0f,  1.0f,
        1.0f, -0.5f,  0.0f,  1.0f,  1.0f,

        0.0f,  0.5f,  0.0f,  0.0f,  0.0f,
        1.0f, -0.5f,  0.0f,  1.0f,  1.0f,
        1.0f,  0.5f,  0.0f,  1.0f,  0.0f
    };
// cube VAO
    unsigned int cubeVAO, cubeVBO;
    glGenVertexArrays(1, &cubeVAO);
    glGenBuffers(1, &cubeVBO);
    glBindVertexArray(cubeVAO);
    glBindBuffer(GL_ARRAY_BUFFER, cubeVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVertices), &cubeVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
    // plane VAO
    unsigned int 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, 5 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
    // transparent VAO
unsigned int transparentVAO, transparentVBO;
    glGenVertexArrays(1, &transparentVAO);
    glGenBuffers(1, &transparentVBO);
    glBindVertexArray(transparentVAO);
    glBindBuffer(GL_ARRAY_BUFFER, transparentVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(transparentVertices), transparentVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
    glBindVertexArray(0);

    // load textures
    // -------------
    unsigned int cubeTexture = loadTexture(FileSystem::getPath("resources/textures/marble.jpg").c_str());
    unsigned int floorTexture = loadTexture(FileSystem::getPath("resources/textures/metal.png").c_str());
    unsigned int transparentTexture = loadTexture(FileSystem::getPath("resources/textures/grass.png").c_str());
// transparent vegetation locations
    // --------------------------------
    vector<glm::vec3> vegetation 
    {
        glm::vec3(-1.5f, 0.0f, -0.48f),
        glm::vec3( 1.5f, 0.0f, 0.51f),
        glm::vec3( 0.0f, 0.0f, 0.7f),
        glm::vec3(-0.3f, 0.0f, -2.3f),
        glm::vec3 (0.5f, 0.0f, -0.6f)
    };

    // shader configuration
    // --------------------
    shader.use();
    shader.setInt("texture1", 0);

    // render loop
    // -----------
    while (!glfwWindowShouldClose(window))
    {
        // per-frame time logic
        // --------------------
        float currentFrame = glfwGetTime();
        deltaTime = currentFrame - lastFrame;
        lastFrame = currentFrame;

        // input
        // -----
        processInput(window);

        // render
        // ------
        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // draw objects
        shader.use();
        glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
        glm::mat4 view = camera.GetViewMatrix();
        glm::mat4 model;
        shader.setMat4("projection", projection);
        shader.setMat4("view", view);
        // cubes
        glBindVertexArray(cubeVAO);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, cubeTexture);
        model = glm::translate(model, glm::vec3(-1.0f, 0.0f, -1.0f));
        shader.setMat4("model", model);
        glDrawArrays(GL_TRIANGLES, 0, 36);
        model = glm::mat4();
        model = glm::translate(model, glm::vec3(2.0f, 0.0f, 0.0f));
        shader.setMat4("model", model);
        glDrawArrays(GL_TRIANGLES, 0, 36);
        // floor
        glBindVertexArray(planeVAO);
        glBindTexture(GL_TEXTURE_2D, floorTexture);
        model = glm::mat4();
        shader.setMat4("model", model);
        glDrawArrays(GL_TRIANGLES, 0, 6);
        // vegetation
        glBindVertexArray(transparentVAO);
        glBindTexture(GL_TEXTURE_2D, transparentTexture);
        for (GLuint i = 0; i < vegetation.size(); i++)
        {
            model = glm::mat4();
            model = glm::translate(model, vegetation[i]);
            shader.setMat4("model", model);
            glDrawArrays(GL_TRIANGLES, 0, 6);
        }


        // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
        // -------------------------------------------------------------------------------
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // optional: de-allocate all resources once they've outlived their purpose:
    // ------------------------------------------------------------------------
    glDeleteVertexArrays(1, &cubeVAO);
    glDeleteVertexArrays(1, &planeVAO);
    glDeleteBuffers(1, &cubeVBO);
    glDeleteBuffers(1, &planeVBO);

    glfwTerminate();
    return 0;
}

// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow *window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);

    if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
        camera.ProcessKeyboard(FORWARD, deltaTime);
    if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
        camera.ProcessKeyboard(BACKWARD, deltaTime);
    if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
        camera.ProcessKeyboard(LEFT, deltaTime);
    if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
        camera.ProcessKeyboard(RIGHT, deltaTime);
}

// glfw: whenever the window size changed (by OS or user resize) this callback function executes
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    // make sure the viewport matches the new window dimensions; note that width and 
    // height will be significantly larger than specified on retina displays.
    glViewport(0, 0, width, height);
}

// glfw: whenever the mouse moves, this callback is called
// -------------------------------------------------------
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
    if (firstMouse)
    {
        lastX = xpos;
        lastY = ypos;
        firstMouse = false;
    }

    float xoffset = xpos - lastX;
    float yoffset = lastY - ypos; // reversed since y-coordinates go from bottom to top

    lastX = xpos;
    lastY = ypos;

    camera.ProcessMouseMovement(xoffset, yoffset);
}

// glfw: whenever the mouse scroll wheel scrolls, this callback is called
// ----------------------------------------------------------------------
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
    camera.ProcessMouseScroll(yoffset);
}

// utility function for loading a 2D texture from file
// ---------------------------------------------------
unsigned int loadTexture(char const * path)
{
    unsigned int textureID;
    glGenTextures(1, &textureID);

    int width, height, nrComponents;
    unsigned char *data = stbi_load(path, &width, &height, &nrComponents, 0);
    if (data)
    {
        GLenum format;
        if (nrComponents == 1)
            format = GL_RED;
        else if (nrComponents == 3)
            format = GL_RGB;
        else if (nrComponents == 4)
            format = GL_RGBA;

        glBindTexture(GL_TEXTURE_2D, textureID);
        glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
        glGenerateMipmap(GL_TEXTURE_2D);

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, format == GL_RGBA ? GL_CLAMP_TO_EDGE : GL_REPEAT); // for this tutorial: use GL_CLAMP_TO_EDGE to prevent semi-transparent borders. Due to interpolation it takes texels from next repeat 
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, format == GL_RGBA ? GL_CLAMP_TO_EDGE : GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        stbi_image_free(data);
    }
    else
    {
        std::cout << "Texture failed to load at path: " << path << std::endl;
        stbi_image_free(data);
    }

    return textureID;
}

混合

雖然直接丟棄片段很好,但它不能讓我們渲染半透明的圖像。我們要么渲染一個片段,要么完全丟棄它。要想渲染有多個透明度級別的圖像,我們需要啟用混合(Blending)。和OpenGL大多數(shù)的功能一樣,我們可以啟用GL_BLEND來啟用混合:

glEnable(GL_BLEND);

啟用了混合之后,我們需要告訴OpenGL它該如何混合。

OpenGL中的混合是通過下面這個方程來實現(xiàn)的:

片段著色器運行完成后,并且所有的測試都通過之后,這個混合方程(Blend Equation)才會應(yīng)用到片段顏色輸出與當(dāng)前顏色緩沖中的值(當(dāng)前片段之前儲存的之前片段的顏色)上。源顏色和目標顏色將會由OpenGL自動設(shè)定,但源因子和目標因子的值可以由我們來決定。我們先來看一個簡單的例子:

我們有兩個方形,我們希望將這個半透明的綠色方形繪制在紅色方形之上。紅色的方形將會是目標顏色(所以它應(yīng)該先在顏色緩沖中),我們將要在這個紅色方形之上繪制這個綠色方形。

問題來了:我們將因子值設(shè)置為什么?嘛,我們至少想讓綠色方形乘以它的alpha值,所以我們想要將Fsrc
設(shè)置為源顏色向量的alpha值,也就是0.6。接下來就應(yīng)該清楚了,目標方形的貢獻應(yīng)該為剩下的alpha值。如果綠色方形對最終顏色貢獻了60%,那么紅色方塊應(yīng)該對最終顏色貢獻了40%,即1.0 - 0.6。所以我們將Fdestination設(shè)置為1減去源顏色向量的alpha值。這個方程變成了:

結(jié)果就是重疊方形的片段包含了一個60%綠色,40%紅色的一種臟兮兮的顏色:

最終的顏色將會被儲存到顏色緩沖中,替代之前的顏色。

這樣子很不錯,但我們該如何讓OpenGL使用這樣的因子呢?正好有一個專門的函數(shù),叫做glBlendFunc

glBlendFunc(GLenum sfactor, GLenum dfactor)函數(shù)接受兩個參數(shù),來設(shè)置源和目標因子。OpenGL為我們定義了很多個選項,我們將在下面列出大部分最常用的選項。注意常數(shù)顏色向量Cˉconstant可以通過glBlendColor函數(shù)來另外設(shè)置。

為了獲得之前兩個方形的混合結(jié)果,我們需要使用源顏色向量的alpha作為源因子,使用1?alpha作為目標因子。這將會產(chǎn)生以下的glBlendFunc

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

也可以使用glBlendFuncSeparate為RGB和alpha通道分別設(shè)置不同的選項:

glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);

這個函數(shù)和我們之前設(shè)置的那樣設(shè)置了RGB分量,但這樣只能讓最終的alpha分量被源顏色向量的alpha值所影響到。

OpenGL甚至給了我們更多的靈活性,允許我們改變方程中源和目標部分的運算符。當(dāng)前源和目標是相加的,但如果愿意的話,我們也可以讓它們相減。glBlendEquation(GLenum mode)允許我們設(shè)置運算符,它提供了三個選項:

通常我們都可以省略調(diào)用glBlendEquation,因為GL_FUNC_ADD對大部分的操作來說都是我們希望的混合方程,但如果你真的想打破主流,其它的方程也可能符合你的要求。


渲染半透明紋理

既然我們已經(jīng)知道OpenGL是如何處理混合的了,是時候?qū)⑽覀兊闹R運用到實戰(zhàn)中了,我們將會在場景中添加幾個半透明的窗戶。我們將使用本節(jié)開始的那個場景,但是這次不再是渲染草的紋理了,我們現(xiàn)在將使用本節(jié)開始時的那個透明的窗戶紋理。

首先,在初始化時我們啟用混合,并設(shè)定相應(yīng)的混合函數(shù):

glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

由于啟用了混合,我們就不需要丟棄片段了,所以我們把片段著色器還原:

#version 330 core
out vec4 FragColor;

in vec2 TexCoords;

uniform sampler2D texture1;

void main()
{             
    FragColor = texture(texture1, TexCoords);
}

現(xiàn)在(每當(dāng)OpenGL渲染了一個片段時)它都會將當(dāng)前片段的顏色和當(dāng)前顏色緩沖中的片段顏色根據(jù)alpha值來進行混合。由于窗戶紋理的玻璃部分是半透明的,我們應(yīng)該能通窗戶中看到背后的場景了。

如果你仔細看的話,你可能會注意到有些不對勁。最前面窗戶的透明部分遮蔽了背后的窗戶?這為什么會發(fā)生呢?

發(fā)生這一現(xiàn)象的原因是,深度測試和混合一起使用的話會產(chǎn)生一些麻煩。當(dāng)寫入深度緩沖時,深度緩沖不會檢查片段是否是透明的,所以透明的部分會和其它值一樣寫入到深度緩沖中。結(jié)果就是窗戶的整個四邊形不論透明度都會進行深度測試。即使透明的部分應(yīng)該顯示背后的窗戶,深度測試仍然丟棄了它們。

所以我們不能隨意地決定如何渲染窗戶,讓深度緩沖解決所有的問題了。這也是混合變得有些麻煩的部分。要想保證窗戶中能夠顯示它們背后的窗戶,我們需要首先繪制背后的這部分窗戶。這也就是說在繪制的時候,我們必須先手動將窗戶按照最遠到最近來排序,再按照順序渲染。

注意,對于草這種全透明的物體,我們可以選擇丟棄透明的片段而不是混合它們,這樣就解決了這些頭疼的問題(沒有深度問題)。


不要打亂順序

要想讓混合在多個物體上工作,我們需要最先繪制最遠的物體,最后繪制最近的物體。普通不需要混合的物體仍然可以使用深度緩沖正常繪制,所以它們不需要排序。但我們?nèi)砸WC它們在繪制(排序的)透明物體之前已經(jīng)繪制完畢了。當(dāng)繪制一個有不透明和透明物體的場景的時候,大體的原則如下:

  • 先繪制所有不透明的物體。
  • 對所有透明的物體排序。
  • 按順序繪制所有透明的物體。

排序透明物體的一種方法是,從觀察者視角獲取物體的距離。這可以通過計算攝像機位置向量和物體的位置向量之間的距離所獲得。接下來我們把距離和它對應(yīng)的位置向量存儲到一個STL庫的map數(shù)據(jù)結(jié)構(gòu)中。map會自動根據(jù)鍵值(Key)對它的值排序,所以只要我們添加了所有的位置,并以它的距離作為鍵,它們就會自動根據(jù)距離值排序了。

std::map<float, glm::vec3> sorted;
for (unsigned int i = 0; i < windows.size(); i++)
{
    float distance = glm::length(camera.Position - windows[i]);
    sorted[distance] = windows[i];
}

結(jié)果就是一個排序后的容器對象,它根據(jù)distance鍵值從低到高儲存了每個窗戶的位置。

之后,這次在渲染的時候,我們將以逆序(從遠到近)從map中獲取值,之后以正確的順序繪制對應(yīng)的窗戶:

for(std::map<float,glm::vec3>::reverse_iterator it = sorted.rbegin(); it != sorted.rend(); ++it) 
{
    model = glm::mat4();
    model = glm::translate(model, it->second);              
    shader.setMat4("model", model);
    glDrawArrays(GL_TRIANGLES, 0, 6);
}

我們使用了map的一個反向迭代器(Reverse Iterator),反向遍歷其中的條目,并將每個窗戶四邊形位移到對應(yīng)的窗戶位置上。這是排序透明物體的一個比較簡單的實現(xiàn),它能夠修復(fù)之前的問題,現(xiàn)在場景看起來是這樣的:

你可以在這里找到帶有排序的完整源代碼。

#include <glad/glad.h> 
#include <GLFW/glfw3.h>  
#include <stb_image.h>  
#include <glm/glm.hpp>  
#include <glm/gtc/matrix_transform.hpp>  
#include <glm/gtc/type_ptr.hpp>  
#include <learnopengl/shader_m.h>  
#include <learnopengl/camera.h>  
#include <learnopengl/model.h>  
#include <iostream>

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void processInput(GLFWwindow *window);
unsigned int loadTexture(const char *path);

// settings
const unsigned int SCR_WIDTH = 1280;
const unsigned int SCR_HEIGHT = 720;

// camera
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
float lastX = (float)SCR_WIDTH / 2.0;
float lastY = (float)SCR_HEIGHT / 2.0;
bool firstMouse = true;

// timing
float deltaTime = 0.0f;
float lastFrame = 0.0f;

int main()
{
    // glfw: initialize and configure
    // ------------------------------
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

#ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // uncomment this statement to fix compilation on OS X
#endif

    // glfw window creation
    // --------------------
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
    glfwSetCursorPosCallback(window, mouse_callback);
    glfwSetScrollCallback(window, scroll_callback);

    // tell GLFW to capture our mouse
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

    // glad: load all OpenGL function pointers
    // ---------------------------------------
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }
// configure global opengl state
    // -----------------------------
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    // build and compile shaders
    // -------------------------
Shader shader("3.2.blending.vs", "3.2.blending.fs");
// set up vertex data (and buffer(s)) and configure vertex attributes
    // ------------------------------------------------------------------
    float cubeVertices[] = {
        // positions          // texture Coords
        -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,
         0.5f, -0.5f, -0.5f,  1.0f, 0.0f,
         0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
         0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,

        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
         0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
         0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
         0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
        -0.5f,  0.5f,  0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,

        -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        -0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
        -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

         0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
         0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
         0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
         0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
         0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
         0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
         0.5f, -0.5f, -0.5f,  1.0f, 1.0f,
         0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
         0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,

        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
         0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
         0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
         0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        -0.5f,  0.5f,  0.5f,  0.0f, 0.0f,
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f
    };
    float planeVertices[] = {
        // positions          // texture Coords 
         5.0f, -0.5f,  5.0f,  2.0f, 0.0f,
        -5.0f, -0.5f,  5.0f,  0.0f, 0.0f,
        -5.0f, -0.5f, -5.0f,  0.0f, 2.0f,

         5.0f, -0.5f,  5.0f,  2.0f, 0.0f,
        -5.0f, -0.5f, -5.0f,  0.0f, 2.0f,
         5.0f, -0.5f, -5.0f,  2.0f, 2.0f
    };
    float transparentVertices[] = {
        // positions         // texture Coords (swapped y coordinates because texture is flipped upside down)
        0.0f,  0.5f,  0.0f,  0.0f,  0.0f,
        0.0f, -0.5f,  0.0f,  0.0f,  1.0f,
        1.0f, -0.5f,  0.0f,  1.0f,  1.0f,

        0.0f,  0.5f,  0.0f,  0.0f,  0.0f,
        1.0f, -0.5f,  0.0f,  1.0f,  1.0f,
        1.0f,  0.5f,  0.0f,  1.0f,  0.0f
    };
// cube VAO
    unsigned int cubeVAO, cubeVBO;
    glGenVertexArrays(1, &cubeVAO);
    glGenBuffers(1, &cubeVBO);
    glBindVertexArray(cubeVAO);
    glBindBuffer(GL_ARRAY_BUFFER, cubeVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVertices), &cubeVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
    // plane VAO
    unsigned int 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, 5 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
    // transparent VAO
    unsigned int transparentVAO, transparentVBO;
    glGenVertexArrays(1, &transparentVAO);
    glGenBuffers(1, &transparentVBO);
    glBindVertexArray(transparentVAO);
    glBindBuffer(GL_ARRAY_BUFFER, transparentVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(transparentVertices), transparentVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
    glBindVertexArray(0);

    // load textures
    // -------------
    unsigned int cubeTexture = loadTexture(FileSystem::getPath("resources/textures/marble.jpg").c_str());
    unsigned int floorTexture = loadTexture(FileSystem::getPath("resources/textures/metal.png").c_str());
    unsigned int transparentTexture = loadTexture(FileSystem::getPath("resources/textures/window.png").c_str());

    // transparent window locations
    // --------------------------------
    vector<glm::vec3> windows
    {
        glm::vec3(-1.5f, 0.0f, -0.48f),
        glm::vec3( 1.5f, 0.0f, 0.51f),
        glm::vec3( 0.0f, 0.0f, 0.7f),
        glm::vec3(-0.3f, 0.0f, -2.3f),
        glm::vec3( 0.5f, 0.0f, -0.6f)
    };

    // shader configuration
    // --------------------
    shader.use();
    shader.setInt("texture1", 0);

    // render loop
    // -----------
    while (!glfwWindowShouldClose(window))
    {
        // per-frame time logic
        // --------------------
        float currentFrame = glfwGetTime();
        deltaTime = currentFrame - lastFrame;
        lastFrame = currentFrame;

        // input
        // -----
        processInput(window);

        // sort the transparent windows before rendering
        // ---------------------------------------------
        std::map<float, glm::vec3> sorted;
        for (unsigned int i = 0; i < windows.size(); i++)
        {
            float distance = glm::length(camera.Position - windows[i]);
            sorted[distance] = windows[i];
        }

        // render
        // ------
        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // draw objects
        shader.use();
        glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
        glm::mat4 view = camera.GetViewMatrix();
        glm::mat4 model;
        shader.setMat4("projection", projection);
        shader.setMat4("view", view);
        // cubes
        glBindVertexArray(cubeVAO);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, cubeTexture);
        model = glm::translate(model, glm::vec3(-1.0f, 0.0f, -1.0f));
        shader.setMat4("model", model);
        glDrawArrays(GL_TRIANGLES, 0, 36);
        model = glm::mat4();
        model = glm::translate(model, glm::vec3(2.0f, 0.0f, 0.0f));
        shader.setMat4("model", model);
        glDrawArrays(GL_TRIANGLES, 0, 36);
        // floor
        glBindVertexArray(planeVAO);
        glBindTexture(GL_TEXTURE_2D, floorTexture);
        model = glm::mat4();
        shader.setMat4("model", model);
        glDrawArrays(GL_TRIANGLES, 0, 6);
        // windows (from furthest to nearest)
        glBindVertexArray(transparentVAO);
        glBindTexture(GL_TEXTURE_2D, transparentTexture);
        for (std::map<float, glm::vec3>::reverse_iterator it = sorted.rbegin(); it != sorted.rend(); ++it)
        {
            model = glm::mat4();
            model = glm::translate(model, it->second);
            shader.setMat4("model", model);
            glDrawArrays(GL_TRIANGLES, 0, 6);
        }


        // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
        // -------------------------------------------------------------------------------
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // optional: de-allocate all resources once they've outlived their purpose:
    // ------------------------------------------------------------------------
    glDeleteVertexArrays(1, &cubeVAO);
    glDeleteVertexArrays(1, &planeVAO);
    glDeleteBuffers(1, &cubeVBO);
    glDeleteBuffers(1, &planeVBO);

    glfwTerminate();
    return 0;
}

// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow *window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);

    if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
        camera.ProcessKeyboard(FORWARD, deltaTime);
    if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
        camera.ProcessKeyboard(BACKWARD, deltaTime);
    if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
        camera.ProcessKeyboard(LEFT, deltaTime);
    if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
        camera.ProcessKeyboard(RIGHT, deltaTime);
}

// glfw: whenever the window size changed (by OS or user resize) this callback function executes
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    // make sure the viewport matches the new window dimensions; note that width and 
    // height will be significantly larger than specified on retina displays.
    glViewport(0, 0, width, height);
}

// glfw: whenever the mouse moves, this callback is called
// -------------------------------------------------------
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
    if (firstMouse)
    {
        lastX = xpos;
        lastY = ypos;
        firstMouse = false;
    }

    float xoffset = xpos - lastX;
    float yoffset = lastY - ypos; // reversed since y-coordinates go from bottom to top

    lastX = xpos;
    lastY = ypos;

    camera.ProcessMouseMovement(xoffset, yoffset);
}

// glfw: whenever the mouse scroll wheel scrolls, this callback is called
// ----------------------------------------------------------------------
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
    camera.ProcessMouseScroll(yoffset);
}

// utility function for loading a 2D texture from file
// ---------------------------------------------------
unsigned int loadTexture(char const * path)
{
    unsigned int textureID;
    glGenTextures(1, &textureID);

    int width, height, nrComponents;
    unsigned char *data = stbi_load(path, &width, &height, &nrComponents, 0);
    if (data)
    {
        GLenum format;
        if (nrComponents == 1)
            format = GL_RED;
        else if (nrComponents == 3)
            format = GL_RGB;
        else if (nrComponents == 4)
            format = GL_RGBA;

        glBindTexture(GL_TEXTURE_2D, textureID);
        glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
        glGenerateMipmap(GL_TEXTURE_2D);

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, format == GL_RGBA ? GL_CLAMP_TO_EDGE : GL_REPEAT); // for this tutorial: use GL_CLAMP_TO_EDGE to prevent semi-transparent borders. Due to interpolation it takes texels from next repeat 
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, format == GL_RGBA ? GL_CLAMP_TO_EDGE : GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        stbi_image_free(data);
    }
    else
    {
        std::cout << "Texture failed to load at path: " << path << std::endl;
        stbi_image_free(data);
    }

    return textureID;
}

雖然按照距離排序物體這種方法對我們這個場景能夠正常工作,但它并沒有考慮旋轉(zhuǎn)、縮放或者其它的變換,奇怪形狀的物體需要一個不同的計量,而不是僅僅一個位置向量。

在場景中排序物體是一個很困難的技術(shù),很大程度上由你場景的類型所決定,更別說它額外需要消耗的處理能力了。完整渲染一個包含不透明和透明物體的場景并不是那么容易。更高級的技術(shù)還有次序無關(guān)透明度(Order Independent Transparency, OIT),但這超出本教程的范圍了?,F(xiàn)在,你還是必須要普通地混合你的物體,但如果你很小心,并且知道目前方法的限制的話,你仍然能夠獲得一個比較不錯的混合實現(xiàn)。

后記

未完,待續(xù)~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容