OGL位图像素数据绘制流程-OGL重心

图像每个元素不是用0,1的位表示,而是用一个整型包含了RGBA通道的图像。.jpg,.png,.tga, .bmp都是经过图像格式包装的图像。
图像可以来自颜色缓存区(后台缓存区,前台缓存区),也可以来自深度缓存区和模板缓存区。
图像中的int可以记录颜色,也可以记录其它数据,例如法向量,高度图,法线贴图就是这样的图像。
图像在GPU端有个纹理内存,存储CPU解包传输过来,经过纹理映射或可以重新映射的纹理,用于光栅化时候进行纹理贴图插值,片段Shader时候进行着色。

一、基本的像素操作

本文操作的图像操作是基于OGL 3.0之前的,在3.1后很多被废弃了,用更强大的FrameBuffer对象来操作,避免为了修改图像数据来回传输。

1.glReadPixels

从帧缓存区(颜色缓存区,模板缓存区,深度缓存区都可以)读取一个矩形像素数组,并把它保存到CPU 内存中。
图像数据在GPU后台缓存区,存储缓存区,CPU内存缓存区,之间传递数据,解析数据时候存在不同的 操作模式,如果简单的读取,复制,显示那么不需要了解这些操作模式,但是如果需要修改图像数据格式以适应不同的存储模式和窗口显示模式,则需要了解不同的图像操作模式。
OGL图像操作都是基于OGL屏幕坐标的, 也就是左下角为原点,向上,向右递增。
glReadPixels (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels);
x,y是原点;width,height是图像的宽高;format是图像的像素格式:
/* PixelFormat */
#define GL_COLOR_INDEX                    0x1900
#define GL_STENCIL_INDEX                  0x1901
#define GL_DEPTH_COMPONENT                0x1902
#define GL_RED                            0x1903
#define GL_GREEN                          0x1904
#define GL_BLUE                           0x1905
#define GL_ALPHA                          0x1906
#define GL_RGB                            0x1907
#define GL_RGBA                           0x1908
#define GL_LUMINANCE                      0x1909
#define GL_LUMINANCE_ALPHA                0x190A
type是图像的数据类型:
/* DataType */
#define GL_BYTE                           0x1400
#define GL_UNSIGNED_BYTE                  0x1401
#define GL_SHORT                          0x1402
#define GL_UNSIGNED_SHORT                 0x1403
#define GL_UNSIGNED_BYTE_2_3_3_REV 0x8362
#define GL_UNSIGNED_SHORT_5_6_5 0x8363
#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364
#define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365
#define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366
#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367
type元素类型没有*REV的是小端存储模式,有*REV的是整型大数据(超过一字节)反转内部字节为单位的顺序,也就是大端存储模式。
pixels是像素存储的CPU内存指针。

如果在双缓存中,还需要glReadBuffer来控制是读取前台还是后台缓存。

2.glDrawPixels

把像素数据从内存写入到帧缓存区(颜色缓存区,模板缓存区,深度缓存区都可以)。
glDrawPixels (GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels);
参数同:glReadPixels
绘制到的屏幕坐标系由glRasterPos2i(x,y)来指定起始位置。
如果在双缓存中,可能需要glDrawBuffer来控制是读取前台还是后台缓存,或者深度模板缓存区,一般是写入后台颜色缓存区。

3.glCopyPixels

从缓存区的一个矩形区域复制像素数据到另一个GPU缓存位置中,该GPU缓存位置就是绘图时候从CPU发送到GPU中的缓存位置,后面需要进过光栅化和片段处理,裁剪/alpha/模板深度检查,融合,抖动逻辑操作才能写入后台颜色缓存区。
glCopyPixels (GLint x, GLint y, GLsizei width, GLsizei height, GLenum type);
并不需要format, type参数,因为不会写入到CPU内存中。
源和目标缓存由glReadBuffer和glDrawBuffer来指定。
OGL 3.0增加了glBlitFramebuffer,包含了glCopyPixels和glPixelZoom()的功能。

 // 复制左下角的数据块,到当前位置绘制像素数据块,相当于read 和 draw 图像或位图数据
   glCopyPixels (0, 0, checkImageWidth, checkImageHeight, GL_COLOR);

4.glPixelZoom()

对图像进行放大缩小操作。

glBitMap没有纹理映射,是将位图直接从CPU内存解包->CPU内存像素存储模式- GPU光栅化->GPU片段操作->帧缓存区。
glTexImage*(), glTexSubImage*()是将图像数据从CPU内存解包,传输到GPU 纹理内存中,以便光栅化时候应用, 后面需要纹理映射。
glGetTexImage是将像素从GPU纹理内存读取回CPU内存。
glCopyTexImage*(),glCopySubTexImage*()是将像素从帧缓存区复制到GPU纹理内存中。

二、像素渲染管线:


上图中的解包和包装并不是.jpg, .png, .tga, .bmp图像的解包包装;而是在这些像素格式解压到内存后的进一步的针对GPU渲染的像素大小端,高地位读取,截取的区域,内存对齐 方面进行的解包和包装。

1.包装和解包

解包是指定图像格式解包后面的操作,按照当前内存中像素数据解释数据为一块适合GPU渲染的内存块,包装是逆过程;内存块内的元素之间的排列顺序是固定的,元素内部的字节之间(像素成分)可能有大小端模式问题需要处理,CPU中的一个元素的像素成分例如是一个float则转换到GPU端帧缓存区或纹理内存中因为节省内存消耗精度会更低,当高精度转换到低精度时会丢失精度。

2.控制图像存储模式

glPixelStore 控制图像在CPU端的存储模式,包括:
一个元素字节间的大小端模式  GL_*_SWAP_BYTES
位图数据从高位开始读取,还是低位开始读取  GL_*_LSB_FIRST,least significant Bits 是否从低位开始读取,默认从高位开始读取。
从图像矩形内部复制子矩形区域,开始跳过的像素数GL_*_SKIP_ROWS默认是0从左下角开始复制,图像的宽高GL_*_ROW_LENGTH,GL_*_ROW_LENGTH 单位是像素数,默认是0也就是glReadPixels, glDrawPixels, glCopyPixels指定的大小。带有Image的是对3D的图像产生影响。
内存对齐方式, GL_*_ALIGNMENT 一般CPU有自己最优的内存访问起始地址,例如32位机器是4字节对齐,64为机器是8字节对齐,每次访问都从4的倍数,或8的倍数开始访问,有最快的存取内存效率。可以通过GL_*_ALIGNMENT指定内存的对齐字节数,默认是4。一个常见的编程错误就是以为像素数据时紧密排列并对齐的,例如自以为GL_*_ALIGNMENT设置为1。因为内存对齐,每行多浪费的字节读取到的值是这块内存设置的初始值,因为图像数据要从磁盘或者外设load到预先初始化的内存中。

glPixelStore — set pixel storage modes

void glPixelStoref( GLenum pname,

GLfloat param);

void glPixelStorei( GLenum pname,

GLint param);
pname

Specifies the symbolic name of the parameter to be set. Six values affect the packing of pixel data into memory: GL_PACK_SWAP_BYTESGL_PACK_LSB_FIRSTGL_PACK_ROW_LENGTHGL_PACK_IMAGE_HEIGHT,GL_PACK_SKIP_PIXELSGL_PACK_SKIP_ROWSGL_PACK_SKIP_IMAGES, and GL_PACK_ALIGNMENT. Six more affect the unpacking of pixel data from memory: GL_UNPACK_SWAP_BYTESGL_UNPACK_LSB_FIRST,GL_UNPACK_ROW_LENGTHGL_UNPACK_IMAGE_HEIGHTGL_UNPACK_SKIP_PIXELSGL_UNPACK_SKIP_ROWSGL_UNPACK_SKIP_IMAGES, and GL_UNPACK_ALIGNMENT.

param

Specifies the value that pname is set to.

glPixelStore sets pixel storage modes that affect the operation of subsequent glReadPixels as well as the unpacking of texture patterns (see glTexImage1DglTexImage2DglTexImage3DglTexSubImage1DglTexSubImage2D,glTexSubImage3D), glCompressedTexImage1DglCompressedTexImage2DglCompressedTexImage3DglCompressedTexSubImage1DglCompressedTexSubImage2D or glCompressedTexSubImage1D.

pname is a symbolic constant indicating the parameter to be set, and param is the new value. Six of the twelve storage parameters affect how pixel data is returned to client memory. They are as follows:


3.像素传输操作和像素映射

1)像素传输操作

像素在CPU内存和GPU帧缓冲区之间传递时候,可以进行的操作叫做像素传输操作。

像素传输操作,可以将像素内部的颜色成分限制在一定的范围内(进行截取,或索引进行截取),可以创建颜色映射将RGBA等颜色模式 和 颜色索引模式之间做转换

颜色缓存,深度,模板缓存之间具有许多相似之处,但也有一些差异,需要不同的设置。

像素传输的有些特征是用glPixelTransfer*()函数设置,其它特征有glPixelMap*()函数指定。

glPixelTransfer — set pixel transfer modes

void glPixelTransferf( GLenum pname,

GLfloat param);

void glPixelTransferi( GLenum pname,

GLint param);
pname

Specifies the symbolic name of the pixel transfer parameter to be set. Must be one of the following: GL_MAP_COLORGL_MAP_STENCILGL_INDEX_SHIFTGL_INDEX_OFFSET,GL_RED_SCALEGL_RED_BIASGL_GREEN_SCALEGL_GREEN_BIASGL_BLUE_SCALEGL_BLUE_BIASGL_ALPHA_SCALEGL_ALPHA_BIASGL_DEPTH_SCALE, or GL_DEPTH_BIAS.

Additionally, if the ARB_imaging extension is supported, the following symbolic names are accepted: GL_POST_COLOR_MATRIX_RED_SCALEGL_POST_COLOR_MATRIX_GREEN_SCALE,GL_POST_COLOR_MATRIX_BLUE_SCALEGL_POST_COLOR_MATRIX_ALPHA_SCALEGL_POST_COLOR_MATRIX_RED_BIASGL_POST_COLOR_MATRIX_GREEN_BIASGL_POST_COLOR_MATRIX_BLUE_BIASGL_POST_COLOR_MATRIX_ALPHA_BIAS,GL_POST_CONVOLUTION_RED_SCALEGL_POST_CONVOLUTION_GREEN_SCALEGL_POST_CONVOLUTION_BLUE_SCALEGL_POST_CONVOLUTION_ALPHA_SCALEGL_POST_CONVOLUTION_RED_BIASGL_POST_CONVOLUTION_GREEN_BIAS,GL_POST_CONVOLUTION_BLUE_BIAS, and GL_POST_CONVOLUTION_ALPHA_BIAS.

param

Specifies the value that pname is set to.

glPixelTransfer sets pixel transfer modes that affect the operation of subsequent glCopyPixelsglCopyTexImage1DglCopyTexImage2DglCopyTexSubImage1D,glCopyTexSubImage2DglCopyTexSubImage3DglDrawPixelsglReadPixelsglTexImage1DglTexImage2DglTexImage3DglTexSubImage1DglTexSubImage2D, and glTexSubImage3Dcommands. Additionally, if the ARB_imaging subset is supported, the routines glColorTableglColorSubTableglConvolutionFilter1DglConvolutionFilter2DglHistogram,glMinmax, and glSeparableFilter2D are also affected. The algorithms that are specified by pixel transfer modes operate on pixels after they are read from the frame buffer (glCopyPixels glCopyTexImage1DglCopyTexImage2DglCopyTexSubImage1DglCopyTexSubImage2DglCopyTexSubImage3D, and glReadPixels), or unpacked from client memory (glDrawPixelsglTexImage1DglTexImage2DglTexImage3DglTexSubImage1DglTexSubImage2D, and glTexSubImage3D). Pixel transfer operations happen in the same order, and in the same manner, regardless of the command that resulted in the pixel operation. Pixel storage modes (see glPixelStore) control the unpacking of pixels being read from client memory and the packing of pixels being written back into client memory.

Pixel transfer operations handle four fundamental pixel types: colorcolor indexdepth, and stencilColor pixels consist of four floating-point values with unspecified mantissa and exponent sizes, scaled such that 0 represents zero intensity and 1 represents full intensity. Color indices comprise a single fixed-point value, with unspecified precision to the right of the binary point. Depth pixels comprise a single floating-point value, with unspecified mantissa and exponent sizes, scaled such that 0.0 represents the minimum depth buffer value, and 1.0 represents the maximum depth buffer value. Finally, stencil pixels comprise a single fixed-point value, with unspecified precision to the right of the binary point

指定了GL_MAP_COLOR, GL_MAP_STENCIL就会启用像素映射功能。

其它是颜色成分的缩放和偏移(限制在范围内),包括索引的位移和偏移(限制在一定范围内).

GL_POST_CONVOLUTION和GL_POST_COLOR_MATRIX系列参数,是只有在OGL实现支持图像处理子集功能时候才可用。

2)像素映射操作

所有的颜色成分,颜色索引和模板索引在进入屏幕内存之前都可以通过表查找的方式进行更改(从CPU端通过映射的方式对GPU中的待光栅化像素数据进行映射更新),用于控制这种映射的函数就是glPixelMap

glPixelMap — set up pixel transfer maps

void glPixelMapfv( GLenum map,

GLsizei mapsize,

const GLfloat * values);
 
void glPixelMapuiv( GLenum map,

GLsizei mapsize,

const GLuint * values);
 
void glPixelMapusv( GLenum map,

GLsizei mapsize,

const GLushort * values);
map

Specifies a symbolic map name. Must be one of the following: GL_PIXEL_MAP_I_TO_IGL_PIXEL_MAP_S_TO_SGL_PIXEL_MAP_I_TO_RGL_PIXEL_MAP_I_TO_GGL_PIXEL_MAP_I_TO_B,GL_PIXEL_MAP_I_TO_AGL_PIXEL_MAP_R_TO_RGL_PIXEL_MAP_G_TO_GGL_PIXEL_MAP_B_TO_B, or GL_PIXEL_MAP_A_TO_A.

mapsize

Specifies the size of the map being defined.

values

Specifies an array of mapsize values.

glPixelMap sets up translation tables, or maps, used by glCopyPixelsglCopyTexImage1DglCopyTexImage2DglCopyTexSubImage1DglCopyTexSubImage2DglCopyTexSubImage3D,glDrawPixelsglReadPixelsglTexImage1DglTexImage2DglTexImage3DglTexSubImage1DglTexSubImage2D, and glTexSubImage3D. Additionally, if the ARB_imaging subset is supported, the routines glColorTableglColorSubTableglConvolutionFilter1DglConvolutionFilter2DglHistogramglMinmax, and glSeparableFilter2D. Use of these maps is described completely in the glPixelTransfer reference page, and partly in the reference pages for the pixel and texture image commands. Only the specification of the maps is described in this reference page.

map is a symbolic map name, indicating one of ten maps to set. mapsize specifies the number of entries in the map, and values is a pointer to an array of mapsize map values.

If a non-zero named buffer object is bound to the GL_PIXEL_UNPACK_BUFFER target (see glBindBuffer) while a pixel transfer map is specified, values is treated as a byte offset into the buffer object's data store.

查询像素映射表的大小:

GLint maxMapTblSize;
glGetIntegerv(GL_MAX_PIXEL_MAP_TABLE, &maxMapTblSize);
颜色索引的更新实例,将颜色索引和表中的索引对应,值是表中的值,这样当前绘制的颜色的索引值就被更新了:
GLfloat mapColor[256] = {
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
       255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
       255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
       255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
       255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
       255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
       255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
       255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
       255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
       255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
   };
   glPixelTransferf(GL_MAP_COLOR, TRUE);
   // 小于101的索引都变成了0,大于100小于256的都变成了255.
   glPixelMapfv(GL_PIXEL_MAP_I_TO_I, 256, mapColor);
更多的map类型值:
// 类型为颜色索引,值也是颜色索引
#define GL_PIXEL_MAP_I_TO_I               0x0C70
// 类型为模板索引,值也是模板索引
#define GL_PIXEL_MAP_S_TO_S               0x0C71
// 类型为颜色索引,值是R、G、B、A值
#define GL_PIXEL_MAP_I_TO_R               0x0C72
#define GL_PIXEL_MAP_I_TO_G               0x0C73
#define GL_PIXEL_MAP_I_TO_B               0x0C74
#define GL_PIXEL_MAP_I_TO_A               0x0C75
前面6个类型 mapsize需要为2的整数次方。后面4个 mapsize可以是1到GL_MAX_PIXEL_MAP_TABLE任意值。

// 类型为颜色通道,值也是对应的R、G、B、A值
#define GL_PIXEL_MAP_R_TO_R               0x0C76
#define GL_PIXEL_MAP_G_TO_G               0x0C77
#define GL_PIXEL_MAP_B_TO_B               0x0C78
#define GL_PIXEL_MAP_A_TO_A               0x0C79

4.纹理内存和纹理操作

上面的像素存储模式,像素传输操作,映射操作,都是设置参数。 glDrawPixels glReadPixels会直接驱动绘制或读取像素数据。
这里的纹理操作也是实际进行纹理操作,例如:
1) 定义纹理函数glTexImageiD( 将CPU中的像素数据传输到GPU端定义纹理对象)
2) 替换一维纹理的部分或全部glTexSubImageiD; 
3) 从帧缓存区(ReadBuffer)创建一个新纹理glCopyTexImageiD;
4) 从帧缓存区替换一个现有纹理的部分或全部glCopyTexSubImageiD.
5) 纹理优先级设置存储常驻在GPU中的策略,OGL都支持,(OGL 3.0以上应该都支持常驻纹理不需要调整优先级,每帧绘制时候绑定纹理标示符 即可,如:glBindTexture(GL_TEXTURE_2D), texName[1]));

纹理内存和纹理操作相关函数:

5.光栅化时的图像的放大,缩小,翻转

在应用图像存储模式和像素传输操作后,图像和位图就进行光栅化。正常情况下图像中的每个像素被写入到屏幕中的一个像素,但是可以使用glPixelZoom函数对图像进行放大,缩小和翻转(负数时候)。
等比的缩放,图像才不会变形,原因是如果放大2倍,那么原来一个像素大小的位置,就会用4个像素大小的位置来代替,整体看过去就是画笔大了点而已,图像内容不会变形。
OGL从下往上并 左往右逐行扫描,填充光栅化颜色缓存,一副图像解析或扫描放入图像或位图数据数组的时候就是按照从下往上并左往右放置在数组的低位到高位,所以光栅化时候顺序的从低位到高位读取像素或位图数组按照从下往上并左往右扫描填充,就可以还原整幅图像或位图了
glPixelZoom (GLfloat xfactor, GLfloat yfactor); xfactor和yfactor默认值是1.0f;
如果需要将一副图像上下翻转,则yfactor = -1即可。
假设当前光栅位置(x,y),如果一组特定的元素(索引或成分)属于n行,m列;那么在窗口坐标下这个矩形边界的区域是:
(x + n*xfactor, y + m*yfactor) 和 (x + (n + 1)*xfactor, y + (m + 1)*yfactor)。

6.片断操作,纹理映射(非像素映射)

位图是不存在的,图像存在这样的纹理映射。光栅化得到的基于图元的纹理uv插值,颜色插值,法向量插值,等片断信息是没有整合的;在片断Fragment Shader操作时候对这些信息进行整合得到颜色输出;后面可能会进行镜面高光辅助颜色混合,雾化处理。例如:
       texColor        SecondColor        Fog
MainColor-------->FragColor1----------->FragColor2----------->FragFinalColor
启用光照后可以直接得到镜面光照效果,会在纹理映射后应用光照计算得到的辅助颜色。
不启用光照下,并且启用了颜色求和模式,那么当前的辅助颜色会添加到经过纹理处理的片段颜色上。
例如:
glDisable(GL_LIGHTING);
glEnable(GL_COLOR_SUM);
glSecondaryColor3f(0.2, 0.2, 0.2);
接着进行 sissor/alpha/stencil/depth test。抗锯齿的混合。逻辑操作,抖动后的颜色。 和后台缓存之前draw call的混合写入

7.绘制,复制,缩放像素数据的例子

#include <GL/glut.h>
#include <stdlib.h>
#include <stdio.h>

/*	Create checkerboard image	*/
#define	checkImageWidth 64
#define	checkImageHeight 64
GLubyte checkImage[checkImageHeight][checkImageWidth][3];

static GLdouble zoomFactor = 1.0;
static GLint height;

void makeCheckImage(void)
{
   int i, j, c;
    
   for (i = 0; i < checkImageHeight; i++) {
      for (j = 0; j < checkImageWidth; j++) {
         c = ((((i&0x8)==0)^((j&0x8))==0))*255;
         checkImage[i][j][0] = (GLubyte) c;
         checkImage[i][j][1] = (GLubyte) c;
         checkImage[i][j][2] = (GLubyte) c;
      }
   }
}

void init(void)
{    
   glClearColor (0.0, 0.0, 0.0, 0.0);
   glShadeModel(GL_FLAT);
   // 从下往上,从左往右生成图像数据数组
   makeCheckImage();
   // 像素存储模式,内存对齐为1
   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
}

void display(void)
{
   glClear(GL_COLOR_BUFFER_BIT);
   // 定义光栅化位置,从左下角开始
   glRasterPos2i(0, 0);
   // 绘制图像数据,每个成分的类型是GL_UNSIGNED_BYTE,整个像素是GL_RGB有三个成分;数据块是checkImage
   glDrawPixels(checkImageWidth, checkImageHeight, GL_RGB, 
                GL_UNSIGNED_BYTE, checkImage);
   glFlush();
}

void reshape(int w, int h)
{
   glViewport(0, 0, (GLsizei) w, (GLsizei) h);
   height = (GLint) h;
   // 正交投影,设置屏幕宽高
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluOrtho2D(0.0, (GLdouble) w, 0.0, (GLdouble) h);

   // 视图模型矩阵是单位矩阵
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
}

void motion(int x, int y)
{
   static GLint screeny;
   screeny = height - (GLint)y;
   printf("y is now %d\n", y);
   printf("screeny is now %d\n", screeny);
   // 改变光栅化位置,就是鼠标x值的位置
   // 因为窗口系统的位置是左上角,OGL屏幕坐标系是左下角,所以要减去screeny = height - (GLint)y;
   glRasterPos2i (x, screeny);
   // 改变图像或位图光栅化时候的缩放值
   glPixelZoom (zoomFactor, zoomFactor);
   // 复制左下角的数据块,到当前位置绘制像素数据块,相当于read 和 draw 图像或位图数据
   glCopyPixels (0, 0, checkImageWidth, checkImageHeight, GL_COLOR);
   // 恢复图像或位图光栅化时候的缩放值
   glPixelZoom (1.0, 1.0);
   glFlush ();
}

void keyboard(unsigned char key, int x, int y)
{
   switch (key) {
      case 'r':
      case 'R':
         zoomFactor = 1.0;
         glutPostRedisplay();
         printf ("zoomFactor reset to 1.0\n");
         break;
      case 'z':
         zoomFactor += 0.5;
         if (zoomFactor >= 3.0) 
            zoomFactor = 3.0;
         printf ("zoomFactor is now %4.1f\n", zoomFactor);
         break;
      case 'Z':
         zoomFactor -= 0.5;
         if (zoomFactor <= 0.5) 
            zoomFactor = 0.5;
         printf ("zoomFactor is now %4.1f\n", zoomFactor);
         break;
      case 27:
         exit(0);
         break;
      default:
         break;
   }
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
   glutInitWindowSize(250, 250);
   glutInitWindowPosition(100, 100);
   glutCreateWindow(argv[0]);
   init();
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyboard);
   // The motion callback for a window is called when the mouse moves within the window while one or more
   // mouse buttons are pressed
   // 按下鼠标并移动
   glutMotionFunc(motion);
   glutMainLoop();
   return 0; 
}

使用缓存区对象传输像素数据

每帧绘制时候不需要将图像或位图数据从CPU内存拷贝到GPU缓存中,而是初始化时候将指定解包,存储模式,传输;只需要传输一次,后面每帧渲染只需要从GPU缓存区对象,绑定指针读取数据即可。而且可以初始化后更改数据。
绘制和获取纹理对象的函数,可以利用缓存区对象指定像素数据,例如:
glDrawPixels(), glTexImage*D(), glCompressedTexImage*D(), glPixelMap*() 以及图像处理子集中接受像素数组为参数的类似函数。

初始化时候,拷贝数据到缓存区对象中
// 1.获取
   glGenBuffers(1, &pixelBuffer);
   // 2.绑定GL_PIXEL_UNPACK_BUFFER全局指针
   glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pixelBuffer);
   // 3.对GL_PIXEL_UNPACK_BUFFER全局指针,拷贝数据
   glBufferData(GL_PIXEL_UNPACK_BUFFER, 3 * checkImageWidth*checkImageHeight, checkImage, GL_STATIC_DRAW);
   // 数据一次拷贝到了GPU缓存区对象中,可以释放CPU中的数据块
   memset(checkImage, 0, 3 * checkImageWidth*checkImageHeight);
每帧绘制时候绑定到缓存区对象全局指针,绘制使用偏移NULL绘制
void display(void)
{
   glClear(GL_COLOR_BUFFER_BIT);
   // 定义光栅化位置,从左下角开始
   glRasterPos2i(0, 0);
   // 4..对GL_PIXEL_UNPACK_BUFFER全局指针,绑定到pixelBuffer缓存区对象
   glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pixelBuffer);
   // 绘制图像数据,每个成分的类型是GL_UNSIGNED_BYTE,整个像素是GL_RGB有三个成分;数据块是checkImage
   // 5.正常绘图,对*data传递的是GL_PIXEL_UNPACK_BUFFER全局指针的开始地址
   glDrawPixels(checkImageWidth, checkImageHeight, GL_RGB, 
                GL_UNSIGNED_BYTE, BUFFER_OFFSET(0));
   glFlush();
}

#include <GL/glew.h>
#include <GL/glut.h>
//#  include <GL/freeglut.h>
#include <stdlib.h>
#include <stdio.h>
#pragma  comment(lib, "glew32d.lib")
/*	Create checkerboard image	*/
#define BUFFER_OFFSET(bytes) ((GLubyte*)NULL + (bytes))
#define	checkImageWidth 64
#define	checkImageHeight 64
GLubyte checkImage[checkImageHeight][checkImageWidth][3];

static GLdouble zoomFactor = 1.0;
static GLint height;
static GLuint pixelBuffer;

void makeCheckImage(void)
{
   int i, j, c;
    
   for (i = 0; i < checkImageHeight; i++) {
      for (j = 0; j < checkImageWidth; j++) {
         c = ((((i&0x8)==0)^((j&0x8))==0))*255;
         checkImage[i][j][0] = (GLubyte) c;
         checkImage[i][j][1] = (GLubyte) c;
         checkImage[i][j][2] = (GLubyte) c;
      }
   }
}

void init(void)
{    
   glewInit();
   glClearColor (0.0, 0.0, 0.0, 0.0);
   glShadeModel(GL_FLAT);
   // 从下往上,从左往右生成图像数据数组
   makeCheckImage();
   // 像素存储模式,内存对齐为1
   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

   // 1.获取
   glGenBuffers(1, &pixelBuffer);
   // 2.绑定GL_PIXEL_UNPACK_BUFFER全局指针
   glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pixelBuffer);
   // 3.对GL_PIXEL_UNPACK_BUFFER全局指针,拷贝数据
   glBufferData(GL_PIXEL_UNPACK_BUFFER, 3 * checkImageWidth*checkImageHeight, checkImage, GL_STATIC_DRAW);
   // 数据一次拷贝到了GPU缓存区对象中,可以释放CPU中的数据块
   memset(checkImage, 0, 3 * checkImageWidth*checkImageHeight);
}

void display(void)
{
   glClear(GL_COLOR_BUFFER_BIT);
   // 定义光栅化位置,从左下角开始
   glRasterPos2i(0, 0);
   // 4..对GL_PIXEL_UNPACK_BUFFER全局指针,绑定到pixelBuffer缓存区对象
   glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pixelBuffer);
   // 绘制图像数据,每个成分的类型是GL_UNSIGNED_BYTE,整个像素是GL_RGB有三个成分;数据块是checkImage
   // 5.正常绘图,对*data传递的是GL_PIXEL_UNPACK_BUFFER全局指针的开始地址
   glDrawPixels(checkImageWidth, checkImageHeight, GL_RGB, 
                GL_UNSIGNED_BYTE, BUFFER_OFFSET(0));
   glFlush();
}

void reshape(int w, int h)
{
   glViewport(0, 0, (GLsizei) w, (GLsizei) h);
   height = (GLint) h;
   // 正交投影,设置屏幕宽高
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluOrtho2D(0.0, (GLdouble) w, 0.0, (GLdouble) h);

   // 视图模型矩阵是单位矩阵
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
}

void motion(int x, int y)
{
   static GLint screeny;
   screeny = height - (GLint)y;
   printf("y is now %d\n", y);
   printf("screeny is now %d\n", screeny);
   // 改变光栅化位置,就是鼠标x值的位置
   // 因为窗口系统的位置是左上角,OGL屏幕坐标系是左下角,所以要减去screeny = height - (GLint)y;
   glRasterPos2i (x, screeny);
   // 改变图像或位图光栅化时候的缩放值
   glPixelZoom (zoomFactor, zoomFactor);
   // 复制左下角的数据块,到当前位置绘制像素数据块,相当于read 和 draw 图像或位图数据
   glCopyPixels (0, 0, checkImageWidth, checkImageHeight, GL_COLOR);
   // 恢复图像或位图光栅化时候的缩放值
   glPixelZoom (1.0, 1.0);
   glFlush ();
}

