以前 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
-
首先将项目 clone 到本地:
git clone https://github.com/googlesamples/cardboard-ios.git -
在你的命令行中,进入到
CardboardSamples里的TreasureHunt文件夹然后执行:pod update -
这将会安装项目所有的依赖。(注:因为众所周知的原因,这个步骤可能会非常缓慢)
tips:CardboardSDK在https://www.gstatic.com/cpdc/97ceadc125bddf66-CardboardSDK-0.7.0.tar.gz
现在你应该能看见TreasureHunt.xcworkspace文件了,用 Xcode 运行起来应该像这个样子:
开始游戏
现在戴上你的耳机,来在这个虚拟现实的空间里搜寻宝物吧!
寻找宝物
-
四处移动你的方向,直到宝物进入你的视野:
-
直视这个宝物,他将会变成橘色:
-
激活开关就可以收集宝物(根据 Cradboard 的不同,可能是拨动物理按钮也可能是触碰屏幕之类的):
代码概览
这个寻宝游戏(TreasureHunt)通过 OpenGL
来为你的双眼呈现不同的讯息,他们是这样工作的:
- 一个
UIViewController拥有一个GCSCardboardView对象 - 一个渲染器遵循
GCSCardboardViewDelegate协议 - 通过
CADisplayLink对象添加一个渲染循环 - 捕获输入
让
UIViewController 拥有一个 GCSCardboardView
这个寻宝游戏定义了一个 UIViewController,也就是
TreasureHuntViewController,他拥有一个
GCSCardboardView,并且有一个遵循
GCSCardboardViewDelegate 协议的
TreasureHuntRenderer 的实例来成为 GCSCardboardView
的代理。 此外,这个应用有一个渲染循环,TreasureHuntRenderLoop 这个类,他有一个
- render 方法来GCSCardboardView。
- (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协议:
#import "GCSCardboardView.h"
/** TreasureHunt renderer. */
@interface TreasureHuntRenderer : NSObject
@end
声明
GCSCardboardViewDelegate 协议中的内容
为了在 GCSCardboardView
显示内容,TreasureHuntRenderer 需要遵循
GCSCardboardViewDelegate 的这些协议:
@protocol GCSCardboardViewDelegate
- (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
接下来我们将实现
willStartDrawing,prepareDrawFrame,和
drawEye 方法。
实现 willStartDrawing
方法
要执行 GL(Graphics Library) 一次性初始化,实现 -
cardboardView:willStartDrawing: 方法,并在其中来加载着色器初始化集合场景并添加到 GL
的参数中,并且还初始化了一个 GCSCardboardAudioEngine 实例:
- (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
绘制状态等。应用将会计算头部的方向并更新音频引擎。
- (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 场景。
- (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
添加渲染循环
为了渲染内容,我们需要 CADisplayLink 来驱动一个渲染循环。 在这个寻宝游戏中,我们用到了
TreasureHuntRenderLoop 来实现这个渲染循环。 这需要调用
GCSCardboardView 中的 - render 方法。 我们在
TreasureHuntViewController 的 - viewWillAppear: and -
viewDidDisappear: 方法中生成它并且在 - viewDidDisappear:
方法中销毁它。
- (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 代理方法。
- (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;
}
}
- 声明: 除非注明,Tuccuay's Blog 文章均为原创,转载请以链接形式标明本文地址。
- 本博客原创文字只代表本人某一时间内的观点或结论,与本人当时或现在所在组织没有任何关系。
- BY-NC-SA 本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
- 第三方若用于商业用途的转载,须取得本人授权。
- 本文作者:Tuccuay
- 本文地址:www.tuccuay.com/2016/03/car…


