Android Studio 中使用 OpenGL (Java 和 NDK 均可)

软件版本

SDK、NDK 和 CMake 都是使用 Android Studio 的 SDK Manager 下载的。

  • 操作系统:macOS Big Sur Beta
  • Android Studio: 4.0.1
  • OpenGL:GLES 2.0
  • SDK:30.0.1
  • NDK:21.0.6113669
  • CMake:3.10.2.4988404
  • Gradle:6.1.1(Android Gradle Plugin Version 是
    4.0.1)

准备工作

使用 Android Studio 创建Native C++项目。具体可参考 Android Studio 中集成 OpenCV (Java 和 NDK 均可,不使用Manager) 中的 创建 Native C++ 项目
这里创建的项目名称为OpenCVNDK(是的,不是OpenGL)。在根据下面的NDK(C++)接口配置和Java接口配置后,项目的整体文件目录如下所示。这里将使用NDK(C++)版本画一个长方形,用Java版本画一个三角形,并可在同一个画面显示。
Android Studio 中使用 OpenGL (Java 和 NDK 均可)_第1张图片

NDK (C++) 接口配置

这里将创建一个基于C++版本的渲染器(cpp_renderer.h和cpp_renderer.cpp),以及JNI调用接口(cpp_renderer_interface.cpp)。这里将用其画一个长方形。

CMakeList.txt 文件设置

在 CMakeList.txt 中设置OpenGL库文件(NDK中自带),以及将C++项目编译成动态库(cpp_renderer.so),如下所示:

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

# ====================================================================================
set(CMAKE_VERBOSE_MAKEFILE on)
include_directories(include)

# =====  OPENGL  =====
set(OPENGL_LIB GLESv2)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11 -fexceptions -frtti")
# ====================================================================================

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
        cpp_renderer

        # Sets the library as a shared library.
        SHARED

        # Provides a relative path to your source file(s).
        cpp_renderer_interface.cpp
        cpp_renderer.cpp)

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
              log-lib

              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
        cpp_renderer
        # Links the target library to the log library
        # included in the NDK.
        # ===========
        ${OPENGL_LIB}
        # ===========
        ${log-lib} )

cpp_renderer渲染器代码

首先是cpp_renderer.h文件

#ifndef OPENCVNDK_CPP_RENDERER_H
#define OPENCVNDK_CPP_RENDERER_H

#include 

class cpp_renderer {
public:
    // Initialize shaders and GL buffers
    void init();

    // Render the OpenGL buffers using the shader
    void draw();

private:
    GLuint mVertexBuffer;
    GLuint mIndexBuffer;

    GLuint mProgram;
    GLint mVertexAttribPos;

    unsigned int mElementCount = 0;
};

#endif //OPENCVNDK_CPP_RENDERER_H

其次是cpp_renderer.cpp

#include "cpp_renderer.h"
#include 
#include 
#include 

// datas to be drawn
// no need to import whole glm for simple example
struct Vertex {
    float x, y, z;
};

const Vertex QUAD[4] = {
        {-0.0f, -0.0f,0.0f},
        {0.7f, -0.0f, 0.0f},
        {-0.0f, 0.7f, 0.0f},
        {0.7f, 0.7f, 0.0f}
};

const unsigned int ORDER[6] = {0, 1, 2, 3, 2, 1};

// for shader loading
#define LOG_TAG "GLES C++"
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)

bool checkGlError(const char *funcName) {
    GLint err = glGetError();
    if(err != GL_NO_ERROR) {
        ALOGE("GL error after %s(): 0x%08x\n", funcName, err);
        return true;
    }
    return false;
}

GLuint  createShader(GLenum shaderType, const char* src) {
    GLuint shader = glCreateShader(shaderType);
    if(!shader) {
        checkGlError("glCreateShader");
        return 0;
    }
    glShaderSource(shader, 1, &src, NULL);

    GLint compiled = GL_FALSE;
    glCompileShader(shader);
    glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
    if(!compiled) {
        GLint infoLogLen = 0;
        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLen);
        if(infoLogLen > 0) {
            GLchar* infoLog = (GLchar*)malloc(infoLogLen);
            if(infoLog) {
                glGetShaderInfoLog(shader, infoLogLen, NULL, infoLog);
                ALOGE("Could not compile %s shader:\n%s\n",
                        shaderType == GL_VERTEX_SHADER ? "vertex" : "fragment",
                        infoLog);
                free(infoLog);
            }
        }
        glDeleteShader(shader);
        return 0;
    }

    return shader;
}

GLuint createProgram(const char* vtxSrc, const char* fragSrc) {
    GLuint vtxShader = 0;
    GLuint fragShader = 0;
    GLuint program = 0;
    GLint linked = GL_FALSE;

    vtxShader = createShader(GL_VERTEX_SHADER, vtxSrc);
    if(!vtxShader)
        goto exit;

    fragShader = createShader(GL_FRAGMENT_SHADER, fragSrc);
    if(!fragShader)
        goto exit;

    program = glCreateProgram();
    if(!program) {
        checkGlError("glCreateProgram");
        goto exit;
    }
    glAttachShader(program, vtxShader);
    glAttachShader(program, fragShader);

    glLinkProgram(program);
    glGetProgramiv(program, GL_LINK_STATUS, &linked);
    if(!linked) {
        ALOGE("Could not link program");
        GLint infoLogLen = 0;
        glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLen);
        if(infoLogLen) {
            GLchar* infoLog = (GLchar*)malloc(infoLogLen);
            if(infoLog) {
                glGetProgramInfoLog(program, infoLogLen, NULL, infoLog);
                ALOGE("Could not link program:\n%s\n", infoLog);
                free(infoLog);
            }
        }
        glDeleteProgram(program);
        program = 0;
    }

    exit:
    glDeleteShader(vtxShader);
    glDeleteShader(fragShader);
    return program;
}

// renderer
void cpp_renderer::draw() {
    glUseProgram(mProgram);

    glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer);
    glEnableVertexAttribArray(mVertexAttribPos);
    glVertexAttribPointer(mVertexAttribPos, 3, GL_FLOAT, GL_FALSE, 0, (const GLvoid*)0);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (void*)0);

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}

void cpp_renderer::init() {
    glGenBuffers(1, &mVertexBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer);
    std::vector<Vertex> testVertices(QUAD, QUAD + 4);
    glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * testVertices.size(), &testVertices[0], GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    glGenBuffers(1, &mIndexBuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer);

    std::vector<unsigned int> testOrder(ORDER, ORDER + 6);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned)*testOrder.size(), &testOrder[0], GL_STATIC_DRAW);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

    static const char VERTEX_SHADER[] =
            "precision highp float;\n"
            "attribute vec4 inPosition;\n"
            "void main(){\n"
            "    gl_Position = inPosition;\n"
            "}\n";
    static const char FRAGMENT_SHADER[] =
            "precision highp float;\n"
            "void main() {\n"
            "    gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
            "}\n";

    mProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
    mVertexAttribPos = glGetAttribLocation(mProgram, "inPosition");
}

最后是JNI接口文件 cpp_renderer_interface.cpp。需要注意的是函数名称:

Java_com_casmc_opencvndk_MyCppRenderer__1init

这里Java_com_casmc_opencvndk为项目包的名称,MyCppRenderer 为调用该接口的Java文件名称,_1init 为调用的函数名称,这里多一个“_1”表示下划线。

//
// Created by 朱晓阳 on 7/19/20.
//

#include 
#include 
#include "cpp_renderer.h"

std::unique_ptr<cpp_renderer> mRenderer;

extern "C" {
    JNIEXPORT void JNICALL
    Java_com_casmc_opencvndk_MyCppRenderer__1init(JNIEnv *env, jobject instance) {
        mRenderer->init();
    }

    JNIEXPORT void JNICALL
    Java_com_casmc_opencvndk_MyCppRenderer__1draw(JNIEnv * env, jobject instance) {
        mRenderer->draw();
    }

    // Create renderer instance
    jint JNI_OnLoad(JavaVM* vm, void* reserved) {
        mRenderer = std::unique_ptr<cpp_renderer> {new cpp_renderer{}};

        JNIEnv* env;
        if(vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
            return -1;
        }
        return JNI_VERSION_1_6;
    }
}

Java 接口配置

这里一共有4个文件,包括主页面(MainActivity)、渲染视图(MyGLSurfaceView)、Java渲染器(MyGLRenderer)和 NDK(C++)渲染器调用(MyCppRenderer)。这里是用其画一个三角形。

