OpenGL的Draw函數

轉載自:http://blog.csdn.net/patient16/article/details/50540011

前言


初學OpenGL時會發現各種各樣的Draw*函數,每種Draw*的功能和適合使用場景是什麼,在這裏做一下整理。對於老式的Draw(OpengGL1,2的glBegin)不做討論,其實理解OpenGL3,4的方法就夠了。

1.圖元類型


Draw是用於畫圖元的,這些圖元包括的類型有:

基本圖元 包含類型
Point Point list
Line Line list, Line strip, Line loop
triangle Triangle list, Triangle strip, Triangle fan

假設Draw輸入了N個點結合它們的拓撲結構看,這些圖元類型特點很好理解: 
這裏寫圖片描述

  • list:獨立的圖元
    取每k個點作爲一個單獨的圖元(point,k=1;line k=2;triangle k=3),每個圖元沒有公共頂點,如N=8時,能畫出8個point,4條line;N=9時,畫出3個triangle。
  • strip:相連着的圖元,line和triangle纔有。
    1)對於line,前面2個頂點組成一條線後,後面的每個頂點都與其前一個頂點組成一條線,因此共N-1條線;
    2)對於triangle,前面3個頂點組成一個三角形後,後面的每個頂點與其前兩個頂點組成一個三角形,因此共N-2個三角形
  • Line loop:類似Line strip,但首尾兩個頂點也構成一條線。
  • Triangle fan:有一個公共頂點,前面三個頂點組成一個三角形後,後面的頂點於前一個頂點及公共頂點組成新的三角形。

談談strip的特點

  • 當模型由許多相連的三角形構成時,使用strip較於list可以顯著減少帶寬。
  • triangle strip或line strip相鄰兩個圖元會有重合的頂點或線,但渲染時不會重複畫像素。
  • triangle strip的所有三角形的繞向都是一致,這樣保證了整個strip都被cull或都不被cull。
  • 當渲染模式爲flat shading時,一個三角形內部所有像素將被渲染成同一種顏色,顏色默認來自三角形的最後一個頂點(也可通過glProvokingVertex選擇第一個頂點)。因此結合上一個特點,可知strip中每個三角形分別爲:(0,1,2),(2,1,3),(2,3,4),(4,3,5),(4,5,6)……flat shading的顏色分別來自頂點2,3,4,5,6……通過一個實驗驗證,定義6個頂點,其顏色分別爲紅,綠,藍,紅,綠,藍。

    void init()
    {
        glViewport(0,0,128,128);
    
        glGenVertexArrays(NumVAOs, VAOs);//生成VAO
        glBindVertexArray(VAOs[Triangles]);//綁定VAO
        GLfloat positions[NumVertex][2]={//6個頂點的位置座標
            {0.0f, 32.0f},
            {16.0f, 0.0f},
            {32.0f, 32.0f},
            {48.0f, 0.0f},
            {64.0f,  32.0f},
            {80.85f, 0.0f}
        };
        GLfloat colors[NumVertex][4]={//6個頂點的Color
            {1.0f, 0.0f, 0.0f, 1.0f},
            {0.0f, 1.0f, 0.0f, 1.0f},
            {0.0f, 0.0f, 1.0f, 1.0f},
            {1.0f, 0.0f, 0.0f, 1.0f},
            {0.0f, 1.0f, 0.0f, 1.0f},
            {0.0f, 0.0f, 1.0f, 1.0f},
        };
    
        glGenBuffers(NumBuffers, Buffers);//生成VBO
        glBindBuffer(GL_ARRAY_BUFFER, Buffers[ArrayBuffer]);//綁定VBO
        glBufferData(GL_ARRAY_BUFFER, sizeof(positions)+sizeof(colors), NULL, GL_STATIC_DRAW);//爲VBO分配大小剛好填充position和color數據的空間
        glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(positions), positions);//填充position數據
        glBufferSubData(GL_ARRAY_BUFFER, sizeof(positions), sizeof(colors), colors);//填充color數據
    
        ShaderInfo shaders[]={
            {GL_VERTEX_SHADER,"triangles.vert"},
            {GL_FRAGMENT_SHADER,"triangles.frag"},
            {GL_NONE,NULL}
        };
    
        GLuint program = LoadShaders(shaders);//編譯,鏈接vs和fs
        glUseProgram(program);
    
        int vPosition_loc = glGetAttribLocation(program, "vPosition");
        int vColor_loc = glGetAttribLocation(program, "vColor");
        //cout<<vPosition_loc<<" "<<vColor_loc<<endl;
        glVertexAttribPointer(vPosition_loc, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));//將VBO中的position數據plumbing到vs
        glEnableVertexAttribArray(vPosition_loc);
        glVertexAttribPointer(vColor_loc, 4, GL_FLOAT, GL_FALSE, 0, (void*)sizeof(positions));//將VBO中的color數據plumbing到vs
        glEnableVertexAttribArray(vColor_loc);
    }
    

    當按正常方式渲染時,vertex Shader的代碼如下:

    #version 110
    in vec4 vPosition;
    in vec4 vColor;
    out vec4 vs_fs_color;
    void main()
    {
        gl_Position.x = (vPosition.x-64)/64;
        gl_Position.y = (vPosition.y-64)/64;
        gl_Position.zw = vPosition.zw;
        vs_fs_color = vColor;
    }
    

    fragment shader:

    #version 110
    out vec4 fColor;
    in vec4 vs_fs_color;
    void main()
    {
        fColor = vs_fs_color;
    }
    

    程序中將viewport設爲(0,0,128,128),VS中的變換是爲了抵消viewport transform,讓vertex buffer中的position即屏幕座標(原點位於Render Target的左下角)。結果爲
    這裏寫圖片描述

