VR入门
本文适用于任何想要开始在UNIGINE中开发虚拟现实项目的人,强烈建议所有新用户阅读本文。 我们将研究 VR Sample 演示,看看里面有什么,并学习如何使用它来创建我们自己的VR项目。 我们还将考虑一些简单的示例,用于修改和扩展此示例的基本功能。
那么,我们开始吧!
VR Sample#
考虑到VR开发人员,我们创建了 VR Sample 演示,使您能够直接进入并开始创建自己的项目。 我们建议使用此演示作为您的VR项目的基础.
该演示基于支持所有开箱即用的 SteamVR 兼容设备的 VR Template。 它在运行时提供控制器模型的自动加载。 此外,该模板还包括基本机制的实现,如抓取和投掷物体,按下按钮,打开/关上桌子抽屉等等。
示例项目是使用组件系统创建的,因此每个对象的功能由附加的组件决定。
您可以通过添加更多组件来扩展对象的功能。 例如,激光指示器对象具有附加的以下组件:
- ObjMovable 允许抓取和抛出一个对象
- ObjLaserPointer 启用投射光线的对象
1、制作模板项目#
那么,我们有一个很酷的演示,里面有一些东西,但是我们如何使用它作为模板? 这很简单,只需打开SDK浏览器,转到Samples选项卡,然后选择Demos。
在Available部分找到VR Sample,然后单击Install。 安装后,演示将出现在Installed部分,您可以单击 Copy as Project 基于此示例创建项目。
在打开的Create New Project窗口中,在相应字段中输入新VR项目的名称,然后单击Create New Project。
2、设置设备和配置项目#
假设您已成功安装所选的头戴式显示器(HMD)。
了解有关为不同 VR 平台设置设备的详细信息。 如果您遇到任何困难,请访问 Steam 支持。 对于 Vive 设备,此故障排除指南可能会有所帮助。
所有SteamVR兼容的设备都支持开箱即用。
默认情况下,VR未初始化。 因此,您需要执行以下操作之一:
-
如果通过UNIGINE SDK浏览器运行应用程序,请在Global Options选项卡中将Stereo 3D选项设置为与已安装HMD(OpenVR或Varjo)相对应的值,然后单击Apply。
-
如果从命令行运行应用程序,请在应用程序启动时指定-vr_app命令行选项。 对于OpenVR和Oculus,它应该如下所示:
your_app_name -vr_app openvr
对于Varjo:
your_app_name -vr_app varjo
或者,您可以在通过SDK浏览器运行应用程序时在Customize Run Options窗口中指定此命令行选项:
3、开放项目的源代码#
要在IDE中打开VR项目,请在UNIGINE SDK浏览器的Projects选项卡上选择它,然后单击Open Code IDE。
当IDE打开时,您可以看到该项目包含许多不同的类。 这个简短的概述将给您一个关于它们是什么的提示。
在Visual Studio中编译代码之前,不要忘记为项目设置适当的平台和配置设置。
现在,我们可以第一次尝试构建我们的应用程序。
以Visual Studio(Build -> Build Solution)或其他方式构建应用程序,并通过在UNIGINE SDK浏览器的Projects选项卡上选择项目并单击Run来启动它。
在通过UNIGINE SDK浏览器运行应用程序之前,请确保通过单击Run按钮下的省略号来选择适当的Customize Run Options(在我们的例子中为Debug版本)。
4、将对象附加到HMD#
有时可能需要将一些对象附加到HMD以跟随它(例如帽子)。 所有可移动对象(已分配movable.prop属性并启用Dynamic标志)都有一个启用此选项的切换。
例如,如果要使桌子上的圆柱体可附加到HMD,只需在 Reference 部分的 World Hierarchy单击Edit中选择名为 "cylinder" 的相应节点,然后启用 Can Attach to Head 选项。
然后选择父 Node Reference,并单击Apply。
同样的事情可以通过代码在运行时完成:
#include "Framework/Components/Objects/ObjMovable.h"
#include <UnigineWorld.h>
using namespace Unigine;
...
// retrieving a NodeReference named "cylinder" and getting its reference node
NodePtr node = checked_ptr_cast<NodeReference>(World::getNodeByName("cylinder"))->getReference();
// checking if this node is a movable object by trying to get its ObjMovable component
ObjMovable *obj = ComponentSystem::get()->getComponent<ObjMovable>(node);
if (obj != nullptr)
{
// making the object attachable to the HMD
obj->can_attach_to_head = 1;
}
5、访问混合现实功能(可选)#
要开始在UNIGINE中开发混合现实应用程序,您应该按照上面的所述设置环境。 不要忘记安装Varjo Base并确保VR成功初始化。
VR Template提供分配给head_menu GUI节点的MixedRealityMenuGui属性。 它演示了可用的混合现实设置的操作:您可以调整它们以查看它们如何影响渲染的图像。
通过此菜单,您可以从真实世界视图切换视频信号,调整相机的不同设置,如白平衡校正,ISO等。 菜单的小部件在运行时初始化,但节点是通过UnigineEditor创建的。
要管理混合现实,请使用UNIGINE API的VRMixedReality和VRMarkerObject类的方法。
6、访问眼动追踪功能(可选)#
在VR Sample中,有分配给同名节点的eyetracking_pointer属性。 它显示凝视指向的节点的名称。 该属性的实现在vr_sample/Demo/Global文件夹中可用。 您可以通过修改EyetrackingPointer组件来扩展或更改功能。
7、将对象附加到控制器#
如果您需要将一些在运行时加载的对象附加到控制器(例如菜单),则可以将AttachToHand属性分配给此对象。
例如,如果您有一个GUI对象并希望将其附加到控制器,请在World Hierarchy中选择此对象,在Parameters窗口中单击添加新属性,并指定AttachToHand属性。
属性设置允许指定对象应附加到的控制器(左或右),以及对象的转换。
在VR Sample中,该组件附加到hand_menu节点,并在运行时初始化小部件。
8、通过手势切换节点(可选)#
当使用Ultraleap集成插件启动应用程序时,您可以用手控制对象。
VR Template提供了分配给vr_layer -> VR -> Ultraleap虚拟节点的NodeSwitchEnableByGesture属性,并且在使用支持手跟踪的VR设备时可用。 属性设置允许指定可以在其中切换的节点数量、要切换的节点以及切换的手势类型。
当您用右手握住左手腕时,菜单就会出现:
9、添加新交互#
假设我们想在我们的项目中扩展激光指示器的功能,我们可以通过添加一个替代的使用动作(当按下某个按钮时,改变被指向的对象的材质)来扩展激光指示器的功能。
因此,我们将为这个新动作在VRInteractable类中添加一个新的altUseIt()方法,并将其映射到某个控制器按钮的状态。
VRInteractable.h
#pragma once
#include <UnigineNode.h>
#include <UniginePhysics.h>
#include "../Framework/ComponentSystem.h"
#include "Players/VRPlayer.h"
using namespace Unigine;
using namespace Math;
class VRPlayer;
class VRInteractable : public ComponentBase
{
public:
// ...
// interact methods
virtual void grabIt(VRPlayer* player, int hand_num) {}
virtual void holdIt(VRPlayer* player, int hand_num) {}
virtual void useIt(VRPlayer* player, int hand_num) {}
virtual void altuseIt(VRPlayer* player, int hand_num) {} //<-- method for new alternative use action
virtual void throwIt(VRPlayer* player, int hand_num) {}
};
分别在ObjLaserPointer.h和ObjLaserPointer.cpp文件中声明并实现对激光笔的altUseIt()方法的重写:
ObjLaserPointer.h
#pragma once
#include <UnigineWorld.h>
#include "../VRInteractable.h"
class ObjLaserPointer : public VRInteractable
{
public:
// ...
// interact methods
// ...
// alternative use method override
void altuseIt(VRPlayer* player, int hand_num) override;
// ...
private:
// ...
int change_material; //<-- "change material" state
// ...
};
ObjLaserPointer.cpp
// ...
void ObjLaserPointer::init()
{
// setting the "change material" state to 0
change_material = 0;
// ...
}
void ObjLaserPointer::update()
{
if (laser->isEnabled())
{
// ...
// show text
if (hit_obj && hit_obj->getProperty() && grabbed)
{
//---------CODE TO BE ADDED TO PERFORM MATERIAL SWITCHING--------------------
if (change_material)// if "alternative use" button was pressed
{
// change object's material to mesh_base
hit_obj->setMaterialPath("Unigine::mesh_base", "*");
}
//---------------------------------------------------------------------------
// ...
}
else
obj_text->setEnabled(0);
}
// unsetting the "change material" state
change_material = 0;
}
// ...
// alternative use method override
void ObjLaserPointer::altuseIt(VRPlayer* player, int hand_num)
{
// setting the "change material" state
change_material = 1;
}
// ...
现在,我们将此操作映射到YB控制器按钮的状态。 为此,我们应该修改VRPlayer类(这是所有VR播放器的基类),在其postUpdate()方法中添加以下代码:
VRPlayer.cpp
// ...
void VRPlayer::postUpdate()
{
for (int i = 0; i < getNumHands(); i++)
{
int hand_state = getHandState(i);
if (hand_state != HAND_FREE)
{
auto &components = getGrabComponents(i);
// ...
//-------------CODE TO BE ADDED--------------------------
// alternative use of the grabbed object
if (getControllerButtonDown(i, BUTTON::YB))
{
for (int j = 0; j < components.size(); j++)
components[j]->altuseIt(this, i);
// add callback processing if necessary
}
//--------------------------------------------------------
}
}
update_button_states();
}
// ...
10、添加新的可交互对象#
扩展VR Sample功能的下一步是添加一个新的可交互对象。
让我们添加一个新类型的可交互对象,我们可以抓取,握在手里,和扔出一个额外的功能:当我们抓住它时,对象会改变它的形式(到某个预设),并在我们释放它时恢复它的初始状态。 如果启用了相应的选项,它还将在控制台中显示某些文本。
因此,我们将使用以下组件:
- ObjMovable 启用基本的抓取和投掷功能
- 新的ObjTransformer组件,以启用形状更改和日志消息打印功能
要执行以下步骤:
-
添加从VRInteractable继承的新ObjTransformer类。 在Visual Studio中,我们可以通过从主菜单中选择Project -> Add Class,单击Add,在打开的窗口中指定类名和基类,然后单击Finish:
-
实现抓取时对指定节点的转换功能,释放节点时恢复原来的形状功能。
下面您将找到我们新的ObjTransformer类的头文件和实现文件:
ObjTransformer.h
#pragma once #include <UnigineNode.h> #include "Components/VRInteractable.h" #include "Framework/Utils.h" class ObjTransformer : public VRInteractable { public: ObjTransformer(const NodePtr &node, int num) : VRInteractable(node, num) {} virtual ~ObjTransformer() {} // property name UNIGINE_INLINE static const char* getPropertyName() { return "transformer"; } // parameters PROPERTY_PARAMETER(Toggle, show_text, 1); // Flag indicating if messages are to be printed to the console PROPERTY_PARAMETER(String, text, "TRANSFORMATION"); // Text to be printed to the console when grabbing or releasing the node PROPERTY_PARAMETER(Node, target_object); // Node to be displayed instead of the transformer-node, when it is grabbed // interact methods void grabIt(VRPlayer* player, int hand_num) override; // override grab action handler void throwIt(VRPlayer* player, int hand_num) override; // override trow action handler void holdIt(VRPlayer* player, int hand_num) override; // override hold action handler protected: void init() override; };
ObjTransformer.cpp
#include "ObjTransformer.h" REGISTER_COMPONENT( ObjTransformer ); // macro for component registration by the Component System // initialization void ObjTransformer::init(){ // hiding the target object (if any) if (target_object){ target_object->setEnabled(0); } } // grab action handler void ObjTransformer::grabIt(VRPlayer* player, int hand_num) { // if a target object is assigned, showing it, hiding the original object and displaying a message in the log if (target_object){ target_object->setEnabled(1); // hide original object's surfaces without disabling components ObjectPtr obj = checked_ptr_cast<Object>(node); for (int i = 0; i < obj->getNumSurfaces(); i++) obj->setEnabled(0, i); if (show_text) Log::message("\n Transformer's message: %s", text.get()); } } // throw action handler void ObjTransformer::throwIt(VRPlayer* player, int hand_num) { // if a target object is assigned, hiding it, and showing back the original object if (target_object){ target_object->setEnabled(0); // show original object's surfaces back ObjectPtr obj = checked_ptr_cast<Object>(node); for (int i = 0; i < obj->getNumSurfaces(); i++) obj->setEnabled(1, i); } } // hold action handler void ObjTransformer::holdIt(VRPlayer* player, int hand_num) { // changing the position of the target object target_object->setWorldPosition(player->getHandNode(hand_num)->getWorldPosition()); }
- 构建您的应用程序并启动它,就像我们之前的一样,将为我们的新组件生成一个新的属性文件(transformer.prop)。
- 在UnigineEditor中打开世界,创建一个新的box图元(Create -> Primitive -> Box),并将其放置在桌子附近的某个地方,创建一个球体图元(Create -> Primitive -> Sphere)用于转换。
-
要将组件添加到box对象,请选择它并在Node Properties部分中单击Add New Property,然后将movable.prop属性拖到出现的新空字段中。 对transformer.prop属性重复相同的操作,并将球体从World Hierarchy窗口拖到Target Object字段。
- 保存您的世界并关闭UnigineEditor。
- 启动应用程序。
11、限制传送#
默认情况下,可以瞬移到现场的任何点。 为了避免VR中的用户交互错误(例如,传送到墙壁或天花板),您可以将传送限制在某些区域。 为此,请执行以下操作:
- 创建一个网格,定义要限制用户传送到的区域。
-
在UnigineEditor中或使用setIntersectionMask()方法为该网格的所需表面设置一个intersection mask:
// defining the teleportation mask as a hexadecimal value (e.g. with only the last bit enabled) int teleport_mask = 0x80000000; // setting the teleportation mask to the MyAreaMesh object's surface with the num index MyAreaMesh->setIntersectionMask(num, teleport_mask);
- 使用以下方法为传送射线设置相同的相交掩码:VRPlayerVR::setTeleportationMask(teleport_mask)。
下一步可以做什么#
祝贺您! 现在,您知道如何在VR Sample演示的基础上创建自己的VR项目,并扩展其功能。 所以,您可以继续自己开发它。 有一些建议给您,可能是有用的:
- 尝试进一步分析示例的源代码并弄清楚它是如何工作的,用它来编写自己的代码。
- 阅读VR最佳实践文章,了解有关为VR准备内容并改善用户体验的更多信息和有用提示。
- 阅读组件系统文章,了解有关使用组件系统的更多信息。
- 查看组件系统使用示例了解有关使用组件系统实现逻辑的更多详细信息。