OpenGL學(xué)習(xí)--你好,三角形

  1. 基本概念
    頂點(diǎn)數(shù)組對(duì)象:Vertex Array Object,VAO
    頂點(diǎn)緩沖對(duì)象:Vertex Buffer Object,VBO
    索引緩沖對(duì)象:Element Buffer Object,EBO或Index Buffer Object,IBO
  2. 圖形渲染管線(管線):3D空間坐標(biāo)-->2D屏幕坐標(biāo)處理,一堆原始圖形數(shù)據(jù)途徑一個(gè)輸送管道,期間經(jīng)過各種變化處理最終出現(xiàn)在屏幕的過程。圖形渲染管線可以被劃分為兩個(gè)主要部分:1)把你的3D坐標(biāo)轉(zhuǎn)換為2D坐標(biāo);2)把2D坐標(biāo)轉(zhuǎn)變?yōu)閷?shí)際有顏色的像素。每個(gè)階段會(huì)把前一個(gè)階段的輸出作為輸入,每個(gè)階段運(yùn)行各自的小程序,從而在圖形渲染管線中快速處理數(shù)據(jù),這些小程序叫做著色器,GPU上運(yùn)行。
  3. 圖形渲染管線階段介紹,以制作三角形為例:
    1)以數(shù)組形式傳遞3個(gè)3D坐標(biāo),用來表示一個(gè)三角形,這個(gè)數(shù)組叫做頂點(diǎn)數(shù)據(jù);圖元:指出的渲染類型
    2)頂點(diǎn)著色器階段:把一個(gè)單獨(dú)的頂點(diǎn)作為輸入,每個(gè)輸入變量也叫頂點(diǎn)屬性,目的:把3D坐標(biāo)轉(zhuǎn)為另一種3D坐標(biāo);頂點(diǎn)著色器允許我們對(duì)頂點(diǎn)屬性進(jìn)行一些基本處理
    3)圖元裝配階段:將頂點(diǎn)著色器輸出的所有頂點(diǎn)作為輸入,所有的點(diǎn)裝配成指定圖元的形狀;例:三角形
    4)幾何著色器:圖元裝配階段的輸出會(huì)傳遞給幾何著色器,把圖元形式的一系列頂點(diǎn)幾何作為輸入,它可以通過產(chǎn)生新頂點(diǎn)構(gòu)造出新的圖元來生成其他形狀。例:生成了另一個(gè)三角形
    5)光柵化階段:幾何著色器的輸出會(huì)被傳入光柵化階段,把圖元映射為最終屏幕上相應(yīng)的像素,生成供片段著色器使用的片段(OpenGL中的一個(gè)片段是OpenGL渲染一個(gè)像素所需的所有數(shù)據(jù))。在片段著色器運(yùn)行之前會(huì)執(zhí)行裁剪:丟棄超出視圖以外的所有像素,用來提升執(zhí)行效率。
    6)片段著色器:計(jì)算一個(gè)像素的最終顏色,所有OpenGL高級(jí)效果產(chǎn)生的部分。通常,片段著色器包含3D場(chǎng)景的數(shù)據(jù)(光照、陰影、光的顏色等等),這些數(shù)據(jù)可以被用來計(jì)算最終像素的顏色。
    7)Alpha測(cè)試和混合階段:檢測(cè)片段對(duì)應(yīng)的深度值,用來判斷這個(gè)像素是其他物體前面還是后面,決定是否應(yīng)該丟棄。這個(gè)階段也會(huì)檢查alpha值(alpha值定義了一個(gè)物體的透明度)并對(duì)物體進(jìn)行混合。
    在現(xiàn)代OpenGL中,我們必須定義至少一個(gè)頂點(diǎn)著色器和一個(gè)片段著色器(因?yàn)镚PU中沒有默認(rèn)的頂點(diǎn)/片段著色器)
    圖形渲染管線階段.png
頂點(diǎn)輸入

