Intelligent Technology's Technical Blog

株式会社インテリジェントテクノロジーの技術情報ブログです。

(今さらながら)Google Cardboard と VR Toolkit

こんにちは。中山です。

以前、DODOcase で注文していた Google Cardboard VR Toolkit が、先日やっと届きました。注文してから、1ヶ月半くらいかかりました・・・。

私自身も、注文したことを忘れかけていたんですけど、せっかくですので、これと、それから Google が提供する VR Toolkit ライブラリを用いて、なんか作ってみたいと思います。

Google Cardboard とは

このあいだの Google I/O 2014 で発表されていた、これのことです。
今回購入したのは、DODOcase が、Google Cardboard の作成に必要な一式をキットにして販売していたものです。

実は、Google Cardboard は、その設計図が公開されていて、かつ、段ボール紙やマジックテープなど、わりと身近にある素材で作成することができるので、当初は私も 100円ショップなどで材料を集めて作ろうとしていました。
しかし、

  • 段ボール紙のカットがとても複雑であること(ほんとにレーザーカッターが必要なレベルです)
  • 適切なレンズ部品の入手が難しいこと

から、DODOcase でキットを買ってしまいました。(その結果、1ヶ月半待つことになりました。)
 

Google Cardboard の組み立て

到着した箱を開けると、以下のような部品が入っていました。

f:id:IntelligentTechnology:20140829204856j:plain

これを説明書通りに組み立てて、以下のようになりました。

f:id:IntelligentTechnology:20140830084914j:plain
 

サンプルアプリを試す

Google 公式の Cardboard アプリAndroid スマートフォンにインストールして試してみます。
今回は「Xperia UL SOL22」(Android OS 4.2)を使いました。

イメージとしてはまさにこんな感じ。(1:00 あたりから。)

Google I/O 2014 - Cardboard: VR for Android - YouTube


・・・ものすごい没入感!段ボールだけどバカにできない!あまりやりすぎると、3D酔いします。
Cardboard を通さないで見ると、こんな感じですが、

https://lh3.ggpht.com/53571SuhRRRHVgMiUvh9CAz-5MUKORaWx0JZmM34wP4J9tg4waSjFt_khIfwEnDTjg=h900

Cardboard から覗くと、これが目の前にあるように見えるのです。

ただひとつ残念だったのは、横にあるマグネットのスイッチを切り替えても、何も反応がなかったこと。これはスマートフォンの機種に依存するのかもしれません。
 

オリジナルの 3D コンテンツを作ってみる(つもりでしたが・・・)

普通の場合は、ここまで実現できたところで、「ふーん、こんなもんかー。」で終わりです。
しかし今回は、もう一歩踏み込んで、自分で、Cardboard 用の 3D コンテンツを作ってみたいと思います。

3D コンテンツ作成のために利用するのは、Google が提供している VR Toolkit ライブラリです。

先に紹介しますと、今回作成したアプリのソースはこちら

・・・と言いますか、今回作ったのは、残念ながら、Google が公開しているサンプルほぼそのまま。
違うのは、もともと、Android Studio 用のプロジェクトだったのを、Eclipse 用プロジェクトに変更して、ちょっと日本語対応したくらい。

私自身の、OpenGL ES のスキルが低いために、パパッとオリジナルのアプリを作る、というところまで今回は到達できませんでした・・・。

それでも、せっかくですので、キーポイントとなるところを、少し見ていこうと思います。
 

キモになるポイント

1. VR Toolkit ライブラリの導入

VR Toolkit ライブラリファイル「cardboard.jar」を、こちらから取得して、自分の Android プロジェクトの「libs」フォルダに配置します。

f:id:IntelligentTechnology:20140904151923p:plain

2. CardboardActivity の継承と、CardboardView.StereoRenderer の実装

メインとなる Activity を、VR Toolkit ライブラリが提供する、「CardboardActivity」クラスを継承してつくります。
また、同じく「CardboardView.StereoRenderer」インタフェースを実装します。

