1. 程式人生 > >Android使用NDK OpenGL ES3.0繪製一個三角形

Android使用NDK OpenGL ES3.0繪製一個三角形

Android使用NDK  OpenGL ES3.0繪製一個三角形

     網上已經有很多OpenCV的教程,不過大都是基於Java層呼叫openGL介面,若使用Java層openGL介面繪製三角形,還是比較簡單的,但要是使用NDK C++ 實現,還是有點複雜。

    本文將使用Android NDK開發,利用C++的 OpenGL ES3.0繪製一個三角形。繪製三角形的C/C++原始碼大部分是參考:《OPENGL ES 3.0程式設計指南 》第二章的程式碼,但該書只有原始碼,沒有工程專案,是用Android.mk配置,要做成Android Studio Demo還是要花點力氣的。本部落格的OpenGL的開發,其配置檔案使用CMakeLists.txt,Android Studio 2.3以上NDK開發很容易啦,其配置方法使用CMakeLists.txt會比使用Android.mk更容易。

    這裡不具體分析繪製三角形的程式碼實現過程了,畢竟《OPENGL ES 3.0程式設計指南 》這本書已經很詳細啦。

開發環境:

  • (1)Android Studio 2.3.3 以上
  • (2)android-ndk-r10d 以上,下載地址:https://developer.android.google.cn/ndk/downloads/index.html

 1、新建專案:

   新建Android工程一定要勾選“Include C++ support”,這樣新建的Android工程會直接支援NDK開發,避免各種配置問題,如果提示沒有NDK,請下載NDK,並在工程“Project Structure”中匯入即可。

我的工程目錄是這樣的:

2、新建RendererJNI類

package opengl.panjq.com.opengl_demo;

import android.opengl.GLSurfaceView.Renderer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.content.Context;
import android.content.res.AssetManager;
import android.opengl.GLSurfaceView;
import android.util.Log;

public class RendererJNI implements GLSurfaceView.Renderer {
    static {
        System.loadLibrary("gltest-lib");
    }
    private AssetManager mAssetMgr = null;
    private final String mLogTag = "ndk-build";

    public native void glesInit();
    public native void glesRender();
    public native void glesResize(int width, int height);

    public native void readShaderFile(AssetManager assetMgr);

    public RendererJNI(Context context) {
        mAssetMgr = context.getAssets();
        if (null == mAssetMgr) {
            Log.e(mLogTag, "getAssets() return null !");
        }
    }

    /**
     * 當建立 GLSurfaceView時,系統呼叫這個方法.使用這個方法去執行只需要發生一次的動作,
     * 例如設定OpenGL環境引數或者初始化OpenGL graphic 物件.
     * @param gl
     * @param config
     */
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        readShaderFile(mAssetMgr);
        glesInit();
    }

    /**
     *  當GLSurfaceView  幾何學發生改變時系統呼叫這個方法.包括 GLSurfaceView  的大小發生改變或者橫豎屏發生改變.
     *  使用這個方法去響應GLSurfaceView 容器的改變
     * @param gl
     * @param width
     * @param height
     */
    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        glesResize(width, height);
    }

    /**
     *    執行渲染工作:當系統每一次重畫 GLSurfaceView 時呼叫.使用這個方法去作為主要的繪製和重新繪製graphic  物件的執行點.
     * @param gl
     */
    @Override
    public void onDrawFrame(GL10 gl) {
        glesRender();
    }
}

3、修改MainActivity.java檔案

package opengl.panjq.com.opengl_demo;

import android.content.Context;
import android.opengl.GLSurfaceView;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

import android.app.ActivityManager;
import android.content.pm.ConfigurationInfo;
import android.util.Log;

import static android.opengl.GLSurfaceView.RENDERMODE_WHEN_DIRTY;


public class MainActivity extends AppCompatActivity {
    private final int CONTEXT_CLIENT_VERSION = 3;
    private GLSurfaceView mGLSurfaceView;
    RendererJNI mRenderer;

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

        mGLSurfaceView = new GLSurfaceView(this);
        mRenderer=new RendererJNI(this);
        if (detectOpenGLES30()) {
            // 設定OpenGl ES的版本
            mGLSurfaceView.setEGLContextClientVersion(CONTEXT_CLIENT_VERSION);
            // 設定與當前GLSurfaceView繫結的Renderer
            mGLSurfaceView.setRenderer(mRenderer);
            // 設定渲染的模式
            mGLSurfaceView.setRenderMode(RENDERMODE_WHEN_DIRTY);
        } else {
            Log.e("opengles30", "OpenGL ES 3.0 not supported on device.  Exiting...");
            finish();
        }

        setContentView(mGLSurfaceView);
    }

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

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

    private boolean detectOpenGLES30() {
        ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
        ConfigurationInfo info = am.getDeviceConfigurationInfo();

        return (info.reqGlEsVersion >= 0x30000);
    }
}

4.新建C++ JNI檔案

RendererJNI.h標頭檔案

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class opengl_panjq_com_opengl_demo_RendererJNI */

#ifndef _Included_opengl_panjq_com_opengl_demo_RendererJNI
#define _Included_opengl_panjq_com_opengl_demo_RendererJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     opengl_panjq_com_opengl_demo_RendererJNI
 * Method:    glesInit
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_opengl_panjq_com_opengl_1demo_RendererJNI_glesInit
  (JNIEnv *, jobject);

/*
 * Class:     opengl_panjq_com_opengl_demo_RendererJNI
 * Method:    glesRender
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_opengl_panjq_com_opengl_1demo_RendererJNI_glesRender
  (JNIEnv *, jobject);

/*
 * Class:     opengl_panjq_com_opengl_demo_RendererJNI
 * Method:    glesResize
 * Signature: (II)V
 */
JNIEXPORT void JNICALL Java_opengl_panjq_com_opengl_1demo_RendererJNI_glesResize
  (JNIEnv *, jobject, jint, jint);


JNIEXPORT void JNICALL Java_opengl_panjq_com_opengl_1demo_RendererJNI_readShaderFile
        (JNIEnv *env, jobject self, jobject assetManager);

#ifdef __cplusplus
}
#endif
#endif

RendererJNI.cpp原始檔

    這裡提供一個方法char* readShaderSrcFile(char *shaderFile, AAssetManager *pAssetManager),用於獲取assets資原始檔*.glsl檔案:

  char *pVertexShader = readShaderSrcFile("shader/vs.glsl", g_pAssetManager);
  char *pFragmentShader = readShaderSrcFile("shader/fs.glsl", g_pAssetManager);

    當然,也可以寫死在檔案中,如: 

    char vShaderStr[] =
            "#version 300 es                          \n"
                    "layout(location = 0) in vec4 vPosition;  \n"
                    "void main()                              \n"
                    "{                                        \n"
                    "   gl_Position = vPosition;              \n"
                    "}                                        \n";

    char fShaderStr[] =
            "#version 300 es                              \n"
                    "precision mediump float;                     \n"
                    "out vec4 fragColor;                          \n"
                    "void main()                                  \n"
                    "{                                            \n"
                    "   fragColor = vec4 ( 1.0, 0.0, 0.0, 1.0 );  \n"
                    "}                                            \n";

     再用LoadShader載入:

    vertexShader = LoadShader ( GL_VERTEX_SHADER, vShaderStr );
    fragmentShader = LoadShader ( GL_FRAGMENT_SHADER, fShaderStr );

    完整的程式碼如下 

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
#include "RendererJNI.h"
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <GLES3/gl3.h>
#include <android/asset_manager_jni.h>
#include <android/log.h>

#define LOG_TAG "ndk-build"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
GLint	g_programObject;
jint	g_width;
jint	g_height;

AAssetManager* g_pAssetManager = NULL;
char* readShaderSrcFile(char *shaderFile, AAssetManager *pAssetManager)
{
    AAsset *pAsset = NULL;
    char *pBuffer = NULL;
    off_t size = -1;
    int numByte = -1;

    if (NULL == pAssetManager)
    {
        LOGE("pAssetManager is null!");
        return NULL;
    }
    pAsset = AAssetManager_open(pAssetManager, shaderFile, AASSET_MODE_UNKNOWN);
    //LOGI("after AAssetManager_open");

    size = AAsset_getLength(pAsset);
    LOGI("after AAssetManager_open");
    pBuffer = (char *)malloc(size+1);
    pBuffer[size] = '\0';

    numByte = AAsset_read(pAsset, pBuffer, size);
    LOGI("%s : [%s]", shaderFile, pBuffer);
    AAsset_close(pAsset);

    return pBuffer;
}


GLuint LoadShader ( GLenum type, const char *shaderSrc )
{
    GLuint shader;
    GLint compiled;

    // Create the shader object
    shader = glCreateShader ( type );

    if ( shader == 0 )
    {
        return 0;
    }

    // Load the shader source
    glShaderSource ( shader, 1, &shaderSrc, NULL );

    // Compile the shader
    glCompileShader ( shader );

    // Check the compile status
    glGetShaderiv ( shader, GL_COMPILE_STATUS, &compiled );

    if ( !compiled )
    {
        GLint infoLen = 0;

        glGetShaderiv ( shader, GL_INFO_LOG_LENGTH, &infoLen );

        if ( infoLen > 1 )
        {
            char *infoLog = (char *)malloc ( sizeof ( char ) * infoLen );

            glGetShaderInfoLog ( shader, infoLen, NULL, infoLog );
            LOGE("Error compiling shader:[%s]", infoLog );

            free ( infoLog );
        }

        glDeleteShader ( shader );
        return 0;
    }

    return shader;

}

