版本記錄
版本號(hào) | 時(shí)間 |
---|---|
V1.0 | 2017.09.05 |
前言
OpenGL 圖形庫(kù)項(xiàng)目中一直也沒(méi)用過(guò)侠碧,最近也想學(xué)著使用這個(gè)圖形庫(kù)息拜,感覺(jué)還是很有意思,也就自然想著好好的總結(jié)一下较幌,希望對(duì)大家能有所幫助揍瑟。
1. OpenGL 圖形庫(kù)使用(一) —— 概念基礎(chǔ)
2. OpenGL 圖形庫(kù)使用(二) —— 渲染模式、對(duì)象乍炉、擴(kuò)展和狀態(tài)機(jī)
3. OpenGL 圖形庫(kù)使用(三) —— 著色器月培、數(shù)據(jù)類型與輸入輸出
4. OpenGL 圖形庫(kù)使用(四) —— Uniform及更多屬性
5. OpenGL 圖形庫(kù)使用(五) —— 紋理
6. OpenGL 圖形庫(kù)使用(六) —— 變換
7. OpenGL 圖形庫(kù)的使用(七)—— 坐標(biāo)系統(tǒng)之五種不同的坐標(biāo)系統(tǒng)(一)
變換矩陣的組合
以下內(nèi)容來(lái)自LearnOpenGL CN
上一篇文章中,每一個(gè)步驟都創(chuàng)建了一個(gè)變換矩陣:模型矩陣恩急、觀察矩陣和投影矩陣。一個(gè)頂點(diǎn)坐標(biāo)將會(huì)根據(jù)以下過(guò)程被變換到裁剪坐標(biāo):
Vclip = Mprojection ? Mview ? Mmodel ? Vlocal
注意矩陣運(yùn)算的順序是相反的(記住我們需要從右往左閱讀矩陣的乘法)纪蜒。最后的頂點(diǎn)應(yīng)該被賦值到頂點(diǎn)著色器中的gl_Position
衷恭,OpenGL
將會(huì)自動(dòng)進(jìn)行透視除法和裁剪。
頂點(diǎn)著色器的輸出要求所有的頂點(diǎn)都在裁剪空間內(nèi)纯续,這正是我們剛才使用變換矩陣所做的随珠。OpenGL
然后對(duì)裁剪坐標(biāo)執(zhí)行透視除法從而將它們變換到標(biāo)準(zhǔn)化設(shè)備坐標(biāo)。OpenGL會(huì)使用glViewPort
內(nèi)部的參數(shù)來(lái)將標(biāo)準(zhǔn)化設(shè)備坐標(biāo)映射到屏幕坐標(biāo)猬错,每個(gè)坐標(biāo)都關(guān)聯(lián)了一個(gè)屏幕上的點(diǎn)窗看。這個(gè)過(guò)程稱為視口變換。
3D
既然我們知道了如何將3D坐標(biāo)變換為2D坐標(biāo)倦炒,我們可以開(kāi)始使用真正的3D物體显沈,而不是枯燥的2D平面了。
在開(kāi)始進(jìn)行3D繪圖時(shí)逢唤,我們首先創(chuàng)建一個(gè)模型矩陣拉讯。這個(gè)模型矩陣包含了位移、縮放與旋轉(zhuǎn)操作鳖藕,它們會(huì)被應(yīng)用到所有物體的頂點(diǎn)上魔慷,以變換它們到全局的世界空間。讓我們變換一下我們的平面著恩,將其繞著x軸旋轉(zhuǎn)院尔,使它看起來(lái)像放在地上一樣。這個(gè)模型矩陣看起來(lái)是這樣的:
glm::mat4 model;
model = glm::rotate(model, glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f));
通過(guò)將頂點(diǎn)坐標(biāo)乘以這個(gè)模型矩陣喉誊,我們將該頂點(diǎn)坐標(biāo)變換到世界坐標(biāo)邀摆。我們的平面看起來(lái)就是在地板上,代表全局世界里的平面裹驰。
接下來(lái)我們需要?jiǎng)?chuàng)建一個(gè)觀察矩陣隧熙。我們想要在場(chǎng)景里面稍微往后移動(dòng),以使得物體變成可見(jiàn)的(當(dāng)在世界空間時(shí)幻林,我們位于原點(diǎn)(0,0,0))贞盯。要想在場(chǎng)景里面移動(dòng)音念,先仔細(xì)想一想下面這個(gè)句子:
將攝像機(jī)向后移動(dòng),和將整個(gè)場(chǎng)景向前移動(dòng)是一樣的躏敢。
這正是觀察矩陣所做的闷愤,我們以相反于攝像機(jī)移動(dòng)的方向移動(dòng)整個(gè)場(chǎng)景。因?yàn)槲覀兿胍笠苿?dòng)件余,并且OpenGL是一個(gè)右手坐標(biāo)系(Right-handed System)
讥脐,所以我們需要沿著z軸的正方向移動(dòng)。我們會(huì)通過(guò)將場(chǎng)景沿著z軸負(fù)方向平移來(lái)實(shí)現(xiàn)啼器。它會(huì)給我們一種我們?cè)谕笠苿?dòng)的感覺(jué)旬渠。
下圖就是一個(gè)右手坐標(biāo)系的示例圖,這里是回顧了端壳,不是新的知識(shí)告丢。
目前來(lái)說(shuō),觀察矩陣是這樣的:
glm::mat4 view;
// 注意损谦,我們將矩陣向我們要進(jìn)行移動(dòng)場(chǎng)景的反方向移動(dòng)岖免。
view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));
最后我們需要做的是定義一個(gè)投影矩陣。我們希望在場(chǎng)景中使用透視投影照捡,所以像這樣聲明一個(gè)投影矩陣:
glm::mat4 projection;
projection = glm::perspective(glm::radians(45.0f), screenWidth / screenHeight, 0.1f, 100.0f);
既然我們已經(jīng)創(chuàng)建了變換矩陣颅湘,我們應(yīng)該將它們傳入著色器。首先栗精,讓我們?cè)陧旤c(diǎn)著色器中聲明一個(gè)uniform變換矩陣然后將它乘以頂點(diǎn)坐標(biāo):
#version 330 core
layout (location = 0) in vec3 aPos;
...
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
// 注意乘法要從右向左讀
gl_Position = projection * view * model * vec4(aPos, 1.0);
...
}
我們還應(yīng)該將矩陣傳入著色器(這通常在每次的渲染迭代中進(jìn)行闯参,因?yàn)樽儞Q矩陣會(huì)經(jīng)常變動(dòng)):
int modelLoc = glGetUniformLocation(ourShader.ID, "model"));
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
... // 觀察矩陣和投影矩陣與之類似
我們的頂點(diǎn)坐標(biāo)已經(jīng)使用模型、觀察和投影矩陣進(jìn)行變換了悲立,最終的物體應(yīng)該會(huì):
- 稍微向后傾斜至地板方向赢赊。
- 離我們有一些距離。
- 有透視效果(頂點(diǎn)越遠(yuǎn)级历,變得越惺鸵啤)
下面看一下效果圖。
它看起來(lái)就像是一個(gè)3D的平面寥殖,靜止在一個(gè)虛構(gòu)的地板上玩讳。如果你得到的不是相同的結(jié)果,請(qǐng)檢查下完整的源代碼嚼贡。
更多的3D
到目前為止熏纯,我們一直都在使用一個(gè)2D平面,而且甚至是在3D空間里粤策!所以樟澜,讓我們大膽地拓展我們的2D平面為一個(gè)3D立方體。要想渲染一個(gè)立方體,我們一共需要36個(gè)頂點(diǎn)(6個(gè)面 x 每個(gè)面有2個(gè)三角形組成 x 每個(gè)三角形有3個(gè)頂點(diǎn))秩贰,這36個(gè)頂點(diǎn)的位置你可以從這里獲取霹俺。
為了有趣一點(diǎn),我們將讓立方體隨著時(shí)間旋轉(zhuǎn):
model = glm::rotate(model, (float)glfwGetTime() * glm::radians(50.0f), glm::vec3(0.5f, 1.0f, 0.0f));
然后我們使用glDrawArrays
來(lái)繪制立方體毒费,但這一次總共有36個(gè)頂點(diǎn)丙唧。
glDrawArrays(GL_TRIANGLES, 0, 36);
應(yīng)該能得到下面這樣的效果:
這的確有點(diǎn)像是一個(gè)立方體,但又有種說(shuō)不出的奇怪觅玻。立方體的某些本應(yīng)被遮擋住的面被繪制在了這個(gè)立方體其他面之上想际。之所以這樣是因?yàn)镺penGL是一個(gè)三角形一個(gè)三角形地來(lái)繪制你的立方體的,所以即便之前那里有東西它也會(huì)覆蓋之前的像素溪厘。因?yàn)檫@個(gè)原因胡本,有些三角形會(huì)被繪制在其它三角形上面,雖然它們本不應(yīng)該是被覆蓋的畸悬。
幸運(yùn)的是打瘪,OpenGL
存儲(chǔ)深度信息在一個(gè)叫做Z緩沖(Z-buffer)
的緩沖中,它允許OpenGL決定何時(shí)覆蓋一個(gè)像素而何時(shí)不覆蓋傻昙。通過(guò)使用Z緩沖,我們可以配置OpenGL來(lái)進(jìn)行深度測(cè)試彩扔。
1. Z緩沖
OpenGL存儲(chǔ)它的所有深度信息于一個(gè)Z緩沖(Z-buffer)
中妆档,也被稱為深度緩沖(Depth Buffer)
。GLFW會(huì)自動(dòng)為你生成這樣一個(gè)緩沖(就像它也有一個(gè)顏色緩沖來(lái)存儲(chǔ)輸出圖像的顏色)虫碉。深度值存儲(chǔ)在每個(gè)片段里面(作為片段的z值)贾惦,當(dāng)片段想要輸出它的顏色時(shí),OpenGL會(huì)將它的深度值和z緩沖進(jìn)行比較敦捧,如果當(dāng)前的片段在其它片段之后须板,它將會(huì)被丟棄,否則將會(huì)覆蓋兢卵。這個(gè)過(guò)程稱為深度測(cè)試(Depth Testing)
习瑰,它是由OpenGL自動(dòng)完成的。
然而秽荤,如果我們想要確定OpenGL真的執(zhí)行了深度測(cè)試甜奄,首先我們要告訴OpenGL我們想要啟用深度測(cè)試;它默認(rèn)是關(guān)閉的窃款。我們可以通過(guò)glEnable函數(shù)來(lái)開(kāi)啟深度測(cè)試课兄。glEnable
和glDisable
函數(shù)允許我們啟用或禁用某個(gè)OpenGL功能。這個(gè)功能會(huì)一直保持啟用/禁用狀態(tài)晨继,直到另一個(gè)調(diào)用來(lái)禁用/啟用它⊙滩現(xiàn)在我們想啟用深度測(cè)試,需要開(kāi)啟GL_DEPTH_TEST
:
glEnable(GL_DEPTH_TEST);
因?yàn)槲覀兪褂昧松疃葴y(cè)試,我們也想要在每次渲染迭代之前清除深度緩沖(否則前一幀的深度信息仍然保存在緩沖中)蜒茄。就像清除顏色緩沖一樣唉擂,我們可以通過(guò)在glClear
函數(shù)中指定DEPTH_BUFFER_BIT
位來(lái)清除深度緩沖:
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
重新運(yùn)行下程序看看OpenGL是否執(zhí)行了深度測(cè)試:
就是這樣!一個(gè)開(kāi)啟了深度測(cè)試扩淀,各個(gè)面都是紋理楔敌,并且還在旋轉(zhuǎn)的立方體!如果你的程序有問(wèn)題可以到這里下載源碼進(jìn)行比對(duì)驻谆。
2. 更多的立方體
現(xiàn)在我們想在屏幕上顯示10個(gè)立方體卵凑。每個(gè)立方體看起來(lái)都是一樣的紧唱,區(qū)別在于它們?cè)谑澜绲奈恢眉靶D(zhuǎn)角度不同花竞。立方體的圖形布局已經(jīng)定義好了焰手,所以當(dāng)渲染更多物體的時(shí)候我們不需要改變我們的緩沖數(shù)組和屬性數(shù)組班套,我們唯一需要做的只是改變每個(gè)對(duì)象的模型矩陣來(lái)將立方體變換到世界坐標(biāo)系中橙困。
首先削彬,讓我們?yōu)槊總€(gè)立方體定義一個(gè)位移向量來(lái)指定它在世界空間的位置烟具。我們將在一個(gè)glm::vec3
數(shù)組中定義10個(gè)立方體位置:
glm::vec3 cubePositions[] = {
glm::vec3( 0.0f, 0.0f, 0.0f),
glm::vec3( 2.0f, 5.0f, -15.0f),
glm::vec3(-1.5f, -2.2f, -2.5f),
glm::vec3(-3.8f, -2.0f, -12.3f),
glm::vec3( 2.4f, -0.4f, -3.5f),
glm::vec3(-1.7f, 3.0f, -7.5f),
glm::vec3( 1.3f, -2.0f, -2.5f),
glm::vec3( 1.5f, 2.0f, -2.5f),
glm::vec3( 1.5f, 0.2f, -1.5f),
glm::vec3(-1.3f, 1.0f, -1.5f)
};
現(xiàn)在袄膏,在游戲循環(huán)中勒魔,我們調(diào)用glDrawArrays 10
次甫煞,但這次在我們渲染之前每次傳入一個(gè)不同的模型矩陣到頂點(diǎn)著色器中。我們將會(huì)在游戲循環(huán)中創(chuàng)建一個(gè)小的循環(huán)用不同的模型矩陣渲染我們的物體10次冠绢。注意我們也對(duì)每個(gè)箱子加了一點(diǎn)旋轉(zhuǎn):
glBindVertexArray(VAO);
for(unsigned int i = 0; i < 10; i++)
{
glm::mat4 model;
model = glm::translate(model, cubePositions[i]);
float angle = 20.0f * i;
model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
ourShader.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
}
這段代碼將會(huì)在每次新立方體繪制出來(lái)的時(shí)候更新模型矩陣抚吠,如此總共重復(fù)10次。然后我們應(yīng)該就能看到一個(gè)擁有10個(gè)正在奇葩地旋轉(zhuǎn)著的立方體的世界弟胀。
你可以對(duì)照一下源代碼 楷力。
后記
未完,待續(xù)~~