1. 程式人生 > >【第二彈】OpenGL深入學習之緩衝區

【第二彈】OpenGL深入學習之緩衝區

1.      《OpenGL超級寶典第5版》

2.      首先緩衝區在OpenGL的用處以及優勢?

緩衝區能夠迅速方便地將資料從一個渲染管線移動到另一個渲染管線,以及從一個物件繫結到另一個物件。可以在無需CPU介入的情況下完成這項工作。

幀緩衝區物件使我們可以離屏對想要的任意資料的緩衝區進行渲染。

緩衝區有很多不同的用途,它們能夠儲存頂點資料、畫素資料、紋理資料、著色器處理的輸入,或者不同著色器階段的輸出。

緩衝區儲存在GPU記憶體中,它們提供高速和高效的訪問。

針對幀緩衝區:幀緩衝區物件是一種容器,他可以儲存其他確實有記憶體儲存並且可以進行渲染的物件,例如紋理或渲染緩衝區。

一個渲染緩衝區物件可以是一個顏色表面、模板表面或者深度/模板組合表面。

#Q: 如何使用緩衝區?

幀緩衝區繫結到紋理,想弄清楚是如何將資料寫到紋理上的。#TODO(continue)

#Q: 新一代的硬體格式是如何提升精度的?

#TODO(continue): 輸入片段->剪裁測試->多重取樣測試->模板測試->深度緩衝測試->混合->邏輯操作->抖動->到幀緩衝區。

#Q: 整個這些階段每個階段起的作用是什麼?又如何對每個階段進行修改?

#Q: 如何逐片段控制深度?Page:329

#TODO(continue): 對統一緩衝區物件的認識

3.      關於物件繫結以及狀態機

#Q: 這其中物件的繫結是一種什麼樣的機制?

glBind*是指將某個物件名稱繫結打某個位置上去,然後可以對這些物件進行一些引數的設定。這種情況可以類比於C語言中的結構體:

{{{

struct Object

{

    int count;

    float opacity;

    char *name;

};

//Create the storage for the object.

Object newObject;

//Put data into the object.

newObject.count = 5;

newObject.opacity = 0.4f;

newObject.name = "Some String";

}}}

OpenGL APIs是跨平臺跨語言的,像這種比較複雜的結構比如結構體它都將它封裝起來了,然後只提供一種操作這種結構體的介面,這樣能夠實現所有的語言有統一的介面來對結構體進行管理。

>Complex aggregates like structs are never directlyexposed in OpenGL. Any such constructs are hidden behind the API. This makes iteasier to expose the OpenGL API to non-C languages without having a complexconversion layer.

在OpenGL裡,更多的看到的是這樣的:

{{{

//Create the storage for the object

GLuint objectName;

glGenObject(1, &objectName);

//Put data into the object.

glBindObject(GL_MODIFY, objectName);

glObjectParameteri(GL_MODIFY, GL_OBJECT_COUNT, 5);

glObjectParameterf(GL_MODIFY, GL_OBJECT_OPACITY, 0.4f);

glObjectParameters(GL_MODIFY, GL_OBJECT_NAME, "SomeString");

}}}

也就相當於對結構體引數的修改是通過呼叫介面glObjectParameteri來執行的。

OpenGL擁有所有OpenGL物件的記憶體,而且這個記憶體對外部來說是封裝起來了的,是不可見的,所以使用者訪問OpenGL物件的唯一方式通過使用兌現過的引用,而他的引用標識就是一個無符號整型資料,這個資料理論來說是唯一的。

在呼叫方法glGen*來生成一個物件時,只是為他分配了一個引用標識,並沒有為他分配記憶體,而只有在呼叫glBind*後OpenGL才會為其分配一塊記憶體,並接下來可以對其上下文引數進行修改。

l       有關OpenGL作為一個狀態機的比喻

幾乎所有的OpenGL函式都是用來設定或回覆OpenGL的狀態的。

>You can think of the state machine as a very largestruct with a great many different fields. This struct is called the OpenGLcontext, and each field in the context represents some informationnecessary for rendering.

可以把這個狀態機必做一個大的結構體,而這個結構體包含了很多不同的屬性區域。這個結構體稱作OpenGLContex,而這每個屬性區域都表現為渲染的必要資訊。

舉例說明:

{{{

struct Values

{

    int iValue1;

    int iValue2;

};

struct OpenGL_Context

{

    ...

    Values *pMainValues;

    Values *pOtherValues;

    ...

};

OpenGL_Context context;

}}}

>To create a Values object,you would call something like glGenValues.You could bind the Values objectto one of two targets: GL_MAIN_VALUES whichrepresents the pointer context.pMainValues,and GL_OTHER_VALUES whichrepresents the pointer context.pOtherValues.You would bind the object with a call to glBindValues,passing one of the two targets and the object. This would set that target'spointer to the object that you created.

There would be a function to setvalues in a bound object. Say, glValueParam. It wouldtake the target of the object, which represents the pointer in the context. Itwould also take an enum representing which value in the object to change. Thevalue GL_VALUE_ONE would represent iValue1, andGL_VALUE_TWO would represent iValue2.