OpenGL僅當(dāng)3D坐標(biāo)在3個(gè)軸(x、y、z)上都為-1.0到1.0的范圍內(nèi)時(shí)才處理它。深度通常為Z坐標(biāo)的輸入,它代表一個(gè)像素空間中和你的距離。以三角形為例:三個(gè)float型頂點(diǎn)的3D位置數(shù)組如下:

float vertices[] = {
    -0.5f, -0.5f, 0.0f,
     0.5f, -0.5f, 0.0f,
     0.0f,  0.5f, 0.0f
};

標(biāo)準(zhǔn)化設(shè)備坐標(biāo)<(-1.0,1.0)區(qū)間>接著會(huì)變換為屏幕空間坐標(biāo)(Screen-space Coordinates),這是使用你通過glViewport函數(shù)提供的數(shù)據(jù),進(jìn)行視口變換(Viewport Transform)完成的。所得的屏幕空間坐標(biāo)又會(huì)被變換為片段輸入到片段著色器中。

定義這樣的頂點(diǎn)數(shù)據(jù)以后,我們會(huì)把它作為輸入發(fā)送給渲染管線的第一個(gè)處理階段:頂點(diǎn)著色器。
它會(huì)在GPU上創(chuàng)建內(nèi)存用于儲(chǔ)存我們的頂點(diǎn)數(shù)據(jù),還要配置OpenGL如何解釋這些內(nèi)存,并且制定其如何發(fā)送給顯卡。頂點(diǎn)著色器接著會(huì)處理我們?cè)趦?nèi)存中指定數(shù)量的頂點(diǎn)。
我們通過頂點(diǎn)緩沖對(duì)象(VBO)管理這個(gè)內(nèi)存,它會(huì)在GPU內(nèi)存(顯存)中儲(chǔ)存大量頂點(diǎn)。使用這些緩沖對(duì)象的好處是我們可以一次性的發(fā)送一大批數(shù)據(jù)到顯卡上,而不是每個(gè)頂點(diǎn)發(fā)送一次,從CPU把數(shù)據(jù)發(fā)送到顯卡相對(duì)較慢,所以盡量一次性發(fā)送盡可能多的數(shù)據(jù)。
頂點(diǎn)緩沖對(duì)象有一個(gè)獨(dú)一無二的ID,可以使用glGenBuffers函數(shù)和一個(gè)緩沖ID生成一個(gè)VBO對(duì)象。

unsigned int VBO;
glGenBuffers(1, &VBO);

OpenGL有很多緩沖對(duì)象類型,頂點(diǎn)緩沖對(duì)象的緩沖類型是GL_ARRAY_BUFFER。OpenGL允許我們同事綁定多個(gè)緩沖,只要他們是不同的緩沖類型。我們可以使用glbindbuffer函數(shù)把新創(chuàng)建的緩沖綁定到GL_ARRAY_BUFFER上:

glBindBuffer(GL_ARRAY_BUFFER, VBO);  

可以調(diào)用glBufferData函數(shù),它會(huì)把之前定義的頂點(diǎn)數(shù)據(jù)復(fù)制到緩沖的內(nèi)存中:

glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

glBufferData是一個(gè)專門用來把用戶定義的數(shù)據(jù)復(fù)制到當(dāng)前綁定緩沖的函數(shù)。它的第一個(gè)參數(shù)是目標(biāo)緩沖的類型:頂點(diǎn)緩沖對(duì)象當(dāng)前綁定到GL_ARRAY_BUFFER目標(biāo)上。第二個(gè)參數(shù)指定傳輸數(shù)據(jù)的大小(以字節(jié)為單位);用一個(gè)簡(jiǎn)單的sizeof計(jì)算出頂點(diǎn)數(shù)據(jù)大小就行。第三個(gè)參數(shù)是我們希望發(fā)送的實(shí)際數(shù)據(jù)。
第四個(gè)參數(shù)指定了我們希望顯卡如何管理給定的數(shù)據(jù)。它有三種形式:
GL_STATIC_DRAW :數(shù)據(jù)不會(huì)或幾乎不會(huì)改變。
GL_DYNAMIC_DRAW:數(shù)據(jù)會(huì)被改變很多。
GL_STREAM_DRAW :數(shù)據(jù)每次繪制時(shí)都會(huì)改變。

頂點(diǎn)著色器
#version 330 core
layout (location = 0) in vec3 aPos;

void main()
{
    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}

gl_Position設(shè)置的值會(huì)成為該頂點(diǎn)著色器的輸出,必須是將三分量轉(zhuǎn)換為四分量,w分量設(shè)置為1.0。

編譯著色器

現(xiàn)在,我們暫時(shí)將頂點(diǎn)著色器的源代碼硬編碼在代碼文件頂部的C風(fēng)格字符串中:

const char *vertexShaderSource = "#version 330 core\n"
    "layout (location = 0) in vec3 aPos;\n"
    "void main()\n"
    "{\n"
    "   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
    "}\0";

為了能夠讓OpenGL使用它,我們必須在運(yùn)行時(shí)動(dòng)態(tài)編譯它的源代碼。

我們首先要做的是創(chuàng)建一個(gè)著色器對(duì)象,注意還是用ID來引用的。所以我們儲(chǔ)存這個(gè)頂點(diǎn)著色器為unsigned int,然后用glCreateShader創(chuàng)建這個(gè)著色器:

unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
/* glShaderSource把要編譯的著色器作為第一個(gè)參數(shù),第二參數(shù)指定了傳遞的源碼字符串?dāng)?shù)量,
這里只有一個(gè)。第三個(gè)參數(shù)是頂點(diǎn)著色器真正的源碼,第四個(gè)參數(shù)我們先設(shè)置為NULL。*/
glCompileShader(vertexShader);
片段著色器

計(jì)算像素最后的顏色輸出,在計(jì)算機(jī)圖形中顏色被表示為有4個(gè)元素的數(shù)組:紅色、綠色、藍(lán)色和alpha(透明度)分量,通常縮寫為RGBA。當(dāng)在OpenGL或GLSL中定義一個(gè)顏色的時(shí)候,我們把顏色每個(gè)分量的強(qiáng)度設(shè)置在0.0到1.0之間。

#version 330 core
out vec4 FragColor;

void main()
{
    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
} 

編譯片段著色器的過程與頂點(diǎn)著色器類似,只不過我們使用GL_FRAGMENT_SHADER常量作為著色器類型:

unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
著色器程序

多個(gè)著色器合并之后并最終鏈接完成的版本,然后在渲染對(duì)象的時(shí)候激活這個(gè)著色器程序,已激活著色器程序的著色器將在我們發(fā)送渲染調(diào)用的時(shí)候被使用。
創(chuàng)建一個(gè)程序?qū)ο?/p>

unsigned int shaderProgram;
shaderProgram = glCreateProgram();//創(chuàng)建一個(gè)程序,并返回新創(chuàng)建程序?qū)ο蟮腎D引用。
//現(xiàn)在我們需要把之前編譯的著色器附加到程序?qū)ο笊希缓笥胓lLinkProgram鏈接它們:
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);

得到的結(jié)果就是一個(gè)程序?qū)ο?,我們可以調(diào)用glUseProgram函數(shù),用剛創(chuàng)建的程序?qū)ο笞鳛樗膮?shù),以激活這個(gè)程序?qū)ο螅?/p>

glUseProgram(shaderProgram);

在glUseProgram函數(shù)調(diào)用之后,每個(gè)著色器調(diào)用和渲染調(diào)用都會(huì)使用這個(gè)程序?qū)ο螅ㄒ簿褪侵皩懙闹?了。

//刪除著色器對(duì)象
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
鏈接頂點(diǎn)屬性

