简体中文 | English


以下教程将阐述如何将GoogleVR中常用的EventSystem和Gaze移植到大朋VR应用中。本教程中使用了GoogleVRForUnity3D V1.10.0,DpnUnity0.5.0d,代码可能有出入,大致思路不变。

下面分为四个步骤详细讲述如何完成移植过程:试用Google VR for Unity3D;剥离EventSystem;剥离Gaze效果;移植到大朋VR应用。

代码可以从这里下载:

试用Google VR for Unity3D

剥离EventSystem

剥离Gaze效果

移植到大朋VR应用

试用Google VR for Unity3D

首先我们需要了解一下GoogleVR for Unity3D的结构。

导入Google VR For Unity3D。

从GitHub下载到unitypackage之后,在Unity3D中点击Assets->Inport Package->Custom Package。

打开这个package,并Import所有内容。

打开DemoScene

找到下图的目录,双击DemoScene,打开该场景。

简单看一下Cardboard双目渲染的原理。实际上,它只是在Camera的Viewport Rect设置了渲染到左半屏或者右半屏。

插件中实现了自己的EventSystem,用以让任何GameObject响应Gaze的事件。

它还提供了一个IGvrGazeResponder的接口,只要是实现了改接口的Component就可以得到Gaze的消息。所以说它并不是响应标准的鼠标事件。

生成和运行应用

点击File->Build Setting。

选择Android平台,然后点击“Build”。或者也可以点击“BuildAndRun”,直接安装到手机运行。





剥离EventSystem

提取脚本

前面已经介绍了GoogleVR重新实现了EventSystem(GazeInputModule.cs),它会从Camera发射一条射线,这条射线与Scene中的GameObject求交,并调用挂在GameObject的Component上的IGvrGazePointer.cs接口。所以我们只需要这两个脚本。

我们新建一个项目,然后把这两个脚本复制过来。

修正代码

我们在所有需要修改的代码处用“DpnHere”标记了,只要搜索这个标记就可以知道做了哪些修改。这里只解释重要的修改。

GazeInputModule.cs中我们添加了InteractCamera变量。这个变量指定了EventSystem的射线跟随哪个相机。

public Camera InteractCamera; //DpnHere
这一段代码取代GoogleVR的GvrViewer,获得InteractCamera的Rotation,然后计算出射线的方向。


Vector2 headPose = NormalizedCartesianToSpherical(InteractCamera.transform.rotation * Vector3.forward);//DpnHere


此外需要注意这段代码,指定了射线发射的方向。其中的“Vector2(0.5f, 0.5f)”表示屏幕中心。

[HideInInspector]

public Vector2 hotspot = new Vector2(0.5f, 0.5f);

取代EventSystem

GazeInputModule.cs挂到EventSystem下面,并将MainCamera指定为InteractCamera。

它会和EventSystem同时生效,但只有它会由InteractCamera生成射线,产生相应的事件。

跟随手机转动

然后我们用一个小脚本,让Camera跟随手机转动。


using UnityEngine;

using System.Collections;


public class SensorFusion : MonoBehaviour

{

// Use this for initialization

void Start()

{

Input.gyro.enabled = true;

}


// Update is called once per frame

void Update()

{

Quaternion att = Input.gyro.attitude;

transform.rotation = Quaternion.Euler(90, 0, 0)

* (new Quaternion(att.x, att.y, -att.z, -att.w));

}

}

将这个脚本挂在MainCamera上

生成运行

然后再Build And Run就可以在手机上看到转动手机时,Button一类的GameObject会由屏幕中心对焦点的变化而出现相应的视觉效果。如果哪些GameObject的Component实现了IGvrGazePointer.cs接口,就可以响应了。但这时还没有Gaze,我们需要在接下来的工作中将Gaze效果剥离出来。







剥离Gaze效果

提取脚本,Shader和材质

这次除了GazeInputModule.cs和IGvrGazePointer.cs这两个脚本外,还需要Gaze相关的Shader和材质。

然后在MainCamera下创建一个空的GameObject。并添加Mesh Renderer和Gvr Reticle两个Component。注意可能Materials和Shader设置不正确,需要设置一下。

修正代码

在GvrGaze.cs中有少量代码需要修改。比较简单,全部以“DpnHere”标记,这里不做详细解释。

生成运行

现在生成并运行,就可以看到在手机中心有一个圆点Gaze,并且对准物体时,这个圆点就会出现放大的动画。




移植到大朋VR应用

DpnUnity插件的使用

在阅读本章节前,请确认已经完全掌握http://developer.deepoon.com/depguide/《Unity3D开发M系列游戏》中的内容,已经可以成功生成M系列一体机VR应用。

导入DpnUnity插件,完成Unity3D的设置。

http://developer.deepoon.com/depguide/Unity3D开发M系列游戏》的流程,将DpnUnity插件导入,并设置好Unity3D。

添加GoogleVR的代码、Shader和材质。

可以将前面教程中的那些剥离的文件复制进本项目。

修正代码

同样可以搜索“DpnHere”找到需要修改的代码。这里着重解释下RaycastAll函数的问题。

由于Unity3D原生EventSystem的RaycastAll只会从渲染到FrameBuffer的MainCamera发射射线,然而在VR应用中,只有左右眼的两个Camera将RenderTexture作为Target,所以原生的RaycastAll就不管用了。

