歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

OpenGL超級寶典學習筆記——像素圖

像素包裝

位圖和像素圖很少會被緊密包裝到內存中。在許多硬件平台上,考慮到性能的原因位圖和像素圖的每一行的數據會從特殊的字節對齊地址開始。絕大多數編譯器會自動把變量和緩沖區放置在當前計算機架構優化的對齊地址上。OpenGL默認是4字節對齊的。在之前的例子中,篝火圖的數據是緊密包裝在一起的,但這不會引起什麼問題,因為篝火圖剛好是按照4字節對齊的,其寬是32位即4字節。如果位圖是34位寬的話,為了按照4字節對齊我們需要為每一行的數據多添加額外的30位(湊齊64位)無用的存儲空間。雖然這會浪費存儲空間,但會提升CPU從內存中抓取數據的效率。

你可以用下面兩個函數改變位圖和像素圖的存儲方式和檢索方式。

void glPixelStorei(GLenum pname, GLint param);

void glPixelStoref(GLenum pname, GLfloat param);

例如你的位圖是緊密的包裝在一起,你可以調用:

glPixelStorei(GL_UNPACK_ALIGMENT, 1);

GL_UNPACK_ALIGMENT告訴OpenGL如何從數據緩沖區中解包圖像數據。同樣地,你可以通過GL_PACK_ALIGMENT告訴OpenGL從顏色緩沖區讀取的數據如何打包然後放到用戶指定的內存空間中去。所有像素存儲可用的模式如下表:

參考:http://www.opengl.org/sdk/docs/man/xhtml/glPixelStore.xml

像素圖

像素圖在內存中的布局類似於位圖,然而每個像素在內存中不僅僅只用一位來表示。存儲像素的位中包含了強度(亮度)和顏色成分。在當前的光柵位置繪制像素圖:

void glDrawPixels(GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels);

頭兩個參數指定了像素圖的寬和高,第三個參數指定圖像數據的格式,第四個參數表示數據類型,最後一個參數是指向數據的指針。不像glBitmap函數,此函數不會改變光柵的位置。數據元素的顏色布局通過format參數指定。format的常量如下表:

GL_STENCIL_INDEX和GL_DEPTH_COMPONENT用於直接從模板緩沖區和深度緩沖區中讀和寫。type參數用於指定*pixels指針存儲的數據類型。它告訴OpenGL在緩沖區中的顏色成分是用什麼樣的數據類型存儲的。其常量表格如下:

常量 描述 GL_UNSIGNED_BYTE 每種顏色成分用 8位無符號整數表示 GL_BYTE 8位有符號整數 GL_BITMAP 單個位,無顏色數據,與glBitmap相同 GL_UNSIGNED_SHORT 無符號16位整數 GL_SHORT 有符號16位整數 GL_UNSIGNED_INT 無符號32位整數 GL_INT 有符號32位整數 GL_FLOAT 單精度浮點數 GL_UNSIGNED_BYTE_3_3_2 經過包裝的RGB值 GL_UNSIGNED_BYTE_2_3_3_REV 經過包裝的RGB值,逆序 GL_UNSIGNED_SHORT_5_6_5 經過包裝的RGB值 GL_UNSIGNED_SHORT_5_6_5_REV 經過包裝的RGB值,逆序 GL_UNSIGNED_SHORT_4_4_4_4 經過包裝的RGBA值 GL_UNSIGNED_SHORT_4_4_4_4_REV 經過包裝的RGBA值,逆序 GL_UNSIGNED_SHORT_5_5_5_1 經過包裝的RGBA值 GL_UNSIGNED_SHORT_1_5_5_5_REV 經過包裝的RGBA值,逆序 GL_UNSIGNED_INT_8_8_8_8 經過包裝的RGBA值 GL_UNSIGNED_INT_8_8_8_8_REV 經過包裝的RGBA值,逆序 GL_UNSIGNED_INT_10_10_10_2 經過包裝的RGBA值 GL_UNSIGNED_INT_2_10_10_10_REV 經過包裝的RGBA值,逆序

