本指南介绍了如何使用 Android 版 Cardboard SDK 打造您自己的虚拟实境 (VR) 体验。
您可以使用 Cardboard SDK 将智能手机转变为 VR 平台。智能手机可以通过立体呈现来显示 3D 场景,跟踪头部移动并做出响应,并通过检测用户何时按下观看器按钮来与应用进行交互。
首先,您将使用 HelloCardboard,这是一款演示游戏,演示了 Cardboard SDK 的核心功能。在游戏中,用户环顾虚拟世界来查找和收集物品。该指南介绍了如何执行以下操作:
- 设置开发环境
- 下载并构建演示版应用
- 扫描 Cardboard 观看器的二维码以保存其参数
- 跟踪用户头部活动
- 通过为每只眼睛设置正确的视图投影矩阵渲染立体图像
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 头文件构建的。如需了解从头开始构建头文件的步骤,请点击此处。
运行以下命令,以便从 GitHub 克隆 Cardboard SDK 和 HelloCardboard 演示应用:
git clone https://github.com/googlevr/cardboard.git
在 Android Studio 中,选择 Open an existing Android Studio Project,然后选择要将 Cardboard SDK 和 HelloCardboard 演示应用克隆到的目录。
您的代码将显示在 Android Studio 的“Project”窗口中。
如需组建 Cardboard SDK,请双击 Gradle 标签页的 cardboard/:sdk/Tasks/build 文件夹(视图 > 工具窗口 > Gradle)中的 compose 选项。
在手机上运行 HelloCardboard 演示应用,方法是依次选择运行 > 运行...,然后选择
hellocardboard-android
目标。
扫描二维码
若要保存设备参数,请扫描 Cardboard 观看器上的二维码:
如果用户按“跳过”,且之前未保存任何参数,Cardboard 会保存 Google Cardboard v1(在 2014 年 Google I/O 大会上发布)参数。
试用演示版
在 HelloCardboard 中,你可以在 3D 空间中查找并收集测地球体。
要找到并收集球体,请执行以下操作:
朝任意方向移动头部,直至您看到一个浮动的形状。
直视球体。这会导致其更改颜色。
按下 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
后,系统会通过调用 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”应用中,我们在 nativeOnPause
、nativeOnResume
和 nativeOnDestroy
中调用它们:
// 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_);