opengl 纹理贴到对应的位置_一步步学OpenGL(27) -《公告牌技术与几何着色器》
教程 27
公告牌技術(shù)與幾何著色器
原文: http://ogldev.atspace.co.uk/www/tutorial27/tutorial27.html
CSDN完整版專欄: https://blog.csdn.net/cordova/article/category/9266966
背景
從最初的一系列教程我們已經(jīng)應(yīng)用過了頂點(diǎn)著色器和片段著色器,但事實(shí)上我們還忽略了一個(gè)非常重要的著色階段,叫做幾何著色器(GS)。幾何著色器在微軟的DirectX 10之后被引入,之后也加入到了核心的OpenGL 3.2中。頂點(diǎn)著色器中是按照頂點(diǎn)一個(gè)一個(gè)執(zhí)行的,片段著色器則是一個(gè)像素一個(gè)像素執(zhí)行,而幾何著色器是以圖元為單位執(zhí)行,這意味著在我們繪制三角形時(shí),每次調(diào)用幾何著色器接收到的就是一個(gè)三角形,而繪制直線每次調(diào)用收到的就是一條直線,等等。這就給幾何著色器提供了一個(gè)看待模型的獨(dú)特的角度,開發(fā)者可以知道頂點(diǎn)和頂點(diǎn)之間拓?fù)潢P(guān)系,從而可以基于此開發(fā)一些新的技術(shù)。
頂點(diǎn)著色器總是以一個(gè)頂點(diǎn)作為輸入,并對(duì)應(yīng)一個(gè)頂點(diǎn)作輸出(不可以自行增加或減少頂點(diǎn)),而幾何著色器卻有著特殊的功能,它可以改變經(jīng)過它的圖元,這種改變包括:
- 改變新傳遞進(jìn)來的圖元的拓?fù)浣Y(jié)構(gòu)。幾何著色器可以接收任何拓?fù)漕愋偷膱D元,但只能輸出頂點(diǎn)列表(point lists)、折線(line strip)和三角帶(triangle strips);
 - 幾何著色器接受一個(gè)圖元作為輸入,在處理過程中它可以將這個(gè)圖元全部丟棄或者輸出一個(gè)或更多的圖元(也就是說它可以產(chǎn)生比它得到的更多或更少的頂點(diǎn))。這個(gè)能力被叫做幾何增長(zhǎng)(growing geometry)。這一章我們將會(huì)利用幾何著色器的這種能力。
 
幾何著色器是可選擇的。如果我們編譯程序的時(shí)候不使用幾何著色器,圖元會(huì)直接的從頂點(diǎn)著色器進(jìn)入片元著色器。這就是為什么我們之前并沒有使用頂點(diǎn)著色器,卻可以直接跳過該階段正常繪制出圖形。
三角形列表中的三角形圖元是以每三個(gè)頂點(diǎn)一組構(gòu)建的,例如,0-2前三個(gè)頂點(diǎn)構(gòu)建起第一個(gè)三角形,3-5三個(gè)頂點(diǎn)構(gòu)建起第二個(gè)三角形,以此類推。為了計(jì)算已有頂點(diǎn)所組成的三角形個(gè)數(shù),只要用頂點(diǎn)數(shù)除以3即可(多余的頂點(diǎn)直接拋棄)。但事實(shí)上,使用三角形帶構(gòu)建更有效,不需要每個(gè)三角形都用專門的三個(gè)頂點(diǎn)構(gòu)建,而是在使用三個(gè)頂點(diǎn)將第一個(gè)三角形構(gòu)建完成后,重復(fù)利用其中的兩個(gè)頂點(diǎn),然后再添加一個(gè)頂點(diǎn)即可構(gòu)建第二個(gè)三角形。例如0-2三個(gè)頂點(diǎn)構(gòu)建起一個(gè)三角形后,再添加一個(gè)頂點(diǎn)3,1-3三個(gè)頂點(diǎn)構(gòu)成第二個(gè)三角形,這樣0-3四個(gè)頂點(diǎn)即可緊密拼接構(gòu)建起兩個(gè)三角形,以此類推,再添加頂點(diǎn)4,2-4三個(gè)頂點(diǎn)又構(gòu)成一個(gè)三角形等等。也就是說,在第一個(gè)三角形使用三個(gè)頂點(diǎn)構(gòu)建完成后,每天添加一個(gè)頂點(diǎn)即可再構(gòu)成一個(gè)三角形。這里有一個(gè)例子如下圖:
可以看到,三角帶中7個(gè)三角形只用了9個(gè)頂點(diǎn),要是在三角形列表中,9個(gè)頂點(diǎn)就只能構(gòu)建3個(gè)三角形。
三角帶有一個(gè)有關(guān)三角形內(nèi)部環(huán)繞順序的重要性質(zhì):奇數(shù)三角形的環(huán)繞順序是反向的。這就意味著如下的順序:[0,1,2],[1,3,2], [2,3,4], [3,5,4],以此類推。下面的圖片顯示了這個(gè)順序:
了解了幾何著色器之后,現(xiàn)在看如何利用幾何著色器來實(shí)現(xiàn)一種非常有用的熱門技術(shù):公告板技術(shù)(billboarding)。公告板是一個(gè)始終朝向相機(jī)的四邊形,當(dāng)相機(jī)在場(chǎng)景中轉(zhuǎn)動(dòng)的時(shí)候,公告板也會(huì)隨著相機(jī)轉(zhuǎn)動(dòng)保證相機(jī)方向向量始終垂直于公告板的正面。這個(gè)和現(xiàn)實(shí)中公路邊的公告板類似,公告板的放置方向會(huì)讓盡可能多路過的開車司機(jī)看到。有了這種面向相機(jī)的四邊形之后,我們就很容易將怪物角色、樹木等任何場(chǎng)景中重復(fù)性高、數(shù)量多的物體以紋理貼圖的形式直接貼到四邊形公告板上(而不需要復(fù)雜的計(jì)算和渲染實(shí)際的3d模型),始終朝向相機(jī)。公告板常常用來創(chuàng)建需要大量樹木的森林效果,由于公告板始終朝向相機(jī),玩家會(huì)誤以為看到的物體是有實(shí)際深度的,而事實(shí)上單純就是個(gè)平面而已。每一個(gè)公告板只需要4個(gè)頂點(diǎn),因此比起使用大量實(shí)際的3d模型代價(jià)就小得多了。
在這個(gè)教程中,我們創(chuàng)建一個(gè)頂點(diǎn)緩沖器,并為公告板存放頂點(diǎn)的世界空間坐標(biāo),每一個(gè)坐標(biāo)就是一個(gè)單獨(dú)的3維坐標(biāo)點(diǎn)。我們會(huì)將這些頂點(diǎn)坐標(biāo)傳送到幾何著色器,然后構(gòu)建相應(yīng)的四邊形作為公告板。這意味著幾何著色器會(huì)輸入頂點(diǎn)列表,然后輸出三角形帶。利用三角帶的優(yōu)點(diǎn)我們可以使用四個(gè)頂點(diǎn)創(chuàng)建出一個(gè)四邊形。
幾何著色器會(huì)負(fù)責(zé)調(diào)整四邊形始終朝向相機(jī),并為每一個(gè)輸出的頂點(diǎn)附加紋理坐標(biāo),這樣片段著色器只需要直接從紋理上采樣就可以得到最終的顏色信息。
現(xiàn)在看怎樣讓公告板總是朝向相機(jī)。在下圖中黑點(diǎn)表示相機(jī),紅點(diǎn)表示公告板的位置。雖然圖中位置看上去好像在一個(gè)平面上,但實(shí)際兩個(gè)點(diǎn)都在世界空間下,世界空間中任意兩個(gè)點(diǎn)都是可能的。
這里創(chuàng)建一個(gè)從公告板到相機(jī)的向量:
然后添加一個(gè)向量(0,1,0):
對(duì)這兩個(gè)向量做叉乘,結(jié)果得到一個(gè)垂直于這兩個(gè)向量所在平面的向量,然后就要沿著這個(gè)結(jié)果向量的方向來擴(kuò)展創(chuàng)建我們的四邊形,保證四邊形平面和相機(jī)朝向垂直,這樣才符合我們想要的結(jié)果。同樣的場(chǎng)景下我們可以得到下面這樣(黃色向量是叉乘的結(jié)果向量):
一個(gè)容易讓開發(fā)者疑惑的事情是關(guān)于向量做叉積的順序問題(A叉積B,還是B叉積A?),兩種情況會(huì)得到兩個(gè)反向的結(jié)果向量。事先知道具體的結(jié)果向量是很關(guān)鍵的,因?yàn)槲覀円@樣輸出頂點(diǎn),使從相機(jī)的視角看時(shí)三角形組成的四邊形呈順時(shí)針方向。這里要用到左手法則了:
如果站在公告板的位置(紅點(diǎn)),食指指向相機(jī),中指指向上方的天空,然后你的大拇指將會(huì)沿著“食指”和“中指”叉乘的結(jié)果的方向(這里剩下的兩個(gè)手指保持握緊)。此教程中,我們將叉乘的結(jié)果稱為“右”向量,因?yàn)閺南鄼C(jī)位置看我們的手,‘右’向量是指向右側(cè)的。反過來,“中指”叉乘“食指”又可以產(chǎn)生一個(gè)反向的“左”向量。(這里我們使用左手定則的原因是因?yàn)槲覀兪褂玫氖亲笫肿鴺?biāo)系(Z軸指向屏幕內(nèi)))。在右手坐標(biāo)系中情況就相反了,那樣就該選用右手坐標(biāo)系了。
源代碼詳解
(billboard_list.h:27)
class BillboardList { public:BillboardList();~BillboardList();bool Init(const std::string& TexFilename);void Render(const Matrix4f& VP, const Vector3f& CameraPos);private:void CreatePositionBuffer();GLuint m_VB;Texture* m_pTexture;BillboardTechnique m_technique; };BillboardList類封裝了創(chuàng)建公告板需要的所有東西,初始化函數(shù)Init()的參數(shù)為一個(gè)文件名,文件就是那個(gè)作為紋理貼圖貼到公告板上的圖像。Render()渲染函數(shù)在主渲染循環(huán)中被調(diào)用,負(fù)責(zé)設(shè)置狀態(tài)和渲染公告板。這個(gè)函數(shù)需要兩個(gè)參數(shù):一個(gè)是視圖和投影組合矩陣,一個(gè)是相機(jī)的世界坐標(biāo)位置。由于公告板的位置也是定義在世界空間中的,所以我們才直接到了視圖和投影階段,跳過世界空間變換的部分。這個(gè)類有三個(gè)私有屬性:一個(gè)存儲(chǔ)公告板位置的頂點(diǎn)緩沖器,一個(gè)指向公告板紋理貼圖的指針,和一個(gè)包含相關(guān)著色器的BillboardTechnique公告板類。
(billboard_list.cpp:80)
void BillboardList::Render(const Matrix4f& VP, const Vector3f& CameraPos) {m_technique.Enable();m_technique.SetVP(VP);m_technique.SetCameraPosition(CameraPos);m_pTexture->Bind(COLOR_TEXTURE_UNIT);glEnableVertexAttribArray(0);glBindBuffer(GL_ARRAY_BUFFER, m_VB);glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vector3f), 0); // position glDrawArrays(GL_POINTS, 0, NUM_ROWS * NUM_COLUMNS);glDisableVertexAttribArray(0); }這個(gè)函數(shù)啟用了BillboardTechnique公告板類,并設(shè)置了OpenGL中一些必要的狀態(tài),繪制頂點(diǎn)且這些頂點(diǎn)之后會(huì)在幾何著色器階段轉(zhuǎn)化成四邊形面。在這個(gè)Demo中,公告板位置是按照嚴(yán)格的行列順序排列的,因此我們可以行列數(shù)相乘得到頂點(diǎn)的數(shù)量。需要注意,我們?cè)诶L制的時(shí)候使用的繪制模式為點(diǎn)模式(GL_POINTS),在幾何著色器中要與其對(duì)應(yīng)。
(billboard_technique.h:24)
class BillboardTechnique : public Technique { public:BillboardTechnique();virtual bool Init();void SetVP(const Matrix4f& VP);void SetCameraPosition(const Vector3f& Pos);void SetColorTextureUnit(unsigned int TextureUnit);private:GLuint m_VPLocation;GLuint m_cameraPosLocation;GLuint m_colorMapLocation; };這里是BillboardTechnique的類接口,它只需要三個(gè)參數(shù)來完成下面的任務(wù):視圖和投影組合矩陣、相機(jī)世界空間位置和與公告板綁定的紋理單元的數(shù)量。
(billboard.vs)
#version 330
layout (location = 0) in vec3 Position;void main() {gl_Position = vec4(Position, 1.0); }這是公告板類的頂點(diǎn)著色器,由于主要工作都將在幾何著色器中完成,因此在這里頂點(diǎn)著色器就不要太簡(jiǎn)單了哈哈。頂點(diǎn)緩沖器只包含頂點(diǎn)坐標(biāo)向量,而且這些坐標(biāo)已經(jīng)在世界空間中定義了,所以可以直接將它們傳給幾何著色器,即可。
(billboard.gs:1)
#version 330layout (points) in; layout (triangle_strip) out; layout (max_vertices = 4) out;公告板技術(shù)的核心就在幾何著色器了,我們分解開一步步來看。開始我們先使用‘layout’關(guān)鍵字聲明一些全局緩沖器。我們要先告訴渲染管線輸入來的參數(shù)結(jié)構(gòu)是點(diǎn)列表,輸出的是三角帶,并且說明輸出的頂點(diǎn)個(gè)數(shù)最多為4個(gè)。這些關(guān)鍵詞也會(huì)提示圖形驅(qū)動(dòng)器從幾何著色器輸出頂點(diǎn)的最大個(gè)數(shù),提前知道頂點(diǎn)個(gè)數(shù)上限可以給驅(qū)動(dòng)器機(jī)會(huì)來優(yōu)化幾何著色器在某些特定情況下的動(dòng)作。我們知道對(duì)于每一個(gè)輸入的頂點(diǎn)要輸出的是一個(gè)擴(kuò)展的四邊形,因此我們?cè)O(shè)置最大頂點(diǎn)數(shù)為4。
(billboard.gs:7)
uniform mat4 gVP; uniform vec3 gCameraPos;out vec2 TexCoord;幾何著色器得到了世界空間坐標(biāo),因此他只需要一個(gè)視圖和投影組合變換矩陣即可。另外還需要知道相機(jī)的位置來計(jì)算如何讓公告板始終朝向它。幾何著色器為片段著色器創(chuàng)建出了紋理坐標(biāo),因此我們也要聲明紋理坐標(biāo)變量。
(billboard.gs:12)
void main() {vec3 Pos = gl_in[0].gl_Position.xyz;上面一行代碼是針對(duì)幾何著色器獨(dú)有的。由于在幾何著色器中執(zhí)行的是一個(gè)完整的圖元,因此事實(shí)上我們可以訪問組成圖元的每一個(gè)頂點(diǎn),這個(gè)通過內(nèi)置的‘gl_in’變量實(shí)現(xiàn)。這個(gè)變量是一個(gè)結(jié)構(gòu)體數(shù)組,每個(gè)結(jié)構(gòu)體都包含了寫入到頂點(diǎn)著色器gl_Position中的位置信息。為了訪問頂點(diǎn)信息,我們可以使用要訪問的頂點(diǎn)在圖元中的索引找到。在這個(gè)特定的例子中,參數(shù)的輸入結(jié)構(gòu)為點(diǎn)列表,所以每個(gè)圖元只有一個(gè)可訪問的單獨(dú)的點(diǎn),可以使用'gl_in[0]'獲取它。如果輸入結(jié)構(gòu)是個(gè)三角形,我們可能還會(huì)使用'gl_in[1]'和'gl_in[2]'來訪問其他點(diǎn)。我么只需要使用頂點(diǎn)位置向量的前三個(gè)xyz分量,通過本地變量'.xyz'提取。
vec3 toCamera = normalize(gCameraPos - Pos);vec3 up = vec3(0.0, 1.0, 0.0);vec3 right = cross(toCamera, up);這里我們利用文章開始背景部分結(jié)尾的原理實(shí)現(xiàn)讓公告板朝向相機(jī)。我們將當(dāng)前公告板的位置點(diǎn)到相機(jī)位置的向量和垂直向上的方向向量做叉積,得到從相機(jī)看公告板視角的‘右’向量,然后我們要使用這個(gè)向量圍著公告板的位置擴(kuò)展一個(gè)四邊形面。
Pos -= (right * 0.5);gl_Position = gVP * vec4(Pos, 1.0);TexCoord = vec2(0.0, 0.0);EmitVertex();Pos.y += 1.0;gl_Position = gVP * vec4(Pos, 1.0);TexCoord = vec2(0.0, 1.0);EmitVertex();Pos.y -= 1.0;Pos += right;gl_Position = gVP * vec4(Pos, 1.0);TexCoord = vec2(1.0, 0.0);EmitVertex();Pos.y += 1.0;gl_Position = gVP * vec4(Pos, 1.0);TexCoord = vec2(1.0, 1.0);EmitVertex();EndPrimitive(); }頂點(diǎn)緩沖器中的點(diǎn)可以被認(rèn)為是四邊形底邊的中點(diǎn),我們要從中點(diǎn)創(chuàng)建兩個(gè)面朝相機(jī)的正面三角形。開始先用中點(diǎn)減去‘右’向量的一半,從而得到四邊形的左下角。然后通過乘以視圖和投影組合變換矩陣計(jì)算該點(diǎn)在裁剪空間的位置,并設(shè)置該點(diǎn)的紋理坐標(biāo)為(0,0),便于將整個(gè)紋理完整貼到這個(gè)平面上。為了將新產(chǎn)生的頂點(diǎn)傳遞到管線的下一個(gè)階段,我們需要調(diào)用內(nèi)置的EmitVertex()函數(shù)。這個(gè)函數(shù)調(diào)用后,我們之前寫入gl_Position的數(shù)據(jù)就無效了,因此我們要為其設(shè)置新值。和左下角點(diǎn)產(chǎn)生方法類似的,我們繼續(xù)創(chuàng)建出四邊形左上角和右下角的點(diǎn),這樣三個(gè)點(diǎn)就構(gòu)建出了第一個(gè)正面三角形。由于幾何著色器的輸出是三角帶,之后我們只需要另外一個(gè)頂點(diǎn)即可構(gòu)建第二個(gè)三角形,使用前面三角形的后兩個(gè)頂點(diǎn)(四邊形的對(duì)角線)和新頂點(diǎn)構(gòu)建。第四個(gè)新頂點(diǎn)也是最后一個(gè)頂點(diǎn),即四邊形的右上角。結(jié)束三角帶的構(gòu)建要調(diào)用內(nèi)置的EndPrimitive()函數(shù)。
(billboard.fs)
#version 330uniform sampler2D gColorMap;in vec2 TexCoord; out vec4 FragColor;void main() {FragColor = texture2D(gColorMap, TexCoord);if (FragColor.r == 0 && FragColor.g == 0 && FragColor.b == 0) {discard;} }片段著色器很簡(jiǎn)單,它的主要工作是使用幾何著色器創(chuàng)建的紋理坐標(biāo)進(jìn)行紋理采樣。這里有一個(gè)新特性:內(nèi)置的關(guān)鍵字'discard'用于在某些情況下將某些像素片元完全丟棄。這個(gè)教程中我們用了Doom中地獄騎士的圖片,展示黑色背景下的怪物場(chǎng)景,但是直接貼上整張圖片會(huì)有黑色的背景,就是說公告板內(nèi)容比怪物要大,怪物背景不是透明的,我們不希望這樣,因此我們可以檢測(cè)文素的顏色,如果是黑色就直接拋棄該像素,這樣就會(huì)只顯示怪物了。可以嘗試注釋掉'discard'語(yǔ)句看效果有什么不同。
總結(jié)
以上是生活随笔為你收集整理的opengl 纹理贴到对应的位置_一步步学OpenGL(27) -《公告牌技术与几何着色器》的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: Outlook收件箱修复工具Inbox
 - 下一篇: Qt实现 QQ好友列表QToolBox