本指南說明如何使用 Android 版 Cardboard SDK 建立自己的虛擬實境 (VR) 體驗。
您可以使用 Cardboard SDK 將智慧型手機變成 VR 平台。智慧型手機可以顯示立體算繪的 3D 場景、追蹤頭部動作並做出反應,以及在偵測到使用者按下觀看者按鈕時與應用程式互動。
首先,請使用 HelloCardboard。這款示範遊戲會展示 Cardboard SDK 的核心功能。在遊戲中,使用者會環顧虛擬世界,尋找並收集物件。這份指南說明如何:
- 設定開發環境
- 下載並建構試用版應用程式
- 掃描 Cardboard 觀影盒的 QR code,儲存其參數
- 追蹤使用者的頭部動作
- 為每隻眼睛設定正確的檢視投影矩陣,即可轉譯立體影像
HelloCardboard 使用 Android NDK。每種原生方法都具有下列特點:
- 唯一繫結至
HelloCardboardApp
類別方法,或 - 建立或刪除該類別的執行個體
設定開發環境
硬體需求:
- 搭載 Android 8.0「Oreo」(API 級別 26) 以上版本的 Android 裝置
- Cardboard 觀影盒
軟體需求:
- Android Studio 2024.1.2「Koala 功能推送」以上版本
- Android SDK 15.0「香草冰淇淋」(API 級別 35) 以上版本
最新版 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 分頁 (依序點選「View」>「Tool Windows」>「Gradle」) 的 cardboard/:sdk/Tasks/build 資料夾中,按兩下「assemble」選項。
依序選取「Run」 >「Run...」,然後選取
hellocardboard-android
目標,在手機上執行 HelloCardboard 試用版應用程式。
掃描 QR code
如要儲存裝置參數,請掃描 Cardboard 觀影盒上的 QR code:
如果使用者按下「略過」,且先前未儲存任何參數,Cardboard 會儲存 Google Cardboard v1 (在 2014 年 Google I/O 大會上推出) 參數。
立即試用
在 HelloCardboard 中,您會在 3D 空間中尋找並收集測地線球體。
如何尋找及收集球體:
朝任何方向移動頭部,直到看到浮動形狀。
直視球體。因此會變更顏色。
按下 Cardboard 觀影盒按鈕「收集」球體。
設定裝置
使用者輕觸齒輪圖示切換 Cardboard 觀看器時,系統會呼叫 nativeSwitchViewer
方法。nativeSwitchViewer
通話
CardboardQrCode_scanQrCodeAndSaveDeviceParams
,開啟掃描觀眾 QR code 的視窗。掃描 QR code 後,系統會更新檢視者的鏡頭變形和其他參數。
// Called by JNI method
void HelloCardboardApp::SwitchViewer() {
CardboardQrCode_scanQrCodeAndSaveDeviceParams();
}
啟用 Android Studio x86 模擬器
如要為 Android Studio x86 模擬器建構,請從 SDK 和 Sample 中的 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 掃描新的 QR code 時,下列程式碼會讀取已儲存的參數,並使用這些參數建立鏡頭失真物件,將適當的鏡頭失真套用至算繪內容:
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_);