之前在学习Android的时候有写过如果在Android中使用OpenGL,当时完全都是用java语言来实现的,现在我们用NDK来实现一次。
实现的思路就是将渲染器中的onDrawFrame,onSurfaceChanged,onSurfaceCreated分别在C中实现,然后将C编译成.so文件之后在Java中直接调用相应的函数就可以了。
步骤就不详细叙述了,代码贴一下。
主Activity:
package com.empty.ndkgl;
import com.example.ndkgl.R;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
public class NdkGlActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GLSurfaceView surface = new GLSurfaceView(this);
surface.setRenderer(new NdkGlRender());
setContentView(surface);
}
static {
//load library
System.loadLibrary("NdkGLRenderer");
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.activity_ndkgl, menu);
return true;
}
}
Render类代码:
package com.empty.ndkgl;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.opengl.GLSurfaceView.Renderer;
public class NdkGlRender implements Renderer{
//declare native function
native private void onNdkSurfaceCreated ();
native private void onNdkSurfaceChanged (int width, int height);
native private void onNdkDrawFrame();
@Override
public void onDrawFrame(GL10 arg0) {
// TODO Auto-generated method stub
onNdkDrawFrame ();
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
// TODO Auto-generated method stub
onNdkSurfaceChanged (width, height);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// TODO Auto-generated method stub
onNdkSurfaceCreated ();
}
}
在工程目录下创建jni文件夹,用下面的命令生成.h文件。
javah -classpath bin/classes -d jni com.empty.ndkgl.NdkGlRender
根据头文件来创建.c文件。
注:虽然生产的 .h文件在编译的时候并没有什么作用,但还是建议做这一步,因为.c文件中的函数名一定要和.h文件中的函数名一致,最后的程序才能正常运行,不然会出现如
java.lang.UnsatisfiedLinkError的bug。
#include <jni.h>
#include <GLES/gl.h>
unsigned int vbo[2];
float positions[12] = {1,-1,0, 1,1,0, -1,-1,0, -1,1,0};
short indices [4] = {0,1,2,3};
JNIEXPORT void JNICALL Java_com_empty_ndkgl_NdkGlRender_onNdkSurfaceCreated (JNIEnv* env, jobject obj)
{
//生成两个缓存区对象
glGenBuffers (2, vbo);
//绑定第一个缓存对象
glBindBuffer (GL_ARRAY_BUFFER, vbo[0]);
//创建和初始化第一个缓存区对象的数据
glBufferData (GL_ARRAY_BUFFER, 4*12, positions, GL_STATIC_DRAW);
//绑定第二个缓存对象
glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, vbo[1]);
//创建和初始化第二个缓存区对象的数据
glBufferData (GL_ELEMENT_ARRAY_BUFFER, 2*4, indices, GL_STATIC_DRAW);
}
JNIEXPORT void JNICALL Java_com_empty_ndkgl_NdkGlRender_onNdkSurfaceChanged(JNIEnv* env, jobject obj, jint width, jint height)
{
//图形最终显示到屏幕的区域的位置、长和宽
glViewport (0,0,width,height);
//指定矩阵
glMatrixMode (GL_PROJECTION);
//将当前的矩阵设置为glMatrixMode指定的矩阵
glLoadIdentity ();
glOrthof(-2, 2, -2, 2, -2, 2);
}
JNIEXPORT void JNICALL Java_com_empty_ndkgl_NdkGlRender_onNdkDrawFrame (JNIEnv* env, jobject obj)
{
//启用顶点设置功能,之后必须要关闭功能
glEnableClientState (GL_VERTEX_ARRAY);
//清屏
glClearColor (0,0,1,1);
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();
glBindBuffer (GL_ARRAY_BUFFER, vbo[0]);
//定义顶点坐标
glVertexPointer (3, GL_FLOAT, 0, 0);
glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, vbo[1]);
//按照参数给定的值绘制图形
glDrawElements (GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT, 0);
//关闭顶点设置功能
glDisableClientState(GL_VERTEX_ARRAY);
}
编写Android.mk
#FileName:Android.mk
#Description:makefile of NdkGl
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := NdkGLRenderer
LOCAL_SRC_FILES := com_empty_ndkgl_NdkGlRender.c
LOCAL_LDLIBS := -lGLESv1_CM
include $(BUILD_SHARED_LIBRARY)
编译库
$NDK_ROOT/ndk-build
在Eclipse中运行程序
通过修改.c的实现,我们就可以绘制不同的图形。
比如网格圆球:
#include <jni.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <GLES/gl.h>
typedef unsigned char byte;
typedef struct {
GLfloat x,y,z;
} XYZ;
float rotateQuad;
#define PI 3.14159265
#define DTOR PI/180
static byte indices[8]={0,1,1,2,2,3,3,0}; //索引数组
void CreateUnitSphere(int dtheta,int dphi)
{
int n;
int theta,phi;
XYZ p[4];
for (theta=-90;theta<=90-dtheta;theta+=dtheta) {
for (phi=0;phi<=360-dphi;phi+=dphi) {
n = 0;
p[n].x = cos(theta*DTOR) * cos(phi*DTOR);
p[n].y = cos(theta*DTOR) * sin(phi*DTOR);
p[n].z = sin(theta*DTOR);
n++;
p[n].x = cos((theta+dtheta)*DTOR) * cos(phi*DTOR);
p[n].y = cos((theta+dtheta)*DTOR) * sin(phi*DTOR);
p[n].z = sin((theta+dtheta)*DTOR);
n++;
p[n].x = cos((theta+dtheta)*DTOR) * cos((phi+dphi)*DTOR);
p[n].y = cos((theta+dtheta)*DTOR) * sin((phi+dphi)*DTOR);
p[n].z = sin((theta+dtheta)*DTOR);
n++;
if (theta >=-90 && theta <= 90) {
p[n].x = cos(theta*DTOR) * cos((phi+dphi)*DTOR);
p[n].y = cos(theta*DTOR) * sin((phi+dphi)*DTOR);
p[n].z = sin(theta*DTOR);
n++;
}
/* Do something with the n vertex facet p */
glVertexPointer(3, GL_FLOAT, 0, p);
glDrawElements(GL_LINES, 8, GL_UNSIGNED_BYTE, indices);
}
}
}
JNIEXPORT void JNICALL Java_com_empty_ndkgl_NdkGlRender_onNdkSurfaceCreated (JNIEnv* env, jobject obj)
{
// 启用阴影平滑
glShadeModel(GL_SMOOTH);
// 黑色背景
glClearColor(0, 0, 0, 0);
// 设置深度缓存
glClearDepthf(1.0f);
// 启用深度测试
glEnable(GL_DEPTH_TEST);
// 所作深度测试的类型
glDepthFunc(GL_LEQUAL);
// 告诉系统对透视进行修正
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);}
JNIEXPORT void JNICALL Java_com_empty_ndkgl_NdkGlRender_onNdkSurfaceChanged(JNIEnv* env, jobject obj, jint width, jint height)
{
//图形最终显示到屏幕的区域的位置、长和宽
glViewport (0,0,width,height);
//指定矩阵
glMatrixMode (GL_PROJECTION);
//将当前的矩阵设置为glMatrixMode指定的矩阵
glLoadIdentity ();
glOrthof(-2, 2, -2, 2, -2, 2);
}
JNIEXPORT void JNICALL Java_com_empty_ndkgl_NdkGlRender_onNdkDrawFrame (JNIEnv* env, jobject obj)
{
//启用顶点设置功能,之后必须要关闭功能
glEnableClientState (GL_VERTEX_ARRAY);
//清屏
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();
glFrontFace(GL_CW);
glRotatef(rotateQuad, 1.0f, 1.0f, 0.0f);//旋转效果
CreateUnitSphere(10,10);
//关闭顶点设置功能
glDisableClientState(GL_VERTEX_ARRAY);
rotateQuad -= 1.5f;
}
立方体
#include <jni.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <GLES/gl.h>
#define col 1.0f
#define pos 1.0f
#define PI 3.14159265
static GLfloat vertex[] = {
-pos,-pos,-pos, /*0*/
-pos,-pos,pos, /*1*/
pos,-pos,pos, /*2*/
pos,-pos,-pos, /*3*/
-pos,pos,-pos, /*4*/
-pos,pos,pos, /*5*/
pos,pos,pos, /*6*/
pos,pos,-pos, /*7*/
};
static GLfloat colors[] = {
col,0,0,col,
0,col,0,col,
0,0,col,col,
col,col,0,col,
col,0,col,col,
0,col,col,col,
0,0,0,col,
col,col,col,col,
};
static GLubyte mindex[] = {
0,2,1, 0,3,2,
5,1,6, 6,1,2,
6,2,7, 7,2,3,
0,4,3, 4,7,3,
4,0,1, 4,1,5,
4,5,6, 4,6,7,
};
static GLfloat angle = 0.0f;
JNIEXPORT void JNICALL Java_com_empty_ndkgl_NdkGlRender_onNdkSurfaceCreated (JNIEnv* env, jobject obj)
{
// 启用阴影平滑
glShadeModel(GL_SMOOTH);
// 黑色背景
glClearColor(0, 0, 0, 0);
// 设置深度缓存
glClearDepthf(1.0f);
// 启用深度测试
glEnable(GL_DEPTH_TEST);
// 所作深度测试的类型
glDepthFunc(GL_LEQUAL);
// 告诉系统对透视进行修正
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
}
static void _gluPerspective(GLfloat fovy, GLfloat aspect, GLfloat zNear, GLfloat zFar)
{
GLfloat top = zNear * ((GLfloat) tan(fovy * PI / 360.0));
GLfloat bottom = -top;
GLfloat left = bottom * aspect;
GLfloat right = top * aspect;
glFrustumf(left, right, bottom, top, zNear, zFar);
}
JNIEXPORT void JNICALL Java_com_empty_ndkgl_NdkGlRender_onNdkSurfaceChanged(JNIEnv* env, jobject obj, jint width, jint height)
{
if (height==0) // 防止被零除
{
height=1; // 将Height设为1
}
glViewport(0, 0, width, height); // 重置当前的视口
glMatrixMode(GL_PROJECTION); // 选择投影矩阵
glLoadIdentity(); // 重置投影矩阵
GLfloat ratio = (GLfloat)width/(GLfloat)height;
// 设置视口的大小
_gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f);
// glOrthof(-2.0f, 2.0f, -2.0f, 2.0f, -2.0f, 2.0f);
glMatrixMode(GL_MODELVIEW); // 选择模型观察矩阵
glLoadIdentity(); // 重置模型观察矩阵
}
JNIEXPORT void JNICALL Java_com_empty_ndkgl_NdkGlRender_onNdkDrawFrame (JNIEnv* env, jobject obj)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(0, 0, -8.0f);
glRotatef(angle, 0, 1.0F, 0);
glRotatef(angle, 0, 0, 1.0F);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glVertexPointer(3,GL_FLOAT,0,vertex);
glColorPointer(4,GL_FLOAT,0,colors);
glDrawElements(GL_TRIANGLES,36,GL_UNSIGNED_BYTE,mindex);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
angle += 1.2f;
}
打完收工。
2013.1.15日更新:
发现一个更强的demo,就在NDK的samples文件夹中,san-angeles就是了!
San Angeles Observation,XX大赛的冠军,原程序只有4K,被google收录到NDK的demo里面了,下面我们就跑一下它。
直接在Eclipse中创建Android工程,选择Android Project From Exiting Code。
直接跑的话会报错,提示无法初始化,我们必须先把c编译成.so.
命令行进入到项目文件夹,执行ndk-build
再修改一下Activity,让它全屏幕现实,只修改onCreate函数就可以了。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mGLView = new DemoGLSurfaceView(this);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(mGLView);
}
运行结果: