版本記錄
版本號(hào) | 時(shí)間 |
---|---|
V1.0 | 2018.01.17 |
前言
OpenGL 圖形庫(kù)項(xiàng)目中一直也沒(méi)用過(guò)丑勤,最近也想學(xué)著使用這個(gè)圖形庫(kù),感覺(jué)還是很有意思吧趣,也就自然想著好好的總結(jié)一下法竞,希望對(duì)大家能有所幫助。下面內(nèi)容來(lái)自歡迎來(lái)到OpenGL的世界强挫。
1. OpenGL 圖形庫(kù)使用(一) —— 概念基礎(chǔ)
2. OpenGL 圖形庫(kù)使用(二) —— 渲染模式岔霸、對(duì)象、擴(kuò)展和狀態(tài)機(jī)
3. OpenGL 圖形庫(kù)使用(三) —— 著色器俯渤、數(shù)據(jù)類(lèi)型與輸入輸出
4. OpenGL 圖形庫(kù)使用(四) —— Uniform及更多屬性
5. OpenGL 圖形庫(kù)使用(五) —— 紋理
6. OpenGL 圖形庫(kù)使用(六) —— 變換
7. OpenGL 圖形庫(kù)的使用(七)—— 坐標(biāo)系統(tǒng)之五種不同的坐標(biāo)系統(tǒng)(一)
8. OpenGL 圖形庫(kù)的使用(八)—— 坐標(biāo)系統(tǒng)之3D效果(二)
9. OpenGL 圖形庫(kù)的使用(九)—— 攝像機(jī)(一)
10. OpenGL 圖形庫(kù)的使用(十)—— 攝像機(jī)(二)
11. OpenGL 圖形庫(kù)的使用(十一)—— 光照之顏色
12. OpenGL 圖形庫(kù)的使用(十二)—— 光照之基礎(chǔ)光照
13. OpenGL 圖形庫(kù)的使用(十三)—— 光照之材質(zhì)
14. OpenGL 圖形庫(kù)的使用(十四)—— 光照之光照貼圖
15. OpenGL 圖形庫(kù)的使用(十五)—— 光照之投光物
16. OpenGL 圖形庫(kù)的使用(十六)—— 光照之多光源
17. OpenGL 圖形庫(kù)的使用(十七)—— 光照之復(fù)習(xí)總結(jié)
18. OpenGL 圖形庫(kù)的使用(十八)—— 模型加載之Assimp
19. OpenGL 圖形庫(kù)的使用(十九)—— 模型加載之網(wǎng)格
20. OpenGL 圖形庫(kù)的使用(二十)—— 模型加載之模型
21. OpenGL 圖形庫(kù)的使用(二十一)—— 高級(jí)OpenGL之深度測(cè)試
22. OpenGL 圖形庫(kù)的使用(二十二)—— 高級(jí)OpenGL之模板測(cè)試Stencil testing
23. OpenGL 圖形庫(kù)的使用(二十三)—— 高級(jí)OpenGL之混合Blending
24. OpenGL 圖形庫(kù)的使用(二十四)—— 高級(jí)OpenGL之面剔除Face culling
25. OpenGL 圖形庫(kù)的使用(二十五)—— 高級(jí)OpenGL之幀緩沖Framebuffers
26. OpenGL 圖形庫(kù)的使用(二十六)—— 高級(jí)OpenGL之立方體貼圖Cubemaps
27. OpenGL 圖形庫(kù)的使用(二十七)—— 高級(jí)OpenGL之高級(jí)數(shù)據(jù)Advanced Data
28. OpenGL 圖形庫(kù)的使用(二十八)—— 高級(jí)OpenGL之高級(jí)GLSL Advanced GLSL
幾何著色器
在頂點(diǎn)和片段著色器之間有一個(gè)可選的幾何著色器(Geometry Shader)
呆细,幾何著色器的輸入是一個(gè)圖元(如點(diǎn)或三角形)的一組頂點(diǎn)。幾何著色器可以在頂點(diǎn)發(fā)送到下一著色器階段之前對(duì)它們隨意變換八匠。然而絮爷,幾何著色器最有趣的地方在于趴酣,它能夠?qū)ⅲㄟ@一組)頂點(diǎn)變換為完全不同的圖元,并且還能生成比原來(lái)更多的頂點(diǎn)坑夯。
廢話(huà)不多說(shuō)岖寞,我們直接先看一個(gè)幾何著色器的例子:
#version 330 core
layout (points) in;
layout (line_strip, max_vertices = 2) out;
void main() {
gl_Position = gl_in[0].gl_Position + vec4(-0.1, 0.0, 0.0, 0.0);
EmitVertex();
gl_Position = gl_in[0].gl_Position + vec4( 0.1, 0.0, 0.0, 0.0);
EmitVertex();
EndPrimitive();
}
在幾何著色器的頂部,我們需要聲明從頂點(diǎn)著色器輸入的圖元類(lèi)型柜蜈。這需要在in關(guān)鍵字前聲明一個(gè)布局修飾符(Layout Qualifier)
仗谆。這個(gè)輸入布局修飾符可以從頂點(diǎn)著色器接收下列任何一個(gè)圖元值:
-
points
:繪制GL_POINTS
圖元時(shí)(1)。 -
lines
:繪制GL_LINES
或GL_LINE_STRIP
時(shí)(2) -
lines_adjacency
:GL_LINES_ADJACENCY
或GL_LINE_STRIP_ADJACENCY
(4) -
triangles
:GL_TRIANGLES淑履、GL_TRIANGLE_STRIP
或GL_TRIANGLE_FAN
(3) -
triangles_adjacency
:GL_TRIANGLES_ADJACENCY或GL_TRIANGLE_STRIP_ADJACENCY
(6)
以上是能提供給glDrawArrays
渲染函數(shù)的幾乎所有圖元了隶垮。如果我們想要將頂點(diǎn)繪制為GL_TRIANGLES
,我們就要將輸入修飾符設(shè)置為triangles鳖谈。括號(hào)內(nèi)的數(shù)字表示的是一個(gè)圖元所包含的最小頂點(diǎn)數(shù)。
接下來(lái)阔涉,我們還需要指定幾何著色器輸出的圖元類(lèi)型缆娃,這需要在out關(guān)鍵字前面加一個(gè)布局修飾符。和輸入布局修飾符一樣瑰排,輸出布局修飾符也可以接受幾個(gè)圖元值:
points
line_strip
triangle_strip
有了這3個(gè)輸出修飾符贯要,我們就可以使用輸入圖元?jiǎng)?chuàng)建幾乎任意的形狀了。要生成一個(gè)三角形的話(huà)椭住,我們將輸出定義為triangle_strip
崇渗,并輸出3個(gè)頂點(diǎn)。
幾何著色器同時(shí)希望我們?cè)O(shè)置一個(gè)它最大能夠輸出的頂點(diǎn)數(shù)量(如果你超過(guò)了這個(gè)值京郑,OpenGL將不會(huì)繪制多出的頂點(diǎn))宅广,這個(gè)也可以在out關(guān)鍵字的布局修飾符中設(shè)置。在這個(gè)例子中些举,我們將輸出一個(gè)line_strip
跟狱,并將最大頂點(diǎn)數(shù)設(shè)置為2個(gè)。
如果你不知道什么是線(xiàn)條(Line Strip)
:線(xiàn)條連接了一組點(diǎn)户魏,形成一條連續(xù)的線(xiàn)驶臊,它最少要由兩個(gè)點(diǎn)來(lái)組成。在渲染函數(shù)中每多加一個(gè)點(diǎn)叼丑,就會(huì)在這個(gè)點(diǎn)與前一個(gè)點(diǎn)之間形成一條新的線(xiàn)关翎。在下面這張圖中,我們有5個(gè)頂點(diǎn):
如果使用的是上面定義的著色器鸠信,那么這將只能輸出一條線(xiàn)段纵寝,因?yàn)樽畲箜旤c(diǎn)數(shù)等于2。
為了生成更有意義的結(jié)果星立,我們需要某種方式來(lái)獲取前一著色器階段的輸出店雅。GLSL提供給我們一個(gè)內(nèi)建(Built-in)
變量政基,在內(nèi)部看起來(lái)(可能)是這樣的:
in gl_Vertex
{
vec4 gl_Position;
float gl_PointSize;
float gl_ClipDistance[];
} gl_in[];
這里,它被聲明為一個(gè)接口塊(Interface Block
闹啦,我們?cè)?a target="_blank" rel="nofollow">上一節(jié)已經(jīng)討論過(guò))沮明,它包含了幾個(gè)很有意思的變量,其中最有趣的一個(gè)是gl_Position
窍奋,它是和頂點(diǎn)著色器輸出非常相似的一個(gè)向量荐健。
要注意的是,它被聲明為一個(gè)數(shù)組琳袄,因?yàn)榇蠖鄶?shù)的渲染圖元包含多于1個(gè)的頂點(diǎn)江场,而幾何著色器的輸入是一個(gè)圖元的所有頂點(diǎn)。
有了之前頂點(diǎn)著色器階段的頂點(diǎn)數(shù)據(jù)窖逗,我們就可以使用2個(gè)幾何著色器函數(shù)址否,EmitVertex
和EndPrimitive
,來(lái)生成新的數(shù)據(jù)了碎紊。幾何著色器希望你能夠生成并輸出至少一個(gè)定義為輸出的圖元佑附。在我們的例子中,我們需要至少生成一個(gè)線(xiàn)條圖元仗考。
void main() {
gl_Position = gl_in[0].gl_Position + vec4(-0.1, 0.0, 0.0, 0.0);
EmitVertex();
gl_Position = gl_in[0].gl_Position + vec4( 0.1, 0.0, 0.0, 0.0);
EmitVertex();
EndPrimitive();
}
每次我們調(diào)用EmitVertex
時(shí)音同,gl_Position
中的向量會(huì)被添加到圖元中來(lái)。當(dāng)EndPrimitive
被調(diào)用時(shí)秃嗜,所有發(fā)射出的(Emitted)頂點(diǎn)都會(huì)合成為指定的輸出渲染圖元权均。在一個(gè)或多個(gè)EmitVertex調(diào)用之后重復(fù)調(diào)用EndPrimitive能夠生成多個(gè)圖元。在這個(gè)例子中锅锨,我們發(fā)射了兩個(gè)頂點(diǎn)叽赊,它們從原始頂點(diǎn)位置平移了一段距離,之后調(diào)用了EndPrimitive必搞,將這兩個(gè)頂點(diǎn)合成為一個(gè)包含兩個(gè)頂點(diǎn)的線(xiàn)條蛇尚。
現(xiàn)在你(大概)了解了幾何著色器的工作方式,你可能已經(jīng)猜出這個(gè)幾何著色器是做什么的了顾画。它接受一個(gè)點(diǎn)圖元作為輸入取劫,以這個(gè)點(diǎn)為中心,創(chuàng)建一條水平的線(xiàn)圖元研侣。如果我們渲染它谱邪,看起來(lái)會(huì)是這樣的:
目前還并沒(méi)有什么令人驚嘆的效果,但考慮到這個(gè)輸出是通過(guò)調(diào)用下面的渲染函數(shù)來(lái)生成的庶诡,它還是很有意思的:
glDrawArrays(GL_POINTS, 0, 4);
雖然這是一個(gè)比較簡(jiǎn)單的例子惦银,它的確向你展示了如何能夠使用幾何著色器來(lái)(動(dòng)態(tài)地)生成新的形狀。在之后我們會(huì)利用幾何著色器創(chuàng)建出更有意思的效果,但現(xiàn)在我們?nèi)詫膭?chuàng)建一個(gè)簡(jiǎn)單的幾何著色器開(kāi)始扯俱。
使用幾何著色器
為了展示幾何著色器的用法书蚪,我們將會(huì)渲染一個(gè)非常簡(jiǎn)單的場(chǎng)景,我們只會(huì)在標(biāo)準(zhǔn)化設(shè)備坐標(biāo)的z平面上繪制四個(gè)點(diǎn)迅栅。這些點(diǎn)的坐標(biāo)是:
float points[] = {
-0.5f, 0.5f, // 左上
0.5f, 0.5f, // 右上
0.5f, -0.5f, // 右下
-0.5f, -0.5f // 左下
};
頂點(diǎn)著色器只需要在z平面繪制點(diǎn)就可以了殊校,所以我們將使用一個(gè)最基本頂點(diǎn)著色器:
#version 330 core
layout (location = 0) in vec2 aPos;
void main()
{
gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0);
}
直接在片段著色器中硬編碼,將所有的點(diǎn)都輸出為綠色:
#version 330 core
out vec4 FragColor;
void main()
{
FragColor = vec4(0.0, 1.0, 0.0, 1.0);
}
為點(diǎn)的頂點(diǎn)數(shù)據(jù)生成一個(gè)VAO和一個(gè)VBO读存,然后使用glDrawArrays
進(jìn)行繪制:
shader.use();
glBindVertexArray(VAO);
glDrawArrays(GL_POINTS, 0, 4);
結(jié)果是在黑暗的場(chǎng)景中有四個(gè)(很難看見(jiàn)的)綠點(diǎn):
但我們之前不是學(xué)過(guò)這些嗎为流?是的,但是現(xiàn)在我們將會(huì)添加一個(gè)幾何著色器让簿,為場(chǎng)景添加活力敬察。
出于學(xué)習(xí)目的,我們將會(huì)創(chuàng)建一個(gè)傳遞(Pass-through)
幾何著色器尔当,它會(huì)接收一個(gè)點(diǎn)圖元莲祸,并直接將它傳遞(Pass)到下一個(gè)著色器:
#version 330 core
layout (points) in;
layout (points, max_vertices = 1) out;
void main() {
gl_Position = gl_in[0].gl_Position;
EmitVertex();
EndPrimitive();
}
現(xiàn)在這個(gè)幾何著色器應(yīng)該很容易理解了,它只是將它接收到的頂點(diǎn)位置不作修改直接發(fā)射出去椭迎,并生成一個(gè)點(diǎn)圖元锐帜。
和頂點(diǎn)與片段著色器一樣,幾何著色器也需要編譯和鏈接侠碧,但這次在創(chuàng)建著色器時(shí)我們將會(huì)使用GL_GEOMETRY_SHADER
作為著色器類(lèi)型:
geometryShader = glCreateShader(GL_GEOMETRY_SHADER);
glShaderSource(geometryShader, 1, &gShaderCode, NULL);
glCompileShader(geometryShader);
...
glAttachShader(program, geometryShader);
glLinkProgram(program);
著色器編譯的代碼和頂點(diǎn)與片段著色器代碼都是一樣的抹估。記得要檢查編譯和鏈接錯(cuò)誤缠黍!
如果你現(xiàn)在編譯并運(yùn)行程序弄兜,會(huì)看到和下面類(lèi)似的結(jié)果:
這和沒(méi)使用幾何著色器時(shí)是完全一樣的!我承認(rèn)這是有點(diǎn)無(wú)聊瓷式,但既然我們?nèi)匀荒軌蚶L制這些點(diǎn)替饿,所以幾何著色器是正常工作的,現(xiàn)在是時(shí)候做點(diǎn)更有趣的東西了贸典!
造幾個(gè)房子
繪制點(diǎn)和線(xiàn)并沒(méi)有那么有趣视卢,所以我們會(huì)使用一點(diǎn)創(chuàng)造力,利用幾何著色器在每個(gè)點(diǎn)的位置上繪制一個(gè)房子廊驼。要實(shí)現(xiàn)這個(gè)据过,我們可以將幾何著色器的輸出設(shè)置為triangle_strip
,并繪制三個(gè)三角形:其中兩個(gè)組成一個(gè)正方形妒挎,另一個(gè)用作房頂绳锅。
OpenGL中,三角形帶(Triangle Strip)是繪制三角形更高效的方式酝掩,它使用頂點(diǎn)更少鳞芙。在第一個(gè)三角形繪制完之后,每個(gè)后續(xù)頂點(diǎn)將會(huì)在上一個(gè)三角形邊上生成另一個(gè)三角形:每3個(gè)臨近的頂點(diǎn)將會(huì)形成一個(gè)三角形。如果我們一共有6個(gè)構(gòu)成三角形帶的頂點(diǎn)原朝,那么我們會(huì)得到這些三角形:(1, 2, 3)驯嘱、(2, 3, 4)、(3, 4, 5)和(4, 5, 6)喳坠,共形成4個(gè)三角形鞠评。一個(gè)三角形帶至少需要3個(gè)頂點(diǎn),并會(huì)生成N-2個(gè)三角形丙笋。使用6個(gè)頂點(diǎn)谢澈,我們創(chuàng)建了6-2 = 4個(gè)三角形。下面這幅圖展示了這點(diǎn):
通過(guò)使用三角形帶作為幾何著色器的輸出御板,我們可以很容易創(chuàng)建出需要的房子形狀锥忿,只需要以正確的順序生成3個(gè)相連的三角形就行了。下面這幅圖展示了頂點(diǎn)繪制的順序怠肋,藍(lán)點(diǎn)代表的是輸入點(diǎn):
變?yōu)閹缀沃魇沁@樣的:
#version 330 core
layout (points) in;
layout (triangle_strip, max_vertices = 5) out;
void build_house(vec4 position)
{
gl_Position = position + vec4(-0.2, -0.2, 0.0, 0.0); // 1:左下
EmitVertex();
gl_Position = position + vec4( 0.2, -0.2, 0.0, 0.0); // 2:右下
EmitVertex();
gl_Position = position + vec4(-0.2, 0.2, 0.0, 0.0); // 3:左上
EmitVertex();
gl_Position = position + vec4( 0.2, 0.2, 0.0, 0.0); // 4:右上
EmitVertex();
gl_Position = position + vec4( 0.0, 0.4, 0.0, 0.0); // 5:頂部
EmitVertex();
EndPrimitive();
}
void main() {
build_house(gl_in[0].gl_Position);
}
這個(gè)幾何著色器生成了5個(gè)頂點(diǎn)敬鬓,每個(gè)頂點(diǎn)都是原始點(diǎn)的位置加上一個(gè)偏移量,來(lái)組成一個(gè)大的三角形帶笙各。最終的圖元會(huì)被光柵化钉答,然后片段著色器會(huì)處理整個(gè)三角形帶,最終在每個(gè)繪制的點(diǎn)處生成一個(gè)綠色房子:
你可以看到杈抢,每個(gè)房子實(shí)際上是由3個(gè)三角形組成的——全部都是使用空間中一點(diǎn)來(lái)繪制的数尿。這些綠房子看起來(lái)是有點(diǎn)無(wú)聊,所以我們會(huì)再給每個(gè)房子分配一個(gè)不同的顏色惶楼。為了實(shí)現(xiàn)這個(gè)右蹦,我們需要在頂點(diǎn)著色器中添加一個(gè)額外的頂點(diǎn)屬性,表示顏色信息歼捐,將它傳遞至幾何著色器何陆,并再次發(fā)送到片段著色器中。
下面是更新后的頂點(diǎn)數(shù)據(jù):
float points[] = {
-0.5f, 0.5f, 1.0f, 0.0f, 0.0f, // 左上
0.5f, 0.5f, 0.0f, 1.0f, 0.0f, // 右上
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, // 右下
-0.5f, -0.5f, 1.0f, 1.0f, 0.0f // 左下
};
然后我們更新頂點(diǎn)著色器豹储,使用一個(gè)接口塊將顏色屬性發(fā)送到幾何著色器中:
#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec3 aColor;
out VS_OUT {
vec3 color;
} vs_out;
void main()
{
gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0);
vs_out.color = aColor;
}
接下來(lái)我們還需要在幾何著色器中聲明相同的接口塊(使用一個(gè)不同的接口名):
in VS_OUT {
vec3 color;
} gs_in[];
因?yàn)閹缀沃魇亲饔糜谳斎氲囊唤M頂點(diǎn)的贷盲,從頂點(diǎn)著色器發(fā)來(lái)輸入數(shù)據(jù)總是會(huì)以數(shù)組的形式表示出來(lái),即便我們現(xiàn)在只有一個(gè)頂點(diǎn)剥扣。
我們并不是必須要用接口塊來(lái)向幾何著色器傳遞數(shù)據(jù)巩剖。如果頂點(diǎn)著色器發(fā)送的顏色向量是out vec3 vColor,我們也可以這樣寫(xiě):
in vec3 vColor[];
然而钠怯,接口塊在幾何著色器這樣的著色器中會(huì)更容易處理一點(diǎn)佳魔。實(shí)際上,幾何著色器的輸入能夠變得非常大呻疹,將它們合并為一個(gè)大的接口塊數(shù)組會(huì)更符合邏輯一點(diǎn)吃引。
接下來(lái)我們還需要為下個(gè)片段著色器階段聲明一個(gè)輸出顏色向量:
out vec3 fColor;
因?yàn)槠沃髦恍枰粋€(gè)(插值的)顏色筹陵,發(fā)送多個(gè)顏色并沒(méi)有什么意義。所以镊尺,fColor
向量就不是一個(gè)數(shù)組朦佩,而是一個(gè)單獨(dú)的向量巢块。當(dāng)發(fā)射一個(gè)頂點(diǎn)的時(shí)候颂郎,每個(gè)頂點(diǎn)將會(huì)使用最后在fColor中儲(chǔ)存的值,來(lái)用于片段著色器的運(yùn)行睦裳。對(duì)我們的房子來(lái)說(shuō)弄砍,我們只需要在第一個(gè)頂點(diǎn)發(fā)射之前仙畦,使用頂點(diǎn)著色器中的顏色填充fColor一次就可以了。
因?yàn)槠沃髦恍枰粋€(gè)(已進(jìn)行了插值的)顏色音婶,傳送多個(gè)顏色沒(méi)有意義慨畸。fColor向量這樣就不是一個(gè)數(shù)組,而是一個(gè)單一的向量衣式。當(dāng)發(fā)射一個(gè)頂點(diǎn)時(shí)寸士,為了它的片段著色器運(yùn)行,每個(gè)頂點(diǎn)都會(huì)儲(chǔ)存最后在fColor中儲(chǔ)存的值碴卧。對(duì)于這些房子來(lái)說(shuō)弱卡,我們可以在第一個(gè)頂點(diǎn)被發(fā)射,對(duì)整個(gè)房子上色前住册,只使用來(lái)自頂點(diǎn)著色器的顏色填充fColor一次:
fColor = gs_in[0].color; // gs_in[0] 因?yàn)橹挥幸粋€(gè)輸入頂點(diǎn)
gl_Position = position + vec4(-0.2, -0.2, 0.0, 0.0); // 1:左下
EmitVertex();
gl_Position = position + vec4( 0.2, -0.2, 0.0, 0.0); // 2:右下
EmitVertex();
gl_Position = position + vec4(-0.2, 0.2, 0.0, 0.0); // 3:左上
EmitVertex();
gl_Position = position + vec4( 0.2, 0.2, 0.0, 0.0); // 4:右上
EmitVertex();
gl_Position = position + vec4( 0.0, 0.4, 0.0, 0.0); // 5:頂部
EmitVertex();
EndPrimitive();
所有發(fā)射出的頂點(diǎn)都將嵌有最后儲(chǔ)存在fColor中的值婶博,即頂點(diǎn)的顏色屬性值。所有的房子都會(huì)有它們自己的顏色了:
僅僅是為了有趣荧飞,我們也可以假裝這是冬天凡人,將最后一個(gè)頂點(diǎn)的顏色設(shè)置為白色,給屋頂落上一些雪垢箕。
fColor = gs_in[0].color;
gl_Position = position + vec4(-0.2, -0.2, 0.0, 0.0); // 1:左下
EmitVertex();
gl_Position = position + vec4( 0.2, -0.2, 0.0, 0.0); // 2:右下
EmitVertex();
gl_Position = position + vec4(-0.2, 0.2, 0.0, 0.0); // 3:左上
EmitVertex();
gl_Position = position + vec4( 0.2, 0.2, 0.0, 0.0); // 4:右上
EmitVertex();
gl_Position = position + vec4( 0.0, 0.4, 0.0, 0.0); // 5:頂部
fColor = vec3(1.0, 1.0, 1.0);
EmitVertex();
EndPrimitive();
最終結(jié)果看起來(lái)是這樣的:
你可以將你的代碼與這里的OpenGL代碼進(jìn)行比對(duì)划栓。
#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("9.1.geometry_shader.vs", "9.1.geometry_shader.fs", "9.1.geometry_shader.gs");
// set up vertex data (and buffer(s)) and configure vertex attributes
// ------------------------------------------------------------------
float points[] = {
-0.5f, 0.5f, 1.0f, 0.0f, 0.0f, // top-left
0.5f, 0.5f, 0.0f, 1.0f, 0.0f, // top-right
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, // bottom-right
-0.5f, -0.5f, 1.0f, 1.0f, 0.0f // bottom-left
};
unsigned int VBO, VAO;
glGenBuffers(1, &VBO);
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(points), &points, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), 0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(2 * sizeof(float)));
glBindVertexArray(0);
// 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 points
shader.use();
glBindVertexArray(VAO);
glDrawArrays(GL_POINTS, 0, 4);
// 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);
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);
}
你可以看到兑巾,有了幾何著色器条获,你甚至可以將最簡(jiǎn)單的圖元變得十分有創(chuàng)意。因?yàn)檫@些形狀是在GPU的超快硬件中動(dòng)態(tài)生成的蒋歌,這會(huì)比在頂點(diǎn)緩沖中手動(dòng)定義圖形要高效很多帅掘。因此,幾何緩沖對(duì)簡(jiǎn)單而且經(jīng)常重復(fù)的形狀來(lái)說(shuō)是一個(gè)很好的優(yōu)化工具堂油,比如體素(Voxel)
世界中的方塊和室外草地的每一根草修档。
爆破物體
盡管繪制房子非常有趣,但我們不會(huì)經(jīng)常這么做府框。這也是為什么我們接下來(lái)要繼續(xù)深入吱窝,來(lái)爆破(Explode)物體!雖然這也是一個(gè)不怎么常用的東西,但是它能向你展示幾何著色器的強(qiáng)大之處院峡。
當(dāng)我們說(shuō)爆破一個(gè)物體時(shí)兴使,我們并不是指要將寶貴的頂點(diǎn)集給炸掉,我們是要將每個(gè)三角形沿著法向量的方向移動(dòng)一小段時(shí)間照激。效果就是发魄,整個(gè)物體看起來(lái)像是沿著每個(gè)三角形的法線(xiàn)向量爆炸一樣。爆炸三角形的效果在納米裝模型上看起來(lái)像是這樣的:
這樣的幾何著色器效果的一個(gè)好處就是俩垃,無(wú)論物體有多復(fù)雜励幼,它都能夠應(yīng)用上去。
因?yàn)槲覀兿胍刂切蔚姆ㄏ蛄课灰泼總€(gè)頂點(diǎn)口柳,我們首先需要計(jì)算這個(gè)法向量苹粟。我們所要做的是計(jì)算垂直于三角形表面的向量,僅使用我們能夠訪問(wèn)的3個(gè)頂點(diǎn)跃闹。你可能還記得在變換小節(jié)中六水,我們使用叉乘來(lái)獲取垂直于其它兩個(gè)向量的一個(gè)向量。如果我們能夠獲取兩個(gè)平行于三角形表面的向量a和b辣卒,我們就能夠?qū)@兩個(gè)向量進(jìn)行叉乘來(lái)獲取法向量了掷贾。下面這個(gè)幾何著色器函數(shù)做的正是這個(gè),來(lái)使用3個(gè)輸入頂點(diǎn)坐標(biāo)來(lái)獲取法向量:
vec3 GetNormal()
{
vec3 a = vec3(gl_in[0].gl_Position) - vec3(gl_in[1].gl_Position);
vec3 b = vec3(gl_in[2].gl_Position) - vec3(gl_in[1].gl_Position);
return normalize(cross(a, b));
}
這里我們使用減法獲取了兩個(gè)平行于三角形表面的向量a和b荣茫。因?yàn)閮蓚€(gè)向量相減能夠得到這兩個(gè)向量之間的差值想帅,并且三個(gè)點(diǎn)都位于三角平面上,對(duì)任意兩個(gè)向量相減都能夠得到一個(gè)平行于平面的向量啡莉。注意港准,如果我們交換了cross函數(shù)中a和b的位置,我們會(huì)得到一個(gè)指向相反方向的法向量——這里的順序很重要咧欣!
既然知道了如何計(jì)算法向量了浅缸,我們就能夠創(chuàng)建一個(gè)explode
函數(shù)了,它使用法向量和頂點(diǎn)位置向量作為參數(shù)魄咕。這個(gè)函數(shù)會(huì)返回一個(gè)新的向量衩椒,它是位置向量沿著法線(xiàn)向量進(jìn)行位移之后的結(jié)果:
vec4 explode(vec4 position, vec3 normal)
{
float magnitude = 2.0;
vec3 direction = normal * ((sin(time) + 1.0) / 2.0) * magnitude;
return position + vec4(direction, 0.0);
}
函數(shù)本身應(yīng)該不是非常復(fù)雜。sin函數(shù)接收一個(gè)time參數(shù)哮兰,它根據(jù)時(shí)間返回一個(gè)-1.0到1.0之間的值毛萌。因?yàn)槲覀儾幌胱屛矬w向內(nèi)爆炸(Implode)
,我們將sin值變換到了[0, 1]的范圍內(nèi)喝滞。最終的結(jié)果會(huì)乘以normal
向量阁将,并且最終的direction向量會(huì)被加到位置向量上。
當(dāng)使用我們的模型加載器繪制一個(gè)模型時(shí)右遭,爆破(Explode)效果的完整片段著色器是這樣的:
#version 330 core
layout (triangles) in;
layout (triangle_strip, max_vertices = 3) out;
in VS_OUT {
vec2 texCoords;
} gs_in[];
out vec2 TexCoords;
uniform float time;
vec4 explode(vec4 position, vec3 normal) { ... }
vec3 GetNormal() { ... }
void main() {
vec3 normal = GetNormal();
gl_Position = explode(gl_in[0].gl_Position, normal);
TexCoords = gs_in[0].texCoords;
EmitVertex();
gl_Position = explode(gl_in[1].gl_Position, normal);
TexCoords = gs_in[1].texCoords;
EmitVertex();
gl_Position = explode(gl_in[2].gl_Position, normal);
TexCoords = gs_in[2].texCoords;
EmitVertex();
EndPrimitive();
}
注意我們?cè)诎l(fā)射頂點(diǎn)之前輸出了對(duì)應(yīng)的紋理坐標(biāo)做盅。
而且別忘了在OpenGL代碼中設(shè)置time變量:
shader.setFloat("time", glfwGetTime());
最終的效果是缤削,3D模型看起來(lái)隨著時(shí)間不斷在爆破它的頂點(diǎn),在這之后又回到正常狀態(tài)吹榴。雖然這并不是非常有用僻他,它的確向你展示了幾何著色器更高級(jí)的用法。你可以將你的代碼和這里完整的源碼進(jìn)行比較腊尚。
#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>
#include <learnopengl/camera.h>
#include <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, 3.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("9.2.geometry_shader.vs", "9.2.geometry_shader.fs", "9.2.geometry_shader.gs");
// load models
// -----------
Model nanosuit(FileSystem::getPath("resources/objects/nanosuit/nanosuit.obj"));
// 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, 1.0f, 100.0f);
glm::mat4 view = camera.GetViewMatrix();;
glm::mat4 model;
shader.use();
shader.setMat4("projection", projection);
shader.setMat4("view", view);
shader.setMat4("model", model);
// add time component to geometry shader in the form of a uniform
shader.setFloat("time", glfwGetTime());
// draw model
nanosuit.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);
}
法向量可視化
在這一部分中吨拗,我們將使用幾何著色器來(lái)實(shí)現(xiàn)一個(gè)真正有用的例子:顯示任意物體的法向量。當(dāng)編寫(xiě)光照著色器時(shí)婿斥,你可能會(huì)最終會(huì)得到一些奇怪的視覺(jué)輸出劝篷,但又很難確定導(dǎo)致問(wèn)題的原因。光照錯(cuò)誤很常見(jiàn)的原因就是法向量錯(cuò)誤民宿,這可能是由于不正確加載頂點(diǎn)數(shù)據(jù)娇妓、錯(cuò)誤地將它們定義為頂點(diǎn)屬性或在著色器中不正確地管理所導(dǎo)致的。我們想要的是使用某種方式來(lái)檢測(cè)提供的法向量是正確的活鹰。檢測(cè)法向量是否正確的一個(gè)很好的方式就是對(duì)它們進(jìn)行可視化哈恰,幾何著色器正是實(shí)現(xiàn)這一目的非常有用的工具。
思路是這樣的:我們首先不使用幾何著色器正常繪制場(chǎng)景志群。然后再次繪制場(chǎng)景着绷,但這次只顯示通過(guò)幾何著色器生成法向量。幾何著色器接收一個(gè)三角形圖元锌云,并沿著法向量生成三條線(xiàn)——每個(gè)頂點(diǎn)一個(gè)法向量荠医。偽代碼看起來(lái)會(huì)像是這樣:
shader.use();
DrawScene();
normalDisplayShader.use();
DrawScene();
這次在幾何著色器中,我們會(huì)使用模型提供的頂點(diǎn)法線(xiàn)桑涎,而不是自己生成彬向,為了適配(觀察和模型矩陣的)縮放和旋轉(zhuǎn),我們?cè)趯⒎ň€(xiàn)變換到裁剪空間坐標(biāo)之前攻冷,先使用法線(xiàn)矩陣變換一次(幾何著色器接受的位置向量是剪裁空間坐標(biāo)娃胆,所以我們應(yīng)該將法向量變換到相同的空間中)。這可以在頂點(diǎn)著色器中完成:
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
out VS_OUT {
vec3 normal;
} vs_out;
uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
mat3 normalMatrix = mat3(transpose(inverse(view * model)));
vs_out.normal = normalize(vec3(projection * vec4(normalMatrix * aNormal, 1.0)));
}
變換后的裁剪空間法向量會(huì)以接口塊的形式傳遞到下個(gè)著色器階段等曼。接下來(lái)里烦,幾何著色器會(huì)接收每一個(gè)頂點(diǎn)(包括一個(gè)位置向量和一個(gè)法向量),并在每個(gè)位置向量處繪制一個(gè)法線(xiàn)向量:
#version 330 core
layout (triangles) in;
layout (line_strip, max_vertices = 6) out;
in VS_OUT {
vec3 normal;
} gs_in[];
const float MAGNITUDE = 0.4;
void GenerateLine(int index)
{
gl_Position = gl_in[index].gl_Position;
EmitVertex();
gl_Position = gl_in[index].gl_Position + vec4(gs_in[index].normal, 0.0) * MAGNITUDE;
EmitVertex();
EndPrimitive();
}
void main()
{
GenerateLine(0); // 第一個(gè)頂點(diǎn)法線(xiàn)
GenerateLine(1); // 第二個(gè)頂點(diǎn)法線(xiàn)
GenerateLine(2); // 第三個(gè)頂點(diǎn)法線(xiàn)
}
像這樣的幾何著色器應(yīng)該很容易理解了涉兽。注意我們將法向量乘以了一個(gè)MAGNITUDE
向量招驴,來(lái)限制顯示出的法向量大懈莩獭(否則它們就有點(diǎn)大了)枷畏。
因?yàn)榉ň€(xiàn)的可視化通常都是用于調(diào)試目的,我們可以使用片段著色器虱饿,將它們顯示為單色的線(xiàn)(如果你愿意也可以是非常好看的線(xiàn)):
#version 330 core
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0, 1.0, 0.0, 1.0);
}
現(xiàn)在拥诡,首先使用普通著色器渲染模型触趴,再使用特別的法線(xiàn)可視化著色器渲染,你將看到這樣的效果:
盡管我們的納米裝現(xiàn)在看起來(lái)像是一個(gè)體毛很多而且?guī)е魺崾痔椎娜丝嗜猓軌蚝苡行У貛椭覀兣袛嗄P偷姆ň€(xiàn)是否正確冗懦。你可以想象到,這樣的幾何著色器也經(jīng)常用于給物體添加毛發(fā)(Fur)仇祭。
你可以在這里找到源碼披蕉。
#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>
#include <learnopengl/camera.h>
#include <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, 3.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("9.3.default.vs", "9.3.default.fs");
Shader normalShader("9.3.normal_visualization.vs", "9.3.normal_visualization.fs", "9.3.normal_visualization.gs");
// load models
// -----------
Model nanosuit(FileSystem::getPath("resources/objects/nanosuit/nanosuit.obj"));
// 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, 1.0f, 100.0f);
glm::mat4 view = camera.GetViewMatrix();;
glm::mat4 model;
shader.use();
shader.setMat4("projection", projection);
shader.setMat4("view", view);
shader.setMat4("model", model);
// draw model as usual
nanosuit.Draw(shader);
// then draw model with normal visualizing geometry shader
normalShader.use();
normalShader.setMat4("projection", projection);
normalShader.setMat4("view", view);
normalShader.setMat4("model", model);
nanosuit.Draw(normalShader);
// 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);
}
后記
本篇結(jié)束,下一篇是實(shí)例化乌奇。