//*********************************************************************************
JNIEXPORT void JNICALL Java_opengl_panjq_com_opengl_1demo_RendererJNI_glesInit
  (JNIEnv *pEnv, jobject obj){
    char vShaderStr[] =
            "#version 300 es                          \n"
                    "layout(location = 0) in vec4 vPosition;  \n"
                    "void main()                              \n"
                    "{                                        \n"
                    "   gl_Position = vPosition;              \n"
                    "}                                        \n";

    char fShaderStr[] =
            "#version 300 es                              \n"
                    "precision mediump float;                     \n"
                    "out vec4 fragColor;                          \n"
                    "void main()                                  \n"
                    "{                                            \n"
                    "   fragColor = vec4 ( 1.0, 0.0, 0.0, 1.0 );  \n"
                    "}                                            \n";

    char *pVertexShader = readShaderSrcFile("shader/vs.glsl", g_pAssetManager);
    char *pFragmentShader = readShaderSrcFile("shader/fs.glsl", g_pAssetManager);

    GLuint vertexShader;
    GLuint fragmentShader;
    GLuint programObject;
    GLint linked;

    // Load the vertex/fragment shaders
    //vertexShader = LoadShader ( GL_VERTEX_SHADER, vShaderStr );
    //fragmentShader = LoadShader ( GL_FRAGMENT_SHADER, fShaderStr );
    vertexShader = LoadShader ( GL_VERTEX_SHADER, pVertexShader );
    fragmentShader = LoadShader ( GL_FRAGMENT_SHADER, pFragmentShader );

    // Create the program object
    programObject = glCreateProgram ( );

    if ( programObject == 0 )
    {
        return;
    }

    glAttachShader ( programObject, vertexShader );
    glAttachShader ( programObject, fragmentShader );

    // Link the program
    glLinkProgram ( programObject );

    // Check the link status
    glGetProgramiv ( programObject, GL_LINK_STATUS, &linked );

    if ( !linked )
    {
        GLint infoLen = 0;

        glGetProgramiv ( programObject, GL_INFO_LOG_LENGTH, &infoLen );

        if ( infoLen > 1 )
        {
            char *infoLog = (char *)malloc ( sizeof ( char ) * infoLen );

            glGetProgramInfoLog ( programObject, infoLen, NULL, infoLog );
            LOGE("Error linking program:[%s]", infoLog );

            free ( infoLog );
        }

        glDeleteProgram ( programObject );
        return;
    }

    // Store the program object
    g_programObject = programObject;

    glClearColor ( 1.0f, 1.0f, 1.0f, 0.0f );
}

/*
 * Class:     opengl_panjq_com_opengl_demo_RendererJNI
 * Method:    glesRender
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_opengl_panjq_com_opengl_1demo_RendererJNI_glesRender
  (JNIEnv *pEnv, jobject obj){
    GLfloat vVertices[] = {  0.0f,  0.5f, 0.0f,
                             -0.5f, -0.5f, 0.0f,
                             0.5f, -0.5f, 0.0f
    };

    // Set the viewport
    glViewport ( 0, 0, g_width, g_height );

    // Clear the color buffer
    glClear ( GL_COLOR_BUFFER_BIT );

    // Use the program object
    glUseProgram ( g_programObject );

    // Load the vertex data
    glVertexAttribPointer ( 0, 3, GL_FLOAT, GL_FALSE, 0, vVertices );
    glEnableVertexAttribArray ( 0 );

    glDrawArrays ( GL_TRIANGLES, 0, 3 );

}

/*
 * Class:     opengl_panjq_com_opengl_demo_RendererJNI
 * Method:    glesResize
 * Signature: (II)V
 */
JNIEXPORT void JNICALL Java_opengl_panjq_com_opengl_1demo_RendererJNI_glesResize
  (JNIEnv *pEnv, jobject obj, jint width, jint height){
    g_width = width;
    g_height = height;

}


JNIEXPORT void JNICALL Java_opengl_panjq_com_opengl_1demo_RendererJNI_readShaderFile
        (JNIEnv *env, jobject self, jobject assetManager){
    if (assetManager && env)
    {
        //LOGI("before AAssetManager_fromJava");
        g_pAssetManager = AAssetManager_fromJava(env, assetManager);
        //LOGI("after AAssetManager_fromJava");
        if (NULL == g_pAssetManager)
        {
            LOGE("AAssetManager_fromJava() return null !");
        }
    }
    else
    {
        LOGE("assetManager is null !");
    }
}

5、修改CMakeLists.txt檔案

    OpenGL ES開發需要把OpenGL的相關依賴庫匯入進來,其方法很簡單,直接在target_link_libraries中新增GLESv2 或者GLESv3都可以,由於需要在NDK使用AAssetManager操作asset資源,因此需要在target_link_libraries中,也把android新增進來,否則會出錯:Error: undefined reference to 'AAssetManager_fromJava'。

# 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)

# 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.
include_directories(${CMAKE_SOURCE_DIR} src/main/cpp)
add_library( # Sets the name of the library.
             gltest-lib

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             src/main/cpp/RendererJNI.cpp )

add_library( # Sets the name of the library.
             native-lib
             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             src/main/cpp/native-lib.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.
                       gltest-lib
                       android # 錯誤:Error: undefined reference to 'AAssetManager_fromJava'
                       GLESv3  # 把opengl庫檔案新增進來,GLESv3
                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )
target_link_libraries( # Specifies the target library.
                       native-lib

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )

Run,Run,Run,效果圖: