在Android中使用NDK调用OpenGl

在Android中使用NDK调用OpenGl

Calling OpenGL from C on Android, Using the NDK

原文地址:http://www.learnopengles.com/calling-opengl-from-android-using-the-ndk/

对于我的系列文章Developing a Simple Game of Air Hockey Using C++ and OpenGL ES 2 for Android, iOS, and the Web的第一节,我们需要用opengl创建一个简单的android工程,这里会用到本地代码渲染场景。

预备知识

android sdk ndk 和一个合适的ide

模拟器或一个支持opengl es 2.0的android设备

本次课程里我们将使用eclipse

测试本次教程里的代码,我使用adt 22.0.1 和 platform 17 ,ndk 8e 和 Eclipse Juno Service Pack 2。

准备工作

首先创建一个新工程,带NDK支持的那种,当然你也可以从 GitHub project获取代码。

在创建新项目之前,建立一个airhockey文件夹,然后建立一个git文件夹,Git可以帮助你管理源代码,比如在你出现错误代码时恢复,学习更多的内容,请点击Git documentation。

File->New->Android Application Project,然后取名为airHockey,application name设置为Air Hockey,包名设置为com.learnopengles.airhockey,其他的选项默认,或者自己填写,保存项目到我们刚才创建的文件夹中。

创建好后,右击project,选择Android Tools->Add Native Support,提问library名称时,输入game,则创建出的库名称为libgame.so,这将在项目文件夹中建立一个jni文件夹。

初始化OpenGl

项目创建后,现在我们可以修改activity和configurate来加载OpenGl,首先我们先在Activity类中添加两个变量

private GLSurfaceView glSurfaceView;
private boolean rendererSet;
现在设置onCreate()

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
 
    ActivityManager activityManager
        = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
    ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
 
    final boolean supportsEs2 =
        configurationInfo.reqGlEsVersion >= 0x20000 || isProbablyEmulator();
 
    if (supportsEs2) {
        glSurfaceView = new GLSurfaceView(this);
 
        if (isProbablyEmulator()) {
            // Avoids crashes on startup with some emulator images.
            glSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0);
        }
 
        glSurfaceView.setEGLContextClientVersion(2);
        glSurfaceView.setRenderer(new RendererWrapper());
        rendererSet = true;
        setContentView(glSurfaceView);
    } else {
        // Should never be seen in production, since the manifest filters
        // unsupported devices.
        Toast.makeText(this, "This device does not support OpenGL ES 2.0.",
                Toast.LENGTH_LONG).show();
        return;
    }
}
首先检测设备是否支持OpenGl ES 2.0 ,支持的话创建一个GlSurfaceView。

configurationInfo.reqGlEsVersion >= 0x20000在模拟器上不可用,所以我们添加

private boolean isProbablyEmulator() {
    return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1
            && (Build.FINGERPRINT.startsWith("generic")
                    || Build.FINGERPRINT.startsWith("unknown")
                    || Build.MODEL.contains("google_sdk")
                    || Build.MODEL.contains("Emulator")
                    || Build.MODEL.contains("Android SDK built for x86"));
}
OpengGl ES 2.0只能在启用了Host GPU的模拟器上使用, 获取功多信息请阅读, Android Emulator Now Supports Native OpenGL ES2.0!
添加一下代码完成对Activity的修改:

@Override
protected void onPause() {
    super.onPause();
 
    if (rendererSet) {
        glSurfaceView.onPause();
    }
}
 
@Override
protected void onResume() {
    super.onResume();
 
    if (rendererSet) {
        glSurfaceView.onResume();
    }
}
我们需要处理Android的生命周期,所以在需要的时候暂停继续游戏。只有在执行了glSurfaceView.setRenderer()后处理才回起作用,否则调用这些方法回使程序跳出。
获得更多的信息,点击 Android Lesson One: Getting Started or  OpenGL ES 2 for Android: A Quick-Start Guide。

创建一个RendererWrapper类

public class RendererWrapper implements Renderer {
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        glClearColor(0.0f, 0.0f, 1.0f, 0.0f);
    }
 
    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        // No-op
    }
 
    @Override
    public void onDrawFrame(GL10 gl) {
        glClear(GL_COLOR_BUFFER_BIT);
    }
}

这个简单的渲染程序用蓝色背景清除屏幕。稍后我们将把这些方法换成c++程序。调用这些方法不用添加gl前缀,只需添加android.opengl.GLES20.*在文件顶部,然后选择Source->Organize Imports。

如果你在编译的时候遇到了错误,确保你加入了下面所有的引用

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

import android.opengl.GLSurfaceView.Renderer;
修改manifest排除不支持OpenGl的设备

把下面的代码加入到manifest的某个地方

从OGl 2.0开始只支持Android2.3.3 api10以上的系统,所以替换use-sdk标签:

工作正常,将看到一下画面


在Android中使用NDK调用OpenGl_第1张图片

加入本地代码

我们已经完成了java上的代码,但我们真正想做的用本地代码调用OpenGl,应该怎么做呢?下面我们将创建一个NDK项目,并把opengl代码移植到c文件里。

为了将来能让ios和web平台共享我们制作的本地代码,所以我们需要在项目文件夹上层建立一个common文件夹,这意味这,在你的Air Hockey项目中有一个android文件夹,里面存放android项目,common文件夹中存放公用代码。

用eclipse连接一个项目文件夹外的文件夹有些麻烦,分以下几步:

右击项目选择属性,Resource->Linked Resources然后选择New

输入COMMON_SRC_LOC作为名称,位置为‘${PROJECT_LOC}\..\common’然后点击Ok。

右键点击项目选择Build Path->Link Source…, 选择 Variables…, 选择 COMMON_SRC_LOC,点击Ok,输入common作为文件夹的名称选择Finish。

