Android 版 Google Cardboard 快速入门

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。

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

您可以使用 Cardboard SDK 将智能手机转变成 VR 平台。智能手机可以呈现立体呈现的 3D 场景、跟踪头部移动并做出反应,还能通过检测用户何时按观看者按钮来与应用互动。

首先,您将使用 HelloHelloboard 演示版,该演示游戏演示了 Cardboard SDK 的核心功能。在游戏中,用户可以环游虚拟世界来查找和收集物品。该指南介绍了如何执行以下操作:

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

HelloCardboard 使用 Android NDK。每种原生方法都是:

  • 绑定到 HelloCardboardApp 类方法,或
  • 创建或删除该类的实例

设置您的开发环境

硬件要求:

  • 搭载 Android 7.0“Nougat”(API 级别 24)或更高版本的 Android 设备
  • Cardboard 查看器

软件要求:

  • Android Studio 4.2.2 或更高版本
  • Android SDK 11.0“R”(API 级别 30)或更高版本
  • 最新版本的 Android NDK 框架

    如需查看或更新已安装的 SDK,请依次转到偏好设置外观和行为

    系统设置 > 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 文件夹中的 compile 选项。

  4. 在手机上依次选择 RunRun...,然后选择 hellocardboard-android 目标,从而在您的手机上运行 HelloCardboard 演示应用。

扫描二维码

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

如果用户按“SKIP”,且之前没有保存任何参数,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();
}

头部跟踪

创建头部智能设备

头部跟踪器在 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 后,通过调用 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;
}

镜头失真

每次扫描新的二维码时,以下代码都会读取已保存的参数,并使用这些参数创建镜头失真对象,该对象会将适当的镜头失真应用于渲染的内容:

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