我們必須在渲染前指定OpenGL該如何解釋頂點(diǎn)數(shù)據(jù)。


頂點(diǎn)緩沖數(shù)據(jù)解析

說明:
1)位置數(shù)據(jù)被儲(chǔ)存為32位(4字節(jié))浮點(diǎn)值。
2)每個(gè)位置包含3個(gè)這樣的值。
3)在這3個(gè)值之間沒有空隙(或其他值)。這幾個(gè)值在數(shù)組中緊密排列(Tightly Packed)。
4)數(shù)據(jù)中第一個(gè)值在緩沖開始的位置。

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

glVertexAttribPointer函數(shù)的參數(shù)非常多,所以我會(huì)逐一介紹它們:
1)第一個(gè)參數(shù)指定我們要配置的頂點(diǎn)屬性。還記得我們?cè)陧旤c(diǎn)著色器中使用layout(location = 0)定義了position頂點(diǎn)屬性的位置值(Location)嗎?它可以把頂點(diǎn)屬性的位置值設(shè)置為0。因?yàn)槲覀兿M褦?shù)據(jù)傳遞到這一個(gè)頂點(diǎn)屬性中,所以這里我們傳入0。
2)第二個(gè)參數(shù)指定頂點(diǎn)屬性的大小。頂點(diǎn)屬性是一個(gè)vec3,它由3個(gè)值組成,所以大小是3。
3)第三個(gè)參數(shù)指定數(shù)據(jù)的類型,這里是GL_FLOAT(GLSL中vec*都是由浮點(diǎn)數(shù)值組成的)。
4)下個(gè)參數(shù)定義我們是否希望數(shù)據(jù)被標(biāo)準(zhǔn)化(Normalize)。如果我們?cè)O(shè)置為GL_TRUE,所有數(shù)據(jù)都會(huì)被映射到0(對(duì)于有符號(hào)型signed數(shù)據(jù)是-1)到1之間。我們把它設(shè)置為GL_FALSE。
5)第五個(gè)參數(shù)叫做步長(zhǎng)(Stride),它告訴我們?cè)谶B續(xù)的頂點(diǎn)屬性組之間的間隔。由于下個(gè)組位置數(shù)據(jù)在3個(gè)float之后,我們把步長(zhǎng)設(shè)置為3 * sizeof(float)。要注意的是由于我們知道這個(gè)數(shù)組是緊密排列的(在兩個(gè)頂點(diǎn)屬性之間沒有空隙)我們也可以設(shè)置為0來讓OpenGL決定具體步長(zhǎng)是多少(只有當(dāng)數(shù)值是緊密排列時(shí)才可用)。一旦我們有更多的頂點(diǎn)屬性,我們就必須更小心地定義每個(gè)頂點(diǎn)屬性之間的間隔,我們?cè)诤竺鏁?huì)看到更多的例子(譯注: 這個(gè)參數(shù)的意思簡(jiǎn)單說就是從這個(gè)屬性第二次出現(xiàn)的地方到整個(gè)數(shù)組0位置之間有多少字節(jié))。
6)最后一個(gè)參數(shù)的類型是void *,所以需要我們進(jìn)行這個(gè)奇怪的強(qiáng)制類型轉(zhuǎn)換。它表示位置數(shù)據(jù)在緩沖中起始位置的偏移量(Offset)。由于位置數(shù)據(jù)在數(shù)組的開頭,所以這里是0。我們會(huì)在后面詳細(xì)解釋這個(gè)參數(shù)。
在OpenGL中繪制一個(gè)物體,代碼會(huì)像是這樣:

// 0. 復(fù)制頂點(diǎn)數(shù)組到緩沖中供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 1. 設(shè)置頂點(diǎn)屬性指針
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 2. 當(dāng)我們渲染一個(gè)物體時(shí)要使用著色器程序
glUseProgram(shaderProgram);
// 3. 繪制物體
someOpenGLFunctionThatDrawsOurTriangle();
頂點(diǎn)數(shù)組對(duì)象(VAO)

