Android NDK 用 Google Cardboard のクイックスタート

このガイドでは、Android 用 Cardboard SDK を使用して、独自のバーチャル リアリティ(VR)エクスペリエンスを作成する方法について説明します。

Cardboard SDK を使ってスマートフォンを VR プラットフォームにすることができます。スマートフォンは、立体画像で 3D シーンを表示し、頭の動きをトラッキングして反応し、ユーザーがビューアのボタンを押したことを検出してアプリを操作できます。

まず、HelloCardboard を使用します。これは、Cardboard SDK のコア機能を示すデモゲームです。このゲームでは、ユーザーは仮想世界を見て回ってオブジェクトを見つけて収集します。以下の方法について説明しています。

  • 開発環境をセットアップする
  • デモアプリのダウンロードとビルド
  • Cardboard ビューアの QR コードをスキャンしてパラメータを保存します
  • ユーザーの頭の動きを追跡する
  • 左右の目に正しいビュー投影行列を設定して立体画像をレンダリングする

HelloCardboard は Android NDK を使用します。すべてのネイティブ メソッドには次のものがあります。

  • HelloCardboardApp クラスメソッドに対して一意にバインドされています。
  • そのクラスのインスタンスを作成または削除します

開発環境をセットアップする

ハードウェア要件:

  • Android 8.0「Oreo」(API レベル 26)以降を搭載している Android デバイス
  • Cardboard ビューア

ソフトウェア要件:

  • Android Studio バージョン 2022.1.1「Electric Eel」以降
  • Android SDK 13.0「Tiramisu」(API レベル 33)以降
  • 最新バージョンの Android NDK フレームワーク

    インストールされている SDK を確認または更新するには、[Preferences] > [Appearance and Behavior] に移動します。

    Android Studio の [System Settings] > [Android SDK]

デモアプリのダウンロードとビルド

Cardboard SDK は、各シェーダーにプリコンパイル済みの Vulkan ヘッダー ファイルを使用してビルドされています。ヘッダー ファイルを最初からビルドする手順については、こちらをご覧ください。

  1. 次のコマンドを実行して、GitHub から Cardboard SDK と HelloCardboard デモアプリのクローンを作成します。

    git clone https://github.com/googlevr/cardboard.git
  2. Android Studio で [Open an existing Android Studio Project] を選択し、Cardboard SDK と HelloCardboard デモアプリのクローンを作成したディレクトリを選択します。

    Android Studio の [Project] ウィンドウにコードが表示されます。

  3. Cardboard SDK をアセンブルするには、[Gradle] タブ([View] > [Tool Windows] > [Gradle])の cardboard/:sdk/Tasks/build フォルダ内の [semb] オプションをダブルクリックします。

  4. スマートフォンで HelloCardboard デモアプリを実行します。[Run] > [Run...] を選択し、hellocardboard-android ターゲットを選択します。

QR コードをスキャンする

デバイスのパラメータを保存するには、Cardboard ビューアで QR コードをスキャンします。

ユーザーが [スキップ] を押し、以前に保存したパラメータがない場合、Cardboard は Google Cardboard v1(Google I/O 2014 でリリース)のパラメータを保存します。

デモを試す

HelloCardboard では、3D 空間で測地線的な球体を見つけて収集します。

球体を見つけて集めるには:

  1. いずれかの方向に頭を動かして、浮遊形にします。

  2. 球体を直接見る。これにより、色が変化します。

  3. Cardboard ビューア ボタンを押して球体を「収集」します。

デバイスを設定する

ユーザーが歯車アイコンをタップして Cardboard ビューアを切り替えると、nativeSwitchViewer メソッドが呼び出されます。nativeSwitchViewerCardboardQrCode_scanQrCodeAndSaveDeviceParams を呼び出し、ビューアの QR コードをスキャンするためのウィンドウを開きます。QR コードがスキャンされると、ビューアのレンズ歪みやその他のパラメータが更新されます。

// Called by JNI method
void HelloCardboardApp::SwitchViewer() {
  CardboardQrCode_scanQrCodeAndSaveDeviceParams();
}

Android Studio x86 エミュレータを有効にする

Android Studio x86 エミュレータ用にビルドするには、SDKSamplebuild.gradle ファイルから次の行を削除します。

abiFilters 'armeabi-v7a', 'arm64-v8a'

これにより、すべての ABI が有効になり、生成される .aar ファイルのサイズが大幅に増加します。詳しくは、Android ABI をご覧ください。

ヘッド トラッキング

ヘッド トラッカーを作成

ヘッド トラッカーは、HelloCardboardApp のコンストラクタで 1 回作成されます。

HelloCardboardApp::HelloCardboardApp(JavaVM* vm, jobject obj, jobject asset_mgr_obj) {
  Cardboard_initializeAndroid(vm, obj); // Must be called in constructor
  head_tracker_ = CardboardHeadTracker_create();
}

VrActivity が作成されると、nativeOnCreate メソッドを呼び出して HelloCardboardApp クラスのインスタンスが生成されます。

public void onCreate(Bundle savedInstance) {
  super.onCreate(savedInstance);
  nativeApp = nativeOnCreate(getAssets());
  //...
}

ヘッド トラッカーの一時停止と再開

ヘッド トラッカーを一時停止、再開、破棄するには、それぞれ CardboardHeadTracker_pause(head_tracker_)CardboardHeadTracker_resume(head_tracker_)CardboardHeadTracker_destroy(head_tracker_) を呼び出す必要があります。「HelloCardboard」アプリでは、これらを nativeOnPausenativeOnResumenativeOnDestroy で呼び出します。

// Code to pause head tracker in hello_cardboard_app.cc

void HelloCardboardApp::OnPause() { CardboardHeadTracker_pause(head_tracker_); }

// Call nativeOnPause in VrActivity
@Override
protected void onPause() {
  super.onPause();
  nativeOnPause(nativeApp);
  //...
}

// Code to resume head tracker in hello_cardboard_app.cc
void HelloCardboardApp::onResume() {
  CardboardHeadTracker_resume(head_tracker_);
  //...
}

// Call nativeOnResume in VrActivity
@Override
protected void onResume() {
  super.onResume();
  //...
  nativeOnResume(nativeApp);
}

// Code to destroy head tracker in hello_cardboard_app.cc
HelloCardboardApp::~HelloCardboardApp() {
  CardboardHeadTracker_destroy(head_tracker_);
  //...
}

// Call nativeOnDestroy in VrActivity
@Override
protected void onDestroy() {
  super.onDestroy();
  nativeOnDestroy(nativeApp);
  nativeApp = 0;
}

レンズ歪み

Cardboard が新しい QR コードをスキャンするたびに、以下のコードは保存されたパラメータを読み取り、それらを使用してレンズ歪みオブジェクトを作成します。このオブジェクトにより、レンダリングされたコンテンツに適切なレンズ歪みが適用されます。

CardboardQrCode_getSavedDeviceParams(&buffer, &size);

CardboardLensDistortion_destroy(lens_distortion_);
lens_distortion_ = CardboardLensDistortion_create(
    buffer, size, screen_width_, screen_height_);

CardboardQrCode_destroy(buffer);

レンダリング

Cardboard でコンテンツをレンダリングする手順は次のとおりです。

  • テクスチャの作成
  • 左右の目に対する視野行列と射影行列の取得
  • レンダラの作成と歪みメッシュの設定
  • 各フレームのレンダリング

テクスチャを作成する

すべてのコンテンツはテクスチャに描画され、左右の目用にセクションに分割されます。これらのセクションは、それぞれ _leftEyeTexture_rightEyeTexture で初期化されます。

