iOS 版 Google Cardboard 快速入門導覽課程

本指南將說明如何使用 iOS 版 Cardboard SDK 打造自己的虛擬實境 (VR) 體驗。

您可以使用 Cardboard SDK 將智慧型手機變成 VR 平台。智慧型手機可以偵測使用者按下觀影盒按鈕的時機,來顯示 3D 場景以立體聲音轉譯、追蹤頭部動作並回應頭部動作,以及與應用程式互動。

一開始,您將使用 HelloCardboard,這是一款示範 Cardboard SDK 核心功能的示範遊戲。在遊戲中,玩家可在虛擬世界中四處尋找並收集物件。內容說明如何:

  • 設定開發環境
  • 下載並建構試用版應用程式
  • 掃描 Cardboard 觀影盒的 QR code,即可儲存對應參數
  • 追蹤使用者的頭部動作
  • 為雙眼設定正確的變形,以便算繪立體立體圖像

設定開發環境

硬體需求:

軟體需求:

下載並建構試用版應用程式

Cardboard SDK 是以預先編譯的通訊協定緩衝區 C++ 來源檔案建構。請參閱這裡,瞭解從頭開始建構來源檔案的步驟。

  1. 執行下列指令,從 GitHub 複製 Cardboard SDK 和 Hello Cardboard 示範應用程式:

    git clone https://github.com/googlevr/cardboard.git
  2. 在存放區根目錄執行下列指令,將通訊協定緩衝區依附元件安裝到 Xcode 專案:

    pod install
  3. 在 Xcode 中開啟 Cardboard 工作區 (Cardboard.xcworkspace)。

  4. 變更應用程式的軟體包 ID,以便向團隊簽署應用程式。

  5. 依序前往「SDK」 >「Build Phases」 >「Link Binary With Libraries」

    1. 如要將 libPods-sdk.a 從清單中移除,請選取該項目,然後按一下「-」按鈕。
    2. 按一下「+」按鈕並選取它,將 libProtobuf-C++.a 新增至清單。如果畫面顯示使用 XCFramework 彈出式視窗,請按一下「Add Anyway」。
  6. 按一下「執行」

掃描 QR code

如要儲存裝置參數,請掃描 Cardboard 觀影盒上的 QR code:

立即試用

在 HelloCardboard 中,你需要在 3D 空間中尋找並收集測地球體;

如何尋找及收集球體:

  1. 朝任何方向移動頭,直到出現漂浮的球體。

  2. 直接看著球體。這會導致色彩改變。

  3. 按下 Cardboard 觀影盒按鈕即可「收集」球體。

設定裝置

當使用者輕觸齒輪圖示切換 Cardboard 觀影盒時,系統會在 HelloCardboardOverlayView 中呼叫 didTapSwitchButton 方法。

- (void)didTapSwitchButton:(id)sender {
  if ([self.delegate respondsToSelector:@selector(didTapBackButton)]) {
    [self.delegate didChangeViewerProfile];
  }
  self.settingsBackgroundView.hidden = YES;
}

這樣做會呼叫 CardboardQrCode_scanQrCodeAndSaveDeviceParams,系統隨即會開啟視窗來掃描檢視器的 QR code。當使用者掃描 QR code 時,系統會更新裝置的扭曲參數。

- (void)switchViewer {
  CardboardQrCode_scanQrCodeAndSaveDeviceParams();
}

- (void)didChangeViewerProfile {
  [self pauseCardboard];
  [self switchViewer];
  [self resumeCardboard];
}

頭部追蹤

建立頭部追蹤器

HelloCardboardViewControllerviewDidLoad 方法中,系統會建立一次頭部追蹤器:

_cardboardHeadTracker = CardboardHeadTracker_create();

暫停及繼續抬頭追蹤器

HelloCardboardViewController 類別中的 pauseCardboardresumeCardboard 方法會分別暫停並繼續主要追蹤器。resumeCardboard 也會設定 _updateParams 標記,導致裝置參數在下一個繪圖呼叫中更新。

- (void)pauseCardboard {
  self.paused = true;
  CardboardHeadTracker_pause(_cardboardHeadTracker);
}

- (void)resumeCardboard {
  // Parameters may have changed.
  _updateParams = YES;

  // Check for device parameters existence in app storage. If they're missing,
  // we must scan a Cardboard QR code and save the obtained parameters.
  uint8_t *buffer;
  int size;
  CardboardQrCode_getSavedDeviceParams(&buffer, &size);
  if (size == 0) {
    [self switchViewer];
  }
  CardboardQrCode_destroy(buffer);

  CardboardHeadTracker_resume(_cardboardHeadTracker);
  self.paused = false;
}

鏡頭變形

每次掃描新的 QR code 時,以下程式碼都會讀取已儲存的參數,並使用這些參數建立鏡頭變形物件,為轉譯的內容套用適當的鏡頭變形效果:

CardboardQrCode_getSavedDeviceParams(&encodedDeviceParams, &size);

// Create CardboardLensDistortion.
CardboardLensDistortion_destroy(_cardboardLensDistortion);
_cardboardLensDistortion =
    CardboardLensDistortion_create(encodedDeviceParams, size, width, height);

// Initialize HelloCardboardRenderer.
_renderer.reset(new cardboard::hello_cardboard::HelloCardboardRenderer(
      _cardboardLensDistortion, _cardboardHeadTracker, width, height));

轉譯

在 Cardboard 中算繪內容的程序如下:

  • 建立紋理
  • 取得左右眼睛的視野及投影矩陣
  • 建立轉譯器並設定變形網格
  • 轉譯每個影格

建立紋理

系統會將內容繪製成紋理,然後分割為左右眼的幾個部分。這些區段分別在 _leftEyeTexture_rightEyeTexture 中初始化。範例應用程式會針對雙眼使用單一紋理,但您也可以為兩眼建立獨立的紋理。

// Generate texture to render left and right eyes.
glGenTextures(1, &_eyeTexture);
glBindTexture(GL_TEXTURE_2D, _eyeTexture);
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, _width, _height, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);

_leftEyeTexture.texture = _eyeTexture;
_leftEyeTexture.left_u = 0;
_leftEyeTexture.right_u = 0.5;
_leftEyeTexture.top_v = 1;
_leftEyeTexture.bottom_v = 0;

_rightEyeTexture.texture = _eyeTexture;
_rightEyeTexture.left_u = 0.5;
_rightEyeTexture.right_u = 1;
_rightEyeTexture.top_v = 1;
_rightEyeTexture.bottom_v = 0;
CheckGLError("Create Eye textures");

這些紋理會以參數的形式傳入 CardboardDistortionRenderer_renderEyeToDisplay

取得左右眼睛的視野及投影矩陣

首先,請擷取左右眼睛的眼部矩陣:

CardboardLensDistortion_getEyeFromHeadMatrix(_lensDistortion, kLeft, _eyeMatrices[kLeft]);
CardboardLensDistortion_getEyeFromHeadMatrix(_lensDistortion, kRight, _eyeMatrices[kRight]);
CardboardLensDistortion_getProjectionMatrix(_lensDistortion, kLeft, kZNear, kZFar,
                                            _projMatrices[kLeft]);
CardboardLensDistortion_getProjectionMatrix(_lensDistortion, kRight, kZNear, kZFar,
                                            _projMatrices[kRight]);

接下來,取得每個眼睛的變形網格,並傳遞至變形轉譯器:

CardboardLensDistortion_getDistortionMesh(_lensDistortion, kLeft, &leftMesh);
CardboardLensDistortion_getDistortionMesh(_lensDistortion, kRight, &rightMesh);

建立轉譯器並設定正確的變形網格

轉譯器只需初始化一次。建立轉譯器後,請根據 CardboardLensDistortion_getDistortionMesh 函式傳回的網格值,設定左右眼睛的新變形網格。

_distortionRenderer = CardboardOpenGlEs2DistortionRenderer_create();
CardboardDistortionRenderer_setMesh(_distortionRenderer, &leftMesh, kLeft);
CardboardDistortionRenderer_setMesh(_distortionRenderer, &rightMesh, kRight);

轉譯內容

CardboardHeadTracker_getPose 擷取目前的頭向:

CardboardHeadTracker_getPose(_headTracker, targetTime, position, orientation);
_headView =
    GLKMatrix4Multiply(GLKMatrix4MakeTranslation(position[0], position[1], position[2]),
                       GLKMatrix4MakeWithQuaternion(GLKQuaternionMakeWithArray(orientation)));

請使用目前的頭像方向與檢視畫面和投影矩陣,編寫檢視區塊投影矩陣,並使用這些方向來算繪每個眼睛的世界內容:

// Draw left eye.
glViewport(0, 0, _width / 2.0, _height);
glScissor(0, 0, _width / 2.0, _height);
DrawWorld(_leftEyeViewPose, GLKMatrix4MakeWithArray(_projMatrices[kLeft]));

// Draw right eye.
glViewport(_width / 2.0, 0, _width / 2.0, _height);
glScissor(_width / 2.0, 0, _width / 2.0, _height);
DrawWorld(_rightEyeViewPose, GLKMatrix4MakeWithArray(_projMatrices[kRight]));

使用 CardboardDistortionRenderer_renderEyeToDisplay 對內容套用變形校正,並將內容算繪至螢幕。

CardboardDistortionRenderer_renderEyeToDisplay(_distortionRenderer, renderTarget, /*x=*/0,
                                               /*y=*/0, _width, _height, &_leftEyeTexture,
                                               &_rightEyeTexture);