[OpenGL] 筆記(1)三角形

小姐姐碎碎念開始,為啥畫三角形這么難... 一會兒要去和姐妹看話劇啦~ 先學(xué)一會兒為了掙錢養(yǎng)自己啦啦啦~ 我爹要是知道我如此努力一定會吐槽我為何還不而給他找個女婿...


畫個三角形
頂點數(shù)組對象:Vertex Array Object撇簿,VAO
頂點緩沖對象:Vertex Buffer Object吭练,VBO
索引緩沖對象:Element Buffer Object昔善,EBO或Index Buffer Object隘竭,IBO

在OpenGL中,任何事物都在3D空間中蟆豫,而屏幕和窗口卻是2D像素數(shù)組。圖形渲染管線(Graphics Pipeline溉跃,大多譯為管線村刨,實際上指的是一堆原始圖形數(shù)據(jù)途經(jīng)一個輸送管道,期間經(jīng)過各種變化處理最終出現(xiàn)在屏幕的過程)可以被劃分為兩個主要部分:第一部分把你的3D坐標(biāo)轉(zhuǎn)換為2D坐標(biāo)撰茎,第二部分是把2D坐標(biāo)轉(zhuǎn)變?yōu)閷嶋H的有顏色的像素嵌牺。

圖形渲染管線可以被劃分為幾個階段,每個階段將會把前一個階段的輸出作為輸入龄糊。所有這些階段都是高度專門化的(它們都有一個特定的函數(shù))逆粹,并且很容易并行執(zhí)行。正是由于它們具有并行執(zhí)行的特性炫惩,當(dāng)今大多數(shù)顯卡都有成千上萬的小處理核心僻弹,它們在GPU上為每一個(渲染管線)階段運(yùn)行各自的小程序,從而在圖形渲染管線中快速處理你的數(shù)據(jù)他嚷。這些小程序叫做著色器(Shader)蹋绽。

因為著色器運(yùn)行在GPU上,所以它們可以給我們節(jié)約寶貴的CPU時間爸舒。OpenGL著色器是用OpenGL著色器語言(OpenGL Shading Language, GLSL)寫成的蟋字。

藍(lán)色部分可以自定義著色器

為了讓OpenGL知道我們的坐標(biāo)和顏色值構(gòu)成的到底是什么,OpenGL需要你去指定這些數(shù)據(jù)所表示的渲染類型扭勉。我們是希望把這些數(shù)據(jù)渲染成一系列的點?一系列的三角形苛聘?還是僅僅是一個長長的線涂炎?做出的這些提示叫做圖元(Primitive),任何一個繪制指令的調(diào)用都將把圖元傳遞給OpenGL设哗。這是其中的幾個:GL_POINTS唱捣、GL_TRIANGLES、GL_LINE_STRIP网梢。

  1. 圖形渲染管線的第一個部分是頂點著色器(Vertex Shader)震缭,它把一個單獨的頂點作為輸入。頂點著色器主要的目的是把3D坐標(biāo)轉(zhuǎn)為另一種3D坐標(biāo)战虏,同時頂點著色器允許我們對頂點屬性進(jìn)行一些基本處理拣宰。

  2. 圖元裝配(Primitive Assembly)階段將頂點著色器輸出的所有頂點作為輸入(如果是GL_POINTS,那么就是一個頂點)烦感,并所有的點裝配成指定圖元的形狀巡社。

  3. 圖元裝配階段的輸出會傳遞給幾何著色器(Geometry Shader)负溪。幾何著色器把圖元形式的一系列頂點的集合作為輸入抄淑,它可以通過產(chǎn)生新頂點構(gòu)造出新的(或是其它的)圖元來生成其他形狀烦味。

  4. 幾何著色器的輸出會被傳入光柵化階段(Rasterization Stage),這里它會把圖元映射為最終屏幕上相應(yīng)的像素楔脯,生成供片段著色器(Fragment Shader)使用的片段(Fragment)。在片段著色器運(yùn)行之前會執(zhí)行裁切(Clipping)姜盈。裁切會丟棄超出你的視圖以外的所有像素径缅,用來提升執(zhí)行效率。
    (OpenGL中的一個片段是OpenGL渲染一個像素所需的所有數(shù)據(jù)姜胖。)

  5. 片段著色器的主要目的是計算一個像素的最終顏色誉帅,這也是所有OpenGL高級效果產(chǎn)生的地方。通常谭期,片段著色器包含3D場景的數(shù)據(jù)(比如光照堵第、陰影、光的顏色等等)隧出,這些數(shù)據(jù)可以被用來計算最終像素的顏色踏志。

  6. 在所有對應(yīng)顏色值確定以后,最終的對象將會被傳到最后一個階段胀瞪,我們叫做Alpha測試和混合(Blending)階段针余。這個階段檢測片段的對應(yīng)的深度(和模板(Stencil))值(后面會講),用它們來判斷這個像素是其它物體的前面還是后面凄诞,決定是否應(yīng)該丟棄圆雁。這個階段也會檢查alpha值(alpha值定義了一個物體的透明度)并對物體進(jìn)行混合(Blend)。所以帆谍,即使在片段著色器中計算出來了一個像素輸出的顏色伪朽,在渲染多個三角形的時候最后的像素顏色也可能完全不同。