void keyboard(unsigned char key, int x, int y)
{
   switch (key) {
      case 'r':
      case 'R':
         zoomFactor = 1.0;
         glutPostRedisplay();
         printf ("zoomFactor reset to 1.0\n");
         break;
      case 'z':
         zoomFactor += 0.5;
         if (zoomFactor >= 3.0) 
            zoomFactor = 3.0;
         printf ("zoomFactor is now %4.1f\n", zoomFactor);
         break;
      case 'Z':
         zoomFactor -= 0.5;
         if (zoomFactor <= 0.5) 
            zoomFactor = 0.5;
         printf ("zoomFactor is now %4.1f\n", zoomFactor);
         break;
      case 27:
         exit(0);
         break;
      default:
         break;
   }
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
   glutInitWindowSize(250, 250);
   glutInitWindowPosition(100, 100);
   glutCreateWindow(argv[0]);
   init();
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyboard);
   // The motion callback for a window is called when the mouse moves within the window while one or more
   // mouse buttons are pressed
   // 按下鼠标并移动
   glutMotionFunc(motion);
   glutMainLoop();
   return 0; 
}

使用缓存区对象读取像素数据

可以将缓存区对象读取会CPU内存,进行更改操作。
对于glReadPixels和glGetTexImage这样的函数可以传入读取缓存区对象指针偏移读取数据到读取缓存区对象中。
和绘制相比,区别是使用GL_PIXEL_PACK_BUFFER做为读取的缓存区对象。
读取完到读取缓存区对象后就可以用glMapBuffer, glGetBufferSubData来访问修改读取缓存区对象内的数据值。
实例:
// 1.获取
   glGenBuffers(1, &pixelBuffer);
   // 2.绑定GL_PIXEL_PACK_BUFFER全局指针
   glBindBuffer(GL_PIXEL_PACK_BUFFER, pixelBuffer);
   // 3.对GL_PIXEL_PACK_BUFFER全局指针,拷贝数据
   glBufferData(GL_PIXEL_PACK_BUFFER, sizeof(GLfloat) * checkImageWidth*checkImageHeight,
  NULL,// 申请内存大小,但是不初始化数据块
 GL_STATIC_READ); // 为读取数据流

// 绘制数据,当前绘制的数据块,不是GL_PIXEL_PACK_BUFFER缓存区数据块
   GLsizei numPixels = checkImageWidth * checkImageHeight;
   glDrawPixels(checkImageWidth, checkImageHeight, GL_RGB,
  GL_UNSIGNED_BYTE, BUFFER_OFFSET(0));
   glReadPixels(0, 0, checkImageWidth, checkImageHeight, GL_RGB,
  GL_UNSIGNED_BYTE, BUFFER_OFFSET(0));
   GLfloat *pixels = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
   for (int i = 0; i < numPixels; i++)
   {
  /*CPU 内存中修改 pixels数据值*/
  pixels[i] = pixels[i] * 0.5f;// 亮度降低0.5
   }
   glUnmapBuffer(GL_PIXEL_PACK_BUFFER);

三、提高像素绘制速度技巧

1.函数调用和帧缓存区使用相同的图像格式,例如都是RGB.

glDrawPixels时候传递RGB,和UNSIGNED_BYTE
如果可能尽量使用较小的数据类型,例如GL_UNSIGNED_BYTE, GL_LUMINANCE_ALPHA格式。
如果考虑CPU内存,和解压图像格式的速度,那么IOS用pvr格式,android使用png格式,windows使用dds格式即可。

2.尽量不适用缩放和偏移

不要对图像进行缩放,偏移,可以有效提高性能。把所有像素数据传输操作函数设置为它的默认值。

3.合并小图为大图,减少传输和drawcall在图像处理上的消耗

减少像素传输,和draw call像素操作。

4.禁用一些纹理操作,减少有alpha透明度图片的应用,例如纹理贴图或混合。

5.使用纹理贴图存储像素数据,使用纹理对象,只需要一次向Opengl传输数据,后面直接应用即可。

6.使用缓存区对象,避免每帧都向GPU传输纹理数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值