OpenGL基础——EGL、纹理
EGL
OpenGL是跨平台接口,面对不同平台的差异,需要有一个介于平-台设备(视窗系统)与OpenGL之间的桥梁帮助OpenGL摆平不同的差异,EGL则是其中的桥梁。EGL同样是一套标准接口,由各大平台设备厂商实现。
EGL提供以下机制
- 与设备的原生窗口系统通信
- 查询绘图表面的可用类型和配置
- 创建绘图表面
- 管理纹理贴图
EGL搭建
Android开发过程中常见的GLSurfaceView已经在底层帮我们构建好了EGL。如果涉及NDK开发,也可以在底层自己根据EGL标准接口进行环境构建。
- Display(EGLDisplay) 是对实际显示设备的抽象
- Surface(EGLSurface)是对用来存储图像的内存区域
- FrameBuffer 的抽象,包括 Color Buffer, Stencil Buffer ,Depth Buffer
- Context (EGLContext) 存储 OpenGL ES绘图的一些状态信息
搭建流程
EGL的环境搭建主要包含以下8个步骤,可以参考图示一起了解。
-
创建OpenGL ES与原生窗口系统的连接 :调用eglGetDisplay方法得到EGLDisplay
EGLDisplay eglGetDisplay(EGLNativeDisplayType displayId);
-
初始化EGL连接 :调用 eglInitialize 方法初始化
EGLBoolean eglInitialize(EGLDisplay display, // 要进行初始化的EGL连接,即上一部中的返回值 EGLint *majorVersion, // 主版本号 EGLint *minorVersion) // 次版本号
-
确定渲染表面的配置信息 :调用 eglChooseConfig 方法得到 EGLConfig
•方法一:查询所有可行的配置(eglGetConfigs),再让EGL从中找出最优选择(eglGetConfigAttrib)
•方法二:指定一组需求,然后再让EGL推荐(eglChooseChofig)最佳配置
-
创建渲染表面 :通过 EGLDisplay 和 EGLConfig ,调用 eglCreateWindowSurface 方法创建渲染表面,得到 EGLSurface
EGLSurface eglCreateWindowSurface( EGLDisplay display, //对应的EGL display连接 EGLConfig config, //EGL frame buffer配置,定义了可用于Surface的frame buffer资源 NativeWindowType native_window, //原生窗口 EGLint const * attrib_list); // attrib_list为Window Surface属性列表,可以为NULL,成功时返回新创建的
-
创建渲染上下文 :通过 EGLDisplay 和 EGLConfig ,调用 eglCreateContext 方法创建渲染上下文,得到 EGLContext
EGLAPI EGLContext EGLAPIENTRY eglCreateContext( EGLDisplay display, EGLConfig config, EGLContext share_context, const EGLint *attribList);
-
绑定上下文 :通过 eglMakeCurrent 方法将EGLSurface、EGLContext、EGLDisplay 三者绑定,接下来就可以使用 OpenGL 进行绘制了
EGLAPI EGLBoolean EGLAPIENTRY eglMakeCurrent( EGLDisplay display, EGLSurface draw, EGLSurface read, EGLContext context);
交换缓冲 :当用 OpenGL 绘制结束后,使用 eglSwapBuffers 方法交换前后缓冲,将绘制内容显示到屏幕上
释放 EGL 环境 :绘制结束,不再需要使用 EGL 时,取消 eglMakeCurrent 的绑定,销毁 EGLDisplay、EGLSurface、EGLContext。
纹理
什么是纹理
纹理是表示物体表面的一幅或几幅二维图形,也称纹理贴图(texture)。纹理在OpenGL中非常常见,除了顶点坐标的确定,纹理绘制、纹理操作的内容都十分庞大。
为什么使用纹理
假设我们只通过对顶点操作完成模型绘制,那么我们就需要绘制尽可能多的顶点来使模型立体逼真,顶点数量的增多无疑会使渲染管线的效率降低。因为只通过顶点操作,所以在给模型上色时则需要设定顶点的颜色。可想而知,单纯的颜色设置相较于纹理的大面积插值会丢失更多细节,模型表面无法像纹理一样丰富。而通过缓冲区渲染到纹理的方式也能极大程度的减少内存拷贝带来的性能耗时。
- 减少渲染管线负荷
- 模型表面更加丰富
- 方便高效
基本概念
-
纹理坐标
通常用UV表示纹理的横纵坐标,纹理坐标左下角为(0,0),右上角为(1,1)
-
纹素
纹理对象通常是通过纹理图片读取到的,这个数据保存到一个二维数组中,这个数组中的元素成为纹素(texel),纹素包含颜色值和alpha值。要想获取纹理对象中的纹素,需要使用纹理坐标(texture coordinate)指定。比如一个256*256大小的纹理对象,要取纹理坐标为(0.5,1)的纹素极为(128,256)。
[图片上传失败...(image-5862be-1719817222342)]
因为一个纹素的大小可能无法对应屏幕上的一个像素,或大于或小于,当一个纹素小于一个像素时称为缩小;一个纹素大于一个像素时称为放大。放大和缩小都则需要设置不同的插值过滤算法使纹理在屏幕上显示达到需要的效果。
纹理的放大和缩小滤波设置相关的控制选项,GL_LINEAR为线性滤波,GL_NEAREST为最近邻过滤滤波。
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
纹理绘制步骤
纹理绘制主要包含4个步骤
- 创建纹理对象
- 绑定纹理对象
- 纹理过滤
- 纹理图像加载
glGenTextures(1, &brushPreview->brushPreviewTextureId);
glBindTexture(GL_TEXTURE_2D, brushPreview->brushPreviewTextureId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, drawWidth, drawHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,NULL);
纹理使用
FBO渲染到纹理
通过帧缓冲渲染到纹理的方式,可以替代数据拷贝带来的耗时影响。FBO渲染到纹理的方式也能帮助实现下一帧需要上一帧渲染结果等业务需求。
//创建一个2D纹理
glGenTextures(1, &m_FboTextureId);
glBindTexture(GL_TEXTURE_2D, m_FboTextureId);
//创建帧缓冲区对象
glGenFramebuffers(1, &m_FboId);
glBindFramebuffer(GL_FRAMEBUFFER, m_FboId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
//使用纹理作为附件
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_FboTextureId, 0);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, drawWidth, drawHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,NULL);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
LOGD("FBO incomplete!!!!!!");
}
glClear(GL_COLOR_BUFFER_BIT);
glBindTexture(GL_TEXTURE_2D, GL_NONE);
glBindFramebuffer(GL_FRAMEBUFFER, GL_NONE);
将纹理绑定到FBO后,普通渲染只要使用上述绑定的纹理即可显示离屏渲染的绘制结果。
// 离屏渲染
glPixelStorei(GL_UNPACK_ALIGNMENT,1);
glViewport(0, 0, m_RenderImage.width, m_RenderImage.height);
glBindFramebuffer(GL_FRAMEBUFFER, m_FboId);
glUseProgram(m_FboProgramObj);
glBindVertexArray(m_VaoIds[1]);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_ImageTextureId);
glUniform1i(m_FboSamplerLoc, 0);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, (const void *)0);
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
// 普通渲染
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0, 0, screenW, screenH);
glUseProgram(m_ProgramObj);
glBindVertexArray(m_VaoIds[0]);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_FboTextureId);
glUniform1i(m_SamplerLoc, 0);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, (const void *)0);
glBindTexture(GL_TEXTURE_2D, GL_NONE);
glBindVertexArray(GL_NONE);
读取颜色缓冲区
void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels)
纹理映射
纹理映射可以简单理解为通过不同的矩阵变换转换纹理的坐标和像素,从而展现不同的纹理结果。纹理映射主要体现在平移、旋转、缩放等。
理映射部分使用场景
- 大尺寸纹理需要裁剪出其中的一部分作为纹理进行渲染
- 纹理叠加时颜色混合处理
- 纹理尺寸与窗口尺寸不匹配时的简单映射
- 纹理像素过大但需要在不同小尺寸下进行渲染
总结
本部分内容的介绍能够帮助我们了解EGL和纹理基本内容。EGL环境的搭建在实际开发过程中使用较少,基本一劳永逸,但纹理的使用则是OpenGL开发过程中随处可见、使用量巨大。纹理的操作关系渲染管线的最终呈现,对纹理的相关操作可以在片段着色器中实现更多不同有趣的效果。