Android NDK 版 Google Cardboard 快速入门

本指南介绍了如何使用 Cardboard SDK 让 Android 打造您自己的虚拟现实 (VR) 体验。

您可以使用 Cardboard SDK 将智能手机转变为 VR 平台。智能手机 可以显示具有立体渲染效果的 3D 场景,跟踪头部动作并做出反应, 以及通过检测用户何时按下观看器按钮来与应用互动。

首先,您将使用 HelloCardboard,这是一款演示游戏,演示了 Cardboard SDK 的各项功能。在游戏中,用户环顾虚拟世界寻找和 收集对象。该指南将介绍如何执行以下操作:

  • 设置您的开发环境
  • 下载并构建演示版应用
  • 扫描 Cardboard 观看器的二维码以保存其参数
  • 跟踪用户头部的移动
  • 通过为每只眼睛设置正确的视图投影矩阵,渲染立体图像

HelloCardboard 使用 Android NDK。每种原生方法都具有以下特点:

  • 唯一限定于某个 HelloCardboardApp 类方法,或
  • 创建或删除该类的实例

设置您的开发环境

硬件要求:

  • 搭载 Android 8.0“Oreo”的 Android 设备(API 级别 26)或更高版本
  • Cardboard 眼镜

软件要求:

  • Android Studio 版本 2022.1.1“Electric Eel”或更高版本
  • Android SDK 13.0“提拉米苏”(API 级别 33)或更高版本
  • 最新版本的 Android NDK 框架

    如需查看或更新已安装的 SDK,请前往偏好设置 >外观和行为

    系统设置 >Android Studio 中的 Android SDK

下载并构建演示版应用

Cardboard SDK 是使用预编译的 Vulkan 构建的 每个着色器的头文件。如需从头开始构建头文件的步骤,请参阅 此处

  1. 运行以下命令以克隆 Cardboard SDK 和 HelloCardboard 演示版 应用:

    git clone https://github.com/googlevr/cardboard.git
  2. 在 Android Studio 中,选择 Open an existing Android Studio Project,然后选择 目录中。

    您的代码将显示在 Android Studio 的“Project”窗口中。

  3. 要组装 Cardboard SDK,请双击内的组装选项 cardboard/:sdk/Tasks/build 文件夹(位于“Gradle”标签页中,依次选择“View”>“Tool Windows”>“Gradle”)。

  4. 依次选择 Run > 在手机上运行 HelloCardboard 演示应用Run...,然后 选择hellocardboard-android目标

扫描二维码

要保存设备参数,请扫描 Cardboard 观看器上的二维码:

如果用户按“跳过”并且之前没有保存任何参数 Google Cardboard v1(2014 年 Google I/O 大会上发布)的参数。

试用演示

在 HelloCardboard 中,您将在 3D 空间中查找和收集测地球体。

要查找并收集球体,请执行以下操作:

  1. 向任意方向转动头部,直到您看到浮动的形状。

  2. 直视球体。这会使它更改颜色。

  3. 按下 Cardboard 眼镜按钮即可“收集”球体。

配置设备

当用户点按齿轮图标切换 Cardboard 眼镜时,nativeSwitchViewer 方法。nativeSwitchViewer 次通话 CardboardQrCode_scanQrCodeAndSaveDeviceParams,用于打开要扫描的窗口 观看者的二维码。查看器的镜头失真情况和其他参数只更新一次 二维码已扫描完毕。

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

启用 Android Studio x86 模拟器

如需针对 Android Studio x86 模拟器进行构建,请从 SDK 中的 build.gradle 文件 和示例

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

这会启用所有 ABI,并显著增加所生成 .aar 文件。请参阅 Android ABI

头部跟踪

创建头部跟踪器

头部跟踪器在 HelloCardboardApp 的构造函数中创建一次:

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

创建 VrActivity 后,系统会生成 HelloCardboardApp 类的实例 方法是调用 nativeOnCreate 方法:

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 扫描新的二维码时,下面的代码都会读取已保存的参数 然后使用这些物体来创建镜头失真对象,从而应用适当的镜头失真效果 附加到渲染内容:

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);

创建渲染程序并设置正确的失真网格

渲染程序只需初始化一次。创建渲染程序后,设置新的 左眼和右眼的失真网格, 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_);