再試試使用flat shading,vertex shader:

    #version 110
    in vec4 vPosition;
    in vec4 vColor;
    flat out vec4 vs_fs_color;
    void main()
    {
        gl_Position.x = (vPosition.x-64)/64;
        gl_Position.y = (vPosition.y-64)/64;
        gl_Position.zw = vPosition.zw;
        vs_fs_color = vColor;
    }

fragment shader:

    #version 110
    out vec4 fColor;
    flat in vec4 vs_fs_color;
    void main()
    {
        fColor = vs_fs_color;
    }

這裏使用flat qualifier,指定color爲flat shading,注意vs的輸出和vs的對應輸入要同時指定爲flat,結果爲:
這裏寫圖片描述

可見每個三角形都以最後一個頂點的顏色做flat shading。

2.OpengGL的Draw函數


2.1認識兩種最基本的Draw函數

  • glDrawArrays(GLenum mode, GLint first, GLsizei count)
  • glDrawElements(GLenum mode, GLsizei count, GLenum type, GLvoid* indices)

DrawArrays和DrawElements是兩種最基本的Draw函數。

2.1.1 glDrawArrays

爲最常用的Draw函數,按mode指定的圖元類型,畫出vertex buffer中第first個起的count個頂點。

2.1.2 glDrawElements

使用glDrawElements前,除了類似glDrawArrays一樣準備好VAO和VBO外,還需要產生並綁定EBO(Element Buffer Object),注入的數據爲一個類型爲type的數組,其元素表示vertex buffer中頂點索引。glDrawElement以按照指定的圖元類型mode,以其前count個元素作爲VBO中頂點數據的索引。

init函數

void init()
{
    glViewport(0,0,128,128);

    glGenVertexArrays(NumVAOs, VAOs);
    glBindVertexArray(VAOs[Triangles]);
    GLfloat positions[NumVertex][2]={
        {0.0f, 32.0f},
        {16.0f, 0.0f},
        {32.0f, 32.0f},
        {48.0f, 0.0f},
        {64.0f,  32.0f},
        {80.85f, 0.0f}
    };
    GLfloat colors[NumVertex][4]={
        {1.0f, 0.0f, 0.0f, 1.0f},
        {0.0f, 1.0f, 0.0f, 1.0f},
        {0.0f, 0.0f, 1.0f, 1.0f},
        {1.0f, 0.0f, 0.0f, 1.0f},
        {0.0f, 1.0f, 0.0f, 1.0f},
        {0.0f, 0.0f, 1.0f, 1.0f},
    };

    GLushort indices [] =
    {
        0,1,2,1,2,3,2,3,4,3,4,5
    };

    glGenBuffers(1, EBOs);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBOs[0]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    glGenBuffers(NumBuffers, Buffers);
    glBindBuffer(GL_ARRAY_BUFFER, Buffers[ArrayBuffer]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(positions)+sizeof(colors), NULL, GL_STATIC_DRAW);
    glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(positions), positions);
    glBufferSubData(GL_ARRAY_BUFFER, sizeof(positions), sizeof(colors), colors);

    ShaderInfo shaders[]={
        {GL_VERTEX_SHADER,"triangles.vert"},
        {GL_FRAGMENT_SHADER,"triangles.frag"},
        {GL_NONE,NULL}
    };

    GLuint program = LoadShaders(shaders);
    glUseProgram(program);

    int vPosition_loc = glGetAttribLocation(program, "vPosition");
    int vColor_loc = glGetAttribLocation(program, "vColor");
    //cout<<vPosition_loc<<" "<<vColor_loc<<endl;
    glVertexAttribPointer(vPosition_loc, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));
    glEnableVertexAttribArray(vPosition_loc);
    glVertexAttribPointer(vColor_loc, 4, GL_FLOAT, GL_FALSE, 0, (void*)sizeof(positions));
    glEnableVertexAttribArray(vColor_loc);
}

dispaly函數

void dispaly(void)
{
    //glProvokingVertex(GL_FIRST_VERTEX_CONVENTION);//LAST_VERTEX_CONVENTION
    glClear(GL_COLOR_BUFFER_BIT);
    glBindVertexArray(VAOs[Triangles]);
    //glDrawArrays(GL_TRIANGLE_STRIP, 0, NumVertex);
    glDrawElements(GL_TRIANGLES, 12, GL_UNSIGNED_SHORT, NULL);
    glFlush();
}

結果爲: 
這裏寫圖片描述

由此,DrawElements同樣用6個頂點畫出了4個三角形,用DrawArrays則需要12個頂點,省下得不僅是存儲頂點數據的buffer。每個頂點都要經由Shader的計算以及Raster的相關處理,當pipeline中cache一定量的已處理頂點並根據index做擊中測試時,DrawElements將4個三角形所需要的計算和數據帶寬降低到4個頂點(當然還要加上index所佔帶寬)。
在display函數中,DrawElements的最後一個參數indices並未使用。這裏的indices一般在舊式中用到。通常是先用glEnableClientState開啓 
GL_VERTEX_ARRAY,當vertex array enble後,使用數組indices直接調用。

2.2基本Draw函數的變種

2.2.1 *Instanced

變種類型 變種函數
*Instanced DrawArraysInstanced

glDrawArraysInstanced(GLenum mode, GLsizei first, GLsizei count, GLsizei primcount)爲例進行說明。
當vertex buffer中的頂點會被多個圖元頻繁使用時,DrawElements比DrawArrays更爲高效;
當需要將一個模型多次渲染,並且每次具有不同的位置或某種屬性時,DrawArraysInstanced爲更爲有效簡潔。 
在應用中有時需要多次畫同一個模型,但每次該模型的部分屬性有所不同。例如畫樓羣,森林時,可以使用同一個模型,每次爲該模型賦予不同的座標變換,顏色等等。這樣需要調用多次Draw函數,每次使用新的屬性數據等。
DrawArraysInstanced便是爲這種需要準備的,它接收5個參數,前4個參數與DrawArrays一致。我們將其中指定的count個頂點構成的模型稱爲一個Instance,第5個參數primcount表示要重複畫多少個instance。使用glVertexAttribDivisor(GLuint index, GLuint divisor)將序數爲index的屬性指定爲instance屬性,每隔divisor個instance,vertex shader中注進buffer一個新的屬性值。 
在init函數中多添加一句代碼:glVertexAttribDivisor(vColor_loc, 1);

glVertexAttribPointer(vColor_loc, 4, GL_FLOAT, GL_FALSE, 0, (void*)sizeof(positions));//將VBO中的color數據plumbing到vs
glEnableVertexAttribArray(vColor_loc);
glVertexAttribDivisor(vColor_loc, 1);

並修改display函數

void dispaly(void)
{       
    //glProvokingVertex(GL_FIRST_VERTEX_CONVENTION);
    glClear(GL_COLOR_BUFFER_BIT);
    glBindVertexArray(VAOs[Triangles]);
    //glDrawArrays(GL_TRIANGLE_STRIP, 0, NumVertex);
    //glDrawElements(GL_TRIANGLES, 12, GL_UNSIGNED_SHORT, NULL);
    glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, NumVertex, 5);
    glFlush();
}

及vertex shader:

#version 410
in vec4 vPosition;
in vec4 vColor;
out vec4 vs_fs_color;
void main()
{
    gl_Position.x = (vPosition.x + gl_InstanceID*20-64)/64;
    gl_Position.y = (vPosition.y+ gl_InstanceID*20-64)/64;
    gl_Position.zw = vPosition.zw;
    vs_fs_color = vColor;
}

對於Instance drawing,在vertex shader中gl_InstanceID表示每個頂點的Instance序數,這裏使用之作簡單地偏移,將不同Instance的位置區分開來。 
結果如下: 
這裏寫圖片描述 
如果用glVertexAttribDivisor(vColor_loc, 2); 
則結果爲 
這裏寫圖片描述

2.2.2 *BaseVertex

用於擴展DrawElements,當根據indices作爲索引從vertex buffer中讀取數據時,有時希望允許一定數目頂點的偏移——例如當vertex buffer中存着動畫的多幀,需要按照一定的偏移取每一幀的數據進行渲染。該偏移通過一個額外的參數GLint basevertex指定。

2.2.3 *BaseInstance

用於擴展Draw*Instanced,通過一個額外的參數GLint baseInstance指定按照baseInstace作爲偏移從buffer中取出instance屬性的數據。

2.2.4 *BaseInstanceBaseVertex

同時具有前面兩種作用。

2.2.5 *Indirect

變種類型 變種函數
*Indiret DrawArraysIndiret

* DrawArraysIndiret具有DrawArraysInstanced一樣的參數及功能,只不過其參數間接從類型爲GL_DRAW_INDIRECT_BUFFER的buffer中讀出。 
* DrawElementsIndiret具有DrawElementsInstanceBaseVertex一樣的參數及功能,只不過其參數間接從buffer中讀出。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章