GLSL vary、atrribute、in、out的区别
這個(gè)問題困擾我很久了,前一段時(shí)間面試,面試官問我GLSL中的vary、atrribute,當(dāng)時(shí)我就懵逼了,一陣心虛,我只說我知道in out,應(yīng)為我學(xué)的就只有in out,回來自己也查了查,GLSL中確實(shí)有vary、atrribute這么用的,但是一直沒搞清楚他們與in、out的區(qū)別聯(lián)系,今天看了這篇文章豁然開朗,原來是vary、attribute是老版本的GLSL中的關(guān)鍵字,現(xiàn)在已經(jīng)統(tǒng)一使用in、out了,當(dāng)時(shí)特么白心虛了,是面試官知識結(jié)構(gòu)太老,當(dāng)時(shí)我說in、out,他貌似不知道。。。廢話不多說了,直接上原文。
OpenGL/GLSL規(guī)范在不斷演進(jìn)著,我們漸漸走進(jìn)可編程管道的時(shí)代的同時(shí),嶄新的功能接口也讓我們有點(diǎn)繚亂的感覺。本文再次從OpenGL和GLSL之間數(shù)據(jù)的傳遞這一點(diǎn),記錄和介紹基于OpenGL3.x的新方式,也會適時(shí)介紹Unform Buffer Objecct(UBO)這一重要特性。——ZwqXin.com
本文可視為大致一年半前的本博客的[OpenGL/GLSL數(shù)據(jù)傳遞小記(2.x)]一文的延續(xù)。對這方面不熟悉的話請先瀏覽一下該文中介紹的基本概念。在該文中,我把這些傳遞分為attribute變量、uniform變量、varying變量和Fragment Shader輸出,這四部分(主要講述前兩部分)。而本文再次按此四部分,談?wù)勗贕L3.x(NVidia 8Series以后顯卡所支持的OpenGL版本)中的數(shù)據(jù)傳遞方式的變化。
本文來源于?ZwqXin?(http://www.zwqxin.com/), 轉(zhuǎn)載請注明
??????原文地址:http://www.zwqxin.com/archives/shaderglsl/communication-between-opengl-glsl-2.html
1. attribute變量
在前文中提及到GLSL中每一個(gè)attribute變量都有一個(gè)“位置”值(Location),在ShaderProgram鏈接(link)前,可以Bind之,鏈接之后,可以Get之。通過這兩種方式都可以建立attribute變量與頂點(diǎn)屬性的聯(lián)系。如今引入第三種方式——直接在GLSL代碼中指定這些位置值:
glsl3.x代碼 (Vertex Program)在上面的Vertex Program代碼中,第一行(#version?330),表明我們現(xiàn)在使用的GLSL版本是GLSL3.3,以區(qū)別于以前的版本并允許我們使用基于GLSL3.3的功能。在過去,OpenGL的版本和GLSL版本是不統(tǒng)一的(前文中的GL2.2所對應(yīng)的是GLSL1.2,而后來的對應(yīng)關(guān)系是GL3.0-GLSL1.3,GL3.1-GLSL1.4,GL3.2-GLSL1.5),直到2010年OpenGL3.3/4.0規(guī)范的提出,khronos委員會決定讓兩者版本統(tǒng)一,所以就有了現(xiàn)在本博客所使用的OpenGL3.3-GLSL3.3的對應(yīng)關(guān)系(注,ShaderModel4.0的顯卡可達(dá)到的最高版本)。
接下來的幾行聲明了4個(gè)attribute變量。在GL2.x中一個(gè)attribute變量通常是“attribute vec3?attrib_position;”這樣來表示,在GL3.x中,廢棄了attribute關(guān)鍵字(以及varying關(guān)鍵字),屬性變量統(tǒng)一用in/out作為前置關(guān)鍵字,對每一個(gè)Shader stage來說,in表示該屬性是作為輸入的屬性,out表示該屬性是用于輸出的屬性。這里,attribute變量作為Vertex Shader的頂點(diǎn)輸入屬性,所以都用in標(biāo)記。另外,這里使用了layout關(guān)鍵字(通常是layout(layoutAttrib1=XXX, layoutAttrib2=XXX, ...)這樣的形式)。這個(gè)關(guān)鍵字用于一個(gè)具體變量前,用于顯式標(biāo)明該變量的一些布局屬性,這里就是顯式設(shè)定了該attribute變量的位置值(location),其作用跟ShaderProgram(著色程序)鏈接前調(diào)用glBindAttribLocation來設(shè)定atribute變量的位置值是等效的。
為什么采用這種方式更好呢?其一當(dāng)然是編碼量減少了,二來也避免了去Get某個(gè)attribute的location帶來的開銷,三來,最重要的是,它重定義了OpenGL和GLSL之間attribute變量屬性的依賴。過去我們的OpenGL端必須首先要知道GLSL端某個(gè)attribute的名字,才能設(shè)置/獲得其位置值,如今兩者只需要location對應(yīng)起來就可以完成繪制時(shí)頂點(diǎn)屬性流的傳遞了。不再需要在ShaderProgram的compile和link之間插入代碼也更方便于其模塊化。
2.uniform變量
對于uniform變量的聲明方式,跟GL2.x的一致,使用uniform關(guān)鍵字就可以了。
glsl代碼每一個(gè)uniform變量也都有其一個(gè)“位置值”(Location),在OpenGL中,我們可以通過glGetUniformLocation來獲得。那么我們可以不可以像attribute變量那樣,在Shader代碼中顯式指定這個(gè)Location呢?(其好處也是跟上述差不多的,但就是如果uniform變量太多的話這樣做也麻煩,因?yàn)榈迷诖a中一個(gè)一個(gè)指定不重復(fù)的location。)嘛,attribute變量location的顯式指定,是經(jīng)由GL擴(kuò)展GL_ARB_explicit_attrib_location實(shí)現(xiàn)的,而事實(shí)上,現(xiàn)在也有GL_ARB_explicit_uniform_location這樣一個(gè)GL擴(kuò)展,能實(shí)現(xiàn)這樣的功能,只不過它是OpenGL4.3標(biāo)準(zhǔn)的一部分,隸屬于GLSL4.3,所以即使GL3.x支持這個(gè)擴(kuò)展,我們還是暫時(shí)不要用的好。
那我們就像往常一樣,在glUseProgram啟用了某個(gè)ShaderProgram之后,一個(gè)一個(gè)地給每個(gè)unifom變量關(guān)聯(lián)數(shù)據(jù)咯(通過其location)——等等,這是在運(yùn)行期間設(shè)置數(shù)據(jù)值吧,那如果我這個(gè)關(guān)聯(lián)數(shù)據(jù)并不是每幀都變化的,甚至它是一個(gè)固定值,這樣做豈不太無聊太浪費(fèi)了?事實(shí)上我們還是可以在glUseProgram之外綁定數(shù)據(jù)的——乃至直接在初始化時(shí)。這得益于glProgramUniform系列函數(shù)的引入,它比起往常的glUniform要多一個(gè)參數(shù)用來接收一個(gè)ShaderProgram的ID。在建立ShaderProgram后,我們也不需要glUseProgram來預(yù)先綁定它就可以直接取得某個(gè)uniform變量的location值并用glProgramUniform系列函數(shù)關(guān)聯(lián)數(shù)據(jù),而且這個(gè)數(shù)據(jù)在其后運(yùn)行期間的每次glUseProgram后都不會失效。從理論上將,這族函數(shù)完全可以替代glUniform系列函數(shù)(是它們功能的一個(gè)超集),但是就不知道會不會有性能上的損失了(這個(gè)暫時(shí)目前找不到說法),所以我暫時(shí)建議是只對那些非動態(tài)變化的uniform變量使用了。
再來看看uniform變量的問題。通常一個(gè)稍微復(fù)雜點(diǎn)點(diǎn)、更多控制參數(shù)的Shader,都會有大量的Uniform變量需要設(shè)置,所以導(dǎo)致了我們很多時(shí)候在glUseProgram之后要調(diào)用一長串的glUniform函數(shù)來傳遞該P(yáng)ass的數(shù)據(jù)。有沒有方法盡量把這些操作合并呢?另外,我們知道一個(gè)Shader的可用Uniform數(shù)據(jù)大小是有一個(gè)上限值的(例如我目前顯卡的一個(gè)vertex shader的GL_MAX_VERTEX_UNIFORM_COMPONENTS值是4096,意味著我在一個(gè)VertexShader里使用的active uniforms,大概就是最多4096個(gè)float/int值了,或者說最多1024個(gè)vec4、最多256個(gè)mat16),那么有沒辦法提高這個(gè)上限呢?在[MD5模型的格式、導(dǎo)入與頂點(diǎn)蒙皮式骨骼動畫II]這篇文章中,因?yàn)閾?dān)心uniform數(shù)量不足以支撐傳入的眾多個(gè)骨骼矩陣,所以優(yōu)先選擇TBO(Texture Buffer Object)作為傳入數(shù)據(jù)的媒介,把數(shù)據(jù)裝入一個(gè)一維紋理的Buffer中以提供給Shader。那么除了使用紋理數(shù)據(jù)外,還有沒有更直接的方式呢?
?Uniform Buffer Object(UBO)
UBO,顧名思義,就是一個(gè)裝載Uniform變量數(shù)據(jù)的Buffer Object。就概念而言,它跟VBO([學(xué)一學(xué),VBO] )之類Buffer Object差不多,反正就是顯存中一塊用于儲存特定數(shù)據(jù)的區(qū)域了。在OpenGL端,它的創(chuàng)建、更新、銷毀的方式都與其他Buffer Object沒什么區(qū)別,我們只不過把一個(gè)或多個(gè)uniform數(shù)據(jù)交給它,以替代glUniform的方式傳遞數(shù)據(jù)而已。這里必須明確一點(diǎn),這些數(shù)據(jù)是給到這個(gè)UBO,存儲于這個(gè)UBO上,而不再是交給ShaderProgram,所以它們不會占用這個(gè)ShaderProgram自身的uniform存儲空間,所以UBO是一種全新的傳遞數(shù)據(jù)的方式,從路徑到目的地,都跟傳統(tǒng)uniform變量的方式不一樣。自然,對于這樣的數(shù)據(jù),在Shader中不能再使用上面代碼中的方式來指涉了。隨著UBO的引入,GLSL也引入了uniform block這種指涉工具。
glsl代碼uniform block是Interface block的一種,(layout意義容后再述)在unifom關(guān)鍵字后直接跟隨一個(gè)block name和大括號,里面是一個(gè)或多個(gè)uniform變量。一個(gè)uniform block可以指涉一個(gè)UBO的數(shù)據(jù)——我們要把block里的uniform變量與OpenGL里的數(shù)據(jù)建立關(guān)聯(lián)。 因?yàn)檫@些uniform變量不是存儲在Shader的“uniform區(qū)域”里的,所以也就沒有那一套“位置值”(location),那么我們通過什么建立關(guān)聯(lián)呢?
對于每一個(gè)uniform block,都有一個(gè)“索引值”(index),這個(gè)索引值我們可以在OpenGL中獲得,并把它與一個(gè)具體的UBO關(guān)聯(lián)起來。這樣block內(nèi)的數(shù)據(jù)聲明就會與UBO中的實(shí)質(zhì)數(shù)據(jù)聯(lián)系起來了:
OpenGL代碼一般我們可以使用glGetUniformBlockIndex來獲取這個(gè)Index,但擴(kuò)展GL_ARB_program_interface_query引入了比較統(tǒng)一的獲取ShaderProgram內(nèi)資源的相關(guān)屬性的API(詳見此擴(kuò)展的spec),所以也可以以GL_UNIFORM_BLOCK調(diào)用glGetProgramResourceIndex來獲取資源的Index。得到名為matVP的uniform block的Index后,我們可以查詢這個(gè)block的相關(guān)信息(glGetActiveUniformBlockiv)。為了建立合適大小的UBO,這里查詢了這個(gè)block所需的字節(jié)大小(GL_UNIFORM_BLOCK_DATA_SIZE)的值(注意這個(gè)值代表此block所占的大小,它可能會比block內(nèi)數(shù)據(jù)實(shí)際相加后的值要大,下面會再述)。
建立一個(gè)UBO的過程跟建立其他類型的Buffer Object相似,不過Target是GL_UNIFORM_BUFFER,數(shù)據(jù)為空。接下來是把一個(gè)UBO(ID為m_nUBO)和Shader內(nèi)的uniform block(Index為nMatVPBlockIndex)相關(guān)聯(lián):把它們都關(guān)聯(lián)到同一個(gè)uniform buffer binding-point。其中前者通過glBindBufferBase或glBindBufferRange來完成,其中第二個(gè)參數(shù)就是binding-point,這里選擇的是binding-point_0(參數(shù)值為0,當(dāng)然你可以輸入1、2、3...以選擇binding-point_1、binding-point_2、binding-point_3…);同樣,對于后者uniform block,也通過glUniformBlockBinding來完成,其中第三個(gè)參數(shù)是binding-point,這里同樣選擇了第0個(gè)binding-point——這樣OpenGL端的UBO和GLSL端的uniform block就聯(lián)系在一起了。Shader中需要使用block中的uniform變量時(shí),就會索引到對應(yīng)的UBO中對應(yīng)的位置的數(shù)據(jù)。
所謂binding-point(或者說binding-location),我理解為是OpenGL的Context上的一個(gè)個(gè)狀態(tài)位。通常來說,我們可以建立非常多的UBO,它們的數(shù)據(jù)區(qū)在顯存中,以ID標(biāo)識,一般通過Context綁定一個(gè)UBO的ID的方式讓OpenGL去尋找對應(yīng)的顯存位置——這是一種非常耗時(shí)的操作(應(yīng)該說,所有bind類的操作都是)。數(shù)據(jù)需要更新就算了,但如果Shader執(zhí)行時(shí)也必須為每個(gè)uniform block去綁定、尋覓數(shù)據(jù)區(qū)……為避免這樣的情況所以就需要一個(gè)足以減少消耗的橋梁物,這個(gè)中間物件保存著能夠直達(dá)具體某個(gè)UBO數(shù)據(jù)區(qū)的“方式”(不妨?xí)杭傧霝樵摂?shù)據(jù)區(qū)的起始顯存地址、長度等),然后我們把這個(gè)中間物件的位置告訴Shader,讓Shader在需要時(shí)直接“來到”這個(gè)中間件中獲取某個(gè)顯存區(qū)的實(shí)質(zhì)數(shù)據(jù)。這里與前者最大的區(qū)別應(yīng)該就是Shader到中間件的用時(shí)——這應(yīng)該足夠快。所以首先這個(gè)中間物件應(yīng)該存儲在OpenGL的Context上(于是它名義上就是一個(gè)OpenGL狀態(tài)),OpenGL內(nèi)的對象的交流是比較便捷的,至少比Bind方式去存取“遙遠(yuǎn)的”顯存數(shù)據(jù)要快不少,其次這個(gè)中間物件自身也應(yīng)該容易表示,讓Shader能“直接認(rèn)門牌”——這些中間物件就是單純Zero-Base數(shù)字序列形式的uniform binding-point,OpenGL通過它一步定位到實(shí)質(zhì)數(shù)據(jù)處。
OpenGL Context本身也應(yīng)該是一個(gè)盡量小體積的東西,所以不便在它身上放太多這種binding-point。在我的顯卡上,GL_MAX_UNIFORM_BUFFER_BINDINGS的個(gè)數(shù)為36,這表示同一時(shí)間能映射的UBO-uniform block關(guān)系最多只有36對(間接也限制了一個(gè)ShaderProgram中uniform block的個(gè)數(shù)),哪怕你有大量的UBO,為了以上機(jī)制的實(shí)行,也只能接受這個(gè)限制。我們就是通過glBindBufferBase/glBindBufferRange來我UBO或UBO中的某分區(qū)的信息存儲至某個(gè)binding-point上,然后通過glUniformBlockBinding來“通知”ShaderProgram某個(gè)uniform block的數(shù)據(jù)信息存儲在哪個(gè)binding-point上。如果把glUniformBlockBinding當(dāng)成glUniform族函數(shù),這個(gè)操作會更親切一點(diǎn):只不過如今對于目標(biāo)block使用的是Index而不是Location(事實(shí)上它的行為更類似上面提到的glProgramUniform族函數(shù),因?yàn)椴恍枰孪萭lUseProgram啟用某個(gè)ShaderProgram而是作為首參罷)。
除了UBO,前面某篇博文[亂彈紀(jì)錄IV:Transform Feedback]中提到的Transform Feedback Buffer也是使用binding-point(參見文中代碼段)的“好手”。因?yàn)镾hader同樣需要快速找出需要feedback的那個(gè)Buffer的所在地,尤其是通過GL_SEPARATE_ATTRIBS的方式為每一個(gè)輸出數(shù)據(jù)獨(dú)立指定buffer時(shí),就需要用到多個(gè)transform-feedback binding-point來儲存各個(gè)buffer的信息了。其限制個(gè)數(shù)其實(shí)就是GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS了(本顯卡此數(shù)字為4)。
這里引出一個(gè)問題:我們能不能像TransformFeedback那樣,為一個(gè)UBO對象非配數(shù)個(gè)binding-point呢?可以的。這樣做的目的也很明確——單個(gè)UBO多個(gè)uniform-block。準(zhǔn)確地說,是每個(gè)uniform block對應(yīng)該UBO存儲區(qū)域中不同的分區(qū)域(sub-region)——glBindBufferRange,就是你了!
OpenGL代碼上面代碼段中,我們把兩個(gè)uniform-block關(guān)聯(lián)到同一個(gè)UBO的兩個(gè)區(qū)域:[0 ~?nBlockDataSize1]、[nUniformBufferAlignSize ~?nUniformBufferAlignSize+nBlockDataSize2]。為什么第二個(gè)block不是映射到[nBlockDataSize1 ~?nBlockDataSize1+nBlockDataSize2]呢?這里有個(gè)比較重要的概念:數(shù)據(jù)對齊。對于uniform-block,可以通過GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT找出這個(gè)對齊值(本顯卡上此數(shù)值是256字節(jié),所以每個(gè)uniform block都是256字節(jié)對齊,相鄰uniform block的間隔必須滿足256字節(jié)的整數(shù)倍,否則會發(fā)現(xiàn)數(shù)據(jù)會對不上)。記住了,block與block的數(shù)據(jù)不是緊緊pack在一起的。很容易想象,跟CPU上存儲結(jié)構(gòu)體一樣,這是為了數(shù)據(jù)存取效率考慮,至于為什么是這個(gè)值,就要更深入研究了。
麻煩可不止這一個(gè)——單個(gè)uniform-block里面的數(shù)據(jù),也有字節(jié)對齊的機(jī)制——這給uniorm變量的數(shù)據(jù)更新帶來更大的麻煩。
先來大致了解一下上面的GLSL代碼的uniform block前面的layout內(nèi)容。一般uniform block按數(shù)據(jù)組織類型可分為三種(目前):packed、shared、std140,我們可以在它們前面用layout去指定該block屬于哪種類型(也可以全局設(shè)置,也就是把layout單獨(dú)作為一語句,此時(shí)它影響隨后的各個(gè)沒前接layout的uniform block)。
UBO的一個(gè)最顯眼的好處就是實(shí)現(xiàn)數(shù)據(jù)共享。譬如我上面的matPV這個(gè)uniform block就是最好的例子:通常渲染場景時(shí),只會有一個(gè)視圖矩陣和一個(gè)投影矩陣([亂彈OpenGL中的矩陣變換(上)] [亂彈OpenGL中的矩陣變換(下)] ),而且它們相對每一幀都是固定數(shù)據(jù)。而我們可能場景里物件用到的Shader不一樣,但它們都得通過這兩個(gè)矩陣計(jì)算最終的頂點(diǎn)輸出啊?以前的話,我得每個(gè)Shader都傳一次這些相同的矩陣數(shù)據(jù),不僅時(shí)間上glUniform族函數(shù)會比較多而且空間上也分別占每個(gè)ShaderProgram本身的同等的存儲資源。如今把它們統(tǒng)一在一個(gè)UBO中,每幀更新就只要更新UBO一次就可以了,而且也只占一份的資源空間(在顯存上)。
為什么突然插播以上“廣告”呢?因?yàn)檫@對數(shù)據(jù)組織形式影響甚大。為了實(shí)現(xiàn)數(shù)據(jù)共享,必須保證各個(gè)shader里的指涉該UBO的unifom block“一模一樣”。但我們也知道([OpenGL/GLSL數(shù)據(jù)傳遞小記(2.x)] ),GLSL編譯器會檢查并自動刪掉那些非active(在shader中沒有實(shí)質(zhì)用途)的uniform變量。那么,假如我們的多個(gè)Shader里都有相同的uniform block,而里面某個(gè)變量x被ShaderA用到而沒背ShaderB用到,那么前者就會把它默默刪掉,這樣數(shù)據(jù)結(jié)構(gòu)不統(tǒng)一,自然映射到同一個(gè)UBO也無法預(yù)計(jì)得到各個(gè)子數(shù)據(jù)的具體位置(必須得針對每個(gè)Shader的uniform block內(nèi)每個(gè)變量查詢它的Offset)——block內(nèi)的這種“檢查”機(jī)制由packed這種layout掌控,為了要關(guān)閉這種機(jī)制,就需要選擇其他三種layout。而shared(順帶一提,這個(gè)是默認(rèn)layout)與std140不同之處在于,它雖然不會“刪掉”block內(nèi)的non-active變量,而且保證這些uniform block內(nèi)的數(shù)據(jù)在存儲分布上的一致性(所以各個(gè)shader能共享同一個(gè)block結(jié)構(gòu)),但它不會去固定統(tǒng)一存儲分布,所以還是有必要去查詢各個(gè)變量的offset(因?yàn)榭赡茉陲@卡A上這個(gè)offset是16在顯卡B上就變32了)。至于std140等,其實(shí)就是排除這些因素而有著嚴(yán)格限制的一個(gè)數(shù)據(jù)組織結(jié)構(gòu)“無優(yōu)化”的版本,所以一般的場合下我們應(yīng)該首選這類std(OpenGL-Standard)的layout。順帶說一下因?yàn)樽畛鮑BO/uniform block是跟隨OpenGL3.1/GLSL1.4引入的所以有此std140之名(其實(shí)現(xiàn)存的類似layout還有個(gè)std430,但它是專門留給OpenGL/GLSL4.3的storage buffer block產(chǎn)生更小的offset而用的,按此不表)。
說了那么多,既然一般應(yīng)用首選std140,那么它那個(gè)固定的offset是多少呢?根據(jù)我的不嚴(yán)格查驗(yàn)(沒驗(yàn)證多個(gè)顯卡),其值為16字節(jié),也就是說數(shù)據(jù)按16字節(jié)對齊。而數(shù)據(jù)中還再分為vector、數(shù)組、矩陣這些,也是按類似規(guī)則限制(不一一舉出,查spec去吧)。舉例一下吧:
glsl代碼要更新這兩個(gè)block對應(yīng)的那個(gè)UBO,應(yīng)該這樣:
C++代碼其實(shí)UBO除了能夠共享unifom變量數(shù)據(jù)外,上面的敘述還隱含有它的兩個(gè)重要優(yōu)點(diǎn):一點(diǎn)是索引、切換binding-point的速度比較快,比起多個(gè)glUniform的調(diào)用傳遞數(shù)據(jù)也更快;另一點(diǎn)是對于顯存中的uniform數(shù)據(jù),可用的存儲空間也大幅增加(而且對比TBO,UBO更適合需要線性存取的數(shù)據(jù))——這回應(yīng)了介紹UBO前的那兩個(gè)問題。
UBO的介紹暫到此為止,真的很費(fèi)口水——因?yàn)槲矣X得它本身就是包含不少要點(diǎn)的OpenGL功能——其實(shí)要點(diǎn)還不止這些,也還是需要更進(jìn)一層地了解才行。uniform數(shù)據(jù)應(yīng)盡量使用UBO來存放,尤其是那些需要Shader共享的數(shù)據(jù),當(dāng)然了零碎細(xì)小的數(shù)據(jù)還是glUniform/glProgramUniform類函數(shù)會更方便點(diǎn)吧~
3.varying變量
?varying變量主要用于在Shader Stage間進(jìn)行傳遞,注意的是在光柵化(Rasterization)的時(shí)候,這些變量也會跟著一起被光柵插值。那如果我們不想某個(gè)頂點(diǎn)屬性被光柵化,該怎么辦呢?在[OpenGL常用命令備忘錄(Part A)]這篇文章提到的一個(gè)古老API,glShadeModel,它在固定管道渲染流水線上能起到控制圖元屬性是否被插值的功效(需要光柵化時(shí)傳入?yún)?shù)GL_SMOOTH,不需要時(shí)傳入GL_FLAT),那么當(dāng)選擇不插值時(shí)(GL_FLAT),流水線上發(fā)生了什么呢?
假設(shè)現(xiàn)在流水線上,經(jīng)過裁剪、歸一化等,生成了一個(gè)屏幕上的三角圖元(三個(gè)頂點(diǎn)上的顏色屬性分別是c1、c2、c3),進(jìn)入光柵化階段。假如進(jìn)行插值,三角圖元里各像(假設(shè)共n個(gè))素會根據(jù)其各自位置對三個(gè)頂點(diǎn)的顏色值進(jìn)行線性插值,生成對應(yīng)的n個(gè)顏色值(cList[n]);假如不插值,則該三角形里所有像素都會是同一個(gè)值(cConst),這個(gè)值可能等于c1、c2或c3其中一個(gè)。到底是哪一個(gè)呢?這取決于哪個(gè)頂點(diǎn)是provoking-vertex(在[亂彈紀(jì)錄I:Geometry Shader] 中也提及過它)。你可以在OpenGL端通過glProvokingVertex函數(shù)改變這個(gè)設(shè)置(參數(shù)GL_FIRST_VERTEX_CONVENTION/GL_LAST_VERTEX_CONVENTION決定取圖元繪制順序的第一個(gè)頂點(diǎn)還是最后一個(gè)頂點(diǎn)作為provoking-vertex)。
其實(shí)要讓GLSL中某個(gè)作為頂點(diǎn)屬性的varying變量不被光柵化,只要在它前面加一個(gè)flat關(guān)鍵字就可以了。這樣它就像上述的那樣,到達(dá)Fragmen Shader的圖元上所有像素的該varying值都是相同的值(provoking vertex上的值):
glsl代碼同樣在[亂彈紀(jì)錄I:Geometry Shader]中也提到這樣一個(gè)問題:一個(gè)ShaderProgram中不能有兩個(gè)同為輸入的同名varing變量,也不能有兩個(gè)同為輸出的同名varing變量存在。所以即使表示的是同一個(gè)變量,也得使其名字不一樣:
glsl代碼這樣的話,在有些場合需要實(shí)現(xiàn)不同shader的組合——譬如實(shí)現(xiàn)一個(gè)可加入也可不加入的Geometry Shader,就難辦了(何況當(dāng)代流水線上的Shader可不止這三個(gè)呢)。為解決這個(gè)麻煩,也為了把變量聲明組織得更“好看”一些,我們再次用到interface block。上面的uniform block是其中一種,但它還包括in block和out block這兩種可用于varing變量的:
C++代碼注意這里使用了block insatnce name(緊隨大括號后的那個(gè)名字),這個(gè)名字對各Shader Stage來說都是獨(dú)特的,所以改成上面這樣的話,block之間也不會發(fā)生名字沖突,block內(nèi)的varying變量也就可以用同一個(gè)名字了。使用時(shí)需要按"blockInstanceName.varyingVariable"的類似結(jié)構(gòu)體內(nèi)變量的樣式來表示:
glsl代碼block自帶組織多個(gè)變量聲明的功效:
glsl代碼另外,對于Transform Feedback([亂彈紀(jì)錄IV:Transform Feedback] ),指定輸出Varing屬性時(shí),也要按上述的結(jié)構(gòu)體內(nèi)變量表示法:
C++代碼4.fragmentShader輸出
最后,再簡單談一下fragmentShader的輸出。一般來說,輸出的是顏色值,輸出目標(biāo)是Frame Buffer。這又包括常規(guī)的輸出到屏幕Buffer、輸出到FBO([學(xué)一學(xué),FBO] ),另外還可以通過MRT(Multi Render Target)輸出到兩個(gè)以上的FBO中。但是,這些對于Fragment Shader來說并沒太多不一樣:通過ShaderProgram鏈接前的glBindFragDataLocation指定輸出到第幾個(gè)Buffer(默認(rèn)是0)。類似于上述的attribute變量,我們也可以直接通過layout來指定這個(gè)location值:
glsl代碼 (fragemnt shader)然后只要在FragmentShader中把結(jié)果對應(yīng)地賦給這些輸出型變量就可以了。但是,這些layout里的關(guān)鍵字其實(shí)還有個(gè)index——只是默認(rèn)為0而已:
glsl代碼 (fragemnt shader)它們同樣是輸出到第0個(gè)緩沖區(qū),但是其中有一個(gè)的index為1——這個(gè)src1Color是所謂的Second Output。它同樣儲存在一塊緩沖區(qū)域中,但我們在OpenGL中怎么獲得這個(gè)區(qū)域的顏色值呢?答案就是由GL_ARB_blend_func_extended擴(kuò)展引入的,新的混合參數(shù)(GL_SRC1_COLOR/GL_SRC1_ALPHA等等這類新舊的enum)。它們作為混合因子而存在——這里輸出的src1Color,就只能作為各個(gè)對應(yīng)像素混合因子來用。簡單舉例:
C++代碼該代碼啟用混合,當(dāng)前繪制的內(nèi)容(混合源src,即fragColor)的混合因子是自己的alpha值,而背景(混合目標(biāo)dst,即繪制前此FrameBuffer的內(nèi)容)處對應(yīng)的被覆蓋像素的混合因子則是該對應(yīng)像素輸出的src1Color值,其中RGBA分量分別用于混合RGBA四個(gè)通道:
finalColor = sourceColor * sourceAlpha + destinationColor * src1Color
?
好了,本文于此結(jié)束。如有批誤或疏忽提醒,請大牛們不膩賜教或指出給ZwqXin,謝謝。
本文來源于?ZwqXin?(http://www.zwqxin.com/), 轉(zhuǎn)載請注明
??????原文地址:http://www.zwqxin.com/archives/shaderglsl/communication-between-opengl-glsl-2.html
總結(jié)
以上是生活随笔為你收集整理的GLSL vary、atrribute、in、out的区别的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用Adaptive cards来构建T
- 下一篇: 关于idea maven ojdbc6.