现在项目里出现了一个新文件夹common。

接下来我们要在common文件夹中创建两个文件game.c和game.h,方法为右键点击项目选择New File。将一下内容添加进game.h:

void on_surface_created();
void on_surface_changed();
void on_draw_frame();
c语言中,.h为头文件,这个文件作为.c文件的声明。这里包含三个我们将在Java中调用的函数。

在game.c中加入下面内容。

#include "game.h"
#include "glwrapper.h"
 
void on_surface_created() {
    glClearColor(1.0f, 0.0f, 0.0f, 0.0f);
}
 
void on_surface_changed() {
    // No-op
}
 
void on_draw_frame() {
    glClear(GL_COLOR_BUFFER_BIT);
}

这些代码可以用红色清除屏幕,且每帧都会进行清除操作。我们使用一个glwrapper.h去包含OpenGl平台特征库,这个文件在其他平台的不同位置都有出现。
加入平台特征码和JNI码

使用这些代码,我们只需要两样东西:定义glwrapper.h和一些JNI结合代码。JNI可以替代Java本地接口,这是在Android上让Java和C互相调用的方法。

在工程中的jni文件夹创建一个glwrapper.h文件,加入以下内容:

#include 

这是android的OpenGl头文件。创建Jni结合,我门首先需要创建暴露native接口的Java类。创建一个calledGameLibJNIWrapper类,加入下列内容:

public class GameLibJNIWrapper {
    static {
        System.loadLibrary("game");
    }
 
    public static native void on_surface_created();
 
    public static native void on_surface_changed(int width, int height);
 
    public static native void on_draw_frame();
}

这个类将加载libgame.so库,这个我们在调用本地库是会用到。创建匹配这个类的C文件,方法是建立工程,打开命令行提示,目录改到bin/class文件夹,运行下面的命令行:

javah -o ../../jni/jni.c com.learnopengles.airhockey.GameLibJNIWrapper

javah必须定位在你的JDKs目录。这段命令将创建一个看起来非常凌乱的jni.c文件,有很多我们不需要的东西。让我们用下面的内容替换他以简化程序:

#include "../../common/game.h"
#include 
 
JNIEXPORT void JNICALL Java_com_learnopengles_airhockey_GameLibJNIWrapper_on_1surface_1created
    (JNIEnv * env, jclass cls) {
    on_surface_created();
}
 
JNIEXPORT void JNICALL Java_com_learnopengles_airhockey_GameLibJNIWrapper_on_1surface_1changed
    (JNIEnv * env, jclass cls, jint width, jint height) {
    on_surface_changed();
}
 
JNIEXPORT void JNICALL Java_com_learnopengles_airhockey_GameLibJNIWrapper_on_1draw_1frame
    (JNIEnv * env, jclass cls) {
    on_draw_frame();
}

好的文件修改完了,且加入了game.h引用,这样可以调用我们的game方法。这里是工作原理:

GameLibJNIWrapper 定义了可以从Java调用的本地c方法。
为了能从Java进行调用,必须用特殊的方式进行命名,每个方法至少有两个参数,几个 JNIEnv指针,和 jclass 。为了方便,我们可以使用javah创建带前缀的jin.c。
我们从jin.c调用game.h定义的方法。这样Java调用本地代码的连接就完成了。

编译本地代码

编译和执行本地代码,我们需要为ndk创建系统提供描述文件。为了达到这个目的,我们需要两个文件Android.mk 和 Application.mk。当我们增加本地支持到项目中时,项目中自动增加了一个叫game.cpp的文件,这个文件不需要,所以你可以删除他。

为Android.mk做以下设置:

LOCAL_PATH := $(call my-dir)
 
include $(CLEAR_VARS)
 
LOCAL_MODULE    := game
LOCAL_CFLAGS    := -Wall -Wextra
LOCAL_SRC_FILES := ../../common/game.c jni.c
LOCAL_LDLIBS := -lGLESv2
 
include $(BUILD_SHARED_LIBRARY)

这个文件描述了我们的代码文件,告诉ndk game.c和jni.c应该编译,然后被创建成一个共享lib库叫做libgame.so。这个库将字运行时动态链接 libGLESv2库。

编写此文件时,不要留下任何多余的空格,否则会造成创建失败。

下一个文件, Application.mk,内容如下:

APP_PLATFORM := android-10
APP_ABI := armeabi-v7a

这行语句告诉ndk项目创建使用的android版本为api 10,所以这里会在你使用了早些版本不支持的特性时发出警告,也告诉编译系统生成

ARMv7-A格式的库,这可以支持浮点数和许多android设备的新功能。

更新RendererWrapper

我们必须更新RendererWrapper以调用我们做的native代码,才能看到我们多绘制的东西,如下:

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    GameLibJNIWrapper.on_surface_created();
}
 
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
    GameLibJNIWrapper.on_surface_changed(width, height);
}
 
@Override
public void onDrawFrame(GL10 gl) {
    GameLibJNIWrapper.on_draw_frame();
}
现在渲染类调用我们之前做的 GameLibJNIWrapper  类,调用jni.c的本地方法,再调用game.c。

运行应用

现在可以运行工程了。在创建后会有个libgame.so文件在 /libs/armeabi-v7a/中创建。运行后,程序看起来是这个样子的:

Second pass

屏幕呈现红蓝交替变换。

下期预告

项目的完整代码在这里 GitHub project。更详细的介绍OpenGL ES 2,请参考 Android Lesson One: Getting Started or OpenGL ES 2 for Android: A Quick-Start Guide。

在接下来的章节里,我们将创建一个ios项目。到时你回发现用oc重新使用common文件夹里的代码是件多么容易的事。如果有问题请留言!(作者)



你可能感兴趣的:(在Android中使用NDK调用OpenGl)