Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發實例 >> Android OpenGL ES 1.x 教程的Native實現

Android OpenGL ES 1.x 教程的Native實現

編輯:Android開發實例

花了一兩天時間,改寫了Android OpenGL ES 1.0 教程,用Native c實現OpenGL繪圖部分

最近打算學習Android OpenGL ES開發,這個教程是個起點。在這裡記錄了從開發環境准備、到實現一個最基本的OpenGL應用的每個步驟

  • Library 工程源代碼
  • Demo 源代碼

Demo程序執行效果:

開發環境

除了一般的Android應用開發環境(Windows+JDK+SDK+Eclipse+ADT),還需要安裝NDK。我用的是VmWare+Ubuntu來跑NDK

Android設備使用2.2。根據Dev Guide,從2.2(API 8)開始支持OpenGL ES 2.0

配置Samba

由於Eclipse工程是在Windows下,而Native代碼需要使用NDK來build。為了在Linux下能夠訪問Eclipse工程,在Windows下將Eclipse的工作空間文件夾共享、並允許修改,然後在Linux下通過Samba訪問共享的工作空間文件夾。設置好網絡後,參考Mount a Windows Shared Folder on Linux with Samba

(1)安裝包 smbclient、smbfs

(2)在/etc/fstab中增加一行

//192.168.1.44/eclipse_GLES /home/toor/shared_eclipse_GLES cifs username=****,password=****,rw,user,noauto,exec,nounix,noserverino 0 0

特別注意其中 nounix,noserverino選項,如果不指定這兩個選項,後面在執行ndk-build時會報錯“Value too large for defined data type”。具體原因可google一下

(3)以toor用戶執行下面命令即可

[email protected]:~$ mount shared_eclipse_GLES/

安裝NDK

NDK解壓即可

[email protected]:~$ tar xjvf android-ndk-r6b-linux-x86.tar.bz2

NDK的運行需要Linux中安裝了make 3.8或以上、awk,詳見docs/OVERVIEW.html、docs/INSTALL.html

程序框架

Android本身提供了android.opengl.GLSurfaceView和android.opengl.GLSurfaceView.Renderer API,但是GLSurfaceView將OpenGL渲染線程封裝在內部,沒有留給應用程序多少控制的自由度(在我看來,例如控制fps)。因此我自己實現了GLSurfaceView(擴展自SurfaceView)和渲染線程

之所以采用Native/JNI,是因為大多數OpenGL/OpenGL ES的教程、示例代碼都是c的。另外,直覺(沒有實際對比驗證過)每個OpenGL命令都走JNI會帶來較大的額外開銷,我認為按照下面的策略來分隔Java與Native可能會好一些:

  1. 整個3D模型的狀態的維護、修改用Native實現
  2. 3D模型的狀態的查詢、修改提供JNI接口給Java層;3D模型的狀態改變提供JNI的接口以回調的方式通知Java層
  3. OpenGL幀渲染以Native實現,提供JNI接口給Java層;Native層被動地按照Java層的指示進行渲染。這樣就能在Java層控制幀速
  4. UI事件(Touch、按鍵等)由Java層捕獲、預處理,並通過JNI通知給Native層;Native層對事件進行處理(導致3D模型狀態的更新;如果必要的話,以回調通知Java層)
  5. 渲染線程(渲染主循環)由Java實現;其生命周期(創建、銷毀、暫停、恢復等)由Activity、SurfaceView控制

GLSurfaceView

自定義的GLSurfaceView擴展自SurfaceView,其主要功能是:(1)提供OpenGL的繪圖窗口,(2)控制渲染線程的生命周期,(3)UI事件捕獲、分發到渲染線程(渲染線程進一步通知到Native層)

public class GLSurfaceView extends SurfaceView implements Callback {

private Renderer renderer;
private RenderRunnable renderRunnable;

public GLSurfaceView(Context context) {
super(context);

SurfaceHolder holder = getHolder();
holder.addCallback(this);

// This is important!
// Not doing this will cause failure when eglCreateWindowSurface()
holder.setFormat(PixelFormat.RGBA_8888);
}

public void setRenderer(Renderer renderer) {
this.renderer = renderer;
}

public void surfaceCreated(SurfaceHolder holder) {

// ......

renderRunnable = new RenderRunnable();
new Thread(renderRunnable, "GL_Thread").start();

// ......
}

public void surfaceDestroyed(SurfaceHolder holder) {

renderRunnable.destroy();

// ......
}

public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {

if (width > 0 && height > 0
&& (width != this.surfaceWidth || height != this.surfaceHeight)) {

if (0 == this.surfaceWidth && 0 == this.surfaceHeight) {

this.surfaceWidth = width;
this.surfaceHeight = height;

// ......

} else {

this.surfaceWidth = width;
this.surfaceHeight = height;

// ......

renderRunnable.queueEvent(new ResizeEvent());
}
}

}

/**
* Called when Activity paused
*/
public void onPause() {
if (null != renderRunnable) {
renderRunnable.pause();
}
}

/**
* Called when Activity resumed
*/
public void onResume() {
if (null != renderRunnable) {
renderRunnable.resume();
}
}

// ......

}

