關(guān)鍵英文術(shù)語
- Vertex Array Object(VAO) 頂點(diǎn)數(shù)組對象
- Vertex Buffer Object(VBO) 頂點(diǎn)緩沖對象
- Element Buffer Object(EBO) 索引緩沖對象
你好牢撼,三角形
在OpenGL中百揭,任何事物都在3D空間中聋迎,而屏幕和窗口卻是2D像素數(shù)組飞涂,這導(dǎo)致OpenGL的大部分工作都是關(guān)于把3D坐標(biāo)轉(zhuǎn)換為適配你屏幕的2D像素嫌褪。
3D坐標(biāo)轉(zhuǎn)換為2D坐標(biāo)的處理過程就是OpenGL的圖形渲染管線(Graphics Pipeline), 實(shí)際上指的就是一堆原始圖形數(shù)據(jù)經(jīng)過一個輸送管道呀枢,期間經(jīng)過各種變化處理,最終出現(xiàn)在屏幕上的過程笼痛。
Shader
圖形渲染管線可以分為幾個階段裙秋,每個階段將會把前一個階段的輸出作為輸入。所有這些階段都是高度專門化的缨伊,并且很容易被并行執(zhí)行摘刑。由于并行的特性,當(dāng)今大多數(shù)的顯卡都具有成千上萬的小處理核心刻坊,他們在GPU上為每一個階段(渲染管線)運(yùn)行著自己的小程序枷恕,從而來快速處理你的數(shù)據(jù)。這些小程序叫做著色器(Shader)谭胚。
有些著色器允許開發(fā)者自己配置徐块,這就允許我們用自己編寫的著色器程序來替換默認(rèn)的。
下圖抽象化展示了圖形渲染管線的每個階段灾而。注意藍(lán)色的部分代表可編程(自定義著色器)的階段胡控。
頂點(diǎn)著色器(vertex shader), 它把一個單獨(dú)的頂點(diǎn)作為輸入。頂點(diǎn)著色器主要的目的是把3D坐標(biāo)轉(zhuǎn)換到ClipSpace中绰疤。
圖元裝配(Primitive Assembly)階段將頂點(diǎn)著色器輸出的所有頂點(diǎn)作為輸入铜犬,并將所有的點(diǎn)裝配為指定的圖元形狀(GL_POINTS, GL_TRIANGLES, GL_LINE_STRIP等形狀)。
圖元裝配階段的輸出將會傳遞給幾何著色器(Geometry Shader)。它把圖元形式的一系列頂點(diǎn)的集合作為輸入癣猾,可以通過產(chǎn)生新頂點(diǎn)構(gòu)造出新圖元來生成其他形狀敛劝。(具體實(shí)現(xiàn)?)
幾何著色器的輸出將會傳遞給光柵化(Rasterization Stage)階段, 這里它會把圖元映射到最終屏幕上相應(yīng)的像素,生成供片段著色器(Fragment Shader)使用的片段纷宇。在片段著色器運(yùn)行之前會執(zhí)行裁切(Clipping)夸盟。裁切會丟棄超出你的視圖以外的所有像素,用于提升執(zhí)行效率像捶。(如何實(shí)現(xiàn)圖元到像素的映射?)
片段著色器的主要目的是計算一個像素的最終顏色上陕,這也是所有OpenGL高級效果產(chǎn)生的地方。
在所有對應(yīng)的顏色值確定之后,最終的對象會被傳到最后一個階段拓春,我們叫做Alpha測試和混合(Blending)階段释簿。這個階段還會檢測片段的對應(yīng)深度(和模板(stencil))值,用它們來判斷這個像素是在其他物體的前面還是后面硼莽,最后決定是否應(yīng)該丟棄庶溶。這個階段也會檢測alpha值并對物體進(jìn)行混合(blend)。(模板檢測作用?)
程序?qū)崿F(xiàn)
頂點(diǎn)輸入
由于我們希望渲染一個三角形懂鸵,我們一共要指定三個頂點(diǎn)偏螺,每個頂點(diǎn)都有一個3d位置。我們會將它們以標(biāo)準(zhǔn)化設(shè)備坐標(biāo)的形式(OpenGl可見區(qū)域)定義一個float
數(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)(Normalized Device Coordinates, NDC)
一旦你的頂點(diǎn)坐標(biāo)已經(jīng)在頂點(diǎn)著色器中處理過套像,它們就應(yīng)該是標(biāo)準(zhǔn)化設(shè)備坐標(biāo)了,標(biāo)準(zhǔn)化設(shè)備坐標(biāo)是一個x终息、y和z值在-1.0到1.0的一小段空間夺巩。任何落在范圍外的坐標(biāo)都會被丟棄/裁剪,不會顯示在你的屏幕上周崭。下面你會看到我們定義的在標(biāo)準(zhǔn)化設(shè)備坐標(biāo)中的三角形(忽略z軸):
NDC與通常的屏幕坐標(biāo)不同劲够,y軸正方向?yàn)橄蛏希?0, 0)坐標(biāo)是這個圖像的中心,而不是左上角休傍。最終你希望所有(變換過的)坐標(biāo)都在這個坐標(biāo)空間中征绎,否則它們就不可見了。
你的標(biāo)準(zhǔn)化設(shè)備坐標(biāo)接著會變換為屏幕空間坐標(biāo)(Screen-space Coordinates)磨取,這是使用你通過glViewport函數(shù)提供的數(shù)據(jù)人柿,進(jìn)行視口變換(Viewport Transform)完成的。所得的屏幕空間坐標(biāo)又會被變換為片段輸入到片段著色器中忙厌。
定義了這樣的頂點(diǎn)數(shù)據(jù)之后凫岖,我們會把它作為輸入發(fā)送給圖形渲染管線的第一個處理階段: 頂點(diǎn)著色器。它會在GPU上創(chuàng)建內(nèi)存用于存儲我們的頂點(diǎn)數(shù)據(jù)逢净,還有配置OpenGL如何解釋這些內(nèi)容哥放,并且指定其如何發(fā)送給顯卡歼指。頂點(diǎn)著色器接著會處理我們在內(nèi)存中指定數(shù)量的頂點(diǎn)。
我們通過VBO來管理內(nèi)存甥雕,它在GPU中可以存儲大量頂點(diǎn)踩身。使用這些緩沖對象的好處是我們可以一次性發(fā)送一大批數(shù)據(jù)到GPU,而不是分批一個個發(fā)送社露。因?yàn)镃PU發(fā)送數(shù)據(jù)到GPU是相對較慢的挟阻,所以我們盡量一次性發(fā)送足夠多的數(shù)據(jù)。
創(chuàng)建VBO對象
unsigned int VBO;
glGenBuffers(1, &VBO);//(定義一個地址存儲buffer數(shù)據(jù))
將新創(chuàng)建的緩沖綁定到GL_ARRAY_BUFFER目標(biāo)上
glBindBuffer(GL_ARRAY_BUFFER, VBO);// 指定buffer類型為GL_ARRAY_BUFFER
從這一刻起峭弟,我們使用的任何在GL_ARRAY_BUFFER目標(biāo)上的緩沖調(diào)用都會用來設(shè)置當(dāng)前綁定的緩沖(VBO)附鸽。然后我們可以調(diào)用glBufferData函數(shù),它會將之前定義的頂點(diǎn)數(shù)據(jù)復(fù)制到緩沖的內(nèi)存中瞒瘸。
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW)//填充GL_ARRAY_BUFFER類型數(shù)據(jù)坷备,數(shù)據(jù)將會存在我們綁定的GL_ARRAY_BUFFER類型vbo中。
它的第一個參數(shù)是目標(biāo)緩沖的類型:頂點(diǎn)緩沖對象當(dāng)前綁定到GL_ARRAY_BUFFER目標(biāo)上情臭。第二個參數(shù)指定傳輸數(shù)據(jù)的大小(以字節(jié)為單位)击你;用一個簡單的sizeof計算出頂點(diǎn)數(shù)據(jù)大小就行。第三個參數(shù)是我們希望發(fā)送的實(shí)際數(shù)據(jù)谎柄。
第四個參數(shù)指定了我們希望顯卡如何管理給定的數(shù)據(jù)。它有三種形式:
- GL_STATIC_DRAW :數(shù)據(jù)不會或幾乎不會改變惯雳。
- GL_DYNAMIC_DRAW:數(shù)據(jù)會被改變很多朝巫。
- GL_STREAM_DRAW :數(shù)據(jù)每次繪制時都會改變。
現(xiàn)在我們已經(jīng)把頂點(diǎn)數(shù)據(jù)儲存在顯卡的內(nèi)存中石景,用VBO這個頂點(diǎn)緩沖對象管理劈猿。下面我們會創(chuàng)建一個頂點(diǎn)和片段著色器來真正處理這些數(shù)據(jù)。現(xiàn)在我們開始著手創(chuàng)建它們吧潮孽。
頂點(diǎn)著色器
下面是一個最基礎(chǔ)的GLSL的頂點(diǎn)著色器源碼:
#version 330 core
layout (location = 0) in vec3 aPos;
void main()
{
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}
代碼中使用了一個in
關(guān)鍵字揪荣,在頂點(diǎn)著色器中聲明了所有的頂點(diǎn)屬性。現(xiàn)在我們只關(guān)心位置數(shù)據(jù)往史,所以我們只需要一個頂點(diǎn)屬性仗颈。由于每個頂點(diǎn)都有一個3D坐標(biāo),我們就創(chuàng)建一個vec3輸入變量aPos椎例。我們同樣也通過layout (location = 0)設(shè)定了輸入變量的位置值(Location)你后面會看到為什么我們會需要這個位置值挨决。(注意這個location = 0)
為了設(shè)置頂點(diǎn)著色器的輸出,我們必須把位置數(shù)值賦值給預(yù)定義的gl_position變量订歪,它在幕后是vec4
類似脖祈。(為什么把w分量設(shè)置為1.0f?)
在真實(shí)的程序里輸入數(shù)據(jù)通常都不是標(biāo)準(zhǔn)化設(shè)備坐標(biāo),所以我們首先必須先把它們轉(zhuǎn)換到OpenGL的可視區(qū)域內(nèi)刷晋。(坐標(biāo)轉(zhuǎn)換)
gl_Position
屬于頂點(diǎn)著色器階段的GL內(nèi)建變量盖高,其輸出值代表的是頂點(diǎn)在裁剪空間(Clip Space)中的值慎陵。
詳細(xì)可見官方描述Built-in Variable (GLSL) - OpenGL Wiki (khronos.org)
編譯著色器
現(xiàn)在,我們暫時將頂點(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)行時動態(tài)編譯它的源代碼席纽。
首先要做的是創(chuàng)建一個著色器對象,注意還是用ID來引用的映凳。
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
把創(chuàng)建的著色器類型以參數(shù)形式提供給 glCreateShader胆筒。因?yàn)槲覀冋趧?chuàng)建一個頂點(diǎn)著色器,所以傳遞的參數(shù)是GL_VERTEX_SHADER诈豌。
下一步仆救,我們將著色器源碼附加到著色器對象上,然后編譯它矫渔。
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
編譯結(jié)果檢測方式(fail or succes)略過彤蔽。
片段著色器
#version 330 core
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}
聲明輸出的變量可以用out
關(guān)鍵字,這里我們命名為FragColor庙洼。
這里我們輸出的是不透明的橘黃色顿痪。
編譯過程和頂點(diǎn)著色器類似,略過油够。
著色器程序
著色器程序?qū)ο?Shader Program Object)是多個著色器合并之后并最終鏈接完成的版本蚁袭。如果要使用剛才編譯的著色器我們必須把它們鏈接(Link)為一個著色器程序?qū)ο螅缓笤阡秩緦ο蟮臅r候激活這個著色器程序石咬。已激活著色器程序的著色器將在我們發(fā)送渲染調(diào)用的時候被使用揩悄。
當(dāng)鏈接著色器時,若輸出和輸入不匹配時鬼悠,你將得到一個鏈接錯誤删性。
鏈接程序如下:
unsigned int shaderProgram;
shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
鏈接成功后,我們需要激活這個程序?qū)ο蟆?/p>
glUseProgram(shaderProgram);
在glUseProgram函數(shù)調(diào)用之后焕窝,每個著色器調(diào)用和渲染調(diào)用都會使用這個程序?qū)ο螅ㄒ簿褪侵皩懙闹?了蹬挺。
現(xiàn)在,我們已經(jīng)將頂點(diǎn)數(shù)據(jù)發(fā)送給了GPU它掂,并創(chuàng)建了自定義著色器程序巴帮,并將其激活。但還未結(jié)束虐秋,OpenGL還不知道它該如何解釋GPU內(nèi)存中的頂點(diǎn)數(shù)據(jù)晰韵,以及它將頂點(diǎn)數(shù)據(jù)鏈接到頂點(diǎn)著色器屬性上。我們需要告訴OpenGL怎么做熟妓。
鏈接頂點(diǎn)屬性
我們的頂點(diǎn)緩沖數(shù)據(jù)會被解析為下面樣子:
- 位置數(shù)據(jù)被儲存為32位(4字節(jié))浮點(diǎn)值雪猪。
- 每個位置包含3個這樣的值。
- 在這3個值之間沒有空隙(或其他值)起愈。這幾個值在 數(shù)組中緊密排列(Tightly Packed)只恨。
- 數(shù)據(jù)中第一個值在緩沖開始的位置译仗。
有了這些信息我們就可以使用glVertexAttribPointer函數(shù)告訴OpenGL該如何解析頂點(diǎn)數(shù)據(jù)了:
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer函數(shù)的參數(shù)非常多,所以我會逐一介紹它們:
- 第一個參數(shù)指定我們要配置的頂點(diǎn)屬性官觅。我們在頂點(diǎn)著色器中使用了
layout(location = 0)
定義了position頂點(diǎn)屬性的位置值(location)纵菌。它可以把頂點(diǎn)屬性的位置值設(shè)置為0。因?yàn)槲覀兿M褦?shù)據(jù)傳遞到這一個頂點(diǎn)屬性中休涤,所以這里我們傳入0咱圆。 - 第二個參數(shù)指定了頂點(diǎn)屬性的大小,頂點(diǎn)屬性是一個
vec3
, 其由3個值構(gòu)成功氨,所以傳3序苏。 - 第三個參數(shù)指定了數(shù)據(jù)的類型,這里是GL_FLOAT捷凄。因?yàn)镚LSL中vec*都是由浮點(diǎn)值組成忱详。
- 第四個參數(shù)定義我們是否需要數(shù)據(jù)被標(biāo)準(zhǔn)化(Normalize)。如果我們設(shè)置為GL_TRUE, 所有數(shù)據(jù)都會被映射到0(對于有符號型signed數(shù)據(jù)是-1)到1之間跺涤。我們把它設(shè)置為GL_FALSE匈睁。
- 第五個參數(shù)叫做步長(stride),它告訴我們在連續(xù)的頂點(diǎn)屬性組之間的間隔。下個頂點(diǎn)數(shù)據(jù)是在3個
float
后桶错,所以我們將步長設(shè)置為3*sizeof(float)
航唆。要注意的是由于我們知道這個數(shù)組是緊密排列的(在兩個頂點(diǎn)屬性之間沒有空隙)我們也可以設(shè)置為0來讓OpenGL決定具體步長是多少(只有當(dāng)數(shù)值是緊密排列時才可用)萧芙。一旦我們有更多的頂點(diǎn)屬性命雀,我們就必須更小心地定義每個頂點(diǎn)屬性之間的間隔,我們在后面會看到更多的例子(譯注: 這個參數(shù)的意思簡單說就是從這個屬性第二次出現(xiàn)的地方到整個數(shù)組0位置之間有多少字節(jié))挂洛。 - 最后一個參數(shù)的類型是
void *
,所以我們需要進(jìn)行這個奇怪的強(qiáng)制類型轉(zhuǎn)換黎比。它表示位置數(shù)據(jù)在緩沖中起始位置的便移量(offset)。由于位置數(shù)據(jù)在數(shù)組的開頭鸳玩,所以這里是0阅虫。我們會在后面詳細(xì)解釋這個參數(shù)。
現(xiàn)在我們已經(jīng)定義了OpenGL該如何解釋頂點(diǎn)數(shù)據(jù)不跟,我們現(xiàn)在應(yīng)該使用glEnableVertexAttribArray颓帝,以頂點(diǎn)屬性位置值作為參數(shù),啟用頂點(diǎn)屬性窝革。
注意: 默認(rèn)情況下购城,出于性能考慮,所有頂點(diǎn)著色器的屬性(Attribute)變量都是關(guān)閉的虐译,意味著數(shù)據(jù)在著色器端是不可見的瘪板,哪怕數(shù)據(jù)已經(jīng)上傳到GPU,由glEnableVertexAttribArray啟用指定屬性漆诽,才可在頂點(diǎn)著色器中訪問逐頂點(diǎn)的屬性數(shù)據(jù)侮攀。glVertexAttribPointer或VBO只是建立CPU和GPU之間的邏輯連接锣枝,從而實(shí)現(xiàn)了CPU數(shù)據(jù)上傳至GPU。但是兰英,數(shù)據(jù)在GPU端是否可見撇叁,即,著色器能否讀取到數(shù)據(jù)畦贸,由是否啟用了對應(yīng)的屬性決定陨闹,這就是glEnableVertexAttribArray的功能,允許頂點(diǎn)著色器讀取GPU(服務(wù)器端)數(shù)據(jù)薄坏。
為了避免每當(dāng)我們繪制一個物體的時重復(fù)執(zhí)行buffer創(chuàng)建趋厉,數(shù)據(jù)綁定,頂點(diǎn)屬性設(shè)置颤殴,頂點(diǎn)屬性啟用這一過程觅廓。我們自然想到,有沒有一些方法可以使我們把所有這些狀態(tài)配置在這樣一個對象中涵但, 并且可以通過綁定這個對象來恢復(fù)狀態(tài)呢杈绸?
頂點(diǎn)數(shù)組對象
VAO可以像頂點(diǎn)緩沖對象那樣被綁定,任何隨后的頂點(diǎn)屬性調(diào)用都會存儲在這個VAO中矮瘟。這樣的好處就是瞳脓,當(dāng)配置頂點(diǎn)屬性指針時,你只需要將那些調(diào)用執(zhí)行一次澈侠,之后再繪制物體的時候只需要綁定相應(yīng)的VAO就行了劫侧。
一個頂點(diǎn)數(shù)組對象會儲存以下這些內(nèi)容:
- glEnableVertexAttribArray和glDisableVertexAttribArray的調(diào)用。
- 通過glVertexAttribPointer設(shè)置的頂點(diǎn)屬性配置哨啃。
- 通過glVertexAttribPointer調(diào)用與頂點(diǎn)屬性關(guān)聯(lián)的頂點(diǎn)緩沖對象烧栋。
創(chuàng)建過程如下,和VBO類似拳球。
unsigned int VAO;
glGenVertexArrays(1, &VAO);
// ..:: 初始化代碼(只運(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();
EBO索引緩沖對象略审姓。
三角形正式繪制完整代碼:
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow *window);
// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
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";
const char *fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";
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);
#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);
// glad: load all OpenGL function pointers
// ---------------------------------------
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
// build and compile our shader program
// ------------------------------------
// vertex shader
unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
// check for shader compile errors
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
unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
// check for shader compile errors
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}
// link shaders
unsigned int shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
// check for linking errors
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
// set up vertex data (and buffer(s)) and configure vertex attributes
// ------------------------------------------------------------------
float vertices[] = {
-0.5f, -0.5f, 0.0f, // left
0.5f, -0.5f, 0.0f, // right
0.0f, 0.5f, 0.0f // top
};
unsigned int VBO, VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// note that this is allowed, the call to glVertexAttribPointer registered VBO as the vertex attribute's bound vertex buffer object so afterwards we can safely unbind
glBindBuffer(GL_ARRAY_BUFFER, 0);
// You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other
// VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary.
glBindVertexArray(0);
// uncomment this call to draw in wireframe polygons.
//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
// render loop
// -----------
while (!glfwWindowShouldClose(window))
{
// input
// -----
processInput(window);
// render
// ------
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// draw our first triangle
glUseProgram(shaderProgram);
glBindVertexArray(VAO); // seeing as we only have a single VAO there's no need to bind it every time, but we'll do so to keep things a bit more organized
glDrawArrays(GL_TRIANGLES, 0, 3);
// glBindVertexArray(0); // no need to unbind it every time
// 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, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteProgram(shaderProgram);
// glfw: terminate, clearing all previously allocated GLFW resources.
// ------------------------------------------------------------------
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);
}
// 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);
}
參考
https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/
https://blog.csdn.net/gongxun1994/article/details/78271870
細(xì)說圖形學(xué)渲染管線 https://positiveczp.github.io/%E7%BB%86%E8%AF%B4%E5%9B%BE%E5%BD%A2%E5%AD%A6%E6%B8%B2%E6%9F%93%E7%AE%A1%E7%BA%BF.pdf