頂點(diǎn)數(shù)組對(duì)象(Vertex Array Object, VAO)可以像頂點(diǎn)緩沖對(duì)象那樣被綁定,任何隨后的頂點(diǎn)屬性調(diào)用都會(huì)儲(chǔ)存在這個(gè)VAO中。這樣的好處就是,當(dāng)配置頂點(diǎn)屬性指針時(shí),你只需要將那些調(diào)用執(zhí)行一次,之后再繪制物體的時(shí)候只需要綁定相應(yīng)的VAO就行了。這使在不同頂點(diǎn)數(shù)據(jù)和屬性配置之間切換變得非常簡(jiǎn)單,只需要綁定不同的VAO就行了。剛剛設(shè)置的所有狀態(tài)都將存儲(chǔ)在VAO中。


頂點(diǎn)數(shù)組對(duì)象

一個(gè)頂點(diǎn)數(shù)組對(duì)象會(huì)儲(chǔ)存以下這些內(nèi)容:
1)glEnableVertexAttribArray和glDisableVertexAttribArray的調(diào)用。
2)通過glVertexAttribPointer設(shè)置的頂點(diǎn)屬性配置。
3)通過glVertexAttribPointer調(diào)用與頂點(diǎn)屬性關(guān)聯(lián)的頂點(diǎn)緩沖對(duì)象。

創(chuàng)建一個(gè)VAO和創(chuàng)建一個(gè)VBO很類似:

unsigned int VAO;
glGenVertexArrays(1, &VAO);

要想使用VAO,要做的只是使用glBindVertexArray綁定VAO。從綁定之后起,我們應(yīng)該綁定和配置對(duì)應(yīng)的VBO和屬性指針,之后解綁VAO供之后使用。當(dāng)我們打算繪制一個(gè)物體的時(shí)候,我們只要在繪制物體前簡(jiǎn)單地把VAO綁定到希望使用的設(shè)定上就行了。這段代碼應(yīng)該看起來像這樣:

// ..:: 初始化代碼(只運(yùn)行一次 (除非你的物體頻繁改變)) :: ..
// 1. 綁定VAO
glBindVertexArray(VAO);
// 2. 把頂點(diǎn)數(shù)組復(fù)制到緩沖中供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. 設(shè)置頂點(diǎn)屬性指針
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

[...]

// ..:: 繪制代碼(渲染循環(huán)中) :: ..
// 4. 繪制物體
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
someOpenGLFunctionThatDrawsOurTriangle();
索引緩沖對(duì)象EBO:專門存儲(chǔ)索引

索引繪制:先要定義(不重復(fù)的)頂點(diǎn),和繪制出矩形所需的索引。例:

float vertices[] = {
    0.5f, 0.5f, 0.0f,   // 右上角
    0.5f, -0.5f, 0.0f,  // 右下角
    -0.5f, -0.5f, 0.0f, // 左下角
    -0.5f, 0.5f, 0.0f   // 左上角
};

unsigned int indices[] = { // 注意索引從0開始! 
    0, 1, 3, // 第一個(gè)三角形
    1, 2, 3  // 第二個(gè)三角形
}
索引緩沖對(duì)象

最后的初始化和繪制代碼現(xiàn)在看起來像這樣:

// ..:: 初始化代碼 :: ..
// 1. 綁定頂點(diǎn)數(shù)組對(duì)象
glBindVertexArray(VAO);
// 2. 把我們的頂點(diǎn)數(shù)組復(fù)制到一個(gè)頂點(diǎn)緩沖中,供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. 復(fù)制我們的索引數(shù)組到一個(gè)索引緩沖中,供OpenGL使用
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 4. 設(shè)定頂點(diǎn)屬性指針
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

[...]

// ..:: 繪制代碼(渲染循環(huán)中) :: ..
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0)
glBindVertexArray(0);
三角形渲染過程
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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