public class MainActivity extends CardboardActivity 
                          implements CardboardView.StereoRenderer {
 ・・・

3. onNewFrame メソッド、onDrawEye メソッドのオーバーライド

CardboardView.StereoRenderer インタフェースの「onNewFrameメソッド、「onDrawEyeメソッドをオーバーライドして実装します。

「onNewFrame」メソッドでは、描画の準備、「onDrawEye」メソッドでは、実際の描画の処理を実装します。

仮想空間の描画の際には、

  1. onNewFrame
  2. onDrawEye
  3. onDrawEye
  4. onFinishFrame

の順番で、CardboardView.StereoRenderer インタフェースのメソッドが順番に、繰り返し呼び出されるようです。
「onDrawEye」が2回呼ばれるのは、右目用、左目用を別々に描画しているためのようですね。

今回の実装内容は、ベースとしている Google のサンプルアプリほぼそのままなのですが、
「onNewFrame」メソッドはこんな感じ、

@Override
public void onNewFrame(HeadTransform headTransform) {
  Log.i(TAG, "onNewFrame");
  GLES20.glUseProgram(mGlProgram);

  mModelViewProjectionParam = GLES20.glGetUniformLocation(mGlProgram, "u_MVP");
  mLightPosParam = GLES20.glGetUniformLocation(mGlProgram, "u_LightPos");
  mModelViewParam = GLES20.glGetUniformLocation(mGlProgram, "u_MVMatrix");
  mModelParam = GLES20.glGetUniformLocation(mGlProgram, "u_Model");
  mIsFloorParam = GLES20.glGetUniformLocation(mGlProgram, "u_IsFloor");

  // Build the Model part of the ModelView matrix.
  Matrix.rotateM(mModelCube, 0, TIME_DELTA, 0.5f, 0.5f, 1.0f);
  // Build the camera matrix and apply it to the ModelView.
  Matrix.setLookAtM(mCamera, 0, 0.0f, 0.0f, CAMERA_Z, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);
  headTransform.getHeadView(mHeadView, 0);
  fcheckGLError("onReadyToDraw");
}

「onDrawEye」メソッドはこんな感じです。

@Override
public void onDrawEye(EyeTransform transform) {
  Log.i(TAG, "onDrawEye");

  GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
  mPositionParam = GLES20.glGetAttribLocation(mGlProgram, "a_Position");
  mNormalParam = GLES20.glGetAttribLocation(mGlProgram, "a_Normal");
  mColorParam = GLES20.glGetAttribLocation(mGlProgram, "a_Color");
  GLES20.glEnableVertexAttribArray(mPositionParam);
  GLES20.glEnableVertexAttribArray(mNormalParam);
  GLES20.glEnableVertexAttribArray(mColorParam);
  checkGLError("mColorParam");

  // ユーザが見ている方向にあわせて視点を移動する
  Matrix.multiplyMM(mView, 0, transform.getEyeView(), 0, mCamera, 0);
  // Set the position of the light
  Matrix.multiplyMV(mLightPosInEyeSpace, 0, mView, 0, mLightPosInWorldSpace, 0);
  GLES20.glUniform3f(mLightPosParam, mLightPosInEyeSpace[0], mLightPosInEyeSpace[1], mLightPosInEyeSpace[2]);
  // Build the ModelView and ModelViewProjection matrices
  // for calculating cube position and light.
  Matrix.multiplyMM(mModelView, 0, mView, 0, mModelCube, 0);
  Matrix.multiplyMM(mModelViewProjection, 0, transform.getPerspective(), 0, mModelView, 0);
  drawCube();

  // Set mModelView for the floor, so we draw floor in the correct location
  Matrix.multiplyMM(mModelView, 0, mView, 0, mModelFloor, 0);
  Matrix.multiplyMM(mModelViewProjection, 0, transform.getPerspective(), 0, mModelView, 0);
  drawFloor(transform.getPerspective());
}

特徴的なのは、「onDrawEye」メソッドの中で、

transform.getEyeView()

メソッドを用いて、現在の「視点」の情報を取得しているところですね。
このほかの描画処理に関しては、OpenGL ES に関する、基本的な知識が必要になると思います。
(逆に、OpenGL ES に関する知識があれば、簡単に実装できる、ということですね。)

全体のソースはこちらにあります。
 

次は仮想空間に「触れて」みたい

段ボール製ではありますが、少ないコストで、ここまで本格的な仮想空間を体験できるとは思っていませんでした。
ここまで体験できると、今度は仮想空間にあるものに「触れて」みたい、と思うかもしれません。
このあたりは、Leap Motion が、Oculus Rift と組んで、それっぽいのをやってるみたいですね。

現時点ではまだ「おもちゃ」の領域を出ないかもしれませんが、それにしても大変おもしろい「おもちゃ」だと思いました。