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,效果圖: