OpenGL ES是OpenGL的子集,针对嵌入式系统和移动设备所设计.
OpenGL ES 管道(Pipeline)
- 管道“工序”大致可以分为 Transformation Stage 和 Rasterization Stage 两大步。
- OpenGL ES 支持的基本图形为 点 Point, 线 Line, 和三角形 Triangle ,其它所有复制图形都是通过这几种基本几何图形组合而成。
- 在发出绘图指令后,会对顶点(Vertices)数组进行指定的坐标变换或光照处理。
- 顶点处理完成后,通过 Rasterizer 来生成像素信息,称为”Fragments“ 。
- 对于 Fragment 在经过 Texture Processing, Color Sum ,Fog 等处理并将最终处理结果存放在内存中(称为FrameBuffer)。
OpenGL 2.0可以通过编程来修改蓝色的步骤,称为 Programmable Shader.
GLSurfaceView:
setDebugFlags(int) 设置 Debug 标志。
setEGLConfigChooser (boolean) 选择一个 Config 接近 16bitRGB 颜色模式,可以打开或关闭深度(Depth)Buffer ,缺省为RGB_565 并打开至少有 16bit 的 depth Buffer。
setEGLConfigChooser(EGLConfigChooser) 选择自定义 EGLConfigChooser。
setEGLConfigChooser(int, int, int, int, int, int) 指定 red ,green, blue, alpha, depth ,stencil 支持的位数,缺省为 RGB_565 ,16 bit depth buffer。
GLSurfaceView 缺省创建为 RGB_565 颜色格式的 Surface,如果需要支持透明度,可以调用 getHolder().setFormat(PixelFormat.TRANSLUCENT)。
GLSurfaceView 的渲染模式有两种,一种是连续不断的更新屏幕,另一种为 on-demand ,只有在调用 requestRender() 在更新屏幕。 缺省为 RENDERMODE_CONTINUOUSLY 持续刷新屏幕。
GLSurfaceView.Renderer回调方法:
onSurfaceCreated:在这个方法中主要用来设置一些绘制时不常变化的参数,比如:背景色,是否打开 z-buffer等。
onDrawFrame:定义实际的绘图操作。
onSurfaceChanged:如果设备支持屏幕横向和纵向切换,这个方法将发生在横向和纵向互换时。此时可以重新设置绘制的纵横比率。
OpenGL 坐标系
三维坐标系,原点在中间,x 轴向右,y 轴向上,z 轴朝向我们,x y z 取值范围都是 [-1, 1]
OpenGL ES支持的图形有 点,线,三角形
先定义点集(vertexArray)(三个坐标一组,一维数组)
indices 为绘制顺序
eg:
glEnableClientState 和 glDisableClientState 可以控制的 pipeline 开关可以有:GL_COLOR_ARRAY (颜色),GL_NORMAL_ARRAY (法线),
GL_TEXTURE_COORD_ARRAY (材质),GL_VERTEX_ARRAY(顶点), GL_POINT_SIZE_ARRAY_OES等。
在打开顶点开关后,将顶点坐标传给 OpenGL 管道的方法为:glVertexPointer:
对应的传入颜色,顶点,材质,法线的方法如下:
矩阵操作,单位矩阵
在进行平移,旋转,缩放变换时,所有的变换都是针对当前的矩阵(与当前矩阵相乘),如果需要将当前矩阵回复最初的无变换的矩阵,可以使用单位矩阵(无平移,缩放,旋转)。
public abstract void glLoadIdentity()。
在栈中保存当前矩阵和从栈中恢复所存矩阵,可以使用
public abstract void glPushMatrix()
和
public abstract void glPopMatrix()。
在进行坐标变换的一个好习惯是在变换前使用glPushMatrix保存当前矩阵,完成坐标变换操作后,再调用glPopMatrix恢复原先的矩阵设置。
glLoadIdentity()将栈顶矩阵恢复为单位矩阵
glPushMatrix()入栈一个栈顶矩阵的copy
glPopMatrix()栈顶矩阵出栈,之前栈顶第二个矩阵变成当前矩阵
我们一直使用的都是栈顶矩阵,栈内至少有一个矩阵
这种入栈出栈的操作是为了保存矩阵的状态,消除当前矩阵的变换对下一组图形变化的影响(理解矩阵变化对空间的影响).
Texture纹理
创建Bitmap对象:
Bitmap bitmap = BitmapFactory.decodeResource(contect.getResources(),R.drawable.icon);创建材质(Generating a texture):
使用OpenGL库创建一个材质(Texture),首先是获取一个Texture Id。
// Create an int array with the number of textures we want,
// in this case 1.
int[] textures = new int[1];
// Tell OpenGL to generate textures.
gl.glGenTextures(1, textures, 0);
有了Texture Id之后,就可以通知OpenGL库使用这个Texture:
gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);设置Texture参数glTexParameter:
用来渲染的Texture可能比要渲染的区域大或者小,这是需要设置Texture需要放大或是缩小时OpenGL的模式:
// Scale up if the texture if smaller.
gl.glTexParameterf(GL10.GL_TEXTURE_2D,GL10.GL_TEXTURE_MAG_FILTER,GL10.GL_LINEAR);
// scale linearly when image smalled than texture
gl.glTexParameterf(GL10.GL_TEXTURE_2D,GL10.GL_TEXTURE_MIN_FILTER,GL10.GL_LINEAR);UV Mapping
纹理坐标系为左上(0,0),右下(1,1)
默认纹理图片的宽高都是1
textureCoordinates[]用来记录纹理坐标点的映射顺序(注意这里的坐标点可以大于或者小于1,起到缩放和放大的效果),用来和vertex[]进行映射,这样才能知道如何将纹理贴到平面上去
float textureCoordinates[] = {0.0f, 2.0f,2.0f, 2.0f,0.0f, 0.0f,2.0f, 0.0f };
GL_REPEAT 重复Texture。
GL_CLAMP_TO_EDGE 只靠边线绘制一次。
对应代码:
gl.glTexParameterf(GL10.GL_TEXTURE_2D,GL10.GL_TEXTURE_WRAP_S,GL10.GL_REPEAT);
gl.glTexParameterf(GL10.GL_TEXTURE_2D,GL10.GL_TEXTURE_WRAP_T,GL10.GL_REPEAT);
然后是将Bitmap资源和Texture绑定起来:
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
使用Texture
为了能够使用上面定义的Texture,需要创建一Buffer来存储UV坐标:
FloatBuffer byteBuf = ByteBuffer.allocateDirect(textureCoordinates.length * 4);
byteBuf.order(ByteOrder.nativeOrder());
textureBuffer = byteBuf.asFloatBuffer();
textureBuffer.put(textureCoordinates);
textureBuffer.position(0);渲染
// Telling OpenGL to enable textures.
gl.glEnable(GL10.GL_TEXTURE_2D);
// Tell OpenGL where our texture is located.
gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
// Tell OpenGL to enable the use of UV coordinates.
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
// Telling OpenGL where our UV coordinates are.
gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, textureBuffer);
// … here goes the rendering of the mesh …
// Disable the use of UV coordinates.
gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
// Disable the use of textures.
gl.glDisable(GL10.GL_TEXTURE_2D);
多个矩阵变换,实际顺序和写的顺序其实是相反的
gluLookAt决定了我们观测的位置,不同的位置观测到的效果不一样,所以通常每次画图的时候要向z轴负方向移动一个单位
OpenGL坐标体系:局部空间,世界空间,观察空间,裁剪空间
http://blog.csdn.net/killallpigs/article/details/53941138
EGL:
EGL是OpenGL ES与底层窗口系统进行通信的桥梁.
- EGL初始化流程
- a, 选择Display
- b, 选择Config
- c, 创建Surface
- d, 创建Context
- e, 指定当前的环境为绘制环境
在c这一步:
要在某个窗口中显示, 则用eglCreateWindowSurface() 来创建,只有这种是在物理显示设备上显示的。
要在内存中以位图保存,则用eglCreatePixmapSurface()来创建,该类型只用于离屏渲染.
要保存在显存中的帧,则用eglCreatePbufferSurface()来创建。
使用EGL的绘图的一般步骤:
初始化阶段:
- 获取 EGLDisplay 对象
- 初始化与 EGLDisplay 之间的连接
- 获取 EGLConfig 对象
- 创建 EGLContext 实例
- 创建 EGLSurface 实例
- 连接 EGLContext 和 EGLSurface
使用阶段: - 使用 GL 指令绘制图形
销毁阶段: - 断开并释放与 EGLSurface 关联的 EGLContext 对象
- 删除 EGLSurface 对象
- 删除 EGLContext 对象
- 终止与 EGLDisplay 之间的连接
一般来说在 Android 平台上开发 OpenGL ES 应用,无需直接使用javax.microedition.khronos.egl 包中的类按照上述步骤来使用 OpenGL ES 绘制图形,
在Android 平台中提供了一个 android.opengl 包,类 GLSurfaceView 提供了对Display,Surface,Context 的管理,大大简化了 OpenGL ES 的程序框架,对应大部分 OpenGL ES 开发,
只需调用一个方法来设置 OpenGLView 用到的 GLSurfaceView.Renderer。
所以,想要抛开GLSurfaceView来进行渲染,就要使用EGL.
On-Screen Rendering
即当前屏幕渲染,在用于显示的屏幕缓冲区中进行,不需要额外创建新的缓存,也不需要开启新的上下文,所以性能较好,但是受到缓存大小限制等因素,一些复杂的操作无法完成。
Off-Screen Rendering
即离屏渲染,指的是在 GPU 的当前屏幕缓冲区外开辟新的缓冲区进行操作。
FrameBufferObject
帧缓存,一般使用OpenGL ES是直接使用GLSurfaceView和GLSurfaceView.Renderer提供的环境进行的,然后渲染也是直接显示在屏幕上,有时候我们并不想直接渲染到屏幕.这时候使用帧缓存可以实现离屏渲染.
帧缓存本身相当于一个容器,RenderBuffer和Texture可以挂载在帧缓存之上,一旦与帧缓存绑定,这种关系一直会存在,除非解绑.
我们可以通过帧缓存进行渲染到纹理操作,然后用渲染后的纹理进行下一个帧缓存渲染的输入,然后可得到输出纹理,如此循环…
最后我们可以拿到最后一次的输出进行渲染到屏幕等操作.
使用纹理:
eg:
android camera都是横着取景的,必须经过矩阵变换才正常.
opengl规定纹理坐标为左下是原点,因为平台特性的原因,在Android上可以认为左上是原点,否则绘制出来的图片就是颠倒的,不过这样也是有弊端的,除了第一次外
(平台的最终image库绘制图片的时候,将图片上下颠倒了(reverse the Y)。………image库………Bingo, 明白了。)
(其实可以这样理解,因为Android屏幕Y轴是向下的,所以图片按照纹理坐标的定义读入进来的话刚好是上下相反的,但当把图片转化为纹理之后,之后的渲染到纹理操作都是在OpenGL的纹理坐标下进行的,
这个时候若是传入相反的纹理坐标时,势必会导致纹理再次上下翻转,所以调整好纹理方向之后,之后的每次操作要么传入正确的坐标,要么传入上下颠倒矩阵,这样才能保证最终纹理方向的正确)
,后续每次转换都要传入颠倒矩阵,否则的话每次操作都会上下翻转图片.
利用PBO可以大幅度提升GLReadPixel的速度(提升十倍,用两个PBO交替读取),不过3.0之前不能使用.
glScissor裁剪操作
glScissor裁剪操作,接下来的渲染操作只在裁剪区域内有效.
使用正交投影可以裁剪画面
OpenGL三维纹理坐标 s,t,r,q
q是一个缩放因子,相当于顶点坐标中的w。实际应用在纹理读取中的坐标应该是s/q,t/q,r/q。默认情况下,q是1.0。通常情况下貌似没什么用,但是在一些产生纹理坐标的高级算法比如阴影贴图中,比较有用。
s、t、r分别相当于普通坐标系中的x、y、z三个方向。分别对应glTexImage3D中的参数width、height、depth。
图像抗锯齿
超级采样抗锯齿(SSAA)
图像放大之后取临近的2-4个像素点,采样混合后生成最终像素,令每个像素拥有邻近像素的特征,像素与像素之间的过渡色彩,就变得近似,
令图形的边缘色彩过渡趋于平滑。再把最终像素还原回原来大小的图像,并保存到帧缓存也就是显存中,替代原图像存储起来,最后输出到显示器,显示出一帧画面。
这样就等于把一幅模糊的大图,通过细腻化后再缩小成清晰的小图
(1.顺序栅格超级采样(Ordered Grid Super-Sampling,简称OGSS),采样时选取2个邻近像素。2.旋转栅格超级采样(Rotated Grid Super-Sampling,简称RGSS),采样时选取4个邻近像素。)
多重采样抗锯齿(MSAA)
多重采样抗锯齿(MultiSampling Anti-Aliasing,简称MSAA)是一种特殊的超级采样抗锯齿(SSAA)。MSAA首先来自于OpenGL。
具体是MSAA只对Z缓存(Z-Buffer)和模板缓存(Stencil Buffer)中的数据进行超级采样抗锯齿的处理。可以简单理解为只对多边形的边缘进行抗锯齿处理。
这样的话,相比SSAA对画面中所有数据进行处理,MSAA对资源的消耗需求大大减弱,不过在画质上可能稍有不如SSAA。
剩下的抗锯齿方式在这里看:https://baike.baidu.com/item/%E6%8A%97%E9%94%AF%E9%BD%BF/7556069?fr=aladdin
Android设备抗锯齿
上面的在Android平台都不靠谱,这才是抗锯齿的最佳方式:
OpenGL——纹理过滤函数glTexParameteri()
图象从纹理图象空间映射到帧缓冲图象空间(映射需要重新构造纹理图像,这样就会造成应用到多边形上的图像失真),这时就可用glTexParmeteri()函数来确定如何把纹理象素映射成像素.
glTexParameteri(int target, int pname, int param)
target —— 目标纹理,必须为GL_TEXTURE_1D或GL_TEXTURE_2D,视频的话对应于GLES11Ext.GL_TEXTURE_EXTERNAL_OES;
pname —— 用来设置纹理映射过程中像素映射的问题等,取值可以为:
- GL_TEXTURE_MIN_FILTER 设置最小过滤,第三个参数决定用什么过滤;
- GL_TEXTURE_MAG_FILTER设置最大过滤,也是第三个参数决定;
- GL_TEXTURE_WRAP_S;纹理坐标一般用str表示,分别对应xyz,2d纹理用st表示
- GL_TEXTURE_WRAP_T 接上面,纹理和你画的几何体可能不是完全一样大的,在边界的时候如何处理呢?就是这两个参数决定的,wrap表示环绕,可以理解成让纹理重复使用,直到全部填充完成;
- param —— 实际上就是pname的值
- GL_LINEAR: 线性过滤, 使用距离当前渲染像素中心最近的4个纹素加权平均值.
- GL_LINEAR_MIPMAP_NEAREST: 使用GL_NEAREST对最接近当前多边形的解析度的两个层级贴图进行采样,然后用这两个值进行线性插值.