在 iOS 上開始創建你的虛擬現實應用

以前 Cradboard 也是支持在 iOS 上使用的,依靠 Unity 來實現,所以你需要用 C# 來編寫 iOS app(聽起來很奇怪對不對?) 而今天在 GDG China 看見 全新 VR 視圖:讓你的應用和網站嵌入沉浸式內容 當然迫不及待的想嘗試一下,於是翻譯了最新的文檔,大家一起來體驗一下在 iOS 上實現虛擬現實的新方式吧。

Cardboard 是 Google 一款能夠很方便讓你的手機變身 VR 設備的產品,如果你還沒有擁有它,可以去某寶買一個 ;)

這個 Cardboard SDK 可以讓你很方便的控制音頻的空間感(例如左右聲道),也可以控制響度,所以你可以讓一段對話在一個小飛船中或者一個很大的地下洞穴中表現得很不一樣。

在這個示例程序中我們完成了一個尋寶遊戲,他演示了 Cardboard 的核心功能。玩家將會在一個虛擬的世界中尋找寶物。你將會學習如何使用光照、空間運動和着色等基本功能如果玩家看見了他要找的東西,將會觸發空間音效和視差效果。

基本要求

爲了能夠運行這個示例程序,你至少需要滿足以下條件:

  • Xcode 7.1 或更高版本
  • CocoaPods, 訪問 CocoaPods 來安裝。
  • 一部運行 iOS 7.0 或更高版本的 iPhone。

下載並構建 app

  1. 首先將項目 clone 到本地:

    1
    
    git clone https://github.com/googlesamples/cardboard-ios.git
    
  2. 在你的命令行中,進入到 CardboardSamples 裏的 TreasureHunt 文件夾然後執行:

    1
    
    pod update
    
  3. 這將會安裝項目所有的依賴。(注:因爲衆所周知的原因,這個步驟可能會非常緩慢)
    tips: CardboardSDK 在 https://www.gstatic.com/cpdc/97ceadc125bddf66-CardboardSDK-0.7.0.tar.gz
    現在你應該能看見 TreasureHunt.xcworkspace 文件了,用 Xcode 運行起來應該像這個樣子:

在 Xcode 上運行 TreasureHunt在 Xcode 上運行 TreasureHunt

開始遊戲

現在戴上你的耳機,來在這個虛擬現實的空間裏搜尋寶物吧!

尋找寶物

  • 四處移動你的方向,直到寶物進入你的視野:

    寶物已經在視野中顯示了寶物已經在視野中顯示了

  • 直視這個寶物,他將會變成橘色:

    直視寶物的時候它變成橘色了直視寶物的時候它變成橘色了

  • 激活開關就可以收集寶物(根據 Cradboard 的不同,可能是撥動物理按鈕也可能是觸碰屏幕之類的):

代碼概覽

這個尋寶遊戲(TreasureHunt)通過 OpenGL 來爲你的雙眼呈現不同的訊息,他們是這樣工作的:

  • 一個 UIViewController 擁有一個 GCSCardboardView 對象
  • 一個渲染器遵循 GCSCardboardViewDelegate 協議
  • 通過 CADisplayLink 對象添加一個渲染循環
  • 捕獲輸入

讓 UIViewController 擁有一個 GCSCardboardView

這個尋寶遊戲定義了一個 UIViewController,也就是 TreasureHuntViewController,他擁有一個 GCSCardboardView,並且有一個遵循GCSCardboardViewDelegate 協議的 TreasureHuntRenderer 的實例來成爲 GCSCardboardView 的代理。 此外,這個應用有一個渲染循環,TreasureHuntRenderLoop 這個類,他有一個 - render 方法來GCSCardboardView

1
2
3
4
5
6
7
8
9
10
11
- (void)loadView {
  _treasureHuntRenderer = [[TreasureHuntRenderer alloc] init];
  _treasureHuntRenderer.delegate = self;

  _cardboardView = [[GCSCardboardView alloc] initWithFrame:CGRectZero];
  _cardboardView.delegate = _treasureHuntRenderer;
  ...
  _cardboardView.vrModeEnabled = YES;
  ...
  self.view = _cardboardView;
}

定義一個遵循 GCSCardboardViewDelegate 協議的渲染器

GCSCardboardView 將會用於向你展示內容,他通過 GCSCardboardViewDelegate 協議來完成這些工作,所以 TreasureHuntRenderer 將會遵循 GCSCardboardViewDelegate協議:

1
2
3
4
5
6
#import "GCSCardboardView.h"

/** TreasureHunt renderer. */
@interface TreasureHuntRenderer : NSObject<GCSCardboardViewDelegate>

@end

聲明 GCSCardboardViewDelegate 協議中的內容

爲了在 GCSCardboardView 顯示內容,TreasureHuntRenderer 需要遵循 GCSCardboardViewDelegate 的這些協議:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@protocol GCSCardboardViewDelegate<NSObject>

- (void)cardboardView:(GCSCardboardView *)cardboardView
         didFireEvent:(GCSUserEvent)event;

- (void)cardboardView:(GCSCardboardView *)cardboardView
     willStartDrawing:(GCSHeadTransform *)headTransform;

- (void)cardboardView:(GCSCardboardView *)cardboardView
     prepareDrawFrame:(GCSHeadTransform *)headTransform;

- (void)cardboardView:(GCSCardboardView *)cardboardView
              drawEye:(GCSEye)eye
    withHeadTransform:(GCSHeadTransform *)headTransform;

- (void)cardboardView:(GCSCardboardView *)cardboardView
   shouldPauseDrawing:(BOOL)pause;

@end