void HelloCardboardApp::GlSetup() {
  LOGD("GL SETUP");

  if (framebuffer_ != 0) {
    GlTeardown();
  }

  // Create render texture.
  glGenTextures(1, &texture_);
  glBindTexture(GL_TEXTURE_2D, texture_);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, screen_width_, screen_height_, 0,
               GL_RGB, GL_UNSIGNED_BYTE, 0);

  left_eye_texture_description_.texture = texture_;
  left_eye_texture_description_.left_u = 0;
  left_eye_texture_description_.right_u = 0.5;
  left_eye_texture_description_.top_v = 1;
  left_eye_texture_description_.bottom_v = 0;

  right_eye_texture_description_.texture = texture_;
  right_eye_texture_description_.left_u = 0.5;
  right_eye_texture_description_.right_u = 1;
  right_eye_texture_description_.top_v = 1;
  right_eye_texture_description_.bottom_v = 0;

  //...
  CHECKGLERROR("GlSetup");
}

これらのテクスチャは、パラメータとして CardboardDistortionRenderer_renderEyeToDisplay に渡されます。

左目と右目の視野行列と射影行列を取得します

まず、左右の目の目の行列を取得します。

CardboardLensDistortion_getEyeFromHeadMatrix(
    lens_distortion_, kLeft, eye_matrices_[0]);
CardboardLensDistortion_getEyeFromHeadMatrix(
    lens_distortion_, kRight, eye_matrices_[1]);
CardboardLensDistortion_getProjectionMatrix(
    lens_distortion_, kLeft, kZNear, kZFar, projection_matrices_[0]);
CardboardLensDistortion_getProjectionMatrix(
    lens_distortion_, kRight, kZNear, kZFar, projection_matrices_[1]);

次に、それぞれの目の歪みメッシュを取得して、歪みレンダラに渡します。

CardboardLensDistortion_getDistortionMesh(lens_distortion_, kLeft, &left_mesh);
CardboardLensDistortion_getDistortionMesh(lens_distortion_, kRight, &right_mesh);

レンダラを作成して正しい歪みメッシュを設定する

レンダラを初期化する必要があるのは 1 回だけです。レンダラが作成されたら、CardboardLensDistortion_getDistortionMesh 関数から返されたメッシュ値に従って、左右の目に新しい歪みメッシュを設定します。

distortion_renderer_ = CardboardOpenGlEs2DistortionRenderer_create();
CardboardDistortionRenderer_setMesh(distortion_renderer_, &left_mesh, kLeft);
CardboardDistortionRenderer_setMesh(distortion_renderer_, &right_mesh, kRight);

コンテンツのレンダリング

フレームごとに、CardboardHeadTracker_getPose から現在の頭の向きを取得します。

CardboardHeadTracker_getPose(head_tracker_, monotonic_time_nano, &out_position[0], &out_orientation[0]);

現在の頭の向きをビュー行列と投影行列で使用して、それぞれの目に対するビュー射影行列を作成し、コンテンツを画面にレンダリングします。

// Draw eyes views
for (int eye = 0; eye < 2; ++eye) {
  glViewport(eye == kLeft ? 0 : screen_width_ / 2, 0, screen_width_ / 2,
             screen_height_);

  Matrix4x4 eye_matrix = GetMatrixFromGlArray(eye_matrices_[eye]);
  Matrix4x4 eye_view = eye_matrix * head_view_;

  Matrix4x4 projection_matrix =
      GetMatrixFromGlArray(projection_matrices_[eye]);
  Matrix4x4 modelview_target = eye_view * model_target_;
  modelview_projection_target_ = projection_matrix * modelview_target;
  modelview_projection_room_ = projection_matrix * eye_view;

  // Draw room and target. Replace this to render your own content.
  DrawWorld();
}

CardboardDistortionRenderer_renderEyeToDisplay を使用してコンテンツに歪み補正を適用し、コンテンツを画面にレンダリングします。

// Render
CardboardDistortionRenderer_renderEyeToDisplay(
    distortion_renderer_, /* target_display = */ 0, /* x = */ 0, /* y = */ 0,
    screen_width_, screen_height_, &left_eye_texture_description_,
    &right_eye_texture_description_);