OpenGL 圖形庫的使用(三十)—— 高級OpenGL之實(shí)例化Instancing

版本記錄

版本號 時(shí)間
V1.0 2018.01.17

前言

OpenGL 圖形庫項(xiàng)目中一直也沒用過,最近也想學(xué)著使用這個(gè)圖形庫安聘,感覺還是很有意思,也就自然想著好好的總結(jié)一下叁温,希望對大家能有所幫助江掩。下面內(nèi)容來自歡迎來到OpenGL的世界学辱。
1. OpenGL 圖形庫使用(一) —— 概念基礎(chǔ)
2. OpenGL 圖形庫使用(二) —— 渲染模式、對象环形、擴(kuò)展和狀態(tài)機(jī)
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 圖形庫的使用(九)—— 攝像機(jī)(一)
10. OpenGL 圖形庫的使用(十)—— 攝像機(jī)(二)
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

實(shí)例化

假設(shè)你有一個(gè)繪制了很多模型的場景,而大部分的模型包含的是同一組頂點(diǎn)數(shù)據(jù)斟赚,只不過進(jìn)行的是不同的世界空間變換着降。想象一個(gè)充滿草的場景:每根草都是一個(gè)包含幾個(gè)三角形的小模型差油。你可能會(huì)需要繪制很多根草拗军,最終在每幀中你可能會(huì)需要渲染上千或者上萬根草任洞。因?yàn)槊恳桓輧H僅是由幾個(gè)三角形構(gòu)成,渲染幾乎是瞬間完成的发侵,但上千個(gè)渲染函數(shù)調(diào)用卻會(huì)極大地影響性能交掏。

如果我們需要渲染大量物體時(shí),代碼看起來會(huì)像這樣:

for(unsigned int i = 0; i < amount_of_models_to_draw; i++)
{
    DoSomePreparations(); // 綁定VAO刃鳄,綁定紋理盅弛,設(shè)置uniform等
    glDrawArrays(GL_TRIANGLES, 0, amount_of_vertices);
}

如果像這樣繪制模型的大量實(shí)例(Instance),你很快就會(huì)因?yàn)槔L制調(diào)用過多而達(dá)到性能瓶頸叔锐。與繪制頂點(diǎn)本身相比挪鹏,使用glDrawArraysglDrawElements函數(shù)告訴GPU去繪制你的頂點(diǎn)數(shù)據(jù)會(huì)消耗更多的性能,因?yàn)镺penGL在繪制頂點(diǎn)數(shù)據(jù)之前需要做很多準(zhǔn)備工作(比如告訴GPU該從哪個(gè)緩沖讀取數(shù)據(jù)愉烙,從哪尋找頂點(diǎn)屬性讨盒,而且這些都是在相對緩慢的CPU到GPU總線(CPU to GPU Bus)上進(jìn)行的)。所以步责,即便渲染頂點(diǎn)非撤邓常快,命令GPU去渲染卻未必蔓肯。

如果我們能夠?qū)?shù)據(jù)一次性發(fā)送給GPU遂鹊,然后使用一個(gè)繪制函數(shù)讓OpenGL利用這些數(shù)據(jù)繪制多個(gè)物體,就會(huì)更方便了蔗包。這就是實(shí)例化(Instancing)秉扑。

實(shí)例化這項(xiàng)技術(shù)能夠讓我們使用一個(gè)渲染調(diào)用來繪制多個(gè)物體,來節(jié)省每次繪制物體時(shí)CPU -> GPU的通信调限,它只需要一次即可邻储。如果想使用實(shí)例化渲染,我們只需要將glDrawArraysglDrawElements的渲染調(diào)用分別改為glDrawArraysInstancedglDrawElementsInstanced就可以了旧噪。這些渲染函數(shù)的實(shí)例化版本需要一個(gè)額外的參數(shù)吨娜,叫做實(shí)例數(shù)量(Instance Count),它能夠設(shè)置我們需要渲染的實(shí)例個(gè)數(shù)淘钟。這樣我們只需要將必須的數(shù)據(jù)發(fā)送到GPU一次宦赠,然后使用一次函數(shù)調(diào)用告訴GPU它應(yīng)該如何繪制這些實(shí)例。GPU將會(huì)直接渲染這些實(shí)例米母,而不用不斷地與CPU進(jìn)行通信勾扭。

這個(gè)函數(shù)本身并沒有什么用。渲染同一個(gè)物體一千次對我們并沒有什么用處铁瞒,每個(gè)物體都是完全相同的妙色,而且還在同一個(gè)位置。我們只能看見一個(gè)物體慧耍!處于這個(gè)原因身辨,GLSL在頂點(diǎn)著色器中嵌入了另一個(gè)內(nèi)建變量丐谋,gl_InstanceID

