使用NDK进行OpenGL开发

缘由

Android SDK 提供了一套 OpenGL ES 接口,但该接口是基于 Java 的,速度非常慢,往往很难满足需要。
NDK 提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动 将 so和 java 应用一起打包成 apk。使用 NDK,我们可以将要求高性能的应用 逻辑使用C开发,从而提高应用程序的执行效率。如 OpenGL ES 的程序。

对于 NDK,在 Java 代码中调用 C/C++ 代码是通过 JNI 实现的。

Java Native Interface(JNI)是 Java 语言的本地编程接口,在java程序中, 我们可以通过 JNI 实现一些用 java 语言不便实现的功能。通常有以下几种情 况我们需要使用 JNI 来实现:

  • java 类库没有提供你的应用程序所需要的功能,通常这些功能是平台相关的;
  • 你希望使用一些已经有的类库或者应用程序,而他们并非用java语言编写的;
  • 程序的某些部分对速度要求比较苛刻,你选择用汇编或者 c 语言来实现并在 java 语言中调用他们。

注意,不到万不得已不要使用 JNI 技术,一方面它需要你把握更多的知识才可 以驾驭,一方面使用了 JNI 你的程序就会丧失可移植性。

OpenGL ESJava 端核心代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class DemoAct extends Activity {

private GLSurfaceView mGLView;

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

mGLView = new DemoGLSurfaceView(this);
setContentView(mGLView);
}

... ...

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

}


class DemoGLSurfaceView extends GLSurfaceView {

DemoRenderer mRenderer;

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

mRenderer = new DemoRenderer();
setRenderer(mRenderer);
}

class DemoRenderer implements GLSurfaceView.Renderer {

private static native void nativeInit();
private static native void nativeResize(int w, int h);
private static native void nativeRender();
private static native void nativeDone();

public void onSurfaceCreated(GL10 gl, EGLConfig config) {
nativeInit();
}

public void onSurfaceChanged(GL10 gl, int w, int h) {

nativeResize(w, h);
}

public void onDrawFrame(GL10 gl) {
nativeRender();
}



JNI 代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <jni.h>
...

/* Call to initialize the graphics state */
void
Java_com_example_AndMii_DemoRenderer_nativeInit( JNIEnv* env )
{
appInit();
}


/* Call to finalize the graphics state */
void
Java_com_example_AndMii_DemoRenderer_nativeDone( JNIEnv* env )
{
free objects.
}

/* Call to render the next GL frame */
void
Java_com_example_AndMii_DemoRenderer_nativeRender( JNIEnv* env )
{
curTime = ...
appRender(curTime, sWindowWidth, sWindowHeight);

}

C 语言端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
void appInit()
{

glEnable(GL_NORMALIZE);
glEnable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
glShadeModel(GL_FLAT);

glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_LIGHT1);
glEnable(GL_LIGHT2);

glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);

}

// Called from the app framework.
/* The tick is current time in milliseconds, width and height
* are the image dimensions to be rendered.
*/
void appRender(long tick, int width, int height)
{

// Prepare OpenGL ES for rendering of the frame.
prepareFrame(width, height);

// Configure environment.
configureLightAndMaterial();

// Draw all the models normally.
drawModels(1);

}

static void prepareFrame(int width, int height)
{
glViewport(0, 0, width, height);

glClearColorx((GLfixed)(0.1f * 65536),
(GLfixed)(0.2f * 65536),
(GLfixed)(0.3f * 65536), 0x10000);
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);

glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45, (float)width / height, 0.5f, 150);

glMatrixMode(GL_MODELVIEW);

glLoadIdentity();
}

static void configureLightAndMaterial()
{
static GLfixed light0Position[] = { -0x40000, 0x10000, 0x10000, 0 };
static GLfixed light0Diffuse[] = { 0x10000, 0x6666, 0, 0x10000 };
static GLfixed light1Position[] = { 0x10000, -0x20000, -0x10000, 0 };
static GLfixed light1Diffuse[] = { 0x11eb, 0x23d7, 0x5999, 0x10000 };
static GLfixed light2Position[] = { -0x10000, 0, -0x40000, 0 };
static GLfixed light2Diffuse[] = { 0x11eb, 0x2b85, 0x23d7, 0x10000 };
static GLfixed materialSpecular[] = { 0x10000, 0x10000, 0x10000, 0x10000 };

glLightxv(GL_LIGHT0, GL_POSITION, light0Position);
glLightxv(GL_LIGHT0, GL_DIFFUSE, light0Diffuse);
glLightxv(GL_LIGHT1, GL_POSITION, light1Position);
glLightxv(GL_LIGHT1, GL_DIFFUSE, light1Diffuse);
glLightxv(GL_LIGHT2, GL_POSITION, light2Position);
glLightxv(GL_LIGHT2, GL_DIFFUSE, light2Diffuse);
glMaterialxv(GL_FRONT_AND_BACK, GL_SPECULAR, materialSpecular);

glMaterialx(GL_FRONT_AND_BACK, GL_SHININESS, 60 << 16);
glEnable(GL_COLOR_MATERIAL);
}


static void drawModels(float zScale)
{

glVertexPointer(......);
glColorPointer(......);

if (normalArray != NULL)
{
glNormalPointer(GL_FIXED, 0, normalArray);
glEnableClientState(GL_NORMAL_ARRAY);
}
else
glDisableClientState(GL_NORMAL_ARRAY);

glDrawArrays(GL_TRIANGLES, 0, object->count);



}

调试

要想在 jni native 代码中看打印信息,printf 是不行的,需使用 __android_log_print,如下所示。

1
__android_log_print(ANDROID_LOG_INFO, "ProjectName", "I am : %d\n", n);

该函数与 printf 用法相似,使用格式字符。打印的结果通过 logcat 查看。注意,使用时需要将头文件 android/log.h 包含进来。为方便使用,往往定义一些宏

1
2
3
4
5
6
#include <android/log.h>
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, "ProjectName", __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG , "ProjectName", __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO , "ProjectName", __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN , "ProjectName", __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR , "ProjectName", __VA_ARGS__)