我们自己实现了简单的矩形和射线求交算法,代码和具体的解释可以在《如何让使用Unity的Canvas响应准心》中找到。我们取了其中的Raycast相关的代码。

public static void RaycastStatic

(BaseRaycaster caster

, Camera interact_camera

, Vector2 screen_position

, Canvas canvas

, List<RaycastResult> resultAppendList

)

{

if (interact_camera == null || canvas == null) return;


Vector3 screen_ray_pos = new Vector3(interact_camera.pixelWidth * screen_position.x, interact_camera.pixelHeight * screen_position.y, 0);

Ray ray = interact_camera.ScreenPointToRay(screen_ray_pos);


List<GraphicHit> sorted_graphic = new List<GraphicHit>();

IList<Graphic> list = GraphicRegistry.GetGraphicsForCanvas(canvas);

for (int i = 0; i < list.Count; ++i)

{

Graphic g = list[i];

if (g.depth == -1 || g.enabled == false || g.raycastTarget == false) continue;


Vector3[] corners = new Vector3[6];

g.rectTransform.GetWorldCorners(corners);

corners[4] = corners[0];

corners[5] = corners[1];


Plane rect_plane = new Plane();

rect_plane.Set3Points(corners[0], corners[1], corners[2]);


float enter;

if (false == rect_plane.Raycast(ray, out enter)) continue;


Vector3 intersection = ray.GetPoint(enter);

bool is_inside = true;

for (int j = 0; j < 4; ++j)

{

Vector3 b = corners[j + 1];

Vector3 ba = corners[j + 0] - b;

Vector3 bc = corners[j + 2] - b;

Vector3 bp = intersection - b;


Vector3 cross_ba_bp = Vector3.Cross(ba, bp);

Vector3 cross_bp_bc = Vector3.Cross(bp, bc);


if (Vector3.Dot(cross_ba_bp, cross_bp_bc) < 0)

{

is_inside = false;

break;

}

}


if (false == is_inside) continue;


GraphicHit hit;

hit.graph = g;

hit.worldPos = intersection;

sorted_graphic.Add(hit);

}


sorted_graphic.Sort((g1, g2) => g2.graph.depth.CompareTo(g1.graph.depth));


for (int i = 0; i < sorted_graphic.Count; ++i)

{

var castResult = new RaycastResult

{

gameObject = sorted_graphic[i].graph.gameObject,

module = caster,

distance = (sorted_graphic[i].worldPos - ray.origin).magnitude,

index = resultAppendList.Count,

depth = sorted_graphic[i].graph.depth,

worldPosition = sorted_graphic[i].worldPos,

};

resultAppendList.Add(castResult);

}

}

这个函数需要传入交互的Canvas,我们在GazeInputModule.cs中添加一个成员变量。

public Canvas _Canvas = null; //DpnHere

并在GazeInputModule的Inspector中设置好。

添加Reticle

在CenterEyeAnchor下面挂接Reticle。

生成并运行

这时,可以生成并在M系列一体机中运行了。

但是我们会发现Gaze的位置非常奇怪,双眼看到的Gaze不能重合。这就需要修改Gaze渲染的位置了。

修改Shader

打开GvrReticleShader.shader,在其中添加这些代码。

首先加一个Slide,设置IPD。不过这里只是个参数,并不表示实际的IPD长度。

_Half_IPD ("Half_IPD", Range(0, 1)) = 0.64 //DpnHere
添加两个Uniform。其中_CurrentEye表示当前渲染的是左眼还是右眼。

uniform float _Half_IPD; //DpnHere

uniform float _CurrentEye; //DpnHere

最后对顶点做平移。
vert_out.x += 10 * (_Half_IPD - 0.1) * ( -1 + 2 * _CurrentEye ); //DpnHere

添加DpnCameraPreRender.cs,设置_CurrentEye

我们需要在双眼的Camera下挂接这个Component,让它设置_CurrentEye。

using UnityEngine;

using System.Collections;


namespace dpn

{

public class DpnCameraPreRender : MonoBehaviour

{


private Camera _camera = null;

// Use this for initialization

void Start()

{

_camera = GetComponent<Camera>();

}


void OnPreRender()

{

Shader.SetGlobalFloat("_CurrentEye", ((float)PLUGIN_EVENT_TYPE.LeftEyeEndFrame == _camera.depth) ? 0 : 1);

}

}

}

其中下面的代码判断了本Camera是左眼还是右眼。

((float)PLUGIN_EVENT_TYPE.LeftEyeEndFrame == _camera.depth) ? 0 : 1
在DpnUnity中,使用了Camera.depth来标记这个Camera是左眼还是右眼。代码在DpnCameraRig.cs中。

private static void CameraSetup

( Camera cam , PLUGIN_EVENT_TYPE event_type

 , float fovy , float aspect_xdy

 , bool hdr )

{

...

// Enforce camera render order

cam.depth = (int)event_type;

...

}

生成和运行

这时生成,并运行在M2中时,就可以看到左右眼的Gaze可以正确地重合了。



Copyright © 2015deepoon.com,All Rights Reserved 沪ICP备15019466号-1 上海乐相科技有限公司