在使用實(shí)例化渲染調(diào)用時(shí)煌珊,gl_InstanceID會(huì)從0開始号俐,在每個(gè)實(shí)例被渲染時(shí)遞增1。比如說定庵,我們正在渲染第43個(gè)實(shí)例吏饿,那么頂點(diǎn)著色器中它的gl_InstanceID將會(huì)是42。因?yàn)槊總€(gè)實(shí)例都有唯一的ID蔬浙,我們可以建立一個(gè)數(shù)組猪落,將ID與位置值對應(yīng)起來,將每個(gè)實(shí)例放置在世界的不同位置畴博。

為了體驗(yàn)一下實(shí)例化繪制许布,我們將會(huì)在標(biāo)準(zhǔn)化設(shè)備坐標(biāo)系中使用一個(gè)渲染調(diào)用,繪制100個(gè)2D四邊形绎晃。我們會(huì)索引一個(gè)包含100個(gè)偏移向量的uniform數(shù)組蜜唾,將偏移值加到每個(gè)實(shí)例化的四邊形上。最終的結(jié)果是一個(gè)排列整齊的四邊形網(wǎng)格:

每個(gè)四邊形由2個(gè)三角形所組成庶艾,一共有6個(gè)頂點(diǎn)袁余。每個(gè)頂點(diǎn)包含一個(gè)2D的標(biāo)準(zhǔn)化設(shè)備坐標(biāo)位置向量和一個(gè)顏色向量。 下面就是這個(gè)例子使用的頂點(diǎn)數(shù)據(jù)咱揍,為了大量填充屏幕颖榜,每個(gè)三角形都很小:

float quadVertices[] = {
    // 位置          // 顏色
    -0.05f,  0.05f,  1.0f, 0.0f, 0.0f,
     0.05f, -0.05f,  0.0f, 1.0f, 0.0f,
    -0.05f, -0.05f,  0.0f, 0.0f, 1.0f,

    -0.05f,  0.05f,  1.0f, 0.0f, 0.0f,
     0.05f, -0.05f,  0.0f, 1.0f, 0.0f,   
     0.05f,  0.05f,  0.0f, 1.0f, 1.0f                   
};  

片段著色器會(huì)從頂點(diǎn)著色器接受顏色向量煤裙,并將其設(shè)置為它的顏色輸出掩完,來實(shí)現(xiàn)四邊形的顏色:

#version 330 core
out vec4 FragColor;

in vec3 fColor;

void main()
{
    FragColor = vec4(fColor, 1.0);
}

到現(xiàn)在都沒有什么新內(nèi)容,但從頂點(diǎn)著色器開始就變得很有趣了:

#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec3 aColor;

out vec3 fColor;

uniform vec2 offsets[100];

void main()
{
    vec2 offset = offsets[gl_InstanceID];
    gl_Position = vec4(aPos + offset, 0.0, 1.0);
    fColor = aColor;
}

這里我們定義了一個(gè)叫做offsets的數(shù)組硼砰,它包含100個(gè)偏移向量且蓬。在頂點(diǎn)著色器中,我們會(huì)使用gl_InstanceID來索引offsets數(shù)組题翰,獲取每個(gè)實(shí)例的偏移向量恶阴。如果我們要實(shí)例化繪制100個(gè)四邊形,僅使用這個(gè)頂點(diǎn)著色器我們就能得到100個(gè)位于不同位置的四邊形豹障。

當(dāng)前冯事,我們?nèi)砸O(shè)置這些偏移位置,我們會(huì)在進(jìn)入渲染循環(huán)之前使用一個(gè)嵌套for循環(huán)計(jì)算:

glm::vec2 translations[100];
int index = 0;
float offset = 0.1f;
for(int y = -10; y < 10; y += 2)
{
    for(int x = -10; x < 10; x += 2)
    {
        glm::vec2 translation;
        translation.x = (float)x / 10.0f + offset;
        translation.y = (float)y / 10.0f + offset;
        translations[index++] = translation;
    }
}

這里血公,我們創(chuàng)建100個(gè)位移向量昵仅,表示10x10網(wǎng)格上的所有位置。除了生成translations數(shù)組之外累魔,我們還需要將數(shù)據(jù)轉(zhuǎn)移到頂點(diǎn)著色器的uniform數(shù)組中:

shader.use();
for(unsigned int i = 0; i < 100; i++)
{
    stringstream ss;
    string index;
    ss << i; 
    index = ss.str(); 
    shader.setVec2(("offsets[" + index + "]").c_str(), translations[i]);
}

在這一段代碼中摔笤,我們將for循環(huán)的計(jì)數(shù)器i轉(zhuǎn)換為一個(gè)string够滑,我們可以用它來動(dòng)態(tài)創(chuàng)建位置值的字符串,用于uniform位置值的索引籍茧。接下來版述,我們會(huì)對offsets uniform數(shù)組中的每一項(xiàng)設(shè)置對應(yīng)的位移向量梯澜。

現(xiàn)在所有的準(zhǔn)備工作都做完了寞冯,我們可以開始渲染四邊形了。對于實(shí)例化渲染晚伙,我們使用glDrawArraysInstancedglDrawElementsInstanced吮龄。因?yàn)槲覀兪褂玫牟皇撬饕彌_,我們會(huì)調(diào)用glDrawArrays版本的函數(shù):

glBindVertexArray(quadVAO);
glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 100);

glDrawArraysInstanced的參數(shù)和glDrawArrays完全一樣咆疗,除了最后多了個(gè)參數(shù)用來設(shè)置需要繪制的實(shí)例數(shù)量漓帚。因?yàn)槲覀兿胍?0x10網(wǎng)格中顯示100個(gè)四邊形,我們將它設(shè)置為100.運(yùn)行代碼之后午磁,你應(yīng)該能得到熟悉的100個(gè)五彩的四邊形尝抖。


實(shí)例化數(shù)組

雖然之前的實(shí)現(xiàn)在目前的情況下能夠正常工作,但是如果我們要渲染遠(yuǎn)超過100個(gè)實(shí)例的時(shí)候(這其實(shí)非常普遍)迅皇,我們最終會(huì)超過最大能夠發(fā)送至著色器的uniform數(shù)據(jù)大小上限昧辽。它的一個(gè)代替方案是實(shí)例化數(shù)組(Instanced Array),它被定義為一個(gè)頂點(diǎn)屬性(能夠讓我們儲(chǔ)存更多的數(shù)據(jù))登颓,僅在頂點(diǎn)著色器渲染一個(gè)新的實(shí)例時(shí)才會(huì)更新搅荞。

使用頂點(diǎn)屬性時(shí),頂點(diǎn)著色器的每次運(yùn)行都會(huì)讓GLSL獲取新一組適用于當(dāng)前頂點(diǎn)的屬性框咙。而當(dāng)我們將頂點(diǎn)屬性定義為一個(gè)實(shí)例化數(shù)組時(shí)咕痛,頂點(diǎn)著色器就只需要對每個(gè)實(shí)例,而不是每個(gè)頂點(diǎn)喇嘱,更新頂點(diǎn)屬性的內(nèi)容了茉贡。這允許我們對逐頂點(diǎn)的數(shù)據(jù)使用普通的頂點(diǎn)屬性,而對逐實(shí)例的數(shù)據(jù)使用實(shí)例化數(shù)組者铜。

為了給你一個(gè)實(shí)例化數(shù)組的例子块仆,我們將使用之前的例子,并將偏移量uniform數(shù)組設(shè)置為一個(gè)實(shí)例化數(shù)組王暗。我們需要在頂點(diǎn)著色器中再添加一個(gè)頂點(diǎn)屬性:

#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aOffset;

out vec3 fColor;

void main()
{
    gl_Position = vec4(aPos + aOffset, 0.0, 1.0);
    fColor = aColor;
}

我們不再使用gl_InstanceID悔据,現(xiàn)在不需要索引一個(gè)uniform數(shù)組就能夠直接使用offset屬性了。

因?yàn)閷?shí)例化數(shù)組和position與color變量一樣俗壹,都是頂點(diǎn)屬性科汗,我們還需要將它的內(nèi)容存在頂點(diǎn)緩沖對象中,并且配置它的屬性指針绷雏。我們首先將(上一部分的)translations數(shù)組存到一個(gè)新的緩沖對象中:

unsigned int instanceVBO;
glGenBuffers(1, &instanceVBO);
glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec2) * 100, &translations[0], GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);

之后我們還需要設(shè)置它的頂點(diǎn)屬性指針头滔,并啟用頂點(diǎn)屬性:

glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
glBindBuffer(GL_ARRAY_BUFFER, 0);   
glVertexAttribDivisor(2, 1);

這段代碼很有意思的地方在于最后一行怖亭,我們調(diào)用了glVertexAttribDivisor。這個(gè)函數(shù)告訴了OpenGL該什么時(shí)候更新頂點(diǎn)屬性的內(nèi)容至新一組數(shù)據(jù)坤检。它的第一個(gè)參數(shù)是需要的頂點(diǎn)屬性兴猩,第二個(gè)參數(shù)是屬性除數(shù)(Attribute Divisor)。默認(rèn)情況下早歇,屬性除數(shù)是0倾芝,告訴OpenGL我們需要在頂點(diǎn)著色器的每次迭代時(shí)更新頂點(diǎn)屬性。將它設(shè)置為1時(shí)箭跳,我們告訴OpenGL我們希望在渲染一個(gè)新實(shí)例的時(shí)候更新頂點(diǎn)屬性晨另。而設(shè)置為2時(shí),我們希望每2個(gè)實(shí)例更新一次屬性谱姓,以此類推借尿。我們將屬性除數(shù)設(shè)置為1,是在告訴OpenGL屉来,處于位置值2的頂點(diǎn)屬性是一個(gè)實(shí)例化數(shù)組路翻。

如果我們現(xiàn)在使用glDrawArraysInstanced,再次渲染四邊形茄靠,會(huì)得到以下輸出:

這和之前的例子是完全一樣的茂契,但這次是使用實(shí)例化數(shù)組實(shí)現(xiàn)的,這讓我們能夠傳遞更多的數(shù)據(jù)到頂點(diǎn)著色器(只要內(nèi)存允許)來用于實(shí)例化繪制嘹黔。