包裝的像素格式

對於一些更小的經過包裝的像素格式,在顯示設備上能夠節省存儲空間和有更快處理速度。這種經過包裝的像素格式仍然可以在一些PC硬件中找到,並且在將來也可能發揮作用。

經過包裝的像素格式把顏色數據盡可能地壓縮的更小。例如:GL_UNSIGNED_BYTE_3_3_2格式用3位存儲第一個成分,3位存儲第二個成分,2位存儲第三個成分。顏色成分的順序依靠於glDrawPixels函數中設置的format參數(如有GL_RGB, GL_BGR等)。顏色成分的順序是從最高位到最低位。GL_UNSIGNED_BYTE_2_3_3_REV反轉了這個順序,即把最後的那個顏色成分用頭兩位表示。

像素圖示例

此程序中,從fire.tga文件中加載圖像數據,然後通過glDrawPixels把圖像數據渲染到顏色緩沖區中。此程序與BITMAP沒有什麼大的區別。不同的僅僅是使用glTools庫中的函數gltLoadTGA來加載後綴為.tga的文件,然後把glBitmap換成glDrawPixels。示例如下:

void RenderScene()
{

glClear(GL_COLOR_BUFFER_BIT);
glColor3f(0.0f, 0.0f, 0.0f);

GLint iWidth = 0;
GLint iHeight = 0;
GLint iComponents = 0;
GLenum eFormat;
void *pImage = gltLoadTGA("fire.tga", &iWidth, &iHeight, &iComponents, &eFormat);

if (pImage)
{
glDrawPixels(iWidth, iHeight, eFormat, GL_UNSIGNED_BYTE, pImage);
free(pImage);
pImage = NULL;
}


glutSwapBuffers();
}

void *pImage = gltLoadTGA("fire.tga", &iWidth, &iHeight, &iComponents, &eFormat);

gltLoadTGA函數加載.tga後綴的圖像文件,第一個參數是文件路徑,第二第三個分別是圖像文件的寬和高,第四個參數是圖像包含多少個成分,最後一個參數是圖像數據的格式。如果函數執行成功,就返回一個圖像數據的指針(使用malloc分配的內存空間),如果失敗就返回為NULL. 函數的實現如下:

GLbyte *gltLoadTGA(const char *szFileName, GLint *iWidth, GLint *iHeight, GLint *iComponents, GLenum *eFormat)
{
FILE *pFile; // File pointer
TGAHEADER tgaHeader; // TGA file header
unsigned long lImageSize; // Size in bytes of image
short sDepth; // Pixel depth;
GLbyte *pBits = NULL; // Pointer to bits

// Default/Failed values
*iWidth = 0;
*iHeight = 0;
*eFormat = GL_BGR_EXT;
*iComponents = GL_RGB8;

// Attempt to open the fil
pFile = fopen(szFileName, "rb");
if(pFile == NULL)
return NULL;

// Read in header (binary)
fread(&tgaHeader, 18/* sizeof(TGAHEADER)*/, 1, pFile);

// Do byte swap for big vs little endian
#ifdef __APPLE__
LITTLE_ENDIAN_WORD(&tgaHeader.colorMapStart);
LITTLE_ENDIAN_WORD(&tgaHeader.colorMapLength);
LITTLE_ENDIAN_WORD(&tgaHeader.xstart);
LITTLE_ENDIAN_WORD(&tgaHeader.ystart);
LITTLE_ENDIAN_WORD(&tgaHeader.width);
LITTLE_ENDIAN_WORD(&tgaHeader.height);
#endif


// Get width, height, and depth of texture
*iWidth = tgaHeader.width;
*iHeight = tgaHeader.height;
sDepth = tgaHeader.bits / 8;

// Put some validity checks here. Very simply, I only understand
// or care about 8, 24, or 32 bit targa's.
if(tgaHeader.bits != 8 && tgaHeader.bits != 24 && tgaHeader.bits != 32)
return NULL;

// Calculate size of image buffer
lImageSize = tgaHeader.width * tgaHeader.height * sDepth;

// Allocate memory and check for success
pBits = (GLbyte*)malloc(lImageSize * sizeof(GLbyte));
if(pBits == NULL)
return NULL;

// Read in the bits
// Check for read error. This should catch RLE or other
// weird formats that I don't want to recognize
if(fread(pBits, lImageSize, 1, pFile) != 1)
{
free(pBits);
return NULL;
}

// Set OpenGL format expected
switch(sDepth)
{
case 3: // Most likely case
*eFormat = GL_BGR_EXT;
*iComponents = GL_RGB8;
break;
case 4:
*eFormat = GL_BGRA_EXT;
*iComponents = GL_RGBA8;
break;
case 1:
*eFormat = GL_LUMINANCE;
*iComponents = GL_LUMINANCE8;
break;
};


// Done with File
fclose(pFile);

// Return pointer to image data
return pBits;
}



你可以注意到iComponents不是被賦值為1,3,或者4而是用GL_LUMINANCE8, GL_RGB8, GL_RGBA8. 當OpenGL操作數據時,會把這些特殊的常量作為維護完整圖像的精度的內部請求。例如:出於性能的原因,一些OpenGL的實現會在內部把24位色的圖像數據降低為16位色的。這種操作在紋理加載尤為常見。因為在許多實現上,顯示輸出分辨率只有16位,但實際加載的圖像具有更高的分辨率。這些常量是對OpenGL實現所作的請求,要求按照完整的每個通道8位的顏色深度進行存儲與使用。

移動像素

OpenGL可以從顏色緩沖區中讀取像素數據,或者拷貝到另一個緩沖區中。讀取緩沖區的函數:

void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels);

x,y指定窗口的坐標(從左下角開始),width,height指定讀取的矩形范圍的寬高,fromat指定像素的格式,type指定數據的類型。如果顏色緩沖區中存儲的數據不是你想要的類型,OpenGL將會自動幫你轉換。最後一個參數是存儲圖像數據的指針。指針指向的內存空間要可以容納轉換後的圖像數據,否則會有內存越界的錯誤。

從顏色緩沖區中拷貝像素到另一個緩沖區中也是很容易的,而且在此操作中不需要分配臨時的內存空間。首先,使用glRasterPos或glWindowsPos函數設置希望圖像數據被拷貝的目標角落(左下角)的光柵位置。然後調用下面的函數進行拷貝。

void glCopyPixels(GLint x, GLint y, GLsizei height, GLsizei width, GLenum type);

x,y指定了要復制的矩形的左下角,width,height指定了寬高(以像素為單位),type為GL_COLOR 拷貝顏色數據,GL_DEPTH拷貝深度緩沖區中的數據,GL_STENCIL拷貝模板緩沖區的數據。

默認情況下,在雙緩沖渲染環境中像素操作是在後緩沖區中進行,單緩沖則是前緩沖區中進行。可以使用下面兩個函數改變像素操作的源或目標:

void glDrawBuffer(GLenum mode);

void glReadBuffer(GLenum mode);

glDrawBuffer決定了glDrawPixels和glCopyPixels往哪個緩沖區渲染。可以選的參數有:GL_NONE, GL_FRONT, GL_BACK, GL_FRONT_AND_BACK, GL_FRONT_LEFT,GL_FRONT_RIGHT。

glReadBuffer接受的參數也是一樣的,決定了glReadPixels和glCopyPixels操作的目標緩沖區。

保存像素

我們可以從前顏色緩沖區中讀取數據保存為圖像文件。下面是gltools庫中把圖像數據保存為tga文件的程序:

GLint gltWriteTGA(const char *szFileName)
{
FILE *pFile; // 文件指針
TGAHEADER tgaHeader; // tga文件頭
unsigned long lImageSize; // 圖像的大小
GLbyte *pBits = NULL; // 圖像數據
GLint iViewport[4]; // 視口
GLenum lastBuffer; // 保存當前讀取緩沖區的設置

// 取得當前視口大小
glGetIntegerv(GL_VIEWPORT, iViewport);

// 獲得圖像大小,因為tga的圖像數據是緊密包裝的,所以用視口的寬高乘以3個字節
lImageSize = iViewport[2] * 3 * iViewport[3];

// 分配內存用於存儲讀取出來的圖像數據
pBits = (GLbyte *)malloc(lImageSize);
if(pBits == NULL)
return 0;

// 設置為逐個像素的方式讀取
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
glPixelStorei(GL_PACK_SKIP_ROWS, 0);
glPixelStorei(GL_PACK_SKIP_PIXELS, 0);

// 保存當前的設置,後面再恢復它
glGetIntegerv(GL_READ_BUFFER, (GLint *)&lastBuffer);
glReadBuffer(GL_FRONT);
glReadPixels(0, 0, iViewport[2], iViewport[3], GL_BGR_EXT, GL_UNSIGNED_BYTE, pBits);
glReadBuffer(lastBuffer);

// 初始化tag文件頭的格式
tgaHeader.identsize = 0;
tgaHeader.colorMapType = 0;
tgaHeader.imageType = 2;
tgaHeader.colorMapStart = 0;
tgaHeader.colorMapLength = 0;
tgaHeader.colorMapBits = 0;
tgaHeader.xstart = 0;
tgaHeader.ystart = 0;
tgaHeader.width = iViewport[2];
tgaHeader.height = iViewport[3];
tgaHeader.bits = 24;
tgaHeader.descriptor = 0;

// 蘋果操作需要,進行大小端的轉換
#ifdef __APPLE__
LITTLE_ENDIAN_WORD(&tgaHeader.colorMapStart);
LITTLE_ENDIAN_WORD(&tgaHeader.colorMapLength);
LITTLE_ENDIAN_WORD(&tgaHeader.xstart);
LITTLE_ENDIAN_WORD(&tgaHeader.ystart);
LITTLE_ENDIAN_WORD(&tgaHeader.width);
LITTLE_ENDIAN_WORD(&tgaHeader.height);
#endif

// 打開文件
pFile = fopen(szFileName, "wb");
if(pFile == NULL)
{
free(pBits);
return 0;
}

// 寫文件頭
fwrite(&tgaHeader, sizeof(TGAHEADER), 1, pFile);

// 寫圖像數據
fwrite(pBits, lImageSize, 1, pFile);

// 釋放臨時分配的內存空間
free(pBits);
fclose(pFile);

return 1;
}

OpenGL超級寶典 第4版 中文版PDF+英文版+源代碼 見  http://www.linuxidc.com/Linux/2013-10/91413.htm

OpenGL編程指南(原書第7版)中文掃描版PDF 下載 http://www.linuxidc.com/Linux/2012-08/67925.htm

OpenGL 渲染篇 http://www.linuxidc.com/Linux/2011-10/45756.htm

Ubuntu 13.04 安裝 OpenGL http://www.linuxidc.com/Linux/2013-05/84815.htm

OpenGL三維球體數據生成與繪制【附源碼】 http://www.linuxidc.com/Linux/2013-04/83235.htm

Ubuntu下OpenGL編程基礎解析 http://www.linuxidc.com/Linux/2013-03/81675.htm

如何在Ubuntu使用eclipse for c++配置OpenGL http://www.linuxidc.com/Linux/2012-11/74191.htm 

更多《OpenGL超級寶典學習筆記》相關知識 見 http://www.linuxidc.com/search.aspx?where=nkey&keyword=34581

Copyright © Linux教程網 All Rights Reserved