總結:繫結所起到的一個作用是當還未為繫結的物件分配記憶體時,那麼繫結後就會給他分配記憶體;另一個作用是將這個繫結的物件置為當前可讀寫狀態。

4.      如何使用緩衝區?

step1: 建立緩衝區:glGenBufffers

step2: 繫結緩衝區:glBindBuffer,繫結緩衝區可以指定緩衝區使用的目的,緩衝區物件繫結點見表:

GL_ARRAY_BUFFER,

GL_PIXEL_PACK_BUFFER\GL_PIXEL_UNPACK_BUFFER(PACK_BUFFER用於glReadPixels之類畫素包裝操作的目標緩衝區,UNPACK_BUFFER用於glTexImage之類紋理更新函式的源緩衝區),

GL_TEXTURE_BUFFER, GL_TRANSFORM_FEEDBACK_BUFFER,GL_UNIFORM_BUFFER,

GL_COPY_READ_BUFFER/GL_COPY_WRITE_BUFFER(READ_BUFFER用於glCopyBufferSubData進行復制的資料來源,WRITE_BUFFER用於glCopyBufferSubData進行復制的目的),

GL_ELEMENT_ARRAY_BUFFER。

step3: 填充緩衝區:glBufferData,指定緩衝區物件的使用方式,見表:

GL_STREAM_DRAW/READ/COPY,

GL_STATIC_DRAW/READ/COPY,

GL_DYNAMIC_DRAW/READ/COPY

其實這些都只是一個性能提示而已。

另外glBufferSubData可以對已經存在的緩衝區中的一部分進行更新,而不會導致緩衝區其他部分的內容變為無效。

{{{

glBufferSubData(GLenum target, intptr offset, sizeiptrsize, const void *data);

}}}

以上三步操作都還僅僅是為緩衝區的使用做準備工作,並沒有真正來使用緩衝區。

#Q: 那麼具體如何使用緩衝區呢?

1)       畫素緩衝區(PBO)

glTexImage*D、glTexSubImage*D、glCompressedTexImage*D和glCompressedTexSubImage*D這些操作將資料和紋理從本地CPU中讀取到幀緩衝區中(也即GPU中)。

畫素緩衝區是為以上這些操作服務的。

>畫素緩衝區經常用來儲存來自一個渲染目標的2D影象、紋理或其他資料來源。

一般在GL_PIXEL_PACK_BUFFER的繫結下,使用glReadPixel方法來將讀取的畫素資料儲存到畫素緩衝區物件記憶體中。

#Q: 一個螢幕的畫素資料大小是多少?

>計算機儲存圖象的方法通常有兩種:一是“向量圖”,一是“畫素圖”。向量圖儲存了圖象中每一幾何物體的位置、形狀、大小等資訊,在顯示圖象時,根據這些資訊計算得到完整的圖象。“畫素圖”是將完整的圖象縱橫分為若干的行、列,這些行列使得圖象被分割為很細小的分塊,每一分塊稱為畫素,儲存每一畫素的顏色也就儲存了整個圖象。

BMP檔案可以儲存:這種檔案格式可以儲存單色點陣圖、16色或256色索引模式畫素圖、24位真彩色圖象,每種模式種單一畫素的大小分別為1/8位元組,1/2位元組,1位元組和3位元組。畫素的資料量並不一定完全等於圖象的高度乘以寬度乘以每一畫素的位元組數,而是可能略大於這個值。原因是BMP檔案採用了一種“對齊”的機制,每一行畫素資料的長度若不是4的倍數,則填充一些資料使它是4的倍數。這樣一來,一個17*15的24位BMP大小就應該是834位元組(每行17個畫素,有51位元組,補充為52位元組,乘以15得到畫素資料總長度780,再加上檔案開始的54位元組,得到834位元組)。分配記憶體時,一定要小心,不能直接使用“圖象的高度乘以寬度乘以每一畫素的位元組數”來計算分配空間的長度,否則有可能導致分配的記憶體空間長度不足,造成越界訪問,帶來各種嚴重後果。

所以一個BMP檔案的畫素資料大小並不是簡單的w*h*sizeof(pixel),而是還需要考慮對齊問題,行畫素資料長度需要是4的倍數。

那麼螢幕畫素的資料大小又是該怎麼定呢?

#TODO(continue): 明天繼續。

首先螢幕的解析度可以進行調節的:

而要求出畫素的儲存大小是根據一個視窗有多少個畫素來定的,這個值應該為:螢幕寬度*螢幕高度,然後一個畫素的大小是多少,一個畫素應該儲存了4個值,即4個通道的值,至於每個通道的值是採用幾個位元組來儲存的,這個應該是8個位元位來儲存,即1個位元組。所以一個螢幕的畫素資料大小為:w*h*4。

