以前 Cradboard 也是支持在 iOS 上使用的,依靠 Unity 来实现,所以你需要用 C# 来编写 iOS app(听起来很奇怪对不对?) 而今天在 GDG China 看见 全新 VR 视图:让你的应用和网站嵌入沉浸式内容 当然迫不及待的想尝试一下,于是翻译了最新的文档,大家一起来体验一下在 iOS 上实现虚拟现实的新方式吧。
Cardboard 是 Google 一款能够很方便让你的手机变身 VR 设备的产品,如果你还没有拥有它,可以去某宝买一个 ;)
这个 Cardboard SDK 可以让你很方便的控制音频的空间感(例如左右声道),也可以控制响度,所以你可以让一段对话在一个小飞船中或者一个很大的地下洞穴中表现得很不一样。
在这个示例程序中我们完成了一个寻宝游戏,他演示了 Cardboard 的核心功能。玩家将会在一个虚拟的世界中寻找宝物。你将会学习如何使用光照、空间运动和着色等基本功能如果玩家看见了他要找的东西,将会触发空间音效和视差效果。
为了能够运行这个示例程序,你至少需要满足以下条件:
首先将项目 clone 到本地:
1
|
git clone https://github.com/googlesamples/cardboard-ios.git
|
在你的命令行中,进入到 CardboardSamples
里的 TreasureHunt
文件夹然后执行:
1
|
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
。
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 |
接下来我们将实现 willStartDrawing
,prepareDrawFrame
,和 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
添加渲染循环 为了渲染内容,我们需要 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; } } |