void glDrawArrays(GLenum mode, GLint first, GLsizei count);
在OpenGL中圖形都是通過分解成三角形的方式進行繪制。繪制圖形通過GL10類中的glDrawArrays方法實現,
參數1:mode 有三種取值
1. GL_TRIANGLES:每三個頂之間繪制三角形,之間不連接
2. GL_TRIANGLE_FAN:以V0V1V2,V0V2V3,V0V3V4,……的形式繪制三角形
3. GL_TRIANGLE_STRIP:順序在每三個頂點之間均繪制三角形。這個方法可以保證從相同的方向上所有三角形均被繪制。
以V0V1V2,V1V2V3,V2V3V4……的形式繪制三角形
2. glPixelStore
void glDrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid * indices);
glDrawElements 和 glDrawArrays 基本一致, 包括參數的使用,
唯一區別就是:
glDrawArrays 傳輸或指定的數據是最終的真實數據,在繪制時效能更好
glDrawElements 指定的是真實數據的調用索引,在內存/顯存占用上更節省
比如畫一個由2個3角形組成的正方形,左上角坐標是l,t,右下角坐標是r,b
使用glDrawArrays繪制時,畫2個三角形,需要這樣傳: (l,t),(r,t),(l,b) (r,t),(r,b),(l,b) 而用glDrawElements畫的話可以這樣:
float coord[4][2]={{l,t},{r,t},{r,b},{l,b}};
繪制時: 0,1,3 1,2,3
3. glPixelStore
像glPixelStorei(GL_PACK_ALIGNMENT, 1)這樣的調用,通常會用於像素傳輸(PACK/UNPACK)的場合。尤其是導入紋理(glTexImage2D)的時候:
-
glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glTexImage2D(,,,, &pixelData); glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
很明顯地,它是在改變某個狀態量,然后再Restore回來。——為什么是狀態?你難道8知道OpenGL就是以狀態機不?——什么狀態?其實名字已經很直白了,glPixelStore這組函數要改變的是像素的存儲格式。
涉及到像素在CPU和GPU上的傳輸,那就有個存儲格式的概念。在本地內存中端像素集合是什么格式?傳輸到GPU時又是什么格式?格式會是一樣么?在glTexImage2D這個函數中,包含兩個關於顏色格式的參數,一個是紋理(GPU端,也可以說server端)的,一個是像素數據(程序內存上,也就是client端)的,兩者是不一定一樣的,哪怕一樣也無法代表GPU會像內存那樣去存儲。或者想象一下,從一張硬盤上的圖片提取到內存的像素數據,上傳給GPU成為一張紋理,這個“紋理”還會是原來的那種RGBARGBA的一個序列完事么?顯然不是的。作為一張紋理,有其紋理ID、WRAP模式、插值模式,指定maipmap時還會有一串各個Level下的map,等等。就紋理的數據來說,本質紋理是邊長要滿足2的n次方(power of two)的數據集合,這樣首先大小上就有可能不一樣,另外排列方式也未必就是RGBA的形式。在OpenGL的“解釋”中,紋理就是一個“可以被采樣的復雜的數據集合”,無論外面世界千變萬化,GPU只認紋理作為自己“圖像數據結構”,這體現着“規范化”這條世界紐帶的偉大之處。
姑且把GPU里面的像素存儲格式看做一個未知數,把該存儲空間內那批像素看做一堆X。不要深究一堆X究竟是什么樣子的,嘛,反正就想象成一堆軟綿綿的,或者模糊不清的,打滿馬賽克的。與此相比,內存中的像素數據實在太規則規范了!可能源文件各種圖片格式,什么bmp、jpg、png甚至dds,但只要你按該格式的算法結構來提取(類似[Bmp文件的結構與基本操作(逐像素印屏版)] ),總可以提取出一列整齊的RGBARGBA(或者RGBRGB什么的,反正很整齊就行了管他呢)的數據堆出來,是可以在程序中實測的東西。
涉及到像素在CPU和GPU上的傳輸,那就有個傳輸方向的概念。那就是大家耳濡目染的PACK和UNPACK。嘛,裝載和卸載也可以,打包和解壓也可以,隨你怎么譯了。結合上述存儲格式的概念:
- PACK —— 把像素從一堆X的狀態轉變到規則的狀態(把一堆泥土裝載進一個花盆,把散散的貨物裝上貨櫃,或者把一堆各樣的文件打包成一個rar壓縮包,等等);
- UNPACK —— 把像素從規則的狀態轉變到一堆X的狀態(把花盆里的泥倒出來,把貨櫃中的貨物卸載到鹽田港,或者解壓壓縮包,等等)。
我認為這兩個概念還是很容易混淆的,所以形象化一點總好點嘛。
從本地內存向GPU的傳輸(UNPACK),包括各種glTexImage、glDrawPixel;
從GPU到本地內存的傳輸(PACK),包括glGetTexImage、glReadPixel等。也正因如此,PBO也有PACK和UNPACK模式的區別。
好像說了好多不相關的事情。嘛,適當也當做延伸。回頭來真正看一下glPixelStore吧。它的第一個參數,譬如ALIGNMENT、ROW_LENGTH、IMAGE_HEIGHT等等,都有PACK和UNPACK的兩種版本,所以對應的也是上述關於PACH和UNPACK的兩類函數。所以對於glTexImage2D,才使用GL_UNPACK_ALIGNMENT的版本。但要說明的是,無論是哪種傳輸方式,它都是針對本地內存端(client端)上的像素數據的。在上述例子中,它起着補充glTexImage2D中關於傳輸起點——本地像素集合的格式,的作用。
一般來說,這些本地的數據集合,只要知道其起始位置、大小(width*height)和顏色格式(譬如GL_RGBA等等)、值格式(GL_UNSIGNED_CHAR、GL_FLOAT等等),就能准確地傳輸。而這些都是需要向glTexImage2D函數(或者上述的其他傳輸型函數)提供的。但是,這里頭也一些細節,其實是需要glPixelStore這個函數來進行設置的。
3.1 GL_UNPACK_ALIGNMENT / GL_PACK_ALIGNMENT
通常,提取一張圖像的時候,我們怎么知道一行的數據量呢?這個一行的數據量應該是:width * sizeof(Pixel) ,應對最一般RGBA、各通道各占一個字節的像素結構,width * sizeof(Pixel) = width * 4 * sizeof(byte),是4的整數倍。但是也有時候,我們的像素數據中一行的數據量不一定是4的整數倍(譬如一張RGB的圖像、寬度150、各通道各占一個字節的像素結構,一行的數據量就是450個字節)。
另一方面,跟編譯器一樣,GPU傳輸時也喜歡4字節對齊,也即是說喜歡對像素數據按4字節存取。所以它更偏向於喜歡每一行的數據量是4的整數倍(按上述,這恰好是比較常見的)。所以為了更高的存取效率,OpenGL默認讓像素數據按4字節4字節的方式傳輸向GPU——但是問題在於,對於行非4字節對齊的像素數據,第一行的最后一次存取的4字節將部分包括第一行的數據部分包括第二行的數據,當然致命的不是在這里,而是在最后一行:存取將很可能會越界。為了防止這樣的情況,一是硬性把像素數據延展成4字節對齊的(就像BMP文件的存儲方式一樣,[Bmp文件的結構與基本操作(逐像素印屏版)] );二是選擇絕對會造成4字節對齊的顏色格式或值格式(GL_RGBA啦,或者GL_INT、GL_FLOAT之類);三是以犧牲一些存取效率為代價,去更改OpenGL的字節對齊方式——這就是glPixelStore結合GL_UNPACK_ALIGNMENT / GL_PACK_ALIGNMENT。
-
glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glTexImage2D(,,,, &pixelData); glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
再次看回這段代碼,這時候就明白了:讓字節對齊從默認的4字節對齊改成1字節對齊(選擇1的話,無論圖片本身是怎樣都是絕對不會出問題的,嘛,以效率的犧牲為代價),UNPACK像素數據,再把字節對齊方式設置回默認的4字節對齊。至於哪種方式更適合,就看你依據硬件環境限制、麻煩程度等,去選擇了。
3.2 GL_UNPACK_ROW_LENGTH/ GL_PACK_ROW_LENGTH 和
GL_UNPACK_SKIP_ROWS / GL_PACK_SKIP_ROWS 、 GL_UNPACK_SKIP_PIXELS/GL_PACK_SKIP_PIXELS
有的時候,我們把一些小圖片拼湊進一張大圖片內,這樣使用大圖片生成的紋理,一來可以使多個原本使用不同的圖片作為紋理的同質物件如今能夠在同一個Batch內,節省了一些狀態切換的開銷,二來也容易綜合地降低了顯存中紋理的總大小。但是,也有些時候,我們需要從原本一張大的圖片中,截取圖片當中的某一部分作為紋理。要能夠做到這樣,可以通過預先對圖片進行裁剪或者在獲得像素數據后,把其中需要的那一部分另外存儲到一個Buffer內再交給glTexImage2D之類的函數。而上述這些參數下glPixelStore的使用將幫助我們更好地完成這個目的:
-
//原圖中需要單獨提取出來制成紋理的區域 RECT subRect = {{100, 80}, {500, 400}}; //origin.x, origin.y, size.width, size.height //假設原圖的寬度為BaseWidth, 高度為BaseHeight glPixelStorei(GL_UNPACK_ROW_LENGTH, BaseWidth); //指定像素數據中原圖的寬度 glPixelStorei(GL_UNPACK_SKIP_ROWS, subRect. origin.y.); //指定紋理起點偏離原點的高度值 glPixelStorei(GL_UNPACK_SKIP_PIXELS, subRect. origin.x); //指定紋理起點偏離原點的寬度值 glTexImage2D(..., subRect.size.width, ubRect.size.height,.. &pixelData); //使用區域的寬高 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
這段代碼本身,即使沒有注釋也很清楚了。注意的是GL_UNPACK_ROW_LENGTH的必要性,因為為了確認區域起點的Offset,就需要把線性數據pixelData上標記起點的“游標”從0移動到OffsetToData = subRect. origin.y * BaseWidth + subRect. origin.x的位置。有了區域紋理原點的在原圖數據的位置,以及區域的尺寸,glTexImage2D就可以確定區域紋理生成所需要的信息了。通過glPixelStore的使用,避免了新建Buffer和自己處理圖像數據的開銷和麻煩了。
說到這里,到底為什么要這樣做來提取區域紋理呢?尤其是原圖若其他部分都是程序所需要的,那是不是就可以直接通過紋理坐標去切割更好呢?我想到的是一種情況(也可以說我是因為這種情況才注意到glPixelStore的這種用法):如果這塊區域紋理需要作重復鋪設(wrap mode選擇GL_REPEAT)呢?這時候紋理坐標的方法就沒用了,因為REPEAT所依據的也是紋理坐標(使用紋理坐標的小數部分進行采樣)。這時候就需要上述做法了。(事實上3DSMAX等軟件紋理導入的類似區域紋理平鋪的功能就能如此實現。)
4. glTexParameter 過濾函數
void glTexParameter*( GLenum target, GLenum pname, GLfloat param);
這時就可用glTexParmeteri()函數來確定如何把紋理象素映射成像素.
你這里的參數功能說明如下:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
GL_TEXTURE_MAG_FILTER: 放大過濾 GL_LINEAR: 線性過濾, 使用距離當前渲染像素中心最近的4個紋素加權平均值.
GL_TEXTURE_MIN_FILTER: 縮小過濾
E1. glShadeModel
在opengl流水線里頭,有一個步驟是柵格化(Rasterization),它在頂點組合的幾何信息處理后執行,目的是“插值”,vertex shader的varying變量就是在這里被柵格化(/插值),然后再傳入fragment shader作象素級別的處理。
兩頂點之間的象素怎么處理呢?就是靠這兩頂點的信息的線性插值。譬如最簡單的,顏色,一條線段兩頂點A和B的顏色分別為紅色和綠色,但是最后“顯示顏色”的不可能是頂點,只有象素,因此這兩點之間的象素就得按照它們臨近的頂點的顏色而獲得,越靠近點A的那些象素越紅,越靠近點B的那些象素越綠,中間因此呈現漸變效果(一般來說,線段中點有一半紅一半綠而成為黃色...)。對於填充的三角形,矩形也是一樣的道理,只不過是平面內的線性插值罷了。
其實glShadeModel作用相當於打開/關閉這種功能(柵格化),傳入參數GL_FLAT,流水線還是要經過這步驟但相應頂點不被處理,故頂點間的象素的顏色只會參考其中一個點的信息。譬如線段AB上的象素點全是紅的或全是綠的——是哪種通常不要緊,因為無論是哪種,出來的結果都會好難看,所以渲染最初(初始化階段)都會把參數設置成GL_SMOOTH,即啟用柵格化功能。當然插值的計算量就上來了.....在渲染不注重效果而只注重速度的時候,譬如我做shadow map的PASS1作場景深度圖的離線渲染時,非象素深度的信息根本對我無用,而且象素深度不是插值來的,故關閉柵格化計算,直接glShadeModel(GL_FLAT)再渲染就可以了,之后記得調回glShadeModel(GL_SMOOTH)。
1. http://www.khronos.org/opengles/sdk/docs/man/
2. http://www.zwqxin.com/archives/opengl/opengl-api-memorandum-2.html