irpas技术客

Android平台使用OpenGL实现图形渲染_lorienzhang_安卓opengl

irpas 3341

一、前言

OpenGL定义了一个跨编程语言、跨平台的专业图形程序接口。可用于二维或三维图像的处理与渲染,它是一个功能强大、调用方便的底层图形库。对于嵌入式设备,其提供了OpenGL ES(OpenGL for Embedded Systems)版本。

由于OpenGL是跨编程语言、跨平台的设计,所以在每个平台上都要有它的具体实现,负责提供OpenGL的上下文环境以及窗口的管理。在Android平台使用EGL提供本地平台对OpenGL ES的实现。

二、搭建OpenGL上下文环境

我们的目标是实现一个小Demo:在Android手机上利用OpenGL绘制一个三角形出来。

当然我们可以直接使用Android提供的GLSurfaceView,通过这种方式使用OpenGL比较简单,不需要我们再去搭建OpenGL上下文环境。本文我们不会去使用GLSurfaceView,而是直接借助EGL的API,在C++环境搭建出OpenGL上下文环境。

2.1 添加EGL库

首先,我们用Android Studio创建一个Native C++工程出来,编写CMakeList.txt文件,加入EGL库,文件内容如下:

cmake_minimum_required(VERSION 3.10.2) project("eglrender") add_library( # Sets the name of the library. egl-render # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). EglJni.cpp common/looper.cpp common/MyLooper.cpp render/GLRender.cpp render/Triangle.cpp gles/EglCore.cpp gles/GLUtils.cpp ) find_library(log-lib log) find_library(EGL-lib EGL) find_library(GLESv2-lib GLESv2) target_link_libraries( # Specifies the target library. egl-render android ${log-lib} ${EGL-lib} ${GLESv2-lib} )

关注target_link_libraries选项,在链接时,我们添加了android、EGL-lib、GLESv2-lib三个库。在搭建OpenGL上下文环境,以及执行OpenGL程序需要用到这三个库中的方法。

2.2 创建EGL上下文

首先,需要获取显示设备,通过eglGetDisplay方法获取

mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); if (mEGLDisplay == EGL_NO_DISPLAY) { return false; }

然后,调用eglInitialize()来初始化这个显示设备:

// 不关心版本号,直接传0即可 if (!eglInitialize(mEGLDisplay, 0, 0)) { return false; }

EGL有了Display之后,我们需要准备配置选项,比如:色彩格式、像素格式、SurfaceType等。最终通过eglChooseConfig得到配置选项信息。

const EGLint attribList[] = {EGL_BUFFER_SIZE, 32, EGL_ALPHA_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_RED_SIZE, 8, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_NONE }; // 获取config EGLConfig config; int numConfigs; if (!eglChooseConfig(mEGLDisplay, attribList, &config, 1, &numConfigs)) { return false; }

有了EGLDisplay和EGLConfig之后,接下来就可以创建上下文环境:EGLContext了,通过eglCreateContext完成。

