总体分享思路
和安卓apk测试类似,除了从用户角度通过“用”来测试,同样需要知道开发一款VRapp是使用了哪些技术方法完成的,只有了解其实现之后才可以从白盒角度辅助分析进行庖丁解牛,然后才有各种测试框架和工具的开发。下面是本次分享的主要章节流程。
VR原理
在谈VR之前我们先看看几个容易混淆的概念,你平时玩的CF、CallofDuty等3D游戏是虚拟现实吗?全景照片呢?虚拟现实和3D电影有什么关系?在理清这些问题前,先看看相关书籍材料中是如何阐述VR虚拟现实的。
什么是VR
“虚拟仿真(virtual Reality)技术,又称虚拟现实技术,是近年来出现的高新技术,虚拟现实利用电脑模拟产生一个三维空间的虚拟环境,通过输出设备提供给使用者关于视觉、听觉、触觉等感官的模拟,让使用者如同身临其境一般,并能够及时、无限制地观察三维空间内的事物,通过各种输入设备与虚拟环境中的事物进行交互。”
----《虚拟仿真与游戏开发实用教程》
“虚拟现实技术是指计算机产生的三维交互环境,在使用中用户“投入”到这个环境中去,并在人工合成的环境里获得“进入角色”的体验。”
“’virtual Reality’一词始于1989年,由VPL Research公司的奠基人Jaron Lanier在有关杂志上使用。”
----《虚拟现实视景仿真技术》
“1993年Grigore C.Burdea在Electro 93国际会议上发表的“Virtual Reality Systemand Application”一文中,提出了虚拟现实技术的三个特征,即:沉浸性、交互性、构想性。”
Figure 1 虚拟现实的3i特征
----《虚拟现实技术》
根据上面的解释,可以简化理解为,一套计算机系统产生的虚拟环境,用户能看到听到“触碰到”虚拟环境中的物体,感觉“活”在对应的虚拟世界。
根据上面的解释后我们再来看看前面的疑问。
3D游戏中的3D图形图像技术的确是虚拟现实的核心技术之一,但是在视觉成像上不是靠视觉距离感知的3D而是逼真的3D建模和持续的交互让我们强制理解为一个“虚拟世界”,当画面停止时,我们看到的就是一副静态的图片,所以其“沉浸”感不符合要求。
全景照片可以通过移动头部看到不同方位的景色,但是观察者对“虚拟世界”的交互几乎为0 ,所看到的只是前期特殊摄像机拍照合成的照片而已。
3D电影则是使用彩色眼镜滤光技术或者偏振眼镜滤光的技术让两眼看到不同的内容形成和真实视觉类似的景深感,用户不可能看到画面中的其他方向的内容,所有人只能看到固定的内容,所以在交互和沉浸上几乎都为0 。
简单总结下这三种观感对于VR的特征满足度:
沉浸 |
交互 |
构想 |
|
3D游戏 |
中 |
强 |
强 |
全景照片 |
强 |
弱 |
弱 |
3D电影 |
弱 |
弱 |
弱 |
人如何感知距离
虽然目前虚拟现实的设备很多,有Oculus这种计算能力靠外部PC输入和屏幕分离的。也有暴风眼镜这种完全借助手机运算、显示一体的封装外壳的设备。他们在最终画面的显示原理上是一致的,只是分辨率和响应速度的差异。而这个原理就需要从人眼和大脑如何确定感知物体的距离说起。
这里要提到的就是三角测量法。每个人左右眼的距离可以理解为固定的。当人看远处的物体上某个点时,点与两眼的连线形成两个夹角。利用三角测量的原理,人脑可以感知该点是有距离的,也就是有深度的。当然大脑会迅速对整个画面中所有点进行快速计算得到整体的深度信息(这也是大脑神奇的地方所在)。从而人知道面前哪些是可以行走的路,哪些是阻挡的障碍物,需要跨越和躲避。
Figure 2 三角测量与距离计算
根据上面的信息,我们总结一下,大脑之所以通过眼睛识别出眼前的景象是否有远近关系,根本上是和三角测量一样的道理。其一是:两眼位置固定,其二是两眼同时观察同一个目标。有了这个共识我们就可以继续下面的分析。
Unity中如何实现虚拟现实
在Unity中,使用摄像头这种对象来观察虚拟世界,摄像头可以有多个,如果我们使用两个摄像头分别投向虚拟世界中,并且把摄像头摆放的位置相对固定,那么它们所拍摄到的内容就可以几乎等同于人进入虚拟世界后,左右眼分别能看到的内容。
在现实世界中我们的3D电影的拍摄就是利用的类似原理。关键字:“双摄像头”,“固定位置”。可以参考下面的图片直观的理解。
Figure 3 3D摄像机的“大眼睛”
下面这张图就是unity中的摄像机的预览窗口,可以看到画面中的枪的角度略有不同,这正是符合实际观察效果的。Unity中在放置摄像头时可以通过编辑界面看到摄像头视角对外的“辐射”范围,使用白色的线条表示出来。
在Unity中可以使用脚本来控制摄像头的方向和支点坐标,也可以把陀螺仪的变化影响到摄像头从而形成视觉跟踪头部运动的效果。在下面会详细解释。
Figure 4 相机与视角的匹配
Unity虚拟世界中的核心概念
Unity作为目前业界占有率最高的游戏开发平台。主要在于其IDE极大的提升了开发者的效率,很多动作拖放即可完成。我们要制作VRdemo首先需要制作一个3D
的虚拟场景,然后利用调整摄像头的运动转化为VR效果,实际在时间投入上制作虚拟场景占用80%以上的时间。下面一部分借助一个小岛上扔球的游戏主要说明unity中核心的概念,这些概念有助于理解虚拟世界的构建以及交互方法。(具体IDE操作熟练不在本分享范围,太详细的操作就省略,代码给出核心部分)
Figure 5 小岛扔球
理解坐标系
在unity中主界面scene窗口是我们布置虚拟世界的主窗口,一般是俯视这个虚拟世界,类似上帝视角。我们布置地面、树木、人物时可以从这个视角操作,可以利用鼠标右键旋转视图,使用滚轮缩放视图。
一般X轴是前后方向,Z轴是左右方向,Y周是上下方向。可以通过右上角的坐标小工具快速切换“正面”、“侧面”、“底面”等视角。理解和熟练操作unity的坐标是一切的开始也是贯穿始终的要点。
Figure 6 unity的坐标系
在这个坐标系中,可以设置物体的X、Y、Z的坐标,想放哪儿就放哪儿。还可以设置物体的朝向,希望它“看”哪个方向。比如你有一把枪。还可以调整物体的缩放比例,你从外部导入一个太大或者太小的模型,可以使用缩放让他以合适的比例存在虚拟世界中。
Figure 7 设置物体位置参数
下面这幅图是采用Component->3DComponent->Terraim制作的岛屿,我们的虚拟世界背景就是这个,面朝大海的小岛,我在小岛岸边不停的扔着球。
Figure 8 小岛方位与坐标系
理解摄像头
前面已经提到,unity使用摄像头来对准虚拟世界的某个角落,通过这个摄像头我们能看到这个角落的物体。摄像头具有多种属性。常用的属性如下:
1
坐标:决定这个摄像机摆放在哪个位置
2
FOV:视野范围,类似高档相机的FOV,代表所能覆盖的范围。
3
Viewport Rect:视野的宽窄、高度。
Figure 9 unity中摄像头主要属性
Viewportrect的w属性(宽度)不能设置的太大和太小,因为人眼观察物体时,重合的区域是有一个区间的。超过这个区间显示的效果就不合理,观察者会迅速头晕目眩。如下图中左右眼FOV的交集是110度左右。
Figure 10 人眼FOV的边界
在本此演示中,使用暴风魔镜SDK中的预设MojingHead,其中子元素的摄像头对准了场景中的QQ墙。从一张近图和一张远图可以看到摄像头的范围。
Figure 11 近距离查看摄像头
Figure 12 远距离查看摄像头的全部视野
模型与纹理
在unity中我们看到的虚拟世界具有丰富多彩的效果,除了3D模型本身的细致之外,模型的贴图也是关键。一般给一个物体添加纹理的流程分为三个步骤。
1
导入外部的图片(PNG、JPEG等)。
2
添加材质球,给材质球添加图片纹理(便于观察各个角度的光线)。
3
用材质球赋予物体光鲜的“色彩”外衣。
Figure 13 给模型添加纹材质
在本场景中,我们使用一个简单的cube添加一个QQ的图标。具体效果如下,被赋予材质的物体可以是球体、长方体、不规则体。
Figure 14 QQ方块组成的墙
运用灯光
现实世界是充满各种光源的(否则是谁夺去了城市上空的星辰),在unity的虚拟世界中有4种光源供我们使用。合理运用光源,让虚拟世界更真实。
A、Directional light方向光。类似于太阳光,所有被照到的物体有同一方向的阴影。
B、Point light点光源。类似于一个灯泡。以球面扩展开的照射角度。
C、Spotlight聚光灯。类似手电筒的效果。
D、Area Light 区域光,一般用于光照贴图烘培。
光源的主要属性有:
1
坐标和方向。这盏灯放在哪里,灯口朝哪个方向。
2
范围,这盏灯的灯口有多宽。
3
亮度,这盏灯有多少瓦。
Figure 15 光源的主要属性
在场景中,我们放置的是一个方向光。大白天的效果。山上的小山包和树木都有相同方向的影子。
Figure 16 一个方向光的实例效果
父子对象的关联
在unity中我们的虚拟场景中会有很多的元素,很多时候我们需要几个物体作为一个整体位移和运动。这个时候可以使用父子关系把元素关联起来,拖动一个元素到另一个元素下面(类似windows上拖动文件到文件夹)即可。一旦形成父子关系,改变父对象的坐标和方向将影响所有子孙节点的坐标和方向。
本场景中我们的QQ墙是由一个个QQ砖块组成的。
在暴风魔镜以及cardboard的库中,使用一组摄像头对象组成了头盔对象。
Figure 17 对象的父子关系
脚本关联对象
虚拟世界中的物体需要按一定的物理原理进行移动,人物的话需要走动。除了运动还有销毁对象等操作,都可以通过代码来完成。在unity中可以通过给对象绑定一个脚本,在脚本中实现指定的接口来达成。
Figure 18 绑定新的脚本
一个新的脚本默认会有两个方法,在update方法中我们可以做各种操作,具体就看场景需要。熟练开发要基于对API的了解。
void Start () |
脚本启动后执行一次,可以用来初始化变量 |
void Update () |
游戏的每一帧中执行的代码,在这一帧中可以根据外部输入调整物体的位置、速度、播放声音、创建或删除元素。 |
在本场景中,我在主摄像头下面挂载了一个空白对象GameObject->Create Empty,取名eyePostion代表眼镜所看到的方向,然后给其设置了一个用来发射小球的脚本。
Figure 19 物体和脚本关联
更多的脚本编写案例在后续实践中会逐步总结。预期会按照场景来提炼,比如如何发射一个子弹,如何让一个物体自传,如何在场景中动态生成敌人,如何让两个物体互相发消息,如何控制一个对象数秒后自动销毁(节省内存)。
使用音效
前面提到虚拟世界给人的感官除了视觉还有听觉,在现实中,声音随着人的移动和方向变化,左右耳听到的声音的大小是随时变化的。快速移动的物体发出的声音还会产生多普勒效应。在unity中使用音效非常的方便,主要以下步骤:
1
使用import命令导入各种声音文件到工程(MP3、wav)。
2
在关联的脚本中添加变量,并通过拖动方式绑定变量和声音文件。
3
在需要播放的时机调用PlayXXX方法。
当然,声音源的制作和剪辑有很多工具,推荐一款老牌实用工具Goldwave。
Figure 20 如何播放声音
使用预设
在各类动画制作软件中都会有模板这个概念,比如Flash。你可以把一些物体创建为模板,后续可以使用类似“new”的语句动态的创建出来,起到复用的效果。Unity也不例外。在这里称作预设(Prefab)。创建prefab有两种方式:
1
先创建空的Prefab,然后把其他元素拖进去。
2
直接把设计好的单个元素拖到project中直接创建一个Prefab。(技巧)
Figure 21 创建空的prefab
有时候我们发现之前创建的一个Prefab后来放到界面中去后,需要调整参数,我们调整的一个具体实例参数后,可以使用“apply”方法应用到Prefab,让所有画面中的实例生效最新的修改。(这也是unity易用性的一个体现)。
Figure 22 实例修改应用到 Prefab
在本场景中我们要发射的小球和QQ墙都是已经设置为了预设。这样方便整体的创建,以及用代码来动态生成。
Figure 23 QQ墙作为了预设
刚体
Unity内置了NVIDIA PhysX物理引擎,可以让虚拟世界的物体自由下落,碰撞,弹起。刚体(RigidBody)用通俗的话说就是赋予一个物体重量。除了重量外还可以设置物体在运动过程中收到的阻力,有普通的运动空气阻力和角速度的阻力(旋转过程中的阻力),下图中是刚体的主要配置界面:
Figure 24 刚体的配置
在刚体运动控制的代码中有下面主要属性:
rotation |
刚体的旋转角度 |
velocity |
刚体的速度向量。这个值会让物体自动在力的方向运行 |
angularVelocity |
刚体角速度向量。旋转时的角速度,也即单位时间内转动的角度 |
这里说一个例子,比如我们希望虚拟场景中有一个物体在场景开始播放时自动的、永远的、随机角度的旋转,可以设置Angular Drag=0(永远的),然后可以这么写。
voidStart () { GetComponent ().angularVelocity = Random.insideUnitSphere * tumble; } 在start中调用,则一开始就会执行这段代码(自动的)。 |
1、上面使用了角速度向量 2、使用Random的InsideUnitSphere,随机返回半径为1的球体内的一个点(随机角度的) 3、然后乘以一个预定值 |
在本场景中,我们让球体和QQ砖块都添加了刚体组件,受到虚拟世界的重力影响。
Figure 25 有刚体组件的方块
碰撞体
物体有了自己的重量,受力后必然会有运动的趋势,一旦运动起来物体之间避免不了碰撞,子弹射击敌人、一面墙挡住人前进、球撞上一面墙,这些需要给物体赋予碰撞体。Unity中根据物体外形的分类,给出了多种碰撞体的模型常见的有box collider、sphere collider、Capsule collider,对应长方体、圆球、胶囊体。对应的预览效果如下:
Figure 26 不同的碰撞体
物体碰撞后我们需要触发代码,实现碎裂、发出爆炸声等效果。处理两个物体碰撞一般有下面几个步骤:
1
给对象设置tag,标记为哪一类物体(敌人、一般物体、玩家)
2
给对象添加合适的碰撞体,并开启“IStrigger”开关,允许触发碰撞
3
在绑定代码中重载OnTriggerEnter(other : Collider )方法,其中other是碰撞的对方物体
function OnTriggerEnter(collider : Collider) { if(collider.tag == "ground") { audioClip.Play(); //播放声音 } } |
在本场景中,我们暂时的创意还不需要再碰撞后有其他效果,所以只需要增加碰撞体即可。
Figure 27 添加碰撞体
获取组件
在unity中一个物体甚至是空的物体根据场景设计需要会添加很多的组件,比如前面的脚本(脚本可以有多个)、刚体、碰撞体,在代码调用中我们会需要用到这些组件。添加组件可以通过Component->Physics->Box Collider菜单方式添加,也可以直接在侦测面板中添加:
Figure 28 添加各类组件
Figure 29 移除组件
在代码中使用组件的典型参考如下:
Figure 30 代码中调用组件
关于调用顺序
在unity的脚本框架中,我们根据场景需要会实现默认的接口函数,比如最常用的Update()和Start()。这两个函数在新建时就有。其他的则需要在代码中主动敲入。在复杂的场景中我们需要准确的了解并控制代码执行的顺序(你所设定的机制处理的逻辑)。对于需要详细了解这些函数的童鞋可以参考MonoBehaviour类。这个类是所有场景中元素的父类。
https://docs.unity3d.com/Reference/MonoBehaviour.html
Figure 31 脚本控制类的父类
在我们调试过程中,不由自主的想知道最初的代码从哪里执行,执行过程的顺序。(学习C语言大家都是从main函数开始)下面就是unity中常见事件的执行顺序。
Figure 32 unity中常见事件执行顺序
小结
以上这些概念必将贯穿各类场景制作中,正确的理解,在需要的时候添加运用。其实这个思路和我们学习一门语言类似,了解其能力后,在合适的时候根据我们的业务场景把这些能力(比如合适的API方法)用起来,形成我们的软件。
点击一下
立即阅读相关好文章
......
近期热文
我来说两句排行榜