Step1 OpenGLの導入とセンサーによる紐付け
1.プロジェクトの作成とManifestの設定
はじめにプロジェクトの作成を行います。各設定項目は以下のように設定してください。
プロジェクト名 | ARGame |
Build Target | Google APIs(Android 2.2) |
Application name | ARGame |
Package name | com.example.argame |
Activity | ARGame |
Min SDK Version | 7 |
続いてAndroidManifest.xmlの設定を行います。
AndroidManifest.xmlを開きます。画面を横向きで実行するために、アプリケーションタブを開き、左下Application NodesよりARGame(Activity)を選択、Screen orientationの項目をlandscapeに設定します。
次にバイブレーションの機能とカメラを使うために、許可のタブを開き、追加、Uses Permissionよりandroid.permission.Camera、android.permission.VIBRATEの2つを登録します。
それでは実際にプログラムを作成していきましょう。
2.Viewの重ね合わせ
まず始めに、OpenGL ESの描画を行う画面の重ね合わせを行います。これは、前回も行なったViewの重ねあわせで実現できます。処理の流れは、OpenGLの描画を行うGLSurfaceViewの作成、描画処理を記述するGLRendererの登録、Viewの重ね合わせとなります。
ARGame.javaのメソッドonCreateは以下のようになります。
private SensorManager sensorManager; private float[] accelerometerValues= new float[3]; private float[] magneticValues = new float[3]; private ARView arView; private GLSurfaceView mGLSurfaceView; private GLRenderer mGLRenderer; private List |
(1)ARViewの取得
前回までARView内では、Canvasを用いた情報の描画を行いました。今回も同様に、2次元での描画はこのクラスで行います。
(2)各種センサーの用意
こちらも前回と同様に、カメラがどこを向いているかの計算を行うために、加速度、磁気センサーを使用します。
(3)OpenGLの用意
mGLSurfaceViewはOpenGLの描画を行うViewとなります。描画処理の内容はGLSurfaceView.Rendererを実装したクラスに記述します。mGLSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT)によって背景を透過します。
(4)Viewの重ね合わせ
作成したViewを重ねます。直感的にはCameraViewの上にGLSurfaceViewを重ねるのが正しいように思えますが、実際にはGLSurfaceViewが一番下になります。また、CameraViewについては、特に変更点もないので前回の内容のものをそのまま使用します。
3.OpenGLの描画
OpenGLの描画処理を行うGLRenderer.javaを作成します。3次元空間中のカメラ設定や、敵の描画処理を記述します。
class GLRenderer implements GLSurfaceView.Renderer {
private Context mContext;
privatee Vibrator vibrator;
float camThetaXZ;
float camThetaY;
final float CAMERA_R = 10;
private Enemy enemy;
public GLRenderer(Context context) {
mContext = context;
// 振動器の用意
vibrator = (Vibrator) mContext
.getSystemService(Context.VIBRATOR_SERVICE);
initialize();
}
private void initialize() {
enemy = new Enemy();
}
// (1)描画処理
public void onDrawFrame(GL10 gl) {
// 描画処理を行う前にバッファの消去を行う
// GL_COLOR_BUFFER_BITではカラーバッファを
// GL10.GL_DEPTH_BUFFER_BITでは陰面消去に使われるデプスバッファを
// 指定しています
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
// 演算対象をモデルビューにする
gl.glMatrixMode(GL10.GL_MODELVIEW);
// 演算行列を単位行列にする
gl.glLoadIdentity();
// (2)端末の向きから視点を計算する
float centerY = CAMERA_R * (float) Math.sin(camThetaY);
float centerX = CAMERA_R * (float) Math.cos(camThetaY)
* (float) Math.cos(-camThetaXZ);
float centerZ = CAMERA_R * (float) Math.cos(camThetaY)
* (float) Math.sin(-camThetaXZ);
// カメラの位置と、視点の中心を指定する
GLU.gluLookAt(gl, 0, 0, 0, centerX, centerY, centerZ, 0f, 1f, 0f);
// 頂点配列を有効化します
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
// カラー配列を有効化します
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
// 敵を描画します
enemy.draw(gl);
}
public void onSurfaceChanged(GL10 gl, int width, int height) {
// 描画領域が変更されたときに呼び出されます
// 描画を領域行う領域を指定します
// ここでは画面全体を指定しています
gl.glViewport(0, 0, width, height);
float ratio = (float) width / height;
// 射影行列を選択する状態にします
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();
// 射影方法を遠近法を使用する透視射影として描画領域を指定します
gl.glFrustumf(-ratio, ratio, -1.0f, 1.0f, 1f, 10000f);
// ディザ処理を無効化し、なめらかな表示を行います
gl.glDisable(GL10.GL_DITHER);
// 透視射影の程度を処理速度を重視したものを指定します
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);
// 背景を透明に設定します
gl.glClearColor(0, 0, 0, 0);
// ポリゴンの背面を描画しなようにします
gl.glEnable(GL10.GL_CULL_FACE);
// 面の描画をなめらかにするようにします
gl.glShadeModel(GL10.GL_SMOOTH);
// デプスバッファを有効化します
gl.glEnable(GL10.GL_DEPTH_TEST);
// テクスチャを有効化します
gl.glEnable(GL10.GL_TEXTURE_2D);
}
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// 描画領域が作成されたときに呼び出されます
}
// (3)センサー情報をセット
public void setState(float thetaXZ, float thetaY) {
// カメラの角度を取得します
camThetaXZ = -thetaXZ;
camThetaY = (float) (-Math.PI / 2 - thetaY);
}
}
|
(1)描画処理
onDrawFrameはフレームの描画を行うメソッドで、描画専用のスレッドから自動的に呼び出されます。ここでは各種設定、カメラの設定と、敵の描画を行なっています。
(2)端末の向きから視点を計算する
カメラの設定はGLU.gluLookAtによって行います。引数はGLオブジェクト、カメラの設置座標(x,y,z)、カメラの目標座標(x,y,z),どの座標系を上にするかの指定となっています。そのため、取得した端末の向きから半径をCAMERA_Rとする球面の座標を計算し、得られた座標をカメラの目標座標とします。
(3)センサーの情報をセット
センサーから得られた角度情報をセットします。
また描画される敵を表現するEnemy.javaの内容は以下のようになります。
package com.example.slaptheghost; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; import javax.microedition.khronos.opengles.GL10; class Enemy { final float ENEMY_DISTANCE = 10f; private float posX; private float posY; private float posZ; private float rotateZ; private float rotateY; private float velocity; private double theta; private double phi; private IntBuffer mVertexBuffer; private IntBuffer mColorBuffer; private ByteBuffer mIndexBuffer; public Enemy() { velocity = 0.01f; initPosition(); int one = 0x10000; int vertices[] = { -one, -one, -one, one, -one, -one, one, one, -one, -one, one, -one, -one, -one, one, one, -one, one, one, one, one, -one, one, one, }; int colors[] = { 0, 0, 0, one, one, 0, 0, one, one, one, 0, one, 0, one, 0, one, 0, 0, one, one, one, 0, one, one, one, one, one, one, 0, one, one, one, }; byte indices[] = { 0, 4, 5, 0, 5, 1, 1, 5, 6, 1, 6, 2, 2, 6, 7, 2, 7, 3, 3, 7, 4, 3, 4, 0, 4, 7, 6, 4, 6, 5, 3, 0, 1, 3, 1, 2 }; ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4); vbb.order(ByteOrder.nativeOrder()); mVertexBuffer = vbb.asIntBuffer(); mVertexBuffer.put(vertices); mVertexBuffer.position(0); ByteBuffer cbb = ByteBuffer.allocateDirect(colors.length * 4); cbb.order(ByteOrder.nativeOrder()); mColorBuffer = cbb.asIntBuffer(); mColorBuffer.put(colors); mColorBuffer.position(0); mIndexBuffer = ByteBuffer.allocateDirect(indices.length); mIndexBuffer.put(indices); mIndexBuffer.position(0); } public void draw(GL10 gl) { gl.glPushMatrix(); // 移動 gl.glTranslatef(posX, posY, posZ); // 回転 gl.glRotatef(rotateY, 0, 1, 0); gl.glRotatef(rotateZ, 0, 0, 1); gl.glFrontFace(GL10.GL_CW); gl.glVertexPointer(3, GL10.GL_FIXED, 0, mVertexBuffer); gl.glColorPointer(4, GL10.GL_FIXED, 0, mColorBuffer); gl.glDrawElements(GL10.GL_TRIANGLES, 36, GL10.GL_UNSIGNED_BYTE, mIndexBuffer); gl.glPopMatrix(); } public void initPosition() { // 座標を初期化する theta = Math.PI * Math.random() - Math.PI / 2; phi = 2 * Math.PI * Math.random(); posY = (float) (ENEMY_DISTANCE * Math.sin(theta)); posX = (float) (ENEMY_DISTANCE * Math.cos(theta) * Math.cos(phi)); posZ = (float) (ENEMY_DISTANCE * Math.cos(theta) * Math.sin(phi)); rotateY = (float) Math.toDegrees(-phi); rotateZ = (float) Math.toDegrees(theta); } } |
今回は敵として四角形を描画する内容になっています。
xz平面の角度となるphiとxz平面とy軸との角度となるthetaを乱数から取得して初期位置を決定します。
Android™ で使用されるOpenGLでは、リッチなAPIが少ないため、簡単に利用できるとは言いがたいです。しかし、3D描画を高速に行えるというのはやはり魅力的と言えます。興味のある方は、ぜひOpenGLについて調べてみて下さい。
4.センサー情報の受け渡し
描画の準備が整ったので、実際にGLRendererにセンサー情報を渡す処理を追加し、プログラムを実行してみましょう。
@Override public void onSensorChanged(SensorEvent event) { switch (event.sensor.getType()) { case Sensor.TYPE_ACCELEROMETER: accelerometerValues = event.values.clone(); break; case Sensor.TYPE_MAGNETIC_FIELD: magneticValues = event.values.clone(); break; } if (magneticValues != null && accelerometerValues != null) { float[] R = new float[16]; float[] I = new float[16]; SensorManager.getRotationMatrix(R, I, accelerometerValues, magneticValues); float[] actual_orientation = new float[3]; SensorManager.getOrientation(R, actual_orientation); mGLRenderer.setState(actual_orientation[0], actual_orientation[2]); } } |
こちらも、前回の内容とほとんど同じとなっています。今回は平面の情報となる方位に加えて、端末の傾きとしてロールを使用しています。
プログラムを実行するとオブジェクトが配置されていることが確認できると思います。カメラを左右上下に向けることで3次元空間に配置されているように見えることが確認出来ます。
第11回 ARゲームを作ってみよう(3)
Step1 OpenGLの導入とセンサーによる紐付け
Step2 敵の動作と終了判定
Step3 OpenGLのタップ処理とスコア表示