渲染線程(RenderRunnable)

渲染線程由GLSurfaceView控制(創建、銷毀、暫停、恢復等);主要任務是:(1)OpenGL初始化、銷毀等,(2)主循環,幀渲染,(3)事件轉發

    private class RenderRunnable implements Runnable {

private Thread renderThread;
private volatile boolean rendering = false;
private AtomicBoolean paused = new AtomicBoolean(false);
private ArrayList<Runnable> eventQueue = new ArrayList<Runnable>();

public void run() {
renderThread = Thread.currentThread();

initEGL();

if (null != renderer) {
renderer.init();
}


// wait until the size of surface is ready
// ......
if (null != renderer) {
renderer.resize(surfaceWidth, surfaceHeight);
}

// The main loop
for (rendering = true; rendering;) {

// Process events
synchronized (eventQueue) {
while (eventQueue.size() > 0) {
Runnable e = eventQueue.remove(0);
e.run();
}
}

// Check pause flag
synchronized (paused) {
if (paused.get()) {
try {
paused.wait();
} catch (InterruptedException e) {
break;
}
}
}

// Render a single frame
if (null != renderer) {
renderer.render();
egl.eglSwapBuffers(eglDisplay, eglSurface);
}

}// main loop

destroyEGL();
}

public void queueEvent(Runnable e) {
if (rendering) {
synchronized (eventQueue) {
eventQueue.add(e);
}
}
}

public void resume() {
if (rendering) {
synchronized (paused) {
if (paused.get()) {
paused.set(false);
paused.notifyAll();
}
}
}
}

public void pause() {
if (rendering) {
synchronized (paused) {
if (!paused.get()) {
paused.set(true);
}
}
}
}

public void destroy() {
rendering = false;
if (null != renderThread) {
renderThread.interrupt();
}

}

private boolean initEGL() {

egl = (EGL10) EGLContext.getEGL();

// ......

return true;
}

private void destroyEGL() {

// ......
}

}

渲染線程與主UI線程之間的同步歸納如下:

序號 同步? 主UI線程 渲染線程 1 Y surfaceCreated():進入     創建   run():開始執行 surfaceCreated():返回   2 Y   初始化EGL   初始化GL surfaceChanged():第一次Resize     OpenGL Resize   進入主循環 3 N surfaceChanged():Resize 事件處理:OpenGL Resize 4 N UI事件 事件處理 5 N onPause() 暫停主循環 6 N onResume() 恢復主循環 7 Y surfaceDestroyed():進入     跳出主循環 surfaceDestroyed():返回  

EGL

關於EGL,請參考eglIntro

EGL作用是連接OpenGL與本地Window系統。對Android而言,本地Window系統為SurfaceHolder

初始化EGL的過程為

    private class RenderRunnable implements Runnable {

private EGL10 egl;
private EGLDisplay eglDisplay;
private EGLSurface eglSurface;
private EGLContext eglContext;

private boolean initEGL() {

egl = (EGL10) EGLContext.getEGL();

//
eglDisplay = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
if (EGL10.EGL_NO_DISPLAY == eglDisplay) {
Log.e(TAG_RENDER_RUNNABLE, "eglGetDisplay() failed");
destroyEGL();
return false;
}

//
int[] eglVersions = new int[2];
if (egl.eglInitialize(eglDisplay, eglVersions)) {
if (DEBUG) {
Log.d(TAG_RENDER_RUNNABLE, "EGL version = "
+ eglVersions[0] + "." + eglVersions[1]);
}
} else {
Log.e(TAG_RENDER_RUNNABLE, "eglInitialize() failed");
destroyEGL();
return false;
}

//
EGLConfig eglConfig;
int[] attrList = new int[] { //
EGL10.EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT, //
EGL10.EGL_RED_SIZE, 8, //
EGL10.EGL_GREEN_SIZE, 8, //
EGL10.EGL_BLUE_SIZE, 8, //
EGL10.EGL_DEPTH_SIZE, 16, //
EGL10.EGL_NONE //
};
EGLConfig[] configs = new EGLConfig[1];
int[] numConfig = new int[1];
if (egl.eglChooseConfig(eglDisplay, attrList, configs, 1, numConfig)
&& numConfig[0] > 0) {
eglConfig = configs[0];
} else {
Log.e(TAG_RENDER_RUNNABLE, "eglChooseConfig() failed");
destroyEGL();
return false;
}

//
eglContext = egl.eglCreateContext(eglDisplay, eglConfig,
EGL10.EGL_NO_CONTEXT, null);
if (EGL10.EGL_NO_CONTEXT == eglContext) {
Log.e(TAG_RENDER_RUNNABLE, "eglCreateContext() failed");
destroyEGL();
return false;
}

//
eglSurface = egl.eglCreateWindowSurface(eglDisplay, eglConfig,
getHolder(), null);
if (EGL10.EGL_NO_SURFACE == eglSurface) {
Log.e(TAG_RENDER_RUNNABLE, "eglCreateWindowSurface() failed");
destroyEGL();
return false;
}

//
if (!egl.eglMakeCurrent(eglDisplay, eglSurface, eglSurface,
eglContext)) {
Log.e(TAG_RENDER_RUNNABLE, "eglMakeCurrent() failed");
destroyEGL();
return false;
}

return true;
}

銷毀EGL的過程:

    egl.eglMakeCurrent(eglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);

egl.eglDestroySurface(eglDisplay, eglSurface);

egl.eglDestroyContext(eglDisplay, eglContext);

egl.eglTerminate(eglDisplay);

Renderer

Renderer接口定義了基本的OpenGL操作:(1)初始化3D場景,(2)Resize,(3)渲染一幀

public interface Renderer {

/**
* Initialize the OpenGL scene
*/
void init();

/**
* Called when window size changed
*/
void resize(int w, int h);

/**
* Render a frame
*/
void render();
}

如果要實現Native的Renderer,需要定義一個擴展Renderer 的 Native 接口,例如:

public class TriangleRenderer implements Renderer {

static {
System.loadLibrary("triangle");
}

public native void init() ;

public native void resize(int w, int h);

public native void render();

}

Activity

Activity主要功能:(1)構造GLSurfaceView、Renderer,(2)生命周期(暫停、恢復)控制

public class LearnGL_TriangleActivity extends Activity {

private GLSurfaceView glSurfaceView;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.main);

glSurfaceView = new GLSurfaceView(this);
glSurfaceView.setRenderer(new TriangleRenderer());
setContentView(glSurfaceView);
}

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

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


}

Native Renderer實現

生成JNI頭文件

定義了Java的JNI接口後,利用JDK的javah工具生成Native的頭文件。在Eclipse工程中,Java類(*.class)放在<PROJECT>/bin目錄中,在此目錄下執行

D:\eclipse_GLES\LearnGL_Triangle\bin>javah -jni -d ../jni learngl.triangle.TriangleRenderer

生成頭文件<PROJECT>/jni/learngl_triangle_TriangleRenderer.h,該頭文件中聲明了Native接口函數的原型:

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

#ifndef _Included_learngl_triangle_TriangleRenderer
#define _Included_learngl_triangle_TriangleRenderer
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: learngl_triangle_TriangleRenderer
* Method: init
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_learngl_triangle_TriangleRenderer_init
(JNIEnv *, jobject);

/*
* Class: learngl_triangle_TriangleRenderer
* Method: resize
* Signature: (II)V
*/
JNIEXPORT void JNICALL Java_learngl_triangle_TriangleRenderer_resize
(JNIEnv *, jobject, jint, jint);

/*
* Class: learngl_triangle_TriangleRenderer
* Method: render
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_learngl_triangle_TriangleRenderer_render
(JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

Native實現

JNI頭文件中使用JNI數據類型(jint等),而OpenGL定義了自己的數據類型(GLint等)。為了讓結構清晰,OpenGL的Native實現並不直接實現JNI頭文件,而是建一個“膠合層”實現JNI頭文件,並將JNI數據類型轉換為對應的OpenGL數據類型、然後調用對應的Native實現:

#include "learngl_triangle_TriangleRenderer.h"
#include "triangle.h"

void Java_learngl_triangle_TriangleRenderer_init(JNIEnv *jni, jobject obj) {
init();
}

void Java_learngl_triangle_TriangleRenderer_resize(JNIEnv *jni, jobject obj,
jint w, jint h) {
resize(w, h);
}

void Java_learngl_triangle_TriangleRenderer_render(JNIEnv *jni, jobject obj) {
render();
}

而Native實現的接口定義在其單獨的頭文件中:

#ifndef _TRIANGLE_H
#define _TRIANGLE_H

#include <GLES/gl.h>

void init();
void resize(GLint w, GLint h);
void render();

#endif // _TRIANGLE_H

Native的實現源文件中不會含有JNI數據類型:

#include "triangle.h"

static float triangleCoords[] = {//
-0.5f, -0.25f, 0, //
0.5f, -0.25f, 0, //
0.0f, 0.559016994f, 0 //
};

static float angle = 0;

void init() {
glClearColor(1.0f, 0.5f, 0.5f, 1.0f);
glEnableClientState(GL_VERTEX_ARRAY);

}

void resize(GLint w, GLint h) {
glViewport(0, 0, w, h);

// make adjustments for screen ratio
float ratio = w / (float) h;
glMatrixMode(GL_PROJECTION); // set matrix to projection mode
glLoadIdentity(); // reset the matrix to its default state
glFrustumf(-ratio, ratio, -1, 1, -1, 7); // apply the projection matrix

}

void render() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// Set GL_MODELVIEW transformation mode
glMatrixMode(GL_MODELVIEW);
glLoadIdentity(); // reset the matrix to its default state

// When using GL_MODELVIEW, you must set the view point
// GLU.gluLookAt(gl, 0, 0, -5, 0f, 0f, 0f, 0f, 1.0f, 0.0f);

// Create a rotation for the triangle
angle += 3;
if (angle > 360) {
angle -= 360;
}
glRotatef(angle, 0.0f, 0.0f, 1.0f);

// Draw the triangle
glColor4f(0.63671875f, 0.76953125f, 0.22265625f, 0.0f);
glVertexPointer(3, GL_FLOAT, 0, triangleCoords);
glDrawArrays(GL_TRIANGLES, 0, 3);
}

上面的代碼在X-Y平面上畫了一個等邊三角形,並且連續逆時針旋轉(每一幀旋轉3度)。由於屏幕長寬比!=1,導致等邊三角形變形了。怎麼解決這個問題,我還不會,留待後面繼續學習。。。

NDK Build

NDK的使用參考以下文檔

  • docs/OVERVIEW.html
  • docs/NDK-BUILD.html
  • docs/ANDROID-MK.html

編寫Android.mk

NDK使用<PROJECT>/jni/Android.mk文件來進行build。關於Android.mk文件的編寫,參考以下文檔:

  • docs/OVERVIEW.html
  • docs/ANDROID-MK.html
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE := triangle
LOCAL_SRC_FILES := learngl_triangle_TriangleRenderer.c \
triangle.c

LOCAL_LDLIBS := -lGLESv1_CM

include $(BUILD_SHARED_LIBRARY)

第一、二行一般都是固定如此。具體含義參考docs/ANDROID-MK.html

LOCAL_MODULE 定義了生成的庫的名稱。假如庫名稱為<lib_name>,則

  • 生成的庫文件的名稱為 lib<lib_name>.so
  • Java Native接口中加載庫的名稱為<lib_name>,不用指定lib前綴和.so擴展名。例如:
    static {
System.loadLibrary("triangle");
}

LOCAL_SRC_FILES 列出用來build出庫的源代碼文件。多個文件用空白字符(SPACE、TAB、換行)分隔,換行在末尾用\脫字符

LOCAL_LDLIBS 列出要build的庫所引用的系統庫,格式為 -l<lib_name>,例如 -lGLESv1_CM對應libGLESv1_CM.so文件。多個文件用空白字符(SPACE、TAB、換行)分隔,換行在末尾用\脫字符

最後一行 include $(BUILD_SHARED_LIBRARY)告訴NDK build出共享庫(Shared library,*.so)。與共享庫相對的是“靜態庫”,命令為include $(BUILD_STATIC_LIBRARY)

ndk-build

在<PROJECT>/jni/或子目錄下執行ndk-build即可build出Native庫:

[email protected]:~/shared_eclipse_GLES/LearnGL_Triangle/jni$ ~/android-ndk-r6b/ndk-build 

生成的庫文件為<PROJECT>/libs/armeabi/lib<lib_name>.so

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved