OpenGL glPixelStore glReadPixels 详解
glPixelStore
像glPixelStoreireiglPixel(GL_PACK_ALIGNMENT, 这种调用通常用于像素传输。(PACK/UNPACK)尤其是导入线路(glTexImage2D)的时候:
C 代码
1. glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
2.
3. GlTexImage222D(,,,, &pixelData);
4.
5. glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
显然,这是在改变某一状态量,然后Restore回来。-为什麽是状态?你们知道OpenGL是以状态机吗?-什么状态?事实上,这个名字已经很直白了,glPixelStore这组函数需要改变像素的存储格式。
存储格式的概念。什么格式的本地内存中端像素集合?在GPU传输过程中又是什么格式?格式是否相同?在glTexImage2D这个函数中,有两个关于颜色格式的参数,一个是纹路(GPU端,或server端),另一个是像素数据(程序内存,即client端),它们不一定相同,即使相同,也不能代表GPU会像内存一样存储。或者想象一下,从硬盘上的图片提取到内存的像素数据,上传到GPU,变成一条线。这条“线”还会是RGBARGBA原来的序列吗?显然不是。有其纹路ID作为一种纹路,、WRAP模式,插值模式,指定maipmap时,每个Level下都会有一串map,等等。就纹理数据而言,实质纹理是边长应满足2的n次方。(power of two)数据集合,这样首先大小可能不同,另外排列方式也不一定是RGBA的方式。在OpenGL的“解释”中,线条是一个复杂的数据集,可以取样。无论外部世界如何变化,GPU只认识到线条作为自己的“图像数据结构”,这显示了世界纽带“标准化”的伟大之处。
一堆X。不要仔细研究一堆X是什么样子的。嗯,反正想象成一堆软软的东西,或者模糊的东西,里面装满了马赛克,(哔-)。相比之下,内存中的像素数据实在是太规范了!各种图片格式的源文件,什么bmp?、jpg、png甚至dds,但是只要你按照这个格式算法结构提取(类似于[Bmp文件的结构和基本操作] ),总能提取出一列整齐的RGBARGBA(或RGBRGB等)数据,总之很整齐就可以管他了),这是一个可以在系统中测量的东西。
传输方向的概念。那就是PACK和UNPACK,大家都很熟悉。嗯,装载和卸载都可以,包装和解压也可以,可以随你翻译。结合上述存储格式的概念:
- PACK —— 把像素从一堆X的状态变成一个规则的状态(把一堆土装在花盆里,把散落的货物装在集装箱里,或者把一堆各种文件打包成说唱压缩包等。);
- UNPACK —— 将像素从规则状态转化为一堆X状态(倒出花盆里的泥,将集装箱里的货物卸载到盐田港,或解压缩袋等)。).
我觉得这两个概念还是很容易混淆的,所以形象化一点总比较好。从本地内存传输到GPU(UNPACK),包括各种glTexImage、glDrawPixel;从GPU到本地内存的传输(PACK),它包含glGetTexImaget、GlReadPixel等。同样因为这个原因,PBO也有PACK和UNPACK的区别。
GL_UNPACK_ALIGNMENT版本。但是需要注意的是,不管是什么样的传输技术,它都是针对当地内存端(client端)上的像素数据。在上面的例子中,它起到了补充GlTexImage2D中相关传输起点-当地像素集合格式的作用。
一般而言,这些本地数据集,只要知道它们的起始位置,大小,(width*height)颜色格式(如GL_RGBA等)、值格式(GL_UNSIGNED_CHAR、GL_FLOAT等),可以准确地传输。而且这些都需要提供glTexImage2D函数(或者上面提到的其它传送函数)。但是,这里也有一些细节,实际上需要glPixelStore这个函数来设置。
3.1GL_UNPACK_ALIGNMENT /GL_PACK_ALIGNMENT
一般来说,在提取图像时,我们如何知道一行的信息量?这一行的信息量应该是:width * sizeof(Pixel) ,对付最普通的RGBA、每个通道都有一个字节像素结构,width * sizeof(Pixel) = width * 4 * sizeof(byte),是4的整数倍。但有时,我们的像素数据中一行的信息量不一定是4的整数倍(例如,RGB的图像,总宽度为1500。、每个通道都有一个字节的像素结构,一行的信息量为450个字节)。
另外,和编译器一样,GPU在传输过程中也喜欢4字节对齐,也就是说,喜欢按4字节存储像素数据。所以它更倾向于喜欢每一行的信息量是4的整数倍(按上面说,这正好是比较常见的)。因此,为了提高存储效率,OpenGL默认允许像素数据通过4字节4字节传输到GPU-但问题是,对于行非4字节对齐的像素数据,第一行最后一行存储的4字节将包含第一行的一些数据部分包含第二行的数据。当然,致命的不是这里,而是最后一行:存储很可能会越界。为了避免这种情况,首先,像素数据被硬性延伸到4字节对齐(就像BMP文件的存储方式一样,[Bmp文件的结构和基本操作] );第二,选择颜色格式或值格式肯定会导致4字节对齐。(GL或者GL__RGBA,INT、GL_FLOAT等等);第三,以牺牲一些存储效率为代价,改变OpenGL的字节对齐方式-这就是GlPixelStore与GL_UNPACK_ALIGNMENT /GL_PACK_ALIGNMENT。
C 代码
1. glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
2.
3. GlTexImage222D(,,,, &pixelData);
4.
5. 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
像glTexImage2D这样的函数。glPixelStore在上述参数下的应用将有助于我们更好地实现这一目标:
1. ///需要在原图中独立提取纹理的区域
2. RECT subRect = {{100, 80}, {500, 400}}; //origin.x, origin.y, size.width, size.height
3.
4. //假设原图的宽度是BaseWidth, BaseHeight高度
5.
6. glPixelStorei(GL_UNPACK_ROW_LENGTH, BaseWidth); //指定像素数据中原图的宽度
7. glPixelStorei(GL_UNPACK_SKIP_ROWS, subRect. origin.y.); ///指定纹路起点偏移起点高度值
8. glPixelStorei(GL_UNPACK_SKIP_PIXELS, subRect. origin.x); //指定纹路起点偏移起点的宽度值
9.
10. GlTexImage222D(..., subRect.size.width, ubRect.size.height,.. &pixelData); 使用区域的宽度和高度
11.
12. glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
13. glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
14. glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
GL_UNPACK_ROW_LENGTH的重要性,因为为了确定区域起点的Offset,需要将标有起点的“游标”从0移动到OffsetToDatatata上的线性数据pixelData =subRect. origin.y * BaseWidth subRect. origin.x的位置。通过GlTexImage2D,可以确定区域纹路生成所需的信息,具有区域纹路起点在原图数据的位置和区域的大小。通过glPixelStore的应用,可以避免新建Buffer和自行处理图像数据的费用和麻烦。
谈到这里,到底为什么要这样做来提取区域纹路呢?尤其是如果原图的其余部分都是程序所需要的,那么是否可以通过纹理坐标直接切割更好?我想到的是一种情况(也可以说,正是因为这种情况,我才注意到glPixelStore的用法):若该区域纹路需要重复铺装(wrap 选择GL_modeREPEAT)呢?在这个时候,纹理坐标的方法是没有用的,因为REPEAT也是基于纹理坐标(用纹理坐标的小部分取样)。这个时候需要以上的做法。(事实上,3DSMAX等软件线路导入的类似区域线路铺设的功能就可以实现这一点。)
4.glScissor
我认为这个函数也应该非常普遍。裁剪测试啊,当初跟Alpha测试,Depth测试,Stencil测试可以并排哦,现在更是不失时尚啊。因为我很难想象在Shader中可以很容易地实现它的功能:裁剪。当然,这只是一个矩形切割,但对于discard渲染中不需要的像素来说,这是相当简单和直接的。我用的最多的是一些二维图片缩略图栏——有时候我们只需要把这些缩略图的显示控制在一个区域,但是我们必须支持滚动。
C 代码 (OpenGL)
1. glEnable(GL_SCISSOR_TEST);
2. glScissor(GLint(m_rtThumbRegion.x), GLint(m_rtThumbRegion.y), GLint(m_rtThumbRegion.width), GLint(m_rtThumbRegion.height));
3.
4. //... Render
5.
6. glDisable(GL_SCISSOR_TEST);
GL_SCISSOR除了TEST之外,只需指出glScissor需要保留显示区域即可。在这个区域之外,像素仍然会被渲染(它不会节省流水线操作,所以不要指望它增加任何功能来提高效率)。在下图中,其实其他照片会继续在左右两侧渲染(或者说,其实这个缩略图栏横跨整个屏幕),但它就在fragment上。 在shader之后,它们将被发现不在这个区域,而是被discard掉了。
本文仅代表作者观点,版权归原创者所有,如需转载请在文中注明来源及作者名字。
免责声明:本文系转载编辑文章,仅作分享之用。如分享内容、图片侵犯到您的版权或非授权发布,请及时与我们联系进行审核处理或删除,您可以发送材料至邮箱:service@tojoy.com




