どのような仕組みで動いているか実際にソースコードを見ながら確認していきましょう。「src」以下には以下の5つのパッケージがあります。
アプリを起動したとき、最初に呼ばれるのが「edu.dhbw.andar.pub.CustomActivity.onCreate()」です。このクラスはAndARActivityを継承しています。
「super.onCreate(savedInstanceState)」で「edu.dhbw.andar.AndARActivity.onCreate()」を呼び、さまざまな初期化処理を行います。その後、「super.setNonARRenderer(renderer)」でレンダラを設定し、「artoolkit.registerARObject(someObject)」で「どのマーカーに対してどのような3Dモデルを描画するのか」という情報を登録しています。
AndARは「AndARActivity.onCreate()」を基点としてARを実現するためのコアな処理を次々と実行していきます。そのため、ここからは「AndARActivity.onCreate()」がどのような処理をしているのか詳しく見ていきます。
処理の流れは、以下のようになっています。
つまり、AndARActivityはカメラプレビューとレンダリングの設定を行っていることになります。これにより、「CameraPreviewHandler.onPreviewFrame()」「AndARRenderer.onDrawFrame()」を定期的に呼ぶようになります。
基本的には、この2つのメソッドが協調して動作することにより、カメラから取得したプレビュー画像上に3Dモデルを表示できます。
具体的には、以下の流れで処理を実行します。
ここからは、「CameraPreviewHandler.onPreviewFrame()」「AndARRenderer.onDrawFrame()」について、それぞれ詳しく見ていきましょう。
まず、CameraPreviewHandler.onPreviewFrame()のソースコードを確認します。
@Override
public synchronized void onPreviewFrame(byte[] data, Camera camera) {
//prevent null pointer exceptions
if (data == null)
return;
if(cam==null)
cam = camera;
//camera.setPreviewCallback(null);
convWorker.nextFrame(data);
markerInfo.detectMarkers(data);
}
ここでは、カメラプレビューを取得し、取得したデータを利用して、それぞれ以下の処理を行っています。
convWorker.nextFrame(data);
markerInfo.detectMarkers(data);
この2つの処理ではThreadを継承した「convWorker」「detectMarkerWorker」というスレッドが動作しています。この2つの処理についてそれぞれ詳しく説明していきます。
まず、「convWorker.nextFrame(data)」について説明します。convWorkerはThreadクラスを継承した「ConversionWorker」というクラスのインスタンスで、カメラプレビューから取得した「YCbCr 4:2:0 SP」という形式のデータをRGB形式に変換する処理を行っています。
このとき、カメラプレビューは定期的に取得され続けるので、1フレームずつ取り出して変換処理に渡す必要があるのですが、すべてのフレームを変換するのはパフォーマンスの面で効率が良くありません。そのため、変換処理が完了するまで新規のフレームを破棄し、受け付けないようにしています。
その制御を行っているのがconvWorker.nextFrame(data)です。このようにconvWorkerではカメラから取得したプレビューのデータを次々とRGB形式に変換していきます。変換したデータは保持され、レンダリング処理時に利用されます。
次に、「markerInfo.detectMarkers(data)」について説明します。markerInfoはARToolkitクラスのインスタンスなので、ARToolkitクラスの「detectMarkers(data)」が呼ばれます。その中で、「detectMarkerWorker.nextFrame(image)」が呼ばれます。
detectMarkerWorkerは、Threadクラスを継承した「DetectMarkerWorker」というクラスのインスタンスです。detectMarkerWorkerはカメラプレビューのデータからマーカーの検出と変換行列の計算を行っています。具体的には以下のメソッドで行います。
artoolkit_detectmarkers(curFrame, transMatMonitor);
このネイティブメソッドの戻り値によりマーカーを検出したかどうかを判断し、検出した場合にはARObjectクラスの「visible」をtureとします。また、このとき変換行列の計算を行い、ARObjectクラスのglMatrixに格納しています。
convWorkerと同様に、カメラから取得可能なプレビューデータ全てからマーカー検出をするのは非効率なので、「detectMarkerWorker.nextFrame(image)」でマーカーを検出するフレームを制御しています。
まとめると、CameraPreviewHandler.onPreviewFrame()ではプレビューのデータを取得し、2つのスレッドを使って以下の処理を行っています。
ここまでで、カメラプレビューの取得とプレビュー内でのマーカーの判定のロジックを確認していきました。次は、実際にマーカー検出時の3Dモデルのレンダリングについて見ていきます。
ここからは、マーカー検出時に、どのように3Dモデルをレンダリングしているのかについて説明します。
3Dモデルのレンダリングには「AndARRenderer」クラスを利用します。先ほど、CustomAcitvityのonCreateでの初期設定により「AndARRenderer.onDrawFrame()」を定期的に呼ぶことは説明しました。
「AndARRenderer.onDrawFrame()」では、具体的に何をしているのかソースコードを確認してみましょう。
@Override
public final void onDrawFrame(GL10 gl) {
// カメラから取得したデータを2Dのテクスチャにしてレンダリングする処理
……(省略)……
//3Dモデルをレンダリングする処理
if(customRenderer != null)
customRenderer.setupEnv(gl);
else {
// 光源の設定
gl.glEnable(GL10.GL_LIGHTING);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, ambientLightBuffer);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, diffuseLightBuffer);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPECULAR, specularLightBuffer);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, lightPositionBuffer);
gl.glEnable(GL10.GL_LIGHT0);
}
markerInfo.draw(gl);
// CustomRendererに描画処理が書かれていれば実行
if(customRenderer != null)
customRenderer.draw(gl);
//スクリーンショットを取得する処理
……(省略)……
}
AndARRenderer.onDrawFrame()では大きく3つの処理を行っています。
今回は「2. 3Dモデルをレンダリングする」についてのみ説明します。3Dモデルをレンダリングしているのは、以下のメソッドです。
markerInfo.draw(gl);
markerInfoはedu.dhbw.andar.ARToolkitクラスのインスタンスです。「ARToolkit.draw()」は以下のようになっており、ARObjectクラスの配列「arobjects」内にあるARObjectのインスタンスを1つずつ取り出して、obj.isVisible()がtrueであるかチェックしています。
public final void draw(GL10 gl) {
if(initialized) {
if(Config.DEBUG)
Log.i("MarkerInfo", "going to draw opengl stuff now");
for (ARObject obj : arobjects) {
if(obj.isVisible())
obj.draw(gl);
}
}
}
arobjectsへのARObjectインスタンスの格納は、「edu.dhbw.andar.pub.CustomActivity」で行います。
@Override
public void onCreate(Bundle savedInstanceState) {
try {
someObject = new CustomObject("test", "patt.hiro", 80.0, new double[]{0,0});
artoolkit.registerARObject(someObject);
} catch (AndARException ex){
//handle the exception, that means: show the user what happened
System.out.println("");
}
}
具体的には、「artoolkit.registerARObject(someObject)」を呼ぶことによって、引数のsomeObjectをarobjectsに格納します。
someObjectはARObjectクラスのサブクラスであるCustomObjectのインスタンスで、インスタンス生成時に検出したいマーカーの種類を第2引数(patt.hiro)で渡しています。ここで指定したマーカーを検出した場合には、someObject.isVisible()が「true」となります。
つまり、ARToolkit.draw()でのobjはCustomObjectのインスタンスであり、マーカーを検出した場合にはobj.isVisible()がtrueとなるため、CustomObjectのdraw()を呼ぶことになります。以下はCustomObject.draw()のコードです。
@Override
public final void draw(GL10 gl) {
super.draw(gl);
// 質感の設定
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR,mat_flash);
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, mat_flash_shiny);
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, mat_diffuse);
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, mat_ambient);
//draw cube
gl.glColor4f(0, 1.0f, 0, 1.0f);
gl.glTranslatef( 0.0f, 0.0f, 12.5f );
box.draw(gl);
}
CustomObjectは3Dモデルの描画を一手に引き受けています。
CustomObjectはARObjectを継承しているので、super.draw(gl)が呼ばれると、edu.dhbw.andar.ARObject.draw()を実行します。マーカー上に3Dモデルを表示するにはマーカーから検出した変換行列を適用する必要がありますが、それらの処理はARObject.draw()が行います。
public synchronized void draw(GL10 gl) {
if(!initialized) {
init(gl);
initialized = true;
}
if(glCameraMatrixBuffer != null) {
glMatrixBuffer.put(glMatrix);
glMatrixBuffer.position(0);
//argDrawMode3D
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
//argDraw3dCamera
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadMatrixf( glCameraMatrixBuffer );
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadMatrixf(glMatrixBuffer);
}
}
具体的には、「glMatrixBuffer.put(glMatrix)」で、検出したマーカー情報から取得した変換行列を適用しています。変換行列の適用が終われば、後は3Dモデルを描画するだけでマーカー上に3Dモデルを表示するようになります。
「super.draw(gl)」を呼ぶだけで変換行列の適用まで行うので、ARObjectのサブクラスのdrawメソッドではsuper.draw(gl)を呼んだ後は、3Dモデルの描画のみに意識をすれば良くなります。
今回、描画すべき3Dモデルは立方体ですが、それはbox.draw(gl)を呼ぶことにより実現します。詳細は割愛しますが、このメソッドはOpenGL ESを使って立方体を描画します。
AndARでは、このようにして、カメラから取得した画像をレンダリングした後、その画像内のマーカー上に3Dモデルをレンダリングすることによってマーカー型のARを実現しています。
次ページでは、ここで中身を紹介したサンプルアプリをカスタマイズしてオリジナルにする方法を解説します。
Copyright © ITmedia, Inc. All Rights Reserved.