可以看到汛蝙,圖形渲染管線非常復(fù)雜烈涮,它包含很多可配置的部分。然而窖剑,對于大多數(shù)場合坚洽,我們只需要配置頂點和片段著色器就行了(因為GPU中沒有默認(rèn)的頂點/片段著色器)。幾何著色器是可選的西土,通常使用它默認(rèn)的著色器就行了讶舰。


頂點輸入

開始繪制圖形之前,我們必須先給OpenGL輸入一些頂點數(shù)據(jù)需了。OpenGL是一個3D圖形庫跳昼,所以我們在OpenGL中指定的所有坐標(biāo)都是3D坐標(biāo)(x、y和z)援所。OpenGL僅當(dāng)3D坐標(biāo)在3個軸(x庐舟、y和z)上都為-1.0到1.0的范圍內(nèi)時才處理它。

讓我們先試試這個頂點三角形(z坐標(biāo)代表一個像素在空間中和你的距離住拭,如果離你遠(yuǎn)就可能被別的像素遮擋挪略,你就看不到它了历帚,它會被丟棄,以節(jié)省資源)

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

頂點著色器:它會在GPU上創(chuàng)建內(nèi)存用于儲存我們的頂點數(shù)據(jù)杠娱,還要配置OpenGL如何解釋這些內(nèi)存挽牢,并且指定其如何發(fā)送給顯卡。

我們通過頂點緩沖對象(Vertex Buffer Objects, VBO)管理這個內(nèi)存摊求,它會在GPU內(nèi)存(通常被稱為顯存)中儲存大量頂點禽拔。使用這些緩沖對象的好處是我們可以一次性的發(fā)送一大批數(shù)據(jù)到顯卡上。

什么是顯卡室叉、GPU睹栖、CPU、顯存茧痕?他們的關(guān)系是啥?
顯卡是插在主板上的擴(kuò)展槽里的野来。它主要負(fù)責(zé)把主機(jī)向顯示器發(fā)出的顯示信號轉(zhuǎn)化為一般電器信號,使得顯示器能明白個人計算機(jī)在讓它做什么踪旷。顯卡主要由顯卡主板曼氛、顯示芯片、顯示存儲器令野、散熱器(散熱片舀患、風(fēng)扇)等部分組成。顯卡的主要芯片叫“顯示芯片”(Video chipset气破,也叫GPU或VPU聊浅,圖形處理器或視覺處理器),是顯卡的主要處理單元现使。顯卡上也有和計算機(jī)存儲器相似的存儲器狗超,稱為“顯示存儲器”,簡稱顯存朴下。
GPU就是圖形核心,也是顯卡的中央處理器苦蒿;CPU中央處理器殴胧,整臺電腦的心臟。

頂點緩沖對象是我們在OpenGL教程中第一個出現(xiàn)的OpenGL對象佩迟。就像OpenGL中的其它對象一樣团滥,這個緩沖有一個獨一無二的ID,所以我們可以使用glGenBuffers函數(shù)和一個緩沖ID生成一個VBO對象:

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

OpenGL有很多緩沖對象類型报强,頂點緩沖對象的緩沖類型是GL_ARRAY_BUFFER灸姊。OpenGL允許我們同時綁定多個緩沖,只要它們是不同的緩沖類型秉溉。我們可以使用glBindBuffer函數(shù)把新創(chuàng)建的緩沖綁定到GL_ARRAY_BUFFER目標(biāo)上:

glBindBuffer(GL_ARRAY_BUFFER, VBO);  

從這一刻起力惯,我們使用的任何(在GL_ARRAY_BUFFER目標(biāo)上的)緩沖調(diào)用都會用來配置當(dāng)前綁定的緩沖VBO碗誉。然后我們可以調(diào)用glBufferData函數(shù),它會把之前定義的頂點數(shù)據(jù)復(fù)制到緩沖的內(nèi)存中:

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

glBufferData是一個專門用來把用戶定義的數(shù)據(jù)復(fù)制到當(dāng)前綁定緩沖的函數(shù)父晶。它的第一個參數(shù)是目標(biāo)緩沖的類型:頂點緩沖對象當(dāng)前綁定到GL_ARRAY_BUFFER目標(biāo)上哮缺。第二個參數(shù)指定傳輸數(shù)據(jù)的大小(以字節(jié)為單位);用一個簡單的sizeof計算出頂點數(shù)據(jù)大小就行甲喝。第三個參數(shù)是我們希望發(fā)送的實際數(shù)據(jù)尝苇。

第四個參數(shù)指定了我們希望顯卡如何管理給定的數(shù)據(jù)。它有三種形式:

  • GL_STATIC_DRAW :數(shù)據(jù)不會或幾乎不會改變埠胖。
  • GL_DYNAMIC_DRAW:數(shù)據(jù)會被改變很多糠溜。
  • GL_STREAM_DRAW :數(shù)據(jù)每次繪制時都會改變。

三角形的位置數(shù)據(jù)不會改變直撤,每次渲染調(diào)用時都保持原樣非竿,所以它的使用類型最好是GL_STATIC_DRAW。如果谊惭,比如說一個緩沖中的數(shù)據(jù)將頻繁被改變汽馋,那么使用的類型就是GL_DYNAMIC_DRAWGL_STREAM_DRAW,這樣就能確保顯卡把數(shù)據(jù)放在能夠高速寫入的內(nèi)存部分圈盔。


頂點著色器

用著色器語言GLSL(OpenGL Shading Language)編寫頂點著色器豹芯,然后編譯這個著色器,這樣我們就可以在程序中使用它了驱敲。下面你會看到一個非程福基礎(chǔ)的GLSL頂點著色器的源代碼:

#version 330 core
// OpenGL 3.3以及和更高版本中,GLSL版本號和OpenGL的版本是匹配的(比如說GLSL 420版本對應(yīng)于OpenGL 4.2)
// 聲明核心模式
layout (location = 0) in vec3 aPos;
// 聲明輸入變量的位置值為0以及pos向量數(shù)
// 在GLSL中一個向量有最多4個分量众眨,每個分量值都代表空間中的一個坐標(biāo)握牧,它們可以通過vec.x、vec.y娩梨、vec.z和vec.w來獲取

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

可以看到沿腰,GLSL看起來很像C語言。每個著色器都起始于一個版本聲明狈定。

然后就可以創(chuà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";

unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);

glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);

glShaderSource函數(shù)把要編譯的著色器對象作為第一個參數(shù)颂龙。第二參數(shù)指定了傳遞的源碼字符串?dāng)?shù)量,這里只有一個(如果第三個參數(shù)傳一個字符串?dāng)?shù)組纽什,就可以用多個字符串拼成一個shader源碼)措嵌。第三個參數(shù)是頂點著色器真正的源碼(字符串),第四個參數(shù)我們先設(shè)置為NULL(多個字符串每個字符串的長度)芦缰。

如果你想知道是不是編譯成功了企巢,還可以用這樣的方式來看有木有錯誤:

int  success;
char infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);

if(!success)
{
    glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
    std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}

片段著色器

片段著色器(Fragment Shader)是第二個也是最后一個我們打算創(chuàng)建的用于渲染三角形的著色器。片段著色器所做的是計算像素最后的顏色輸出让蕾。

片段著色器只需要一個輸出變量浪规,這個變量是一個4分量向量或听,它表示的是最終的輸出顏色:

#version 330 core
out vec4 FragColor;

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

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

unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);

著色器程序

著色器程序?qū)ο?Shader Program Object)是多個著色器合并之后并最終鏈接完成的版本罗丰。如果要使用剛才編譯的著色器我們必須把它們鏈接(Link)為一個著色器程序?qū)ο笊袼В缓笤阡秩緦ο蟮臅r候激活這個著色器程序。已激活著色器程序的著色器將在我們發(fā)送渲染調(diào)用的時候被使用萌抵。
(感覺和app很像)

當(dāng)鏈接著色器至一個程序的時候找御,它會把每個著色器的輸出鏈接到下個著色器的輸入。當(dāng)輸出和輸入不匹配的時候绍填,你會得到一個連接錯誤霎桅。

創(chuàng)建一個程序?qū)ο蠛芎唵危?/p>

unsigned int shaderProgram;
shaderProgram = glCreateProgram();

glCreateProgram函數(shù)創(chuàng)建一個程序,并返回新創(chuàng)建程序?qū)ο蟮腎D引用√钟溃現(xiàn)在我們需要把之前編譯的著色器附加到程序?qū)ο笊咸鲜唬缓笥?code>glLinkProgram鏈接它們:

glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);

link是否成功也可判斷:

glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if(!success) {
    glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
    ...
}

得到的結(jié)果就是一個程序?qū)ο螅覀兛梢哉{(diào)用glUseProgram函數(shù)卿闹,用剛創(chuàng)建的程序?qū)ο笞鳛樗膮?shù)揭糕,以激活這個程序?qū)ο?/strong>:

glUseProgram(shaderProgram);

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

對了著角,在把著色器對象鏈接到程序?qū)ο笠院螅浀脛h除著色器對象旋恼,我們不再需要它們了:

glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);

現(xiàn)在吏口,我們已經(jīng)把輸入頂點數(shù)據(jù)發(fā)送給了GPU,并指示了GPU如何在頂點和片段著色器中處理它冰更。就快要完成了产徊,但還沒結(jié)束,OpenGL還不知道它該如何解釋內(nèi)存中的頂點數(shù)據(jù)蜀细,以及它該如何將頂點數(shù)據(jù)鏈接到頂點著色器的屬性上舟铜。我們需要告訴OpenGL怎么做。


告訴程序如何解釋輸入的頂點數(shù)據(jù)

頂點著色器允許我們指定任何以頂點屬性為形式的輸入奠衔。這使其具有很強(qiáng)的靈活性的同時深滚,它還的確意味著我們必須手動指定輸入數(shù)據(jù)的哪一個部分對應(yīng)頂點著色器的哪一個頂點屬性。所以涣觉,我們必須在渲染前指定OpenGL該如何解釋頂點數(shù)據(jù)。

假設(shè)頂點緩沖數(shù)據(jù)輸入是醬紫:


頂點數(shù)據(jù)
  • 位置數(shù)據(jù)被儲存為32位(4字節(jié))浮點值血柳。
  • 每個位置包含3個這樣的值官册。
  • 在這3個值之間沒有空隙(或其他值)。這幾個值在數(shù)組中緊密排列难捌。
  • 數(shù)據(jù)中第一個值在緩沖開始的位置膝宁。

我們就可以使用glVertexAttribPointer函數(shù)告訴OpenGL該如何解析頂點數(shù)據(jù)(應(yīng)用到逐個頂點屬性上)了:

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

glVertexAttribPointer函數(shù)的參數(shù)非常多鸦难,所以我會逐一介紹它們:

  • 第一個參數(shù)指定我們要配置的頂點屬性。對應(yīng)頂點著色器里面的位置值(location)
  • 第二個參數(shù)指定頂點屬性的大小员淫。頂點屬性是一個vec3合蔽,它由3個值組成,所以大小是3介返。
  • 第三個參數(shù)指定數(shù)據(jù)的類型拴事。
  • 下個參數(shù)定義我們是否希望數(shù)據(jù)被標(biāo)準(zhǔn)化(Normalize)。如果我們設(shè)置為GL_TRUE圣蝎,所有數(shù)據(jù)都會被映射到0(對于有符號型signed數(shù)據(jù)是-1)到1之間刃宵。
  • 第五個參數(shù)叫做步長,它告訴我們在連續(xù)的頂點屬性組之間的間隔徘公。由于下個組位置數(shù)據(jù)在3個float之后牲证,我們把步長設(shè)置為3 * sizeof(float)。要注意的是由于我們知道這個數(shù)組是緊密排列的(在兩個頂點屬性之間沒有空隙)我們也可以設(shè)置為0來讓OpenGL決定具體步長是多少(只有當(dāng)數(shù)值是緊密排列時才可用)关面。
  • 最后一個參數(shù)的類型是void*坦袍,所以需要我們進(jìn)行這個奇怪的強(qiáng)制類型轉(zhuǎn)換。它表示位置數(shù)據(jù)在緩沖中起始位置的偏移量等太。由于位置數(shù)據(jù)在數(shù)組的開頭捂齐,所以這里是0。

每個頂點屬性從一個VBO管理的內(nèi)存中獲得它的數(shù)據(jù)澈驼,而具體是從哪個VBO(程序中可以有多&個VBO)獲取則是通過在調(diào)用glVertexAttribPointer的VBO決定的辛燥。由于在調(diào)用glVertexAttribPointer之前綁定的是先前定義的VBO對象,頂點屬性0現(xiàn)在會鏈接到它的頂點數(shù)據(jù)缝其。

現(xiàn)在我們已經(jīng)定義了OpenGL該如何解釋頂點數(shù)據(jù)挎塌,我們現(xiàn)在應(yīng)該使用glEnableVertexAttribArray,以頂點屬性位置值作為參數(shù)内边,啟用頂點屬性榴都;頂點屬性默認(rèn)是禁用的。

自此漠其,所有東西都已經(jīng)設(shè)置好了:我們使用一個頂點緩沖對象將頂點數(shù)據(jù)初始化至緩沖中嘴高,建立了一個頂點和一個片段著色器,并告訴了OpenGL如何把頂點數(shù)據(jù)鏈接到頂點著色器的頂點屬性上和屎。在OpenGL中繪制一個物體拴驮,代碼會像是這樣:

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

每當(dāng)我們繪制一個物體的時候都必須重復(fù)這一過程。這看起來可能不多柴信,但是如果有超過5個頂點屬性套啤,上百個不同物體呢(這其實并不罕見)。有沒有一些方法可以使我們把所有這些狀態(tài)配置儲存在一個對象中随常,并且可以通過綁定這個對象來恢復(fù)狀態(tài)呢潜沦?


頂點數(shù)組對象

頂點數(shù)組對象(Vertex Array Object, VAO)可以像頂點緩沖對象那樣被綁定萄涯,任何隨后的頂點屬性調(diào)用都會儲存在這個VAO中。這樣的好處就是唆鸡,當(dāng)配置頂點屬性指針時涝影,你只需要將那些調(diào)用執(zhí)行一次,之后再繪制物體的時候只需要綁定相應(yīng)的VAO就行了争占。如果我們綁定VAO失敗燃逻,OpenGL會拒絕繪制任何東西。

image.png

一個頂點數(shù)組對象會儲存以下這些內(nèi)容:

  • glEnableVertexAttribArray和glDisableVertexAttribArray的調(diào)用燃乍。
  • 通過glVertexAttribPointer設(shè)置的頂點屬性配置唆樊。
  • 通過glVertexAttribPointer調(diào)用與頂點屬性關(guān)聯(lián)的頂點緩沖對象。

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

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

要想使用VAO刻蟹,要做的只是使用glBindVertexArray綁定VAO逗旁。從綁定之后起,我們應(yīng)該綁定和配置對應(yīng)的VBO和屬性指針舆瘪,之后解綁VAO供之后使用片效。

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

[...]

// ..:: 繪制代碼(渲染循環(huán)中) :: ..
// 4. 繪制物體
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3); // someOpenGLFunctionThatDrawsOurTriangle();

當(dāng)我們打算繪制物體的時候就拿出相應(yīng)的VAO,綁定它英古,繪制完物體后淀衣,再解綁VAO。

glDrawArrays函數(shù)第一個參數(shù)是我們打算繪制的OpenGL圖元的類型召调。由于我們在一開始時說過膨桥,我們希望繪制的是一個三角形,這里傳遞GL_TRIANGLES給它唠叛。第二個參數(shù)指定了頂點數(shù)組的起始索引只嚣,我們這里填0。最后一個參數(shù)指定我們打算繪制多少個頂點艺沼,這里是3(我們只從我們的數(shù)據(jù)中渲染一個三角形册舞,它只有3個頂點長)。


索引緩沖對象

索引緩沖對象(Element Buffer Object障般,EBO调鲸,也叫Index Buffer Object,IBO)挽荡。要解釋索引緩沖對象的工作方式最好還是舉個例子:假設(shè)我們不再繪制一個三角形而是繪制一個矩形藐石。我們可以繪制兩個三角形來組成一個矩形(OpenGL主要處理三角形)。這會生成下面的頂點的集合:

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,  // 右下角
    -0.5f, -0.5f, 0.0f, // 左下角
    -0.5f, 0.5f, 0.0f   // 左上角
};

可以看到定拟,有幾個頂點疊加了贯钩。我們指定了右下角和左上角兩次!一個矩形只有4個而不是6個頂點,這樣就產(chǎn)生50%的額外開銷角雷。當(dāng)我們有包括上千個三角形的模型之后這個問題會更糟糕,這會產(chǎn)生一大堆浪費性穿。更好的解決方案是只儲存不同的頂點勺三,并設(shè)定繪制這些頂點的順序。

很幸運(yùn)需曾,索引緩沖對象的工作方式正是這樣的吗坚。和頂點緩沖對象一樣,EBO也是一個緩沖呆万,它專門儲存索引商源,OpenGL調(diào)用這些頂點的索引來決定該繪制哪個頂點。首先谋减,我們先要定義(不重復(fù)的)頂點牡彻,和繪制出矩形所需的索引:

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, // 第一個三角形
    1, 2, 3  // 第二個三角形
};

當(dāng)時用索引的時候,我們只定義了4個頂點出爹,而不是6個庄吼。下一步我們需要創(chuàng)建索引緩沖對象:

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

與VBO類似,我們先綁定EBO然后用glBufferData把索引復(fù)制到緩沖里严就。同樣总寻,和VBO類似,我們會把這些函數(shù)調(diào)用放在綁定和解綁函數(shù)調(diào)用之間梢为,只不過這次我們把緩沖的類型定義為·渐行、GL_ELEMENT_ARRAY_BUFFER

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

要注意的是铸董,我們傳遞了GL_ELEMENT_ARRAY_BUFFER當(dāng)作緩沖目標(biāo)祟印。最后一件要做的事是用glDrawElements來替換glDrawArrays函數(shù),來指明我們從索引緩沖渲染袒炉。使用glDrawElements時旁理,我們會使用當(dāng)前綁定的索引緩沖對象中的索引進(jìn)行繪制

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

第一個參數(shù)指定了我們繪制的模式,這個和glDrawArrays的一樣我磁。第二個參數(shù)是我們打算繪制頂點的個數(shù)孽文,這里填6,也就是說我們一共需要繪制6個頂點夺艰。第三個參數(shù)是索引的類型芋哭,這里是GL_UNSIGNED_INT。最后一個參數(shù)里我們可以指定EBO中的偏移量(或者傳遞一個索引數(shù)組郁副,但是這是當(dāng)你不在使用索引緩沖對象的時候)减牺,但是我們會在這里填寫0。

不過VAO同樣可以保存索引緩沖對象的綁定狀態(tài)。VAO綁定時正在綁定的索引緩沖對象會被保存為VAO的元素緩沖對象拔疚。綁定VAO的同時也會自動綁定EBO肥隆。

VAO & VBO & EBO

當(dāng)目標(biāo)是GL_ELEMENT_ARRAY_BUFFER的時候,VAO會儲存glBindBuffer的函數(shù)調(diào)用稚失。這也意味著它也會儲存解綁調(diào)用栋艳,所以確保你沒有在解綁VAO之前解綁索引數(shù)組緩沖,否則它就沒有這個EBO配置了句各。注意哦吸占,如果是正常的VBO的glBindBuffer是不存儲的,只存儲GL_ELEMENT_ARRAY_BUFFER也就是EBO的

來畫正方形啦:

// ..:: 初始化代碼 :: ..
// 1. 綁定頂點數(shù)組對象
glBindVertexArray(VAO);
// 2. 把我們的頂點數(shù)組復(fù)制到一個頂點緩沖中凿宾,供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. 復(fù)制我們的索引數(shù)組到一個索引緩沖中矾屯,供OpenGL使用
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 4. 設(shè)定頂點屬性指針
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);
  • 線框模式(Wireframe Mode)
    要想用線框模式繪制你的三角形,你可以通過glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)函數(shù)配置OpenGL如何繪制圖元初厚。第一個參數(shù)表示我們打算將其應(yīng)用到所有的三角形的正面和背面件蚕,第二個參數(shù)告訴我們用線來繪制。之后的繪制調(diào)用會一直以線框模式繪制三角形惧所,直到我們用glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)將其設(shè)置回默認(rèn)模式骤坐。

bind到0就是解綁哦~


Homework

1. 畫個漸變?nèi)切?/h5>

這里要說一下就是如果你給三個頂點賦值顏色,會自動插值填充漸變哈~ 以及顏色的輸入也是要和頂點數(shù)據(jù)放在一起噠下愈,都輸入給頂點shader纽绍,然后頂點把顏色輸出給著色shader醬紫~

這是在片段著色器中進(jìn)行的所謂片段插值(Fragment Interpolation)的結(jié)果。當(dāng)渲染一個三角形時势似,光柵化(Rasterization)階段通常會造成比原指定頂點更多的片段拌夏。光柵會根據(jù)每個片段在三角形形狀上所處相對位置決定這些片段的位置。

基于這些位置履因,它會插值(Interpolate)所有片段著色器的輸入變量障簿。比如說,我們有一個線段栅迄,上面的端點是綠色的站故,下面的端點是藍(lán)色的。如果一個片段著色器在線段的70%的位置運(yùn)行毅舆,它的顏色輸入屬性就會是一個綠色和藍(lán)色的線性結(jié)合西篓;更精確地說就是30%藍(lán) + 70%綠。

這正是在這個三角形中發(fā)生了什么憋活。我們有3個頂點岂津,和相應(yīng)的3個顏色,從這個三角形的像素來看它可能包含50000左右的片段悦即,片段著色器為這些像素進(jìn)行插值顏色吮成。如果你仔細(xì)看這些顏色就應(yīng)該能明白了:紅首先變成到紫再變?yōu)樗{(lán)色橱乱。片段插值會被應(yīng)用到片段著色器的所有輸入屬性上。

漸變?nèi)切?/div>

來來來上代碼了~

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

const char *fragmentShaderSource = "#version 330 core\n"
    "in vec3 outColor;\n"
    "out vec4 FragColor;\n"
    "void main()\n"
    "{\n"
    "   FragColor = vec4(outColor, 1.0);\n"
    "}\0";

void drawGradientTriangle()
{
    // draw triangle
    float vertices[] = {
        -0.5f, -0.5f, 0.0f, 1.f, 0.f, 0.f,
         0.5f, -0.5f, 0.0f, 0.f, 1.f, 0.f,
         0.0f,  0.5f, 0.0f, 0.f, 0.f, 1.f,
    };
    
    unsigned int VBO;
    glGenBuffers(1, &VBO);
    
    unsigned int VAO;
    glGenVertexArrays(1, &VAO);
    
    glBindVertexArray(VAO);
    
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    
    // read vertex
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);
    
    // unbind VBO
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    
    // unbind VAO
    glBindVertexArray(0);
    
    // create vertices shader
    unsigned int vertexShader;
    vertexShader = glCreateShader(GL_VERTEX_SHADER);

    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);
    
    int  success;
    char infoLog[512];
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);

    if(!success)
    {
        glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    
    // color shader
    unsigned int fragmentShader;
    fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);
    
    // program
    unsigned int shaderProgram;
    shaderProgram = glCreateProgram();
    
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);
    
    // use this program
    glUseProgram(shaderProgram);
    glBindVertexArray(VAO);
    // draw
    glDrawArrays(GL_TRIANGLES, 0, 3);
    
    // delete shader
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);
}

需要注意的點是頂點shader輸出的值和著色器shader輸入的值的變量名都必須一樣哈~粱甫,以及我們這次終于懂了location的用法了泳叠,也就是第幾個參數(shù)的意義了。另外后面的參數(shù)的偏移記得需要??float的size哈~

漸變?nèi)堑淖⒁馐马?/div>

2. 繪制兩個彼此相連的三角形
相連三角形

這里其實就是傳入6個頂點茶宵,然后指定要畫六個頂點即可析二,其他沒區(qū)別~ 他會自動3個頂點一組去繪制噠~ 那可能會疑惑,如果我想把這6個點彼此相連节预,搞成3個三角形呢,那你就要用EBO了哦~~

float vertices[] = {
    -0.5f, -0.5f, 0.0f, 1.f, 0.f, 0.f,
    0.0f, -0.5f, 0.0f, 0.f, 1.f, 0.f,
    -0.25f,  0.5f, 0.0f, 0.f, 0.f, 1.f,
    0.5f, -0.5f, 0.0f, 1.f, 0.f, 0.f,
    0.0f, -0.5f, 0.0f, 0.f, 1.f, 0.f,
    0.25f,  0.5f, 0.0f, 0.f, 0.f, 1.f,
};

// draw
glDrawArrays(GL_TRIANGLES, 0, 6);

3. 使用不同的VAO和VBO創(chuàng)建兩個三角形

當(dāng)我想用不同著色shader來渲染兩個三角形的時候属韧,那就不能用同一份頂點了哈安拟,也就不是同一個program了,那么如果用兩個VAO / VBO嘞宵喂?

// draw triangle
float vertices[] = {
    -0.5f, -0.5f, 0.0f, 1.f, 0.f, 0.f,
    0.0f, -0.5f, 0.0f, 0.f, 1.f, 0.f,
    -0.25f,  0.5f, 0.0f, 0.f, 0.f, 1.f,
};

float vertices2[] = {
    0.5f, -0.5f, 0.0f, 1.f, 0.f, 0.f,
    0.0f, -0.5f, 0.0f, 0.f, 1.f, 0.f,
    0.25f,  0.5f, 0.0f, 0.f, 0.f, 1.f,
};

unsigned int VBO[2];
glGenBuffers(2, VBO);

unsigned int VAO[2];
glGenVertexArrays(2, VAO);

glBindVertexArray(VAO[0]);

glBindBuffer(GL_ARRAY_BUFFER, VBO[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

// read vertex
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);

// unbind VBO
glBindBuffer(GL_ARRAY_BUFFER, 0);

// unbind VAO
glBindVertexArray(0);

// -------------- next VAO & VBO ---------------

glBindVertexArray(VAO[1]);

glBindBuffer(GL_ARRAY_BUFFER, VBO[1]);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices2, GL_STATIC_DRAW);

// read vertex
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);

// unbind VBO
glBindBuffer(GL_ARRAY_BUFFER, 0);

// unbind VAO
glBindVertexArray(0);

這里需要注意的是創(chuàng)建的是數(shù)組哈糠赦,以及因為數(shù)據(jù)本身就是地址(第一個元素的地址),所以無需使用&啦:

unsigned int VBO[2];
glGenBuffers(2, VBO);

然后就是繪制啦~

// use this program
glUseProgram(shaderProgram);
glBindVertexArray(VAO[0]);
// draw
glDrawArrays(GL_TRIANGLES, 0, 3);

// next
glBindVertexArray(VAO[1]);
// draw
glDrawArrays(GL_TRIANGLES, 0, 3);

最后畫出來的效果和上面的其實是一樣的锅棕。


4. 創(chuàng)建兩個著色器程序繪制兩個顏色不同的三角形

現(xiàn)在就要用上面的兩套頂點來用不同的program啦~


截屏2021-02-04 下午11.37.35.png

需要新增一個著色器shader:

const char *fixedFragmentShaderSource = "#version 330 core\n"
    "out vec4 FragColor;\n"
    "void main()\n"
    "{\n"
    "   FragColor = vec4(1.0, 1.0, 0.0, 1.0);\n"
    "}\0";

然后創(chuàng)建兩個program分別用不同的VAO~

// color shader
unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);


unsigned int fragmentShader2;
fragmentShader2 = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader2, 1, &fixedFragmentShaderSource, NULL);
glCompileShader(fragmentShader2);

// program
unsigned int shaderProgram;
shaderProgram = glCreateProgram();

glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);

// program
unsigned int shaderProgram2;
shaderProgram2 = glCreateProgram();

glAttachShader(shaderProgram2, vertexShader);
glAttachShader(shaderProgram2, fragmentShader2);
glLinkProgram(shaderProgram2);

// use this program
glUseProgram(shaderProgram);
glBindVertexArray(VAO[0]);
// draw
glDrawArrays(GL_TRIANGLES, 0, 3);

glUseProgram(shaderProgram2);
// next
glBindVertexArray(VAO[1]);
// draw
glDrawArrays(GL_TRIANGLES, 0, 3);

好啦大功告成~ 睡啦睡啦~

refer to : https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/
debug: https://learnopengl.com/In-Practice/Debugging
https://www.cnblogs.com/icyhusky/p/10686294.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拙泽,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子裸燎,更是在濱河造成了極大的恐慌顾瞻,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,807評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件德绿,死亡現(xiàn)場離奇詭異荷荤,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)移稳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評論 3 399
  • 文/潘曉璐 我一進(jìn)店門蕴纳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人个粱,你說我怎么就攤上這事古毛。” “怎么了都许?”我有些...
    開封第一講書人閱讀 169,589評論 0 363
  • 文/不壞的土叔 我叫張陵稻薇,是天一觀的道長。 經(jīng)常有香客問我梭稚,道長颖低,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,188評論 1 300
  • 正文 為了忘掉前任弧烤,我火速辦了婚禮忱屑,結(jié)果婚禮上蹬敲,老公的妹妹穿的比我還像新娘。我一直安慰自己莺戒,他們只是感情好伴嗡,可當(dāng)我...
    茶點故事閱讀 69,185評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著从铲,像睡著了一般瘪校。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上名段,一...
    開封第一講書人閱讀 52,785評論 1 314
  • 那天阱扬,我揣著相機(jī)與錄音,去河邊找鬼伸辟。 笑死麻惶,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的信夫。 我是一名探鬼主播窃蹋,決...
    沈念sama閱讀 41,220評論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼静稻!你這毒婦竟也來了警没?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,167評論 0 277
  • 序言:老撾萬榮一對情侶失蹤振湾,失蹤者是張志新(化名)和其女友劉穎杀迹,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體恰梢,經(jīng)...
    沈念sama閱讀 46,698評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡佛南,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,767評論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了嵌言。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嗅回。...
    茶點故事閱讀 40,912評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖摧茴,靈堂內(nèi)的尸體忽然破棺而出绵载,到底是詐尸還是另有隱情,我是刑警寧澤苛白,帶...
    沈念sama閱讀 36,572評論 5 351
  • 正文 年R本政府宣布娃豹,位于F島的核電站,受9級特大地震影響购裙,放射性物質(zhì)發(fā)生泄漏懂版。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,254評論 3 336
  • 文/蒙蒙 一躏率、第九天 我趴在偏房一處隱蔽的房頂上張望躯畴。 院中可真熱鬧民鼓,春花似錦、人聲如沸蓬抄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,746評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嚷缭。三九已至饮亏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間阅爽,已是汗流浹背路幸。 一陣腳步聲響...
    開封第一講書人閱讀 33,859評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留付翁,地道東北人劝赔。 一個月前我還...
    沈念sama閱讀 49,359評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像胆敞,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子杂伟,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,922評論 2 361

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