為了更有趣一點(diǎn)账嚎,我們也可以使用gl_InstanceID,從右上到左下逐漸縮小四邊形:

void main()
{
    vec2 pos = aPos * (gl_InstanceID / 100.0);
    gl_Position = vec4(pos + aOffset, 0.0, 1.0);
    fColor = aColor;
}

結(jié)果就是儡蔓,第一個(gè)四邊形的實(shí)例會(huì)非常小郭蕉,隨著繪制實(shí)例的增加,gl_InstanceID會(huì)越來越接近100喂江,四邊形也就越來越接近原始大小召锈。像這樣將實(shí)例化數(shù)組與gl_InstanceID結(jié)合使用是完全可行的。

如果你還是不確定實(shí)例化渲染是如何工作的获询,或者想看看所有代碼是如何組合起來的涨岁,你可以在這里找到程序的源代碼。

#include <glad/glad.h>  
#include <GLFW/glfw3.h>  
#include <learnopengl/shader.h>  
#include <iostream>

void framebuffer_size_callback(GLFWwindow* window, int width, int height);

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

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

    // 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("10.1.instancing.vs", "10.1.instancing.fs");
 // generate a list of 100 quad locations/translation-vectors
    // ---------------------------------------------------------
    glm::vec2 translations[100];
    int index = 0;
    float offset = 0.1f;
    for (int y = -10; y < 10; y += 2)
    {
        for (int x = -10; x < 10; x += 2)
        {
            glm::vec2 translation;
            translation.x = (float)x / 10.0f + offset;
            translation.y = (float)y / 10.0f + offset;
            translations[index++] = translation;
        }
    }

    // store instance data in an array buffer
    // --------------------------------------
    unsigned int instanceVBO;
    glGenBuffers(1, &instanceVBO);
    glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec2) * 100, &translations[0], GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    // set up vertex data (and buffer(s)) and configure vertex attributes
    // ------------------------------------------------------------------
    float quadVertices[] = {
        // positions     // colors
        -0.05f,  0.05f,  1.0f, 0.0f, 0.0f,
         0.05f, -0.05f,  0.0f, 1.0f, 0.0f,
        -0.05f, -0.05f,  0.0f, 0.0f, 1.0f,

        -0.05f,  0.05f,  1.0f, 0.0f, 0.0f,
         0.05f, -0.05f,  0.0f, 1.0f, 0.0f,
         0.05f,  0.05f,  0.0f, 1.0f, 1.0f
    };
    unsigned int quadVAO, quadVBO;
    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, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(2 * sizeof(float)));
    // also set instance data
    glEnableVertexAttribArray(2);
    glBindBuffer(GL_ARRAY_BUFFER, instanceVBO); // this attribute comes from a different vertex buffer
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glVertexAttribDivisor(2, 1); // tell OpenGL this is an instanced vertex attribute.


    // render loop
    // -----------
    while (!glfwWindowShouldClose(window))
    {
        // render
        // ------
        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // draw 100 instanced quads
        shader.use();
        glBindVertexArray(quadVAO);
        glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 100); // 100 triangles of 6 vertices each
        glBindVertexArray(0);

        // 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, &quadVAO);
    glDeleteBuffers(1, &quadVBO);

    glfwTerminate();
    return 0;
}

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

雖然很有趣吉嚣,但是這些例子并不是實(shí)例化的好例子梢薪。是的,它們的確讓你知道實(shí)例化是怎么工作的尝哆,但是我們還沒接觸到它最有用的一點(diǎn)秉撇,繪制巨大數(shù)量的相似物體。出于這個(gè)原因,我們將會(huì)在下一部分進(jìn)入太空探險(xiǎn)琐馆,見識實(shí)例化渲染真正的威力规阀。

這些例子不是實(shí)例的好例子,不過挺有意思的瘦麸。它們可以讓你對實(shí)例的工作方式有一個(gè)概括的理解谁撼,但是當(dāng)繪制擁有極大數(shù)量的相同物體的時(shí)候,它極其有用滋饲,現(xiàn)在我們還沒有展示呢厉碟。出于這個(gè)原因,我們將在接下來的部分進(jìn)入太空來看看實(shí)例渲染的威力了赌。


小行星帶

想象這樣一個(gè)場景墨榄,在宇宙中有一個(gè)大的行星玄糟,它位于小行星帶的中央勿她。這樣的小行星帶可能包含成千上萬的巖塊,在很不錯(cuò)的顯卡上也很難完成這樣的渲染阵翎。實(shí)例化渲染正是適用于這樣的場景逢并,因?yàn)樗械男⌒行嵌伎梢允褂靡粋€(gè)模型來表示。每個(gè)小行星可以再使用不同的變換矩陣來進(jìn)行少許的變化郭卫。

