openGL ES 常用接口剖析

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)的时候:
C++代码
1. ```
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文件的结构与基本操作(逐像素印屏版)](http://www.zwqxin.com/archives/image-processing/bmp-operate-print-pixel.html)] ),总可以提取出一列整齐的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,才使用<span>GL_UNPACK_ALIGNMENT的版本。</span><span>但要说明的是,无论是哪种传输方式,它都是针对本地内存端(client端)上的像素数据的。</span><span>在上述例子中,它起着补充</span>glTexImage2D中关于传输起点——本地像素集合的<span>格式,的作用。</span>

一般来说,这些本地的数据集合,只要知道其起始位置、大小(width*height)和颜色格式(譬如GL_RGBA等等)、值格式(GL_UNSIGNED_CHAR、GL_FLOAT等等),就能准确地传输。而这些都是需要向glTexImage2D函数(或者上述的其他传输型函数)提供的。但是,这里头也一些细节,其实是需要glPixelStore这个函数来进行设置的。

*3.1<span><span>GL_UNPACK_ALIGNMENT /</span></span><span><span>GL_PACK_ALIGNMENT</span></span>*

通常,提取一张图像的时候,我们怎么知道一行的数据量呢?这个一行的数据量应该是: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文件的结构与基本操作(逐像素印屏版)](http://www.zwqxin.com/archives/image-processing/bmp-operate-print-pixel.html)] );二是选择绝对会造成4字节对齐的颜色格式或值格式(GL_RGBA啦,或者GL_INT、GL_FLOAT之类);三是以牺牲一些存取效率为代价,去更改OpenGL的字节对齐方式——这就是glPixelStore结合<span>GL_UNPACK_ALIGNMENT /</span><span>GL_PACK_ALIGNMENT。</span>
C++代码
1. ```
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的使用将帮助我们更好地完成这个目的:
C++代码
1. ```
//原图中需要单独提取出来制成纹理的区域
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);


<span style="font-size: 14px; line-height: 1.5">这段代码本身,即使没有注释也很清楚了。注意的是</span><span style="font-size: 14px; line-height: 1.5">GL_UNPACK_ROW_LENGTH的必要性,因为为了确认区域起点的Offset,就需要把</span><span style="font-size: 14px; line-height: 1.5">线性数据pixelData上标记起点的</span><span style="font-size: 14px; line-height: 1.5">“游标”从0移动到</span>**OffsetToData = subRect. origin.y * BaseWidth + subRect. origin.x**<span style="font-size: 14px; line-height: 1.5">的位置。有了区域纹理原点的在原图数据的位置,以及区域的尺寸,</span><span style="font-size: 14px; line-height: 1.5">glTexImage2D就可以确定区域纹理生成所需要的信息了。通过</span><span style="font-size: 14px; line-height: 1.5">glPixelStore的使用,避免了新建Buffer和自己处理图像数据的开销和麻烦了。</span>


<span>说到这里,到底为什么要这样做来提取区域纹理呢?尤其是原图若其他部分都是程序所需要的,那是不是就可以直接通过纹理坐标去切割更好呢?我想到的是一种情况(也可以说我是因为这种情况才注意到</span><span>glPixelStore的这种用法</span><span>):如果这块区域纹理需要作重复铺设(wrap mode选择GL_REPEAT)呢?这时候纹理坐标的方法就没用了,因为REPEAT所依据的也是纹理坐标(使用纹理坐标的小数部分进行采样)。这时候就需要上述做法了。(事实上3DSMAX等软件纹理导入的类似区域纹理平铺的功能就能如此实现。)</span>

4.<span style="font-size: 15px">**[glTexParameter](http://www.cnblogs.com/yujunyong/archive/2011/04/16/2018451.html)过滤函数**</span>

void glTexParameter*( GLenum target, GLenum pname, GLfloat param);
```
图象从纹理图象空间映射到帧缓冲图象空间(映射需要重新构造纹理图像,这样就会造成应用到多边形上的图像失真),

这时就可用glTexParmeteri()函数来确定如何把纹理象素映射成像素.

你这里的参数功能说明如下:param: GL_CLAMP: 将纹理坐标限制在0.0,1.0的范围之内.如果超出了会如何呢.不会错误,只是会边缘拉伸填充.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);GL_TEXTURE_WRAP_S/T: S/T方向上的贴图模式.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

GL_TEXTURE_MAG_FILTER: 放大过滤 GL_LINEAR: 线性过滤, 使用距离当前渲染像素中心最近的4个纹素加权平均值.

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);

GL_TEXTURE_MIN_FILTER: 缩小过滤GL_LINEAR_MIPMAP_NEAREST: 使用GL_NEAREST对最接近当前多边形的解析度的两个层级贴图进行采样,然后用这两个值进行线性插值.具体可看见如下ppt课件: http://wenku.baidu.com/view/d39ec52458fb770bf78a55d5.html


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
    原文链接: https://www.cnblogs.com/yaozhongxiao/archive/2013/05/16/3082106.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/88816

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月9日 下午11:48
下一篇 2023年2月9日 下午11:48

相关推荐