int attrib2_list[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; EGLContext context = eglCreateContext(mEGLDisplay, config, sharedContext, attrib2_list); 2.2 创建显示EGLSurface

有了上下文EGLContext之后,接下来需要创建EGLSurface,这是用来指定OpenGL渲染输出的目的地。就Android平台来说,它代表SurfaceView的surface,surface对象将从java传到native层。

override fun surfaceCreated(holder: SurfaceHolder) { onSurfaceCreated(holder.surface) }

对应的JNI代码如下:

extern "C" JNIEXPORT void JNICALL Java_com_baidu_eglrender_MainActivity_onSurfaceCreated( JNIEnv *env, jobject instance, jobject surface) { if (mWindow) { ANativeWindow_release(mWindow); mWindow = NULL; } mWindow = ANativeWindow_fromSurface(env, surface); if (mLooper) { LOGD("post MsgSurfaceCreated") mLooper->post(MsgSurfaceCreated, mWindow); } }

我们通过ANativeWindow_fromSurface方法从surface中拿到ANativeWindow对象指针。然后再根据ANativeWindow指针创建出EGLSurface对象

EGLSurface EglCore::createWindowSurface(ANativeWindow *window) { if (window == NULL) { return NULL; } int surfaceAttribs[] = { EGL_NONE }; mEglSurface = eglCreateWindowSurface(mEGLDisplay, mEGLConfig, window, surfaceAttribs); if (mEglSurface == NULL) { return NULL; } return mEglSurface; }

需要明确一点,OpenGL的渲染操作需要在一个新的线程中执行,而且还必须为该线程绑定显示设备(surface)和上下文环境(Context),这样才可以执行OpenGL指令。

if (!eglMakeCurrent(mEGLDisplay, mEglSurface, mEglSurface, mEGLContext)) { LOGD("eglMakeCurrent error") }

我们还需要创建一个线程,用来执行OpenGL指令。我们计划在Native层实现一个Looper 线程,功能类似Android的异步消息机制。这个Looper线程的设计可参考官方native-codec项目,本文就不展开了。

三、执行OpenGL绘制

接下来我们可以执行OpenGL指令,绘制一个三角形出来。我们编写一个Triangle.cpp实现具体功能。

#include "Triangle.h" const GLint COORDS_PER_VERTEX = 3; const GLint vertexStride = COORDS_PER_VERTEX * 4; Triangle::Triangle() {} Triangle::~Triangle() {} int Triangle::init() { char vertexShader[] = "#version 300 es\n" "layout(location = 0) in vec4 a_position;\n" "layout(location = 1) in vec4 a_color;\n" "out vec4 v_color;" "void main()\n" "{\n" " gl_Position = a_position;\n" " v_color = a_color;\n" "}\n"; char fragmentShader[] = "#version 300 es\n" "precision mediump float;\n" "in vec4 v_color;\n" "out vec4 fragColor;\n" "void main()\n" "{\n" " fragColor = v_color;\n" "}\n"; // 根据着色器,创建GL程序 programHandle = createProgram(vertexShader, fragmentShader); if (programHandle <= 0) { LOGD("create program error, programHandle=%d\n", programHandle) return -1; } // 清屏 glClearColor(0.0f, 0.0f, 0.0f, 0.0f); return 0; } void Triangle::onDraw(int width, int height) { // 顶点坐标 GLfloat vertices[] = { 0.0f, 0.5f, 0.0f, -0.5f, -0.5f, 0.0f, 0.5f, -0.5f, 0.0f }; // 三角形颜色 GLfloat color[] = { 1.0f, 0.0f, 0.0f, 1.0f }; GLint vertexCount = sizeof(vertices) / (sizeof(vertices[0]) * COORDS_PER_VERTEX); glViewport(0, 0, width, height); glClear(GL_COLOR_BUFFER_BIT); // 使用Program glUseProgram(programHandle); // 获取GL程序顶点着色器的a_position变量 GLint positionHandle = glGetAttribLocation(programHandle, "a_position"); // 设置顶点位置信息 glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX, GL_FLOAT, GL_FALSE, vertexStride, vertices); glEnableVertexAttribArray(positionHandle); // 设置片远着色器的color变量 glVertexAttrib4fv(1, color); // 执行绘制 glDrawArrays(GL_TRIANGLES, 0, vertexCount); glDisableVertexAttribArray(positionHandle); } void Triangle::destroy() { if (programHandle > 0) { glDeleteProgram(programHandle); } programHandle = -1; }

看下init方法,首先创建顶点着色器vertexShader和片元着色器代码fragmentShader,然后根据这两个着色器调用createProgram创建出GL程序。该方法会返回一个programe句柄。接着在onDraw()方法中就是具体的绘制逻辑,最终通过glDrawArrays()方法完成OpenGL绘制。

离屏幕展示渲染结果还差最后一步:调用eglSwapBuffers()方法,完成前后台FrameBuffer交换。代码如下:

void GLRender::surfaceChanged(int width, int height) { // 执行OpenGL指令,绘制三角形 mTriangle->onDraw(width, height); // 交换前后台FrameBuffer mEglCore->swapBuffers(); } // 调用eglSwapBuffers交换前后台FrameBuffer bool EglCore::swapBuffers(EGLSurface eglSurface) { return eglSwapBuffers(mEGLDisplay, eglSurface); }

这里说明下EGL的双缓冲模式:EGL内部有两个FrameBuffer(帧缓冲区),当EGL将一个FrameBuffer显示到屏幕上的时候,另一个FrameBuffer就在后台等待OpenGL ES进行渲染输出了。直到调用eglSwapBuffers指令,把前台FrameBuffer和后台FrameBuffer进行交换,这样用户就可以在屏幕上看到刚才OpenGL ES渲染输出的结果了。

编译运行程序,就可以看到Demo成功绘制了一个红色的三角形,如下图所示:

源码地址:EGLRender


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #安卓opengl #ESOpenGL #for #Embedded #Systems版本