為了展示實(shí)例化渲染的作用砍聊,我們首先會(huì)不使用實(shí)例化渲染,來渲染小行星繞著行星飛行的場景贰军。這個(gè)場景將會(huì)包含一個(gè)大的行星模型玻蝌,它可以在這里下載,以及很多環(huán)繞著行星的小行星词疼。小行星的巖石模型可以在這里下載俯树。

在代碼例子中,我們將使用在模型加載小節(jié)中定義的模型加載器來加載模型贰盗。

為了得到想要的效果许饿,我們將會(huì)為每個(gè)小行星生成一個(gè)變換矩陣,用作它們的模型矩陣舵盈。變換矩陣首先將小行星位移到小行星帶中的某處陋率,我們還會(huì)加一個(gè)小的隨機(jī)偏移值到這個(gè)偏移量上,讓這個(gè)圓環(huán)看起來更自然一點(diǎn)秽晚。接下來瓦糟,我們應(yīng)用一個(gè)隨機(jī)的縮放,并且以一個(gè)旋轉(zhuǎn)向量為軸進(jìn)行一個(gè)隨機(jī)的旋轉(zhuǎn)赴蝇。最終的變換矩陣不僅能將小行星變換到行星的周圍菩浙,而且會(huì)讓它看起來更自然,與其它小行星不同。最終的結(jié)果是一個(gè)布滿小行星的圓環(huán)芍耘,其中每一個(gè)小行星都與眾不同址遇。

unsigned int amount = 1000;
glm::mat4 *modelMatrices;
modelMatrices = new glm::mat4[amount];
srand(glfwGetTime()); // 初始化隨機(jī)種子    
float radius = 50.0;
float offset = 2.5f;
for(unsigned int i = 0; i < amount; i++)
{
    glm::mat4 model;
    // 1. 位移:分布在半徑為 'radius' 的圓形上,偏移的范圍是 [-offset, offset]
    float angle = (float)i / (float)amount * 360.0f;
    float displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
    float x = sin(angle) * radius + displacement;
    displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
    float y = displacement * 0.4f; // 讓行星帶的高度比x和z的寬度要小
    displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
    float z = cos(angle) * radius + displacement;
    model = glm::translate(model, glm::vec3(x, y, z));

    // 2. 縮放:在 0.05 和 0.25f 之間縮放
    float scale = (rand() % 20) / 100.0f + 0.05;
    model = glm::scale(model, glm::vec3(scale));

    // 3. 旋轉(zhuǎn):繞著一個(gè)(半)隨機(jī)選擇的旋轉(zhuǎn)軸向量進(jìn)行隨機(jī)的旋轉(zhuǎn)
    float rotAngle = (rand() % 360);
    model = glm::rotate(model, rotAngle, glm::vec3(0.4f, 0.6f, 0.8f));

    // 4. 添加到矩陣的數(shù)組中
    modelMatrices[i] = model;
}  

這段代碼看起來可能有點(diǎn)嚇人斋竞,但我們只是將小行星的x和z位置變換到了一個(gè)半徑為radius的圓形上倔约,并且在半徑的基礎(chǔ)上偏移了-offset到offset。我們讓y偏移的影響更小一點(diǎn)坝初,讓小行星帶更扁平一點(diǎn)浸剩。接下來,我們應(yīng)用了縮放和旋轉(zhuǎn)變換鳄袍,并將最終的變換矩陣儲(chǔ)存在modelMatrices中绢要,這個(gè)數(shù)組的大小是amount。這里拗小,我們一共生成1000個(gè)模型矩陣重罪,每個(gè)小行星一個(gè)。

在加載完行星和巖石模型哀九,并編譯完著色器之后剿配,渲染的代碼看起來是這樣的:

// 繪制行星
shader.use();
glm::mat4 model;
model = glm::translate(model, glm::vec3(0.0f, -3.0f, 0.0f));
model = glm::scale(model, glm::vec3(4.0f, 4.0f, 4.0f));
shader.setMat4("model", model);
planet.Draw(shader);

// 繪制小行星
for(unsigned int i = 0; i < amount; i++)
{
    shader.setMat4("model", modelMatrices[i]);
    rock.Draw(shader);
}  

我們首先繪制了行星的模型,并對它進(jìn)行位移和縮放阅束,以適應(yīng)場景呼胚,接下來,我們繪制amount數(shù)量的巖石模型息裸。在繪制每個(gè)巖石之前蝇更,我們首先需要在著色器內(nèi)設(shè)置對應(yīng)的模型變換矩陣。

最終的結(jié)果是一個(gè)看起來像是太空的場景呼盆,環(huán)繞著行星的是看起來很自然的小行星帶:

這個(gè)場景每幀包含1001次渲染調(diào)用年扩,其中1000個(gè)是巖石模型。你可以在這里找到源代碼宿亡。

#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.h](https://learnopengl.com/code_viewer_gh.php?code=includes/learnopengl/shader.h)>  
#include <[learnopengl/camera.h](https://learnopengl.com/code_viewer_gh.php?code=includes/learnopengl/camera.h)> 
#include <[learnopengl/model.h](https://learnopengl.com/code_viewer_gh.php?code=includes/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);

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

// camera
Camera camera(glm::vec3(0.0f, 0.0f, 55.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("10.2.instancing.vs", "10.2.instancing.fs");
    // load models
    // -----------
    Model rock(FileSystem::getPath("resources/objects/rock/rock.obj"));
    Model planet(FileSystem::getPath("resources/objects/planet/planet.obj"));

    // generate a large list of semi-random model transformation matrices
    // ------------------------------------------------------------------
    unsigned int amount = 1000;
    glm::mat4* modelMatrices;
    modelMatrices = new glm::mat4[amount];
    srand(glfwGetTime()); // initialize random seed 
    float radius = 50.0;
    float offset = 2.5f;
    for (unsigned int i = 0; i < amount; i++)
    {
        glm::mat4 model;
        // 1. translation: displace along circle with 'radius' in range [-offset, offset]
        float angle = (float)i / (float)amount * 360.0f;
        float displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
        float x = sin(angle) * radius + displacement;
        displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
        float y = displacement * 0.4f; // keep height of asteroid field smaller compared to width of x and z
        displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
        float z = cos(angle) * radius + displacement;
        model = glm::translate(model, glm::vec3(x, y, z));

        // 2. scale: Scale between 0.05 and 0.25f
        float scale = (rand() % 20) / 100.0f + 0.05;
        model = glm::scale(model, glm::vec3(scale));

        // 3. rotation: add random rotation around a (semi)randomly picked rotation axis vector
        float rotAngle = (rand() % 360);
        model = glm::rotate(model, rotAngle, glm::vec3(0.4f, 0.6f, 0.8f));

        // 4. now add to list of matrices
        modelMatrices[i] = model;
    }

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

        // configure transformation matrices
        glm::mat4 projection = glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 1000.0f);
        glm::mat4 view = camera.GetViewMatrix();;
        shader.use();
        shader.setMat4("projection", projection);
        shader.setMat4("view", view);

        // draw planet
        glm::mat4 model;
        model = glm::translate(model, glm::vec3(0.0f, -3.0f, 0.0f));
        model = glm::scale(model, glm::vec3(4.0f, 4.0f, 4.0f));
        shader.setMat4("model", model);
        planet.Draw(shader);

        // draw meteorites
        for (unsigned int i = 0; i < amount; i++)
        {
            shader.setMat4("model", modelMatrices[i]);
            rock.Draw(shader);
        }     

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

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

當(dāng)我們開始增加這個(gè)數(shù)字的時(shí)候常遂,你很快就會(huì)發(fā)現(xiàn)場景不再能夠流暢運(yùn)行了,幀數(shù)也下降很厲害挽荠。當(dāng)我們將amount設(shè)置為2000的時(shí)候克胳,場景就已經(jīng)慢到移動(dòng)都很困難的程度了。

現(xiàn)在圈匆,我們來嘗試使用實(shí)例化渲染來渲染相同的場景漠另。我們首先對頂點(diǎn)著色器進(jìn)行一點(diǎn)修改:

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 2) in vec2 aTexCoords;
layout (location = 3) in mat4 instanceMatrix;

out vec2 TexCoords;

uniform mat4 projection;
uniform mat4 view;

void main()
{
    gl_Position = projection * view * instanceMatrix * vec4(aPos, 1.0); 
    TexCoords = aTexCoords;
}

我們不再使用模型uniform變量,改為一個(gè)mat4的頂點(diǎn)屬性跃赚,讓我們能夠存儲(chǔ)一個(gè)實(shí)例化數(shù)組的變換矩陣笆搓。然而性湿,當(dāng)我們頂點(diǎn)屬性的類型大于vec4時(shí),就要多進(jìn)行一步處理了满败。頂點(diǎn)屬性最大允許的數(shù)據(jù)大小等于一個(gè)vec4肤频。因?yàn)橐粋€(gè)mat4本質(zhì)上是4個(gè)vec4,我們需要為這個(gè)矩陣預(yù)留4個(gè)頂點(diǎn)屬性算墨。因?yàn)槲覀儗⑺奈恢弥翟O(shè)置為3宵荒,矩陣每一列的頂點(diǎn)屬性位置值就是3、4净嘀、5和6报咳。

接下來,我們需要為這4個(gè)頂點(diǎn)屬性設(shè)置屬性指針挖藏,并將它們設(shè)置為實(shí)例化數(shù)組:

// 頂點(diǎn)緩沖對象
unsigned int buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, amount * sizeof(glm::mat4), &modelMatrices[0], GL_STATIC_DRAW);

for(unsigned int i = 0; i < rock.meshes.size(); i++)
{
    unsigned int VAO = rock.meshes[i].VAO;
    glBindVertexArray(VAO);
    // 頂點(diǎn)屬性
    GLsizei vec4Size = sizeof(glm::vec4);
    glEnableVertexAttribArray(3); 
    glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)0);
    glEnableVertexAttribArray(4); 
    glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)(vec4Size));
    glEnableVertexAttribArray(5); 
    glVertexAttribPointer(5, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)(2 * vec4Size));
    glEnableVertexAttribArray(6); 
    glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)(3 * vec4Size));

    glVertexAttribDivisor(3, 1);
    glVertexAttribDivisor(4, 1);
    glVertexAttribDivisor(5, 1);
    glVertexAttribDivisor(6, 1);

    glBindVertexArray(0);
}  

注意這里我們將MeshVAO從私有變量改為了公有變量暑刃,讓我們能夠訪問它的頂點(diǎn)數(shù)組對象。這并不是最好的解決方案膜眠,只是為了配合本小節(jié)的一個(gè)簡單的改動(dòng)岩臣。除此之外代碼就應(yīng)該很清楚了。我們告訴了OpenGL應(yīng)該如何解釋每個(gè)緩沖頂點(diǎn)屬性的緩沖柴底,并且告訴它這些頂點(diǎn)屬性是實(shí)例化數(shù)組婿脸。

接下來粱胜,我們再次使用網(wǎng)格的VAO柄驻,這一次使用glDrawElementsInstanced進(jìn)行繪制:

// 繪制小行星
instanceShader.use();
for(unsigned int i = 0; i < rock.meshes.size(); i++)
{
    glBindVertexArray(rock.meshes[i].VAO);
    glDrawElementsInstanced(
        GL_TRIANGLES, rock.meshes[i].indices.size(), GL_UNSIGNED_INT, 0, amount
    );
}

這里,我們繪制與之前相同數(shù)量amount的小行星焙压,但是使用的是實(shí)例渲染鸿脓。結(jié)果應(yīng)該是非常相似的,但如果你開始增加amount變量涯曲,你就能看見實(shí)例化渲染的效果了野哭。沒有實(shí)例化渲染的時(shí)候,我們只能流暢渲染1000到1500個(gè)小行星幻件。而使用了實(shí)例化渲染之后拨黔,我們可以將這個(gè)值設(shè)置為100000,每個(gè)巖石模型有576個(gè)頂點(diǎn)绰沥,每幀加起來大概要繪制5700萬個(gè)頂點(diǎn)篱蝇,但性能卻沒有受到任何影響!

上面這幅圖渲染了10萬個(gè)小行星徽曲,半徑為150.0f零截,偏移量等于25.0f。你可以在這里找到實(shí)例化渲染的代碼秃臣。

#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.h](https://learnopengl.com/code_viewer_gh.php?code=includes/learnopengl/shader.h)>  
#include <[learnopengl/camera.h](https://learnopengl.com/code_viewer_gh.php?code=includes/learnopengl/camera.h)>  
#include <[learnopengl/model.h](https://learnopengl.com/code_viewer_gh.php?code=includes/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);

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

// camera
Camera camera(glm::vec3(0.0f, 0.0f, 155.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 asteroidShader("[10.3.asteroids.vs](https://learnopengl.com/code_viewer_gh.php?code=src/4.advanced_opengl/10.3.asteroids_instanced/10.3.asteroids.vs)", "[10.3.asteroids.fs](https://learnopengl.com/code_viewer_gh.php?code=src/4.advanced_opengl/10.3.asteroids_instanced/10.3.asteroids.fs)"); 
    Shader planetShader("[10.3.planet.vs](https://learnopengl.com/code_viewer_gh.php?code=src/4.advanced_opengl/10.3.asteroids_instanced/10.3.planet.vs)", "[10.3.planet.fs](https://learnopengl.com/code_viewer_gh.php?code=src/4.advanced_opengl/10.3.asteroids_instanced/10.3.planet.fs)");
   // load models
    // -----------
    Model rock(FileSystem::getPath("resources/objects/rock/rock.obj"));
    Model planet(FileSystem::getPath("resources/objects/planet/planet.obj"));

    // generate a large list of semi-random model transformation matrices
    // ------------------------------------------------------------------
    unsigned int amount = 100000;
    glm::mat4* modelMatrices;
    modelMatrices = new glm::mat4[amount];
    srand(glfwGetTime()); // initialize random seed 
    float radius = 150.0;
    float offset = 25.0f;
    for (unsigned int i = 0; i < amount; i++)
    {
        glm::mat4 model;
        // 1. translation: displace along circle with 'radius' in range [-offset, offset]
        float angle = (float)i / (float)amount * 360.0f;
        float displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
        float x = sin(angle) * radius + displacement;
        displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
        float y = displacement * 0.4f; // keep height of asteroid field smaller compared to width of x and z
        displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
        float z = cos(angle) * radius + displacement;
        model = glm::translate(model, glm::vec3(x, y, z));

        // 2. scale: Scale between 0.05 and 0.25f
        float scale = (rand() % 20) / 100.0f + 0.05;
        model = glm::scale(model, glm::vec3(scale));

        // 3. rotation: add random rotation around a (semi)randomly picked rotation axis vector
        float rotAngle = (rand() % 360);
        model = glm::rotate(model, rotAngle, glm::vec3(0.4f, 0.6f, 0.8f));

        // 4. now add to list of matrices
        modelMatrices[i] = model;
    }

    // configure instanced array
    // -------------------------
    unsigned int buffer;
    glGenBuffers(1, &buffer);
    glBindBuffer(GL_ARRAY_BUFFER, buffer);
    glBufferData(GL_ARRAY_BUFFER, amount * sizeof(glm::mat4), &modelMatrices[0], GL_STATIC_DRAW);

    // set transformation matrices as an instance vertex attribute (with divisor 1)
    // note: we're cheating a little by taking the, now publicly declared, VAO of the model's mesh(es) and adding new vertexAttribPointers
    // normally you'd want to do this in a more organized fashion, but for learning purposes this will do.
    // -----------------------------------------------------------------------------------------------------------------------------------
    for (unsigned int i = 0; i < rock.meshes.size(); i++)
    {
        unsigned int VAO = rock.meshes[i].VAO;
        glBindVertexArray(VAO);
        // set attribute pointers for matrix (4 times vec4)
        glEnableVertexAttribArray(3);
        glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)0);
        glEnableVertexAttribArray(4);
        glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(sizeof(glm::vec4)));
        glEnableVertexAttribArray(5);
        glVertexAttribPointer(5, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(2 * sizeof(glm::vec4)));
        glEnableVertexAttribArray(6);
        glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(3 * sizeof(glm::vec4)));

        glVertexAttribDivisor(3, 1);
        glVertexAttribDivisor(4, 1);
        glVertexAttribDivisor(5, 1);
        glVertexAttribDivisor(6, 1);

        glBindVertexArray(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);

        // configure transformation matrices
        glm::mat4 projection = glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 1000.0f);
        glm::mat4 view = camera.GetViewMatrix();
        asteroidShader.use();
        asteroidShader.setMat4("projection", projection);
        asteroidShader.setMat4("view", view);
        planetShader.use();
        planetShader.setMat4("projection", projection);
        planetShader.setMat4("view", view);
        
        // draw planet
        glm::mat4 model;
        model = glm::translate(model, glm::vec3(0.0f, -3.0f, 0.0f));
        model = glm::scale(model, glm::vec3(4.0f, 4.0f, 4.0f));
        planetShader.setMat4("model", model);
        planet.Draw(planetShader);

        // draw meteorites
        asteroidShader.use();
        asteroidShader.setInt("texture_diffuse1", 0);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, rock.textures_loaded[0].id); // note: we also made the textures_loaded vector public (instead of private) from the model class.
        for (unsigned int i = 0; i < rock.meshes.size(); i++)
        {
            glBindVertexArray(rock.meshes[i].VAO);
            glDrawElementsInstanced(GL_TRIANGLES, rock.meshes[i].indices.size(), GL_UNSIGNED_INT, 0, amount);
            glBindVertexArray(0);
        }

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

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

在某些機(jī)器上涧衙,10萬個(gè)小行星可能會(huì)太多了,所以嘗試修改這個(gè)值,直到達(dá)到一個(gè)你能接受的幀率弧哎。

可以看到雁比,在合適的環(huán)境下,實(shí)例化渲染能夠大大增加顯卡的渲染能力撤嫩。正是出于這個(gè)原因章贞,實(shí)例化渲染通常會(huì)用于渲染草、植被非洲、粒子鸭限,以及上面這樣的場景,基本上只要場景中有很多重復(fù)的形狀两踏,都能夠使用實(shí)例化渲染來提高性能败京。

后記

本篇結(jié)束,下一篇與抗鋸齒有關(guān)梦染。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末赡麦,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子帕识,更是在濱河造成了極大的恐慌泛粹,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肮疗,死亡現(xiàn)場離奇詭異晶姊,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)伪货,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進(jìn)店門们衙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人碱呼,你說我怎么就攤上這事蒙挑。” “怎么了愚臀?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵忆蚀,是天一觀的道長。 經(jīng)常有香客問我姑裂,道長馋袜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任炭分,我火速辦了婚禮桃焕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘捧毛。我一直安慰自己观堂,他們只是感情好让网,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著师痕,像睡著了一般溃睹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上胰坟,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天因篇,我揣著相機(jī)與錄音,去河邊找鬼笔横。 笑死竞滓,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的吹缔。 我是一名探鬼主播商佑,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼厢塘!你這毒婦竟也來了茶没?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤晚碾,失蹤者是張志新(化名)和其女友劉穎抓半,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體格嘁,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡笛求,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了讥蔽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片涣易。...
    茶點(diǎn)故事閱讀 39,696評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖冶伞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情步氏,我是刑警寧澤响禽,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站荚醒,受9級特大地震影響芋类,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜界阁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一侯繁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧泡躯,春花似錦贮竟、人聲如沸丽焊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽技健。三九已至,卻和暖如春惰拱,著一層夾襖步出監(jiān)牢的瞬間雌贱,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工偿短, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留欣孤,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓昔逗,卻偏偏與公主長得像导街,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子纤子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評論 2 353

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