glReadPixel:從幀緩衝區中讀取一個畫素塊。此函式可以讀取幀緩衝區中的模板/深度/顏色值。是通過指定第5個引數,type,比如GL_RGBA, GL_STENCIL_INDEX,GL_DEPTH_INDEX,分別可以取出其中的顏色值、模板索引值、深度值。而當指定為GL_RGB時,則是讀取每個畫素的RED, GREEN, BLUE的值。那麼顏色緩衝區中對應每個畫素應該存有4個值。glReadPixel可以指定從幀緩衝區中讀取的內容的資料型別進行轉換,也可以通過type來指定要從幀緩衝區中讀取的畫素的內容。這樣就可以指定出一個PBO應該分配多少空間了。

#Q: 幀緩衝的概念?

顏色緩衝區就是幀緩衝區?這個還有待多方驗證一下。

>OPENGL系統的幀緩衝區是由所有儲存著畫素資訊的緩衝區組成的,包括:顏色緩衝區、深度緩衝區、模板緩衝區、累積緩衝區。

幀緩衝區由所有儲存著畫素資訊的緩衝區組成。

但是畫素緩衝區的一個弊端是:畫素緩衝區它通過glReadPixel從幀緩衝區中讀取的畫素塊是針對整個螢幕的,而不能針對某個模型的紋理而言,所以當我使用畫素緩衝區時,要麼將它用來渲染整個螢幕,要麼將它用來儲存圖片,而並不能用來對某個模型進行紋理渲染。

總結一些畫素緩衝區物件(PBO)的用途:先通過glReadPixel從幀緩衝區中將畫素資料複製到畫素緩衝區中,然後通過glTexImage*將畫素緩衝區中的資料複製到幀緩衝區中,作為紋理用。

我對幀緩衝區的理解:是一切為渲染所準備好的資料,都可直接用於渲染。

補充:glTexImage*D中有引數internalFormat,此引數指定這個紋理中顏色分量的數量。那麼可以為這個引數賦予GL_RGB4, GL_RGB8,GL_RGB12等等,這些是什麼含義呢?

表示的是通道的位數。而引數format只能是:GL_RGB, GL_RGBA等等,無法指定通道的位數,一般預設應該是8位。

2)       紋理緩衝區(TBO)

>一個紋理包含兩個主要組成部分:紋理取樣狀態和包含紋理值的資料緩衝區。

紋理緩衝區須繫結GL_TEXTURE_BUFFER。

最終還是需要將紋理緩衝區物件繫結到紋理上才有用,需要呼叫glTexBuffer。

{{{

void glTexBuffer(GLenumtarget, GLenum internalFormat, GLuint buffer);

}}}

其中target必須為GL_TEXTURE_BUFFER,internalFormat為指定buffer的儲存中資料的內部格式。

#Q: 為什麼glReadPixel、glTexImage*D、glTexBuffer中都要指定源資料或者目的資料格式和資料型別?這個就不能統一嗎?難道是為了適應新一代硬體的格式?我覺得首先要弄清楚源資料本身儲存的資料格式和資料型別,然後再弄清楚目的資料是用來幹嘛的,這樣才能比較好確定目的資料的資料格式和資料型別該如何定義。

#A: 源資料主要有兩個,第一個是影象檔案,第二個是螢幕畫素(也就是幀緩衝區中顏色緩衝區中的資料)。影象檔案有BMP、TGA等檔案,螢幕畫素如果按照幾何著色器中的程式所述顏色值範圍為[0.0f, 1.0f],那麼螢幕畫素的顏色格式則是浮點型,帶四個通道的RGBA格式了。glReadPixel為讀取幀緩衝區中的畫素塊,gltReadTGABits和gltReadBMPBits為讀取TGA、BMP檔案的資料。

關於glReadPixel:

{{{

void glReadPixel(Glint x, Glint y, GLsizeiwidth, GLsizei height,

GLenum format, GLenum type,

GLvoid *data);

}}}

如果format為GL_RED, GL_GREEN, GL_BLUE,GL_RGB, GL_BGR, GL_RGBA或GL_BGRA,且type為GL_FLOAT,那麼每個分量都會原封不動的(或者在它與所使用的GL不同時,轉換成客戶端的單精度浮點格式)進行傳遞。這句話也就是說螢幕畫素的儲存型別為浮點型,因為正因為螢幕畫素的儲存型別是浮點型,然後type指定為GL_FLOAT型時,才不用經過型別的轉換直接進行傳遞

通過呼叫glTexBuffer對紋理緩衝區物件進行繫結之後,就可以在片段著色器中使用texelFetch方法對紋理紋理進行取值,它使用的取樣器是samplerBuffer。

注意:texelFetch接受的是從0到TBO緩衝區大小值的整數索引,如果紋理查詢座標進行了標準化,那麼我們需要通過乘以TBO的大小值再減去1的結果,然後再將得到的結果轉化為整數的方式轉換成索引。例如下:

{{{

uniform samplerBuffercolorMap;

void main(void)

{

……

int offset =int(vColor.r*(1024-1));

lumFactor.r =texelFetch(colorMap, offset).r;

}

}}}

#Q: 這個color怎麼成了儲存紋理座標的工具了?儲存紋理的變數型別不一般都是vec2嗎?而且都是帶xy座標的。

#TODO(continue):等把第8章理解完後,還得好好實踐一下這個章節的三個例子,正好也結合實踐一下矩陣轉換的知識。

3)       幀緩衝區(FBO)

>一旦一個FBO被建立、設定和繫結,大多數OpenGL操作就將向是在渲染到一個視窗一樣執行,但是輸出結果將儲存在繫結到FBO的影象中。

這裡說的FBO的影象中實際上是與FBO繫結的各類緩衝區中(如顏色緩衝區、深度緩衝區、模板緩衝區)。

幀緩衝區實際上是一個容器,它本身不帶緩衝區,而是可以和其他的渲染物件進行關聯。

a.       渲染緩衝區物件(RBO)

RBO是一種影象表面,是專門為繫結FBO而設計的。表面就以為這繪製的東西會放在這些表面上。

RBO有:顏色表面、深度表面、深度/模板組合表面。

通過在分配記憶體的時候指定資料格式,這樣與緩衝區的用途進行匹配。

{{{

glBindRenderBuffer(GL_RENDERBUFFER, colorRBO);

glRenderbufferStorage(GL_RENDERBUFFER,GL_RGBA8, screenWidth, screenHeight);

glBindRenderBuffer(GL_RENDERBUFFER, depthRBO);

glRenderbufferStorage(GL_RENDERBUFFER,GL_DEPTH_COMPONENT32, screenWidth, screenHeight);

}}}

然後繫結RBO:

{{{

glBindFramebuffer(GL_DRAW_FRAMEBUFFER,fboName);

glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER,GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthBufferName);

glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER,GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorBufferName[0]);

glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER,GL_COLOR_ATTACHMENT1, GL_RENDERBUFFER, colorBufferName[1]);

glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER,GL_COLOR_ATTACHMENT2, GL_RENDERBUFFER, colorBufferName[2]);

}}}

通過這樣一繫結,就基本上確認了各RBO是用作什麼用處了。

那剩下的動作就是往緩衝區裡面寫資料了。#TODO(continue)。

>要獲得對渲染緩衝區的訪問,有兩個重要步驟。第一步是確保片段著色器設定正確,第二步是確保輸出被引導到正確的位置。

也就是說對渲染緩衝區資料的寫入是通過片段著色器進行的,至少資料的填充部分是在片段著色器中進行的,然後就是將設定對映到緩衝區中。

著色器輸出:其中在著色器中可輸出的目前我知道的只有顏色值,單個顏色輸出可採用gl_FragColor,輸出顏色陣列可用gl_FragData[n],這兩個變數不可同時使用。

緩衝區對映:>預設的行為是一個單獨的顏色輸出將被髮送到顏色繫結0,即GL_COLOR_ATACHMENT0;如果不告訴OpenGL如何處理著色器輸出,那麼很自由第一個輸出被路由通過,即使我們有多個著色器輸出和多個顏色緩衝區繫結到幀緩衝區物件上也是如此。

用函式glDrawBuffers來對著色器輸出進行路由。程式例:

{{{

GLenum fboBuffs[] ={GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2};

glDrawBuffers(3,fboBuffers);

}}}

>如果預設的FBO被繫結,那麼可以使用與視窗相關聯的顏色緩衝區名稱,最普遍的是GL_BACK_LEFT。

>不要忘記在使用完FBO之後恢復繪製緩衝區的設定,否則將會產生GL錯誤

現在涉及到對渲染緩衝區的讀取:

首先通過呼叫glReadBuffer方法,

glReadBuffer:為畫素選擇一個顏色緩衝區源。

{{{

voidglReadBuffer(GLenum mode);

}}}

glReadBuffer指定一個顏色緩衝區作為後續glReadPixel、glCopyTexImage*D和glCopyTexSubImage*D命令的源,還有glBlitFramebuffer

mode指定一個顏色緩衝區,可接受的值為:GL_NONE、GL_FRONT_LEFT、GL_FRONT_RIGHT、GL_BACK_LEFT、GL_BACK_RIGHT、GL_FRONT、GL_BACK、GL_LEFT和GL_RIGHT,還有GL_COLOR_ATTACHMENTn

其中:GL_FRONT、GL_LEFT、GL_FRONT_LEFT均都指定左前緩衝區;GL_RIGHT、GL_FRONT_RIGHT均都指定右前緩衝區;GL_BACK、GL_BACK_LEFT均都指定為左後緩衝區;GL_BACK_RIGHT指定為右後緩衝區。

最後就是呼叫glBlitFrameBuffer實現bit級資料/記憶體複製,也即不需要經由CPU干預。

#Q: 現在有以下幾個疑問:glutSwapBuffer、glClear、glDrawBuffers、glReadBuffer、glBlitFramebuffer、glReadPixel這些函式有什麼關聯?

#A: 當我們在初始化顯示模式的時候,申請了雙緩衝區:

{{{

glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA|GLUT_DEPTH|GLUT_STENCIL);

}}}

指定了雙緩衝渲染環境,這就意味著將在後臺緩衝區進行渲染,然後在結束時交換到前臺,這種形式能夠防止觀察者看到可能伴隨著動畫幀與動畫幀之間閃爍的渲染流程。呼叫glutSwapBuffer方法進行交換。

glClear用作清除緩衝區並對數值進行預置,可清除的緩衝區有顏色緩衝區、深度緩衝區和模板緩衝區,分別對應GL_COLOR_BUFFER_BIT、GL_DEPTH_BUFFER_BIT、GL_STENCIL_BUFFER_BIT。其中對數值進行預置,就是預置到由glClearColor、glClearDepth和glClearStencil選擇的數值。

那多重顏色緩衝區如何清除glDrawBuffers指定一個要繪製到的顏色緩衝區的列表(定義一個要將片段著色器資料寫入到其中的顏色緩衝區的列表)。先通過glDrawBuffers指定顏色緩衝區列表,再呼叫glClear可以清除緩衝區列表中的緩衝區。

{{{

glDrawBuffers(n,buffers);

glClear(GL_COLOR_BUFFER_BIT);

}}}

#Q:glBlitFramebuffer把由glReadBuffer指定的顏色緩衝區的內容複製到哪裡呢?

#A:glBlitFramebuffer從指定為讀取的幀緩衝區中的由glReadBuffer指定的緩衝區的內容複製到指定為繪製的幀緩衝區的有glDrawBuffers指定的緩衝區列表中。這個有可能是一對多的關係。也即讀取幀緩衝區中的緩衝區資料複製到n個繪製幀緩衝區中的緩衝區列表中。

glDrawBuffers能指定的顏色緩衝區有:GL_NONE、GL_FRONT_LEFT、GL_FRONT_RIGHT、GL_BACK_LEFT、GL_BACK_RIGHT、GL_COLOR_ATTACHMENTn;

glReadBuffers能制定的顏色緩衝區有:GL_NONE、GL_FRONT_LEFT、GL_FRONT_RIGHT、GL_BACK_LEFT、GL_BACK_RIGHT、GL_COLOR_ATTACHMENTn,只能指定其中的一個,不可進行或新增。

總結一下渲染緩衝區的使用:首先通過與幀緩衝區物件繫結,並確定其用途(用作顏色緩衝區、深度緩衝區或深度模板組合緩衝區),然後指定幀緩衝區物件為繪製幀緩衝區,並通過片段著色器輸出顏色陣列值(使用gl_FragData[n]),並在OpenGL中呼叫glDrawBuffers指定這些顏色陣列值的輸出,最後指定幀緩衝區物件為讀取幀緩衝區,並通過glReadBuffer指定glReadPixel、glBlitFramebuffer等函式將要讀取的顏色緩衝區,使用glBlitFramebuffer函式時會直接將資料複製到由glDrawBuffers重新指定的緩衝區中,從而實現bit級複製。同一時間只能繫結一個繪製幀緩衝區和一個讀取幀緩衝區,且這兩個緩衝區可為同一個緩衝區

#TODO(complete):實踐。

#result:其中有一個計算就是計算顏色的灰度值:

{{{

float grey = dot(vColor.rgb, vec3(0.3, 0.59,0.11));

gl_FragData[1] = vec4(grey, grey, grey, 1.0f);

}}}

這個是通過點乘進行的。其中這個點乘向量可隨意設定!!

幀緩衝區的完整性檢查:glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER),glCheckFramebufferStatus(GL_READ_FRAMEBUFFER),若返回值為GL_FRAMEBUFFER_COMPLETE,則表示FBO設定正確。

不難看出,渲染緩衝區也是繪製的整個螢幕的圖形,而並非某個模型的畫素。

b.       渲染到紋理

渲染到紋理與RBO的不同點就是:FBO繪製的畫素放置的地方改變了,其他的設定均沒有改變。著色器顏色的輸出以及繪製對映部分均沒有改變,改變的只是將紋理與幀緩衝區進行繫結。而後對紋理的使用就跟對普通紋理的使用是一樣的。

幀緩衝區繫結紋理的方法:

{{{

voidglFramebufferTexture1D(GLenum target, GLenum attachment,

GLenum textarget,GLuint texture, Glint level);

voidglFramebufferTexture2D(GLenum target, GLenum attachment,

GLenum textarget,GLuint texture, Glint level);

voidglFramebufferTexture3D(GLenum target, GLenum attachment,

GLenum textarget,GLuint texture, Glint level,

Glint layer);

}}}

使用示例:

{{{

glGenTextures(1,&mirrorTexture);

glBindTexture(GL_TEXTURE_2D,mirrorTexture);

glTexImage2D(GL_TEXTURE_2D,0, GL_RGBA8, 800, 800, 0, GL_RGBA, GL_FLOAT, NULL);

glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER,GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mirrorTexture, 0);

}}}

#Q: glTexImage*D的作用是什麼呢?

#A: 用來定義紋理影象。也即為此紋理物件設定引數,比如描述紋理影象的高度、寬度、邊界寬度層次細節數量和提供的顏色分量數量。

總結綜上對幀緩衝區的使用流程,自定義幀緩衝區有兩種:繫結GL_DRAW_FRAMEBUFFER的繪製幀緩衝區,繫結GL_READ_FRAMEBUFFER的讀取幀緩衝區,幀緩衝區本身並不帶有儲存記憶體,它僅僅是一個緩衝區容器,它是通過繫結其他的緩衝區來進行關聯。其中渲染緩衝區是專門為這種繫結進行設計的,渲染緩衝區包括顏色緩衝區、深度緩衝區和深度/模板組合緩衝區。幀緩衝區繫結渲染緩衝區後,又要為其寫入內容,其中寫入內容是在片段著色器中進行的,片段著色器可以輸出顏色陣列,用gl_FragData[n],然後還需要通過glDrawBuffers來將gl_FragData和與GL_COLOR_ATTACHMENTn系列繫結的渲染緩衝區物件進行對映,glDrawBuffers接受一個對映陣列,這樣,片段著色器中的輸出顏色陣列就會寫入到與之對應對映的渲染緩衝區中;在渲染緩衝區中已經寫入了資料之後,通過呼叫glReadBuffer來制定要讀取GL_COLOR_ATTACHMENTn中的哪一個顏色緩衝區,這個地方還是一種對映關係,最後呼叫bij級資料複製的方法glBlitFramebuffer來讀取由glReadbuffer指定的讀取幀緩衝區中的顏色緩衝區,並複製到由glDrawBuffers指定的繪製緩衝區中的顏色緩衝區組中。

其中glDrawBuffers方法的用途在於指定要繪製的緩衝區陣列,這樣在呼叫glClear方法對相應緩衝區進行清除時會清楚glDrawBuffers指定的響應緩衝區。

關於顏色緩衝區:顏色緩衝區有很多,比如左前、右前、左後、右後緩衝區,還有使用者可指定自定義緩衝區的系列GL_COLOR_ATTACHMENTn,當glDrawBuffers指定的繪製緩衝區包括其中的多種顏色緩衝區時,那麼在呼叫glClear(GL_COLOR_BUFFER_BIT)時會將這些顏色緩衝區的內容都清空。

而glReadBuffer只是為glReadPixel、glBlitFramebuffer等方法提供指定的讀取資料來源的作用。

OpenGL說白了,很多操作都是隱藏了對指標的直接操作,代而提供了統一的介面,以達到跨平臺跨語言的目的。

4)       緩衝區更高階應用

緩衝區更高階的應用體現在:可以在OpenGL中對緩衝區的資料進行直接操作。

OpenGL提供的方法是將緩衝區的記憶體地址映射出來,供使用者進行可讀寫訪問。

兩個方法:

{{{

void* glMapBuffer(GLenum target, GLenumaccess);

void* glMapBufferRange(GLenum target,GLintptr offset, GLsizeptr length, GLenum access);

}}}

第一個方法是對指定繫結點target的緩衝區的整塊部分進行讀寫訪問,access可設定許可權,為GL_READ_ONLY、GL_WRITE_ONLY或GL_READ_WRITE。

第二個方法是對指定繫結點target的緩衝區的指定位置offset指定長度length進行讀寫訪問,access可設定為:。

其中target即為在建立緩衝區物件時的繫結點。

這也就相當於由使用者自定義的緩衝區使用者可對其進行比較自由的操作,即我可以為其分配記憶體,也可通過對映獲取自定義緩衝區的記憶體地址,以直接對其記憶體進行操作。

在對緩衝區記憶體操作完成之後需要呼叫glUnMapBuffer方法來解除對映,解除對映之後此上兩個方法返回的緩衝區記憶體地址就無效了。

對這塊操作的使用案例:

>在許多OPENGL操作中,我們都向OPENGL傳送一大塊資料,例如向它傳遞需要處理得頂點陣列資料。傳輸這種資料可能非常簡單,例如把資料從系統的記憶體中複製到圖形卡。但是,由於OPENGL是按照客戶機-伺服器模式設計的,在OPENGL需要資料的任何時候,都必須把資料從客戶機記憶體傳到伺服器。如果資料並沒有修改,或者客戶機和伺服器位於不同的計算機(分散式渲染),資料的傳輸可能會比較緩慢,或者是冗餘的。OPENGL 1.5版本增加了緩衝區物件(buffer object),允許應用程式顯式地指定把哪些資料儲存在圖形伺服器中。

CUDA會經常用到緩衝區???

在自定義的緩衝區(除開幀緩衝區和渲染緩衝區)中,其中有以下幾項是跟紋理相關的:GL_TEXTURE_BUFFER、GL_PIXEL_PACK_BUFFER、GL_PIXEL_UNPACK_BUFFER,這些資料一般都是從源資料(比如影象檔案、幀緩衝區的畫素緩衝區),這些資料一般都不會進行直接修改,所以我感覺跟紋理相關的緩衝區不會怎麼去大幅度進行修改;其他的比如頂點陣列:GL_ARRAY_BUFFER,對這個的讀寫操作可能會比較多。

但至於頂點緩衝區的用法現在還未涉及。。。。#TODO(continue)

另外緩衝區的複製操作:

{{{

voidglCopyBufferSubData(GLenum readtarget, GLenum writetarget,

GLintptrreadoffset, GLintptr writeoffset,

GLsizeptrsize);

}}}

將緩衝區物件資料儲存的一部分複製到其他緩衝區物件的資料儲存中。radtarget和writetarget是glBindBuffer繫結點裡指定的繫結點。如果readtarget和writetarget被繫結為同一個繫結點,那有readoffset、writeoffset和size指定的範圍絕不能重疊。也即:[readoffset,readoffset+size-1] 與[writeoffset,writeoffset+size-1]區間不可以有重疊部分。

至於緩衝區的高階操作將在頂點陣列緩衝區中進行應用,現在就學一下頂點陣列緩衝區的內容。#TODO(continue)

5)       緩衝區中關於頂點的應用

Page352-Page389

使用GLBatch類對頂點、顏色、法線等資料進行設定時,這些資料都是儲存在CPU中,當在呼叫glDrawArrays、glDrawElements之類的方法時,還需要每次都得將這些資料從CPU中挪到GPU中進行渲染,這樣操作在有的情況下效率會相當慢。

這些情況有:如果對於每個幀來說,要進行渲染的資料基本上相同,或者如果在單個幀中隊同樣資料的多個副本進行渲染,那麼一次將這些資料複製到GPU的本地記憶體中,再很多次重複使用這個副本是非常有利的。

所以因此提出了使用緩衝區來儲存頂點資料。

VAO(vertex array object,頂點陣列物件),是一個特殊的容器物件,用來管理多個VBO(頂點緩衝區物件)和許多頂點屬性。

{{{

glGenVertexArrays(1, &vao);

glBindVertexArray(vao);

}}}

VBO(頂點緩衝區物件),用來儲存頂點資料的緩衝區物件,用GL_ARRAY_BUFFER繫結。

{{{

glGenBuffers(1, &one_buffer);

glBindBuffer(GL_ARRAY_BUFFER, one_buffer);

}}}

方法glVertexAttribPointer的使用:#TODO(complete)

{{{

void glVertexAttribPointer(GLuint index, Glintsize, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid*pointer);

}}}

定義一個通用頂點屬性資料的陣列(the generic vertexattribute array),也即在頂點緩衝區中填充了資料之後,需要使用此方法來為這些資料定義頂點屬性,但是它不會另外開闢一塊記憶體空間來儲存這些屬性,而是連結的這個頂點緩衝區

要啟用和禁止一個通用頂點屬性陣列,可以呼叫以index即指定頂點屬性為引數的glEnableVertexAttribArray和glDisableVertexAttribArray。

通用頂點屬性陣列將在glDrawArrays、glMultiDrawArrays、glDrawElements、glMultiDrawElements或glDrawRangeElements被呼叫時使用。具體在這些方法裡怎麼用或者要怎麼設定還是在下面對這些方法的使用進行分析。

引數index為指定將要修改的一般頂點屬性的索引,如GLT_ATTRIBUTE_VERTEX等;

size為定義頂點屬性資料的分量數,必須為1、2、3或4,比如紋理座標資料位vec2,則size為2;顏色資料為vec4,則size為4;

type指定陣列中每個分量的資料型別;

pointer指定一個指向當前繫結到GL_ARRAY_BUFFER目標的緩衝區的資料儲存中的陣列中第一個通用頂點屬性的第一個分量的指標。初始值為0。

函式glVertexAttribPointer是作用於頂點緩衝區的,它的作用是指定緩衝區中哪一部分是定義的頂點屬性,哪一部分是定義的法線屬性,哪一部分等等等!!

示例一:建立單個緩衝區,在其中不同的位置存入一些資料,然後設定幾個頂點屬性指標指向這些資料的偏置。這個示例演示瞭如何使用一個緩衝區來儲存幾個獨立的屬性,並同時為每個屬性儲存所有資料。

{{{

static const GLfloat positions[] = {/**/};

static const GLfloat colors[] = {/**/};

static const GLfloat normals[] = {/**/};

glGenBuffers(1, &one_buffer);

glBindBuffer(GL_ARRAY_BUFFER, one_buffer);

glBufferData(GL_ARRAY_BUFFER,sizeof(positions)+sizeof(colors)+sizeof(normals), NULL, GL_STATIC_DRAW);

glBufferSubData(GL_ARRAY_BUFFER, 0,sizeof(positions), positions);

glBufferSubData(GL_ARRAY_BUFFER,sizeof(positions), sizeof(colors), colors);

glBufferSubData(GL_ARRAY_BUFFER,sizeof(positions)+sizeof(colors), sizeof(normals), normals);

glVertexAttribPointer(GLT_ATTRIBUTE_VERTEX,4, GL_FLOAT, GL_FALSE, 0, (const GLvoid*)0);

glVertexAttribPointer(GLT_ATTRIBUTE_COLOR, 4,GL_FLOAT, GL_FALSE, 0, (const GLvoid*)sizeof(positions));

glVertexAttribPointer(GLT_ATTRIBUTE_NORMAL,3, GL_FLOAT, GL_FALSE, 0, (const GLvoid*)(sizeof(positions)+sizeof(colors)));

}}}

示例二:用單個緩衝區來儲存交叉存取的屬性資料。資料被宣告為一個C結構體,並被直接複製到緩衝區中。glVertexAttribPointer的stride引數用來告訴OpenGL,記憶體中屬性之間的間隔是多少位。單個頂點的所有這些屬性最終在緩衝區中是一個接一個緊接著放置的。

{{{

struct VERTEX

{

       vec4position;

       vec4color;

       vec3normal;

}

extern VERTEX vertices[];

glBufferData(GL_ARRAY_BUFFER,vertex_count*sizeof(VERTEX), vertices, GL_STATIC_DRAW);

glVertexAttribPointer(GLT_ATTRIBUTE_VERTEX,4, GL_FLOAT, GL_FALSE, sizeof(VERTEX), (const GLvoid*)OFFSETOF(VERTEX,position));

glVertexAttribPointer(GLT_ATTRIBUTE_COLOR, 4,GL_FLOAT, GL_FALSE, sizeof(VERTEX), (const GLvoid*)OFFSETOF(VERTEX, color));

glVertexAttribPointer(GLT_ATTRIBUTE_NORMAL,3, GL_FLOAT, GL_FALSE, sizeof(VERTEX), (const GLvoid*)OFFSETOF(VERTEX, normal));

}}}

>glVertexAttribPointer不僅告訴OpenGL能夠找到一個頂點屬性資料的緩衝區偏置,它還告訴OpenGL哪個緩衝區包含了這些資料。

>和某些其他的OpenGL物件不同,這裡沒有預設緩衝區物件。這就意味著我們必須先建立一個頂點緩衝區物件並對其進行繫結,然後才能呼叫glVertexAttribPointer。

offsetof巨集用法:offsetof(結構體,屬性),功能是找出屬性在結構體中的偏移量。

接下來就是對這些資料的繪製使用了#TODO(continue)

兩個繪製函式:

{{{

glDrawArrays(GLenum mode, Glint first,GLsizei count);

glDrawElements(GLenum mode, GLsizei count,GLenum type, const GLvoid* indices);

}}}

這兩個函式均為從陣列資料渲染圖元。

glDrawArrays的用法:first引數:

>指定啟用的陣列中的起始索引。

#Q: 在方法中並沒有指定使用哪個陣列來進行繪製。那麼這個陣列又該是什麼呢?

#A: 由glVertexAttribPointer定義的通用頂點屬性資料陣列在啟用時可由glDrawArrays和glDrawElements方法使用。也就是說它應該是使用的通用頂點資料陣列了。

glDrawElements的用法:

對於type和indices引數不解,type為什麼必須要是GL_UNSIGNED_BYTE、GL_UNSIGNED_SHORT和GL_UNSIGNED_INT中的一個;然後indices指定一個指向索引儲存位置的指標,這是啥意思?

那麼基本上能夠確定glDrawArraysglDrawElements方法都是用的通用頂點屬性資料陣列了,而glVertexAttribPointer又是從頂點緩衝區中來定義的通用****陣列。另外還有一個方法glVertexPointer方法貌似也可以定義通用****陣列。

#Q: 裡面講述了這幾個方法的用法。不過感覺裡面的方法還是沒怎麼理清楚,一個是glVertexPointer方法是否是既容許定義CPU記憶體資料也容許定義頂點緩衝區的資料?另一個是glVertexAttribPointer方法是否是隻能定義頂點緩衝區的資料?第三個問題是VBO一定要繫結VAO才能夠使用嗎?第四個問題是具體glVertexPointer和glVertexAttribointer的用法,是否一定要每次顯示指定glEnableClientState(GL_VERTEX_ARRAY),而且在使用VAO時繫結和解繫結的用法。

#TODO(continue):何寶新的AB是一家?VAO與VBO

學一學,VBO

OpenGL裡的客戶端和服務端:

>事實上我也無法很清楚地告訴你區別之處,反正你把你電腦上的具體程式,包括它用到的記憶體等等看作客戶端,把你電腦裡面的——顯卡里的OpenGL“模組”,乃至整張擁有OpenGL流水線、硬體實現OpenGL功能的顯示卡,作為服務端

glEnable/glDisable和glEnableClientState/glDisableClientState的異同:

這兩對函式都是用來開啟/關閉某種狀態的,前者操作的物件時服務端,後者操作的物件時客戶端。

在出現VBO之前有兩類工具可儲存頂點屬性資料:VA(頂點陣列)顯示列表

VA是將頂點屬性資料儲存在客戶端,在要渲染的時候就將這些資料從客戶端複製到服務端進行渲染,這種效率會很低;而顯示列表則是將頂點屬性資料直接在初始化階段完成,它繞過客戶端,直接通知服務端把之前初始化時設定的程式碼段所對映的硬體設定“啟亮”,這種效率會相當高,但是有一個缺陷是對資料進行初始化之後就不能再進行修改。

而現在VBO存在的特性

它直接將頂點屬性資料傳送到服務端,並且在某一刻我們可以把該幀到達了流水線的頂點資料捏回客戶端修改(Vertex mapping),再提交回流水線(Vertex unmapping),(或者用glBufferData或glBufferSubData重新全部/部分提交更改了的頂點資料,)這是VBO的另一特性。

#TODO(continue):使用VBO、VA和顯示列表