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; }
移动像素
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文件的程序: