Android平台使用OpenGL实现图形渲染

一、前言

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层。

扫描二维码关注公众号,回复: 14867730 查看本文章
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

猜你喜欢

转载自blog.csdn.net/H_Zhang/article/details/123989671