Java 渲染器(MyGLRenderer)

package com.casmc.opencvndk;

import android.opengl.GLES20;
import android.opengl.GLSurfaceView;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public class MyGLRenderer implements GLSurfaceView.Renderer {
    final private Triangle mTriangle;
    final private MyCppRenderer mCppRenderer;

    MyGLRenderer() {
        mTriangle = new Triangle();
        mCppRenderer = new MyCppRenderer();
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig eglConfig) {
        gl.glClearColor(1.0f, 0.0f, 1.0f, 1.0f);
        mTriangle.init();
        mCppRenderer.init();
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        gl.glViewport(0, 0, width, height);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
        mTriangle.draw();
        mCppRenderer.draw();
    }

    static int loadShader(int type, String shaderCode) {
        int shader = GLES20.glCreateShader(type);

        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);

        return shader;
    }


    private static class Triangle {
        private FloatBuffer vertexBuffer;
        private int mProgram;
        private int mPositionHandle;
        private int mColorHandle;

        private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
        private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex

        private final String vertexShaderCode =
                "attribute vec4 vPosition;" + "void main() {" + "gl_Position = vPosition;" + "}";
        private final String fragmentShaderCode =
                "precision mediump float;" + "uniform vec4 vColor;" + "void main() {" + "gl_FragColor = vColor;" + "}";

        static final int COORDS_PER_VERTEX = 3;
        static  float triangleCoords[] = {
                0.0f, 0.122008459f, 0.0f,
                -0.7f, -0.711004243f, 0.0f,
                0.1f, -0.711004243f, 0.0f
        };
        float color[] = {0.63671875f, 0.76953125f, 0.22265625f, 1.0f};

        public Triangle() {
            ByteBuffer bb = ByteBuffer.allocateDirect(
                    triangleCoords.length *4);
            bb.order(ByteOrder.nativeOrder());

            vertexBuffer = bb.asFloatBuffer();
            vertexBuffer.put(triangleCoords);
            vertexBuffer.position(0);
        }

        public void init() {
            int vertexShader = MyGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
            int fragmentShader = MyGLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);

            mProgram = GLES20.glCreateProgram();
            GLES20.glAttachShader(mProgram, vertexShader);
            GLES20.glAttachShader(mProgram, fragmentShader);
            GLES20.glLinkProgram(mProgram);
        }

        public void draw() {
            GLES20.glUseProgram(mProgram);
            mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
            GLES20.glEnableVertexAttribArray(mPositionHandle);
            GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
                    GLES20.GL_FLOAT, false,
                    vertexStride, vertexBuffer);

            mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
            GLES20.glUniform4fv(mColorHandle, 1, color, 0);
            GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);
            GLES20.glDisableVertexAttribArray(mPositionHandle);
        }
    }
}

NDK(C++)渲染器调用(MyCppRenderer)

package com.casmc.opencvndk;

public class MyCppRenderer {
    void draw() {
        _draw();
    }

    void init() {
        _init();
    }

    private native void _init();
    private native void _draw();
    // Used to load the 'cpp_renderer' library on application startup.
    static {
        System.loadLibrary("cpp_renderer");
    }
}

渲染视图(MyGLSurfaceView)

package com.casmc.opencvndk;

import android.content.Context;
import android.opengl.GLSurfaceView;

public class MyGLSurfaceView extends GLSurfaceView{
    private final MyGLRenderer myGLRenderer;
    public MyGLSurfaceView(Context context) {
        super(context);
        setEGLContextClientVersion(2);
        myGLRenderer = new MyGLRenderer();
        setRenderer(myGLRenderer);
    }
}

主页面(MainActivity)

package com.casmc.opencvndk;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    public MyGLSurfaceView myGLSurfaceView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        myGLSurfaceView = new MyGLSurfaceView(this);
        setContentView(myGLSurfaceView);
    }

    @Override
    protected void onPause() {
        super.onPause();
        myGLSurfaceView.onPause();
    }

    @Override
    protected void onResume() {
        super.onResume();
        myGLSurfaceView.onResume();
    }

项目结果

完成编译后,如下图所示:
Android Studio 中使用 OpenGL (Java 和 NDK 均可)_第2张图片

参考链接

JavaCppGLES

你可能感兴趣的:(工具安装)