在 iOS 上开始创建你的虚拟现实应用

在 iOS 上开始创建你的虚拟现实应用
原文链接 : Getting Started | Cardboard | Google Developers
译文链接 : 在 iOS 上开始创建你的虚拟现实应用

以前 Cardboard 也是支持在 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
  1. 在你的命令行中,进入到 CardboardSamples 里的 TreasureHunt 文件夹然后执行:
1
pod update

这将会安装项目所有的依赖。(注:因为众所周知的原因,这个步骤可能会非常缓慢)
tips: CardboardSDKhttps://www.gstatic.com/cpdc/97ceadc125bddf66-CardboardSDK-0.7.0.tar.gz
现在你应该能看见 TreasureHunt.xcworkspace 文件了,用 Xcode 运行起来应该像这个样子:

在 Xcode 上运行 TreasureHunt

开始游戏

现在戴上你的耳机,来在这个虚拟现实的空间里搜寻宝物吧!

寻找宝物

  • 四处移动你的方向,直到宝物进入你的视野:

宝物已经在视野中显示了

  • 直视这个宝物,他将会变成橘色:

直视宝物的时候它变成橘色了

  • 激活开关就可以收集宝物(根据 Cardboard 的不同,可能是拨动物理按钮也可能是触碰屏幕之类的):

代码概览

这个寻宝游戏(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: 和 -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;
}

捕获输入

Cardboard 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;
}
}