接下來我們將實現 willStartDrawingprepareDrawFrame,和 drawEye 方法。

實現 willStartDrawing 方法

要執行 GL(Graphics Library) 一次性初始化,實現 - cardboardView:willStartDrawing: 方法,並在其中來加載着色器初始化集合場景並添加到 GL 的參數中,並且還初始化了一個 GCSCardboardAudioEngine 實例:

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)cardboardView:(GCSCardboardView *)cardboardView
     willStartDrawing:(GCSHeadTransform *)headTransform {
  // Load shaders and bind GL attributes.
  // Load mesh and model geometry.
  // Initialize GCSCardboardAudio engine.
  _cardboard_audio_engine =
  [[GCSCardboardAudioEngine alloc]initWithRenderingMode:
      kRenderingModeBinauralHighQuality];
  [_cardboard_audio_engine preloadSoundFile:kSampleFilename];
  [_cardboard_audio_engine start];
  ...
  [self spawnCube];
}

實現 prepareDrawFrame 方法

通過實現 - cardboardView:prepareDrawFrame: 方法,將可以決定將要呈現在人眼前內容的邏輯。任何對於特定幀內容的操作應該在這裏實現,在這裏更新模型並清除 GL 繪製狀態等。應用將會計算頭部的方向並更新音頻引擎。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (void)cardboardView:(GCSCardboardView *)cardboardView
     prepareDrawFrame:(GCSHeadTransform *)headTransform {
  GLKMatrix4 head_from_start_matrix = [headTransform headPoseInStartSpace];
  // Update audio listener's head rotation.
  const GLKQuaternion head_rotation =
      GLKQuaternionMakeWithMatrix4(GLKMatrix4Transpose(
      [headTransform headPoseInStartSpace]));
  [_cardboard_audio_engine setHeadRotation:head_rotation.q[0]
                                         y:head_rotation.q[1]
                                         z:head_rotation.q[2]
                                         w:head_rotation.q[3]];
  // Update the audio engine.
  [_cardboard_audio_engine update];

  // Clear the GL viewport.
  glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
  glEnable(GL_DEPTH_TEST);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glEnable(GL_SCISSOR_TEST);
}

實現 drawEye 方法

這裏將會是整個渲染代碼的核心,就像你建立一個常規的 OpenGL ES 應用一樣。下面這段代碼將爲你展示如何在 - drawEye 方法中爲 每個 眼球呈現場景的變換和透視效果。注意,這個方法會爲每一個眼球調用,如果 GCSCardboardView 沒有啓用 VR 模式,那麼眼球將會被設置爲最中間。這種單眼渲染模式也是有用的,他能在非 VR 視圖下也展現 3D 場景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- (void)cardboardView:(GCSCardboardView *)cardboardView
              drawEye:(GCSEye)eye
    withHeadTransform:(GCSHeadTransform *)headTransform {
  // Set the viewport.
  CGRect viewport = [headTransform viewportForEye:eye];
  glViewport(viewport.origin.x, viewport.origin.y, viewport.size.width,
      viewport.size.height);
  glScissor(viewport.origin.x, viewport.origin.y, viewport.size.width,
      viewport.size.height);

  // Get the head matrix.
  const GLKMatrix4 head_from_start_matrix =
      [headTransform headPoseInStartSpace];

  // Get this eye's matrices.
  GLKMatrix4 projection_matrix = [headTransform
      projectionMatrixForEye:eye near:0.1f far:100.0f];
  GLKMatrix4 eye_from_head_matrix =
      headTransform eyeFromHeadMatrix:eye];

  // Compute the model view projection matrix. GLKMatrix4
  model_view_projection_matrix = GLKMatrix4Multiply(projection_matrix,
      GLKMatrix4Multiply(eye_from_head_matrix, head_from_start_matrix));

  // Render from this eye.
  [self renderWithModelViewProjectionMatrix:model_view_projection_matrix.m];
}

返回這個方法的調用以後,GCSCardboardView 會將它渲染到屏幕上。

爲了渲染內容,我們需要 CADisplayLink 來驅動一個渲染循環。 在這個尋寶遊戲中,我們用到了 TreasureHuntRenderLoop 來實現這個渲染循環。 這需要調用 GCSCardboardView 中的 - render 方法。 我們在 TreasureHuntViewController 的 - viewWillAppear: and - viewDidDisappear: 方法中生成它並且在 - viewDidDisappear: 方法中銷燬它。

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)viewWillAppear:(BOOL)animated {
  [super viewWillAppear:animated];

  _renderLoop = [[TreasureHuntRenderLoop alloc]
   initWithRenderTarget:_cardboardView selector:@selector(render)];
}

- (void)viewDidDisappear:(BOOL)animated {
  [super viewDidDisappear:animated];

  [_renderLoop invalidate];
  _renderLoop = nil;
}

捕獲輸入

Cradboard SDK 可以接受到輸入的事件(通常是撥動 Cardboard 上的按鈕),你要在用戶觸發這個按鈕的時候做一些事情,只需要實現- cardboardView:didFireEvent 代理方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (void)cardboardView:(GCSCardboardView *)cardboardView
         didFireEvent:(GCSUserEvent)event {
  switch (event) {
    case kGCSUserEventBackButton:
    // If the view controller is in a navigation stack or
    // over another view controller, pop or dismiss the
    // view controller here.
    break;
    case kGCSUserEventTrigger:
     NSLog(@"User performed trigger action");
     // Check whether the object is found.
     if (_is_cube_focused) {
       // Vibrate the device on success.
       AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
       // Generate the next cube.
       [self spawnCube];
     }
     break;
  }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章