适用范围
开发文档适用于Unity 5.5及更新的版本,包括Unity 2017, Unity 2018, Unity 2019。
zSpace 插件构建
Unity开发人员无需修改zSpace插件中的任何内容即可使用它的所有功能。但是了解插件体系结构对加快应用的开发。本节着重探讨插件结构。
ZCore MonoBehaviour
zSpace插件包括一个名为ZCore的MonoBehaviour。为了向后兼容,zCore.prefab仍然可用,但由于Unity支持通过UnityEngine.Camera API进行立体渲染,因此已将其简化为单个GameObject。如果不使用zCore.prefab,也可以将ZCore MonoBehaviour脚本添加至场景中的任意GameObject。
使用以下Unity立体渲染相机API:
Camera.SetStereoViewMatrix(Camera.StereoscopicEye eye, Matrix4x4 matrix);
Camera.SetStereoProjectionMatrix(Camera.StereoscopicEye eye, Matrix4x4 matrix);
ZCore MonoBehaviour负责更新分配给ZCore.CurrentCameraObject的摄像头,以确保为zSpace正确实施立体渲染。以下示例说明了如何将ZCore MonoBehaviour的立体视图和投影矩阵应用于Unity摄像头:
// note: zCore and camera should be private members that are cached upon
// initialization for more optimal performance. The following is written for brevity.
ZCore zCore = GameObject.FindObjectOfType<ZCore>();
Camera camera = this.GetComponent<Camera>();
// Update the left and right view matrices.
Matrix4x4 leftView = zCore.GetFrustumViewMatrix(ZCore.Eye.Left) * camera.worldToCameraMatrix;
Matrix4x4 rightView = zCore.GetFrustumViewMatrix(ZCore.Eye.Right) * camera.worldToCameraMatrix;
camera.SetStereoViewMatrix(Camera.StereoscopicEye.Left, leftView);
camera.SetStereoViewMatrix(Camera.StereoscopicEye.Left, leftView);
// Update the left and right projection matrices.
Matrix4x4 leftProjection = zCore.GetFrustumProjectionMatrix(ZCore.Eye.Left);
Matrix4x4 rightProjection = zCore.GetFrustumProjectionMatrix(ZCore.Eye.Right);
camera.SetStereoProjectionMatrix(Camera.StereoscopicEye.Left, leftProjection);
camera.SetStereoProjectionMatrix(Camera.StereoscopicEye.Right, rightProjection);
以下操作适用于插件中的所有帧。
图2: 插件工作流程
插件定义
·
*更新L/R检测 *处理所有待处理的L/R同步请求。
·
·
*更新CoreSDK *更新所有基本Core SDK跟踪和立体相关信息。
·
·
*检查摄像头变更检查当前摄像头是否改变。
·
·
*检查检查器字段变更检查是否有任何检查器字段发生了变化(如IPD,观看者比例等),并据此将更新推送至Core SDK。
·
·
更新补间(tween)更新已排队的所有内部补间,如自动立体补间、摄像头导航补间等。
·
·
*更新自动立体状态根据跟踪摄像头是否看到头部目标,更新内部自动立体状态。
·
·
*更新显示器信息更新所有缓存显示器信息,如zSpace显示器角度。
·
·
*更新立体信息更新所有缓存立体信息,如视窗大小、视窗位置、每只眼睛的视图/投影矩阵等等。
·
·
*更新坐标空间信息更新立体设备相应立体视窗的所有缓存坐标空间转换。
·
·
*更新跟踪信息更新所有缓存跟踪信息,如各个坐标空间中各个目标的姿势。
·
·
*更新摄像头使用适当的左右视图/投影矩阵更新CurrentCameraObject的Camera组件。
·
·
*帧尾更新更新zSpace预览窗口(只能通过Unity Editor使用)
·
编辑器UI属性与调试信息
可以通过Unity编辑器访问zSpace插件的多种功能,包括可设置属性、调试可视化,以及来自跟踪系统的实时数据。
zCore检查器(Inspector)
zCore对象的检查器面板是获取zSpace执行数据的主要工具。该检查器面板包括多个部分,本节将分别介绍各个部分。
基本信息
“基本信息”部分包含zSpace插件和zSpace系统运行时的版本信息。
调试(Debug)
Debug部分包含若干复选框,用于控制在Unity编辑器Scene窗口中可见的debug可视化功能。这些可视化功能能够动态更新,且提供编辑模式和播放模式。 Figure 3 显示了这些可视化功能的示例。
图3: zCore调试信息可视化
复选框定义如下:
· **显示标签(Show Labels 控制是否显示所有zSpace可视化的文本标签。
· **显示视窗(零视差)(Show Viewport (Zero Parallax) ) 显示正在渲染的视窗。视窗为白色,与显示器共面。
· **显示舒适区(负视差)(Show Comfort Zone (Negative Parallax)) 显示屏幕前方立体舒适区。此复选框为黑色。
· **显示舒适区(正视差)(Show Comfort Zone (Positive Parallax)) 显示屏幕后方立体舒适区。此复选框为红色。
· **显示显示器(Show Display) 显示物理显示器的位置和方向。此复选框为黄色。
· **显示真实世界向上方向(Show Real-World Up) 此绿色箭头用于显示现实世界的向上方向。它与重力矢量方向相反。
· **显示地平面(Show Ground Plane) 显示物理地平面。现实世界向上矢量为该平面的正交面。此复选框为白色。
· **显示眼镜(Show Glasses) 显示代表眼镜中心位置的坐标系统的位置与方向。
· **显示触控笔(Show Stylus) 显示表示触控笔末端位置的坐标系统的位置和方向。
此外,调试部分还包含以下按钮:
· **打开/关闭预览窗口(Open/Close Preview Window ) 在打开和关闭zSpace预览窗口之间切换。
立体摄像头
即zSpace立体视图检查器控制属性的立体摄像头部分。这部分介绍了立体摄像头的属性和功能。
当前摄像头此属性包含当前应用的摄像头信息,将根据适当的左右视图/投影矩阵进行更新,以便为zSpace启用精确的头部跟踪立体渲染。 视窗中心主要视窗的世界坐标系的中心位置。修改视窗中心将更新当前摄像头转换。 视窗旋转主视窗在世界坐标系中的旋转。修改视窗旋转将更新当前摄像头的转换。 启用自动转换至单像插件可以跟踪眼镜,以确定眼镜当前是否可见。当眼镜从可见变为不可见时,插件可以从立体视图变为单像视图,反之亦然。该属性可开启立体与单像之间的转换。请注意,在单色模式下渲染时,仍在继续对左右眼进行渲染,只是渲染时使用同一个视锥体而已。 IPD是系统用于分离左眼和右眼的瞳距。应用应尽可能使用最准确的瞳距值,以最大限度地提高立体舒适度。 观看者比例默认情况下,zSpace使用1.0代表1米的比例。所有头部和触控笔跟踪信息都按照这个比例进行定义和处理。建议应用使用真实世界比例对所有事物建模。但有时无法按照真实世界比例建模,则可以使用观看者比例建模。观看者比例是视锥体计算的直接乘数。如果比例大于1.0,视锥体和当前摄像头到显示器的距离都会增加,比例小于1.0,视锥体和当前摄像头到显示器的距离都会减小。 比例因子直接映射至真实世界测量值。例如,显示器默认宽度为0.521米,而视锥体将与真实世界测量值相匹配。如果观看者比例设置为10.0,那么有效屏幕宽度将是5.21米。请注意,这一比例也会增加当前摄像头到显示器的距离。如果当前摄像头距显示器0.41米远,那么在观看者比例为10.0的情况下,此距离将变为4.1米。应用可以调整当前摄像头的观看者比例与位置,以匹配应用的建模比例。 注:通过检查器修改观看者比例,当前摄像头的转换也将随之更新,以维护视窗的当前中心和旋转(通过检查器指定)。 自动立体延迟与持续时间 该功能将控制自动立体模式转换为单像模式的延迟和持续时间。 眼镜检查员的“眼镜”部分提供有关眼镜的实时数据。目前该信息包括眼镜的姿势数据。 跟踪者坐标系姿势跟踪器坐标系姿势是指眼镜在跟踪器坐标系中的当前位置和方向。该位置位于眼镜中心。方向是指X、Y和Z轴的旋转量(以度为单位)。 世界坐标系姿势世界坐标系姿势是指眼镜在世界坐标系中的当前位置和方向。该位置位于眼镜中心。方向是指X、Y和Z轴的旋转量(以度为单位)。 触控笔检查器的触控笔部分提供有关触控笔的实时数据,以及用于启用触控笔功能的复选框。触控笔功能包括鼠标模拟与鼠标自动隐藏。 启用鼠标模拟该复选框用于启用和禁用鼠标模拟。许多应用都提供用户界面元素,可以模拟鼠标的使用。想像从触控笔笔尖发出的虚拟光线,跟随它直至其与显示器相交,交点处就是2D鼠标应在的位置将其与触控笔上的三个按钮结合在一起,即可使用触控笔轻松模拟鼠标,这就是鼠标模拟功能。 启用鼠标自动隐藏功能该复选框用于启用或禁用此功能。默认情况下,鼠标光标在zSpace窗口中可见。与2D用户界面元素交互时,有时需要显示鼠标光标,但光标也有可能在立体环境下分散注意力。鼠标自动隐藏功能将在鼠标移动或按下鼠标按钮一段时间之后关闭光标可见性。 跟踪者坐标系姿势跟踪器坐标系姿势是指触控笔在跟踪器坐标系内的当前位置和方向,此位置位于触控笔末端。方向是指X、Y和Z轴的旋转量(以度为单位)。 世界坐标系姿势世界坐标系姿势是指触控笔在世界坐标系中的当前位置和方向。此位置位于触控笔末端。方向是指X、Y和Z轴的旋转量(以度为单位)。 显示器检查器的显示器部分展示了与系统连接的显示器属性实时数据。所有显示器都将显示以下信息。 位置即显示器左上角相对于整个Windows虚拟桌面的位置。此位置以像素为单位。 尺寸 显示器物理尺寸,以米为单位。 分辨率显示器的当前分辨率,以像素为单位。 角度显示器的当前角度。角度是指围绕水平轴的旋转角度,以度为单位。目前仅跟踪围绕X轴的旋转。
简单的触控笔访问
利用编辑器属性,多数Unity应用都可以进行头部跟踪。本节介绍了一些简单代码,用于访问触控笔。有关触控笔功能的详细信息,请参见第页的Core API。
触控笔事件通过该插件可以读取触笔移动和按钮事件。使用ZCore脚本,您可以针对触控笔移动、按下按钮、释放按钮等事件添加监听器。以下代码展示了如何执行此操作。
_core = GameObject.FindObjectOfType<ZCore>();
// Register event handlers.
_core.TargetMove += HandleMove;
_core.TargetButtonPress += HandleButtonPress;
_core.TargetButtonRelease += HandleButtonRelease;
所有事件回调都采用以下形式。
private void HandleMove(ZCore sender, ZCore.TrackerEventInfo info)
{
}
private void HandleButtonPress(ZCore sender, ZCore.TrackerButtonEventInfo info)
{
}
private void HandleButtonRelease(ZCore sender, ZCore.TrackerButtonEventInfo info)
{
}
TrackerEventInfo包含生成事件的跟踪器目标、目标类型以及事件发生时的世界坐标系姿势。对于按钮事件,还包括按钮ID。有关更多信息,请参阅第页的Tracker Events部分。
触控笔游戏对象操作
使用触控笔拾取并直接操作对象是zSpace应用中的常见操作。StylusObjectManipulationSample场景和脚本对此操作的执行方式进行了说明,但本文提供了更高层级的详细信息。 该示例采取的机制是,按下主按钮后,触控笔将抓住 最靠近的对象。但该对象必须处于触控笔指向的方向。按下按钮后,对象将被放置于虚拟触控笔射线与对象的交点处。 每一帧都将检查触控笔是否正在抓取物体。如果没有抓取,请检查触控笔射线是否与任何物体相交。请注意,只有带有碰撞器的物体才能被报告为与射线相交。
Core.Pose pose = _core.GetTargetPose(ZCore.TargetType.Primary, ZCore.CoordinateSpace.World);
RaycastHit hit;
if (Physics.Raycast(pose.Position, pose.Direction, out hit))
{
// Update the stylus beam length.
_stylusBeamLength = hit.distance;
// If the front stylus button was pressed, initiate a grab.
if (isButtonPressed && !_wasButtonPressed)
{
// Begin the grab.
this.BeginGrab(hit.collider.gameObject, hit.distance, pose.Position, pose.Rotation);
_stylusState = StylusState.Grab;
}
}
如果按下按钮时检测到存在对象,将触动抓取操作。抓取开始时,一定要对状态进行缓存。
private void BeginGrab(GameObject hitObject,
float hitDistance,
Vector3 inputPosition,
Quaternion inputRotation)
{
Vector3 inputEndPosition = inputPosition + (inputRotation * (Vector3.forward * hitDistance));
// Cache the initial grab state.
_initialGrabOffset = Quaternion.Inverse(hitObject.transform.localRotation) *
(hitObject.transform.localPosition - inputEndPosition);
_initialGrabRotation = Quaternion.Inverse(inputRotation) * hitObject.transform.localRotation;
}
首先,计算对象的世界旋转,然后计算从触控笔末端到对象末端的当前偏移量。这是一个_initialGrabOffset变量。请在触控笔移动时保持此偏移量。另外,请务必缓存触控笔与对象的累积旋转,即_initialGrabRotation变量。也是添加新触控笔旋转时使用的起始旋转。 在当前帧中抓取对象时,根据触控笔的当前姿势更新抓取对象的位置和方向。
case StylusState.Grab:
{
// Update the grab.
this.UpdateGrab(pose.Position, pose.Rotation);
// End the grab if the front stylus button was released.
if (!isButtonPressed && _wasButtonPressed)
{
_stylusState = StylusState.Idle;
}
}
break;
private void UpdateGrab(Vector3 inputPosition, Quaternion inputRotation)
{
Vector3 inputEndPosition =
inputPosition + (inputRotation * (Vector3.forward * _initialGrabDistance));
// Update the grab object's rotation.
Quaternion objectRotation = inputRotation * _initialGrabRotation;
_grabObject.transform.rotation = objectRotation;
// Update the grab object's position.
Vector3 objectPosition = inputEndPosition + (objectRotation * _initialGrabOffset);
_grabObject.transform.position = objectPosition;
}
对象的新旋转是指将当前触控笔旋转添加至起始旋转所得的旋转。新位置是指将新旋转转换的起始偏移量添加至触控笔的当前末端所得的位置。 请参阅StylusObjectManipulationSample场景和脚本,以了解此功能的详细代码和逻辑。
核心API
本文到目前为止介绍的所有功能都可以通过编程方式和编辑器进行访问。本部分的功能可以通过附加于zCore游戏对象的zSpace.Core.ZCore脚本访问。脚本提供的功能多于编辑器提供的功能。本节介绍了zSpace.Core.ZCore脚本提供的所有可用功能。
公共属性
ZCore脚本包括数个可作为公共属性的功能。
调试信息可视化以下属性用于控制调试可视化。
bool ShowLabels
bool ShowViewport
bool ShowCCZone
bool ShowUCZone
bool ShowDisplay
bool ShowRealWorldUp
bool ShowGroundPlane
bool ShowGlasses
bool ShowStylus
插件操作应用可以动态更改启用了立体渲染的摄像头。如需更改,请设置以下属性。
GameObject CurrentCameraObject
立体控制通过几种属性对立体渲染进行全面控制。 以下属性更改系统采用的瞳距。
float Ipd
以下属性更改观看者比例。与ZCore检查器中的Viewer Scale(观看者比例)属性不同,通过ZCore API修改ViewerScale属性并不会对CurrentCameraObject的转换进行任何更改。
float ViewerScale
以下属性控制自动立体功能的启用,调整自动立体动画开始之前的延迟时间,以及立体至单像转换动画的持续时间。
bool EnableAutoStereo
float AutoStereoDelay
float AutoStereoDuration
鼠标功能这些属性能够控制鼠标模拟与鼠标自动隐藏功能。 要启用或禁用这两种功能中的任何一种,请使用以下两个属性。
bool EnableMouseEmulation
bool EnableMouseAutoHide
以下属性控制在无鼠标活动的情况下等待多长时间隐藏光标。
float MouseAutoHideDelay
zSpace插件
本文的其余部分介绍了ZCore脚本提供的可用方法以及插件的基本操作。下面的几种方法允许应用查看插件是否已初始化,读取插件版本信息,以及系统的当前zSpace运行时系统。
bool IsInitialized()
string GetPluginVersion()
string GetRuntimeVersion()
下面几种方法允许应用启用或禁用跟踪系统,并检查跟踪系统的启用状态。如果禁用了跟踪功能,则系统不再跟踪眼镜和触控笔。
void SetTrackingEnabled(bool isEnabled)
bool IsTrackingEnabled()
立体
ZCore脚本中的其余大部分方法都直接映射到本机zSpace SDK,因此需要做几点解释。此处介绍的方法旨在实现两种目的。首先,它们能够对本机SDK为Unity应用提供的所有参数和功能进行低级访问。它们也是绑定于zSpace本机SDK的C#语言初始定义。 在本机SDK中,多个zSpace对象都由ZCHandle对象引用。在C#绑定中,此类型被表示为IntPtr。可以在通用C#应用中使用这些方法。Unity应用会为系统创建这些zSpace低级对象并进行初始化。SDK并不强制应用携带这些句柄,并提供通常不需要任何句柄的版本。在这种情况下,可以将这些方法用于适用于Unity应用的低级zSpace对象。例如,以下两种方法具有相同功能:
void SetFrustumAttribute(FrustumAttribute attribute, bool value)
void SetFrustumAttribute(IntPtr frustumHandle, FrustumAttribute attribute, bool value)
Unity应用程序员将使用第一个版本,因为该方法适用于Unity应用的视锥体对象。而一般C#应用将使用第二种方法。为完整起见,本文档列出了两个版本,但Unity应用将始终使用不带句柄的版本。
显示器
本节介绍了与显示器及其属性有关的所有方法。 应用将在zSpace运行时初始化时发现显示器,不过也可以使用以下函数重新发现显示器。
void RefeshDisplays()
zSpace运行时可以找到三种类型的显示器,即zSpace、Generic和Unknown。zSpace类型显示器可以提供所有显示器信息供应用检索,而其他类型的显示器可能不会返回zSpace SDK定义的所有信息。有几种函数能够遍历应用发现的显示器, 这些函数将返回发现的显示器数量,不过第二个版本只返回指定类型的显示器数量。
int GetNumDisplays()
int GetNumDisplays(DisplayType displayType)
以下函数将帮助您获得指定位置的显示器信息。该位置通过虚拟桌面像素坐标指定。
IntPtr GetDisplay(int x, int y)
这些函数帮助您获得给定索引处的显示器信息。请注意,第一个函数指定的索引相与所有显示器相对,而第二个函数的索引仅与该类型显示器相关。
IntPtr GetDisplay(int index)
IntPtr GetDisplay(DisplayType displayType, int index)
获得显示器句柄后,即可检索多种显示器属性。以下函数可读取显示器类型。
DisplayType GetDisplayType()
DisplayType GetDisplayType(IntPtr displayHandle)
以下函数检索由分辨率控制面板窗口定义的显示器数量。
int GetDisplayNumber()
int GetDisplayNumber(IntPtr displayHandle)
适配器索引是连接至显示器的GPU的索引。
int GetDisplayAdapterIndex()
int GetDisplayAdapterIndex(IntPtr displayHandle)
显示器索引是连接到特定适配器或GPU的显示器的索引。以下函数用于多部显示器连接到单个GPU的情况。
int GetDisplayMonitorIndex()
int GetDisplayMonitorIndex(IntPtr displayHandle)
由Windows指定的几种字符串属性可以使用以下函数检索。这些函数可获取属性字符串值。多数此类属性直接映射至windows定义的值,其中的Model属性是与zSpace对应的值,指定了zSpace显示器的类型,其可能赋值包括 100、200、300或Zvr。 应用可通过以下函数读取特定于zSpace显示器的属性信息:
string GetDisplayAttributeString(DisplayAttribute attribute)
string GetDisplayAttributeString(IntPtr displayHandle, DisplayAttribute attribute)
以下函数用于检索显示器的物理尺寸(以米为单位)。
Vector2 GetDisplaySize()
Vector2 GetDisplaySize(IntPtr displayHandle)
以下函数检索zSpace显示屏左上角在虚拟桌面中的位置。
Vector2 GetDisplayPosition()
Vector2 GetDisplayPosition(IntPtr displayHandle)
以下函数检索zSpace显示器的像素分辨率。
Vector2 GetDisplayNativeResolution()
Vector2 GetDisplayNativeResolution(IntPtr displayHandle)
以下函数返回zSpace显示器的当前显示角度。
Vector3 GetDisplayAngle()
Vector3 GetDisplayAngle(IntPtr displayHandle)
显示器角度从水平平面位置测量,单位为度。请注意,除zSpace 100外,此值为动态值且可以持续更改。如果需要此信息,应用必须为每一幅渲染帧检索该值。以下函数将返回显示器刷新率。
float GetDisplayVerticalRefreshRate()
float GetDisplayVerticalRefreshRate(IntPtr displayHandle)
以下函数用于表明显示器是否连接到系统。
bool IsDisplayHardwarePresent()
bool IsDisplayHardwarePresent(IntPtr displayHandle)
应用需要确定的另一种常见特性是,从触控笔末端发出的指向触控笔方向的虚拟射线将在何处与显示器相交。以下函数用于计算该交点位置,并考虑到了所有zSpace显示器属性。
DisplayIntersectionInfo IntersectDisplay(Pose pose)
DisplayIntersectionInfo IntersectDisplay(IntPtr displayHandle, Pose pose)
姿势信息位于跟踪器坐标系中,可以直接在跟踪器系统中检索。返回的信息包括是否触碰显示器、交叉点所在的虚拟桌面的标准化和非标准化像素坐标,以及触控笔末端到交点之间的距离(以米为单位)。
视窗
视窗对象在发生立体渲染的zSpace显示器定义了一个窗口。视窗并不实施渲染;而是充当数据容器,确保计算正确。应用可以按照自己选择的方式定义多个视窗,但需要确保其在渲染各个视窗时使用适当的数据。以下函数负责创建及销毁视窗。Unity应用通常不使用这些方法,因为Unity通过插件处理所有这些操作。
IntPtr CreateViewport()
void DestroyViewport(IntPtr viewportHandle)
除此之外,视窗只需要设置并读取视窗的位置和大小。
void SetViewportPosition(IntPtr viewportHandle, int x, int y)
Vector2 GetViewportPosition()
Vector2 GetViewportPosition(IntPtr viewportHandle)
void SetViewportSize( IntPtr viewportHandle, int width, int height)
Vector2 GetViewportSize()
Vector2 GetViewportSize(IntPtr viewportHandle)
应用需要确保渲染窗口与视窗位置和大小保持同步,否则渲染将发生错误。该插件将为Unity应用完成这项任务,不需要实施额外操作。
视锥体
视锥体是指屏幕上的可见3D区域。它表示由当前头部位置以及zSpace显示器位置和方向所定义的立体视锥体。可以在立体视锥体中修改多种属性,视锥体对象与视窗对象同时自动创建和销毁。应用使用以下函数读取视锥体对象。
IntPtr GetFrustum(IntPtr viewportHandle)
多数应用使用基本设置和逻辑就能保证良好运行,不过开发者仍可调整多种属性来为应用定制立体体验。对于多数属性,可以使用以下get和set函数来设置其赋值。
void SetFrustumAttribute(FrustumAttribute attribute, float value)
void SetFrustumAttribute(IntPtr frustumHandle, FrustumAttribute attribute, float value)
float GetFrustumAttributeFloat(FrustumAttribute attribute)
float GetFrustumAttributeFloat(IntPtr frustumHandle, FrustumAttribute attribute)
void SetFrustumAttribute(FrustumAttribute attribute, bool value)
void SetFrustumAttribute(IntPtr frustumHandle, FrustumAttribute attribute, bool value)
bool GetFrustumAttributeBool(FrustumAttribute attribute)
bool GetFrustumAttributeBool(IntPtr frustumHandle, FrustumAttribute attribute)
如果该属性需要浮点值,则使用该函数的浮点版本。如果属性为布尔值,则使用该函数的布尔版本。类型之间没有自动转换。如果将函数的浮点版本用于布尔属性,则会发生错误。对浮点属性使用布尔API也是如此。
视锥体属性与定义
FrustumAttribute枚举定义了可以检索的一组属性,每一种属性描述如下。这些属性将影响离轴视锥体的实际形状。可以在《zSpace开发者指南-SDK简介》中查看这些属性的视觉描述。
· Ipd眼睛之间的物理距离(即瞳距),以米为单位进行测量。将IPD设为零(0)即意味着两只眼睛处于同一位置,因此将禁用立体功能。
· ViewerScale观看者比例,计算视锥体的直接乘数。如果比例大于1.0,视锥体和当前摄像头到显示器的距离都会增加,比例小于1.0,视锥体和当前摄像头到显示器的距离都会减小。
· HeadScale统一比例因子,适用于视锥体即将采取的头部姿势。
· NearClip视锥体近剪切平面,以米为单位。
· FarClip视锥体远剪切平面,以米为单位。
· GlassesOffset鼻梁架与鼻梁之间的距离,以米为单位。
立体舒适度
舒适的立体观看体验来源于多种因素。《了解zSpace设计美学》对这些因素进行了说明。该文件定义了耦合区(立体舒适区)、交叉区(负视差)和非交叉区(正视差)。 这些区域的极限值(按照像素视差衡量)可以作为视锥体属性进行检索,如下文所示。
· CCLimit耦合区内交叉图像的最大像素视差(负视差)。
· UCLimit耦合区内非交叉图像的最大像素视差(正视差)。
· CULimit非耦合区内交叉图像的最大像素视差(负视差)。
· UULimit非耦合区内非交叉图像的最大像素视差(正视差)。为方便起见,应用也可读取这些属性的耦合区边缘物理深度极限。
· CCDepth耦合区负视差的最大深度(米)。
· UCDepth耦合区正视差的最大深度(米)。此外,为了方便起见,还有两种实用函数可用于帮助应用创建立体舒适场景。
Bounds GetFrustumCoupledBoundingBox()
Bounds GetFrustumCoupledBoundingBox(IntPtr frustumHandle)
此函数返回摄像头坐标系中耦合区的boundingBox(边界框)。它告诉应用应将场景主焦点放置于何处。
门户模式
正如《zSpace开发者指南--SDK简介》描述的那样,zSpace实施鱼缸虚拟现实风格系统。视窗是进入虚拟世界的门户。视窗在显示器上移动或显示角度改变时,可以通过两种方法对这些事件作出反应:您可以随视窗或显示器角度移动虚拟世界,也可以保持虚拟世界静止,并在虚拟世界中移动视窗。应用通过调整视锥体使用的门户模式来控制这一行为。可以使用以下函数调整门户模式值。
void SetFrustumPortalMode(int portalModeFlags)
void SetFrustumPortalMode(IntPtr frustumHandle, int portalModeFlags)
int GetFrustumPortalMode()
int GetFrustumPortalMode(IntPtr frustumHandle)
门户模式标志是代表启用门户模式的位掩码。标志如下:
· 无(None)场景随视窗移动,忽略显示器角度。
· 角度(Angle)场景的方向相对于物理桌面是固定的。
· 位置(Position)场景的位置相对于显示器中心是固定的。
· 全部(All) - 除“none”之外的所有门户模式已启用。 如果门户模式为None,则应用可能仍然需要模拟显示器角度的更改。通过设置以下视锥体属性可以做到这一点。
· DisplayAngleX
· DisplayAngleY
· DisplayAngleZ显示器角度是相对于X、Y和Z轴的角度,以度为单位。 实际的系统显示器角度仍然可以通过GetDisplayAngle检索。上述视锥体显示器角度值仅在门户模式设置为None时使用。
焦点和零视差
应用经常需要将对象放置于零视差平面。零视差平面与应用定义的当前摄像头之间具有固定距离,可将其视为焦点。该距离在zSpace插件中以摄像头偏移矢量表示。可以通过以下函数读取或设置该矢量。
void SetFrustumCameraOffset(Vector3 cameraOffset)
void SetFrustumCameraOffset(IntPtr frustumHandle, Vector3 cameraOffset)
Vector3 GetFrustumCameraOffset()
Vector3 GetFrustumCameraOffset(IntPtr frustumHandle)
摄像头偏移矢量的长度是指应用当前摄像头到显示器/视窗中心的距离。应用可以使用该值来了解虚拟摄像头相对于显示/视窗中心的放置距离。应用也可以修改摄像头偏移矢量,但不推荐进行这种操作。
Camera Control
应用经常对当前摄像头进行定位和定向,以使对象位于zSpace显示器中心。ZCore脚本提供了两个实用函数来协助此操作。
void SetViewportWorldTransform(
Vector3 center,
Quaternion rotation,
float viewerScale
)
此函数用于更新CurrentCameraObject的转换和ViewerScale属性,使得主视窗根据指定的中心、旋转和viewerScale参数定位于世界中心,并完成定向。
Vector3 ComputeCameraPosition(
Vector3 focalPoint,
Quaternion cameraRotation,
float viewerScale
)
此函数返回应用放置当前摄像头的世界坐标系位置,使给定焦点位于视窗/显示器中心。给定的cameraRotation表示当前摄像头从哪个方向朝向焦点对象。 有关更高级的摄像头控制操作,请参阅CameraNavigationSample脚本和场景。
头部姿势默认情况下,zSpace插件能够自动读取头部姿势,并将其运用于所有已知视锥体。如果禁用跟踪功能,则应用可以手动设置并读取视锥体使用的头部姿态,以计算立体变换和投影。
void SetFrustumHeadPose(IntPtr frustumHandle, Pose headPose)
Pose GetFrustumHeadPose()
Pose GetFrustumHeadPose(IntPtr frustumHandle)
这种功能有时用于在调试期间模拟头部跟踪。
转换zSpace计算出的两个转换已集成于Unity系统的渲染转换。这两种转换与左右眼一一相对,为眼睛渲染图像时需要正确使用这些转换。 zSpace获取代表眼镜中心位置和方向的头部姿态,然后计算适用于立体视锥体的两种变换。视图矩阵变换代表相对变换,结合了瞳距、从眼镜到眼睛的偏移量,以及从摄像头坐标系到显示坐标系的变换。可以使用以下函数检索视图变换。
Matrix4x4 GetFrustumViewMatrix(Eye eye)
Matrix4x4 GetFrustumViewMatrix(IntPtr frustumHandle, Eye eye)
zSpace插件能够自动将视图矩阵应用于摄像头,不过有时也可帮助应用获取视图矩阵,以用于其他目的。Unity Camera.SetStereoViewMatrix() API的调用需要使视图矩阵处于右手空间,因此视图矩阵将返回右手空间(而不是Unity默认的左手空间)。 第二个转换是一个投影矩阵。以下函数用于获取投影矩阵。
Matrix4x4 GetFrustumProjectionMatrix(Eye eye)
Matrix4x4 GetFrustumProjectionMatrix(IntPtr frustumHandle, Eye eye)
投影矩阵将离轴投影编码成矩阵。以下函数的矩阵是一个OpenGL风格的投影矩阵。还有其他两种方法可用于获取离轴投影信息。第一组函数返回视锥体边界信息,作为标准六边界值:左、右、上、下、近、远。
FrustumBounds GetFrustumBounds(Eye eye)
FrustumBounds GetFrustumBounds(IntPtr frustumHandle, Eye eye)
Vector3 GetFrustumEyePosition(Eye eye, CoordinateSpace coordinateSpace)
Vector3 GetFrustumEyePosition(
IntPtr frustumHandle,
Eye eye,
CoordinateSpace coordinateSpace
)
第二组函数则允许应用在任意坐标系中检索眼睛位置。可将此函数用于实时光线追踪应用。通过获取眼睛在视窗坐标系内的位置,并使用视窗位置和大小,应用可以构建适当的起始光线进行渲染。
坐标系统《zSpace开发者指南-SDK简介》介绍了zSpace使用的坐标空间。为了给zSpace创建更好的体验,我们有必要了解这些坐标空间。除标准SDK坐标系之外,Unity插件还支持世界坐标空间,因为该插件能够访问当前摄像头的“摄像头-世界”转换。Core中有两个函数用于坐标空间处理:
Matrix4x4 GetCoordinateSpaceTransform(CoordinateSpace a, CoordinateSpace b)
Matrix4x4 GetCoordinateSpaceTransform(
IntPtr viewportHandle,
CoordinateSpace a,
CoordinateSpace b
)
以下函数用于获得从坐标空间“a”到坐标空间“b”的坐标系转换。
Matrix4x4 TransformMatrix(CoordinateSpace a, CoordinateSpace b, Matrix4x4 matrix)
Matrix4x4 TransformMatrix(
IntPtr viewportHandle,
CoordinateSpace a,
CoordinateSpace b,
Matrix4x4 matrix
)
跟踪
跟踪系统为连接到zSpace系统的所有跟踪设备及其跟踪目标提供支持。本节介绍了跟踪设备、跟踪目标以及跟踪目标可能支持的所有功能。Unity示例场景展示了多种功能的使用情况,并在适当情况下对这些功能加以识别。
跟踪器设备跟踪系统对多种跟踪器设备进行了定义。应用可以使用跟踪器设备函数对所有已知的跟踪器设备进行迭代。 以下函数可以帮助应用遍历所有跟踪器设备,或获取特定的指定跟踪器设备。
int GetNumTrackerDevices()
IntPtr GetTrackerDevice(int index)
IntPtr GetTrackerDevice(string deviceName)
以下函数允许应用启用或禁用跟踪器设备,并检查跟踪器设备是否已启用。
void SetTrackerDeviceEnabled(IntPtr deviceHandle, bool isEnabled)
bool IsTrackerDeviceEnabled(IntPtr deviceHandle)
以下函数允许应用读取跟踪器设备名称。
string GetTrackerDeviceName(IntPtr deviceHandle)
跟踪器目标跟踪器设备具有与其关联的多个跟踪器目标。跟踪器目标也具有与其关联的类型。共有两种跟踪器目标类型,包括Head和Primary。头目标为眼镜,主要目标为触控笔。 有几种函数可用于查询或设置跟踪器目标属性。如果给定跟踪器目标不支持该属性或功能,则将返回CapabilityNotFoundException。例如,检查头部跟踪器目标按钮状态的功能。为遍历跟踪器目标,可以使用以下函数。 以下函数能够读取已知目标的数量和指定索引位置的目标。
int GetNumTargets(IntPtr deviceHandle)
IntPtr GetTarget(IntPtr deviceHandle, int index)
这些函数能够读取特定类型的已知目标的数量,以及位于指定索引位置的该类型的目标数量。
int GetNumTargets(TargetType targetType)
IntPtr GetTarget(TargetType targetType, int index)
以下函数用于检索具有指定名称的目标。
IntPtr GetTarget(IntPtr deviceHandle, string targetName)
此函数能够检索跟踪器目标的数种属性。以下函数用于读取跟踪器目标名称。
string GetTargetName(TargetType targetType)
string GetTargetName(IntPtr targetHandle)
这些函数允许应用启用或禁用跟踪器目标,并确定目标当前是否启用。
void SetTargetEnabled(TargetType targetType, bool isEnabled)
void SetTargetEnabled(IntPtr targetHandle, bool isEnabled)
bool IsTargetEnabled(TargetType targetType)
bool IsTargetEnabled(IntPtr targetHandle)
以下函数将告知应用:目标在跟踪系统中是否可见。
bool IsTargetVisible(TargetType targetType)
bool IsTargetVisible(IntPtr targetHandle)
如果跟踪器目标不可见,应用可能需要实施特定操作。插件使用这种功能来实施头部跟踪器目标的自动立体化。
跟踪器目标姿势跟踪器目标将生成姿势,姿势是指一个矩阵,对姿势的位置和方向进行编码,包括位置、旋转、前进方向、姿势发生时的时间戳,以及姿势的坐标空间。应用可能会在任何时间要求读取跟踪器目标的姿势数据。请注意,插件Update()将缓存所有跟踪器目标的当前姿势信息,因此为检索姿势数据而多次调用将返回相同数值。以下函数用于在指定坐标空间中读取姿态。
Pose GetTargetPose(TargetType targetType, CoordinateSpace coordinateSpace)
Pose GetTargetPose(IntPtr targetHandle, CoordinateSpace coordinateSpace)
触控笔的某些操作可能会导致触控笔在操作过程中改变姿势。例如按下触控笔上的按钮时,按下按钮的动作将稍微改变触控笔在按压开始时的方向。zSpace插件具有一种“姿势缓冲”功能,有助于缓解此问题。启用姿势缓冲后,运行时系统将保留该目标的最后N个姿势,其中“N”根据姿势缓冲区的容量定义。接下来,应用可以读取该姿势缓冲区,并以其选择的任意方式处理缓冲区。 以下函数用于启用或禁用姿势缓冲,并检查其是否启用。
void SetTargetPoseBufferingEnabled(TargetType targetType, bool isPoseBufferingEnabled)
void SetTargetPoseBufferingEnabled(IntPtr targetHandle, bool isPoseBufferingEnabled)
bool IsTargetPoseBufferingEnabled(TargetType targetType)
bool IsTargetPoseBufferingEnabled(IntPtr targetHandle)
以下函数用于检索姿势缓冲区。
IList<Pose> GetTargetPoseBuffer(
TargetType targetType,
float minDelta,
float maxDelta,
int maxNumPoses
)
IList<Pose> GetTargetPoseBuffer(
IntPtr targetHandle,
float minDelta,
float maxDelta,
int maxNumPoses
)
以下函数用于查询姿势缓冲区容量,还可调整容量。容量是指缓冲能够保存的姿势的数量。缓冲区内保存着最后N个姿势。
void ResizeTargetPoseBuffer(TargetType targetType, int capacity)
void ResizeTargetPoseBuffer(IntPtr targetHandle, int capacity)
int GetTargetPoseBufferCapacity(TargetType targetType)
int GetTargetPoseBufferCapacity(IntPtr targetHandle)
按钮主要跟踪器目标可能具有N个按钮。可以使用以下函数来查询按钮数量与每个按钮的状态。
int GetNumTargetButtons(TargetType targetType)
int GetNumTargetButtons(IntPtr targetHandle)
bool IsTargetButtonPressed(TargetType targetType, int buttonId)
bool IsTargetButtonPressed(IntPtr targetHandle, int buttonId)
这些函数可用于以轮询方式跟踪按钮状态。为此,需要跟踪最后一个按钮的状态,并将其与当前按钮状态进行比较。由StylusObjectManipulationSample场景和脚本实施此逻辑。该脚本摘录如下。
private bool _wasButtonPressed = false;
private StylusState _stylusState = StylusState.Idle;
void Update() {
bool isButtonPressed = _core.IsTargetButtonPressed(ZCore.TargetType.Primary, 0);
switch (_stylusState) {
case StylusState.Idle:
// If the front stylus button was pressed, initiate a grab.
if (isButtonPressed && !_wasButtonPressed) {
_stylusState = StylusState.Grab;
}
}
break;
case StylusState.Grab:
// End the grab if the front stylus button was released.
if (!isButtonPressed && _wasButtonPressed) {
_stylusState = StylusState.Idle;
}
break;
default:
break;
// Cache state for next frame.
_wasButtonPressed = isButtonPressed;
}
每次调用Update()时,代码都将检查按钮状态。如果按钮原先未被按下而现在被按下,则发生按钮按压事件。如果按钮原先被按下,而现在未被按压,则发生按钮释放事件。也可以通过事件监视按钮状态。参见跟踪器事件的描述。
LED
触控笔中间部分有一只LED,可以使用zSpace ZCore脚本改变LED的状态和颜色。以下函数用于启用或禁用LED,并检查其是否启用。
void SetTargetLedEnabled(TargetType targetType, bool isLedEnabled)
void SetTargetLedEnabled(IntPtr targetHandle, bool isLedEnabled)
bool IsTargetLedEnabled(TargetType targetType)
bool IsTargetLedEnabled(IntPtr targetHandle)
以下函数用于设置及读取LED颜色。请注意,LED无法表示出所有可能的指定RGB值,因此系统将与指定颜色进行最接近的匹配。有关如何设置LED颜色的示例,请参阅StylusLedSample场景。
void SetTargetLedColor(TargetType targetType, Color ledColor)
void SetTargetLedColor(IntPtr targetHandle, Color ledColor)
Color GetTargetLedColor(TargetType targetType)
Color GetTargetLedColor(IntPtr targetHandle)
振动
zSpace触控笔内置振动功能。开发者可对振动模式进行编程,并控制该功能于何时启动和停止。 以下函数用于启用和禁用振动功能,并检查当前是否启用振动。
void SetTargetVibrationEnabled(TargetType targetType, bool isVibrationEnabled)
void SetTargetVibrationEnabled(IntPtr targetHandle, bool isVibrationEnabled)
bool IsTargetVibrationEnabled(TargetType targetType)
bool IsTargetVibrationEnabled(IntPtr targetHandle)
振动功能被定义为重复的开关周期。也可以指定触控笔在一段时间内的振动强度。不过,有些老式触控笔设备可能会忽略强度参数,始终采用最大振动强度。 以下函数用于启动振动模式。“开启”和“关闭”时间以秒为单位。强度以0.0到1.0之间的百分比表示。例如,将强度设置为0.5,即表示50%强度。
void StartTargetVibration(
TargetType targetType,
float onPeriod,
float offPeriod,
int numTimes,
float intensity
)
void StartTargetVibration(
IntPtr targetHandle,
float onPeriod,
float offPeriod,
int numTimes,
float intensity
)
这些振动函数用于帮助开发者检查触控笔是否正在振动,并停止触控笔振动。如需查看目前使用的振动API,请参阅StylusVibrationSample场景和脚本。
bool IsTargetVibrating(TargetType targetType)
bool IsTargetVibrating(IntPtr targetHandle)
void StopTargetVibration(TargetType targetType)
void StopTargetVibration(IntPtr targetHandle)
~~
轻敲
触控笔为开发者提供了另外一种输入功能,即触控笔可以探测到何时敲击显示器表面。以下函数用于检查触控笔是否正在轻敲显示屏。
鼠标模拟
许多应用都提供用户界面元素,可以与鼠标互动。想像从触控笔笔尖发出的虚拟光线,跟随它直至其与显示器相交,交点处就是2D鼠标应在的位置。将其与触控笔上的三个按钮结合在一起,即可使用触控笔轻松模拟鼠标。这就是鼠标模拟功能。使用以下函数来启用、禁用鼠标模拟,或检查当前是否启用了鼠标模拟。
void SetMouseEmulationEnabled(bool isEnabled)
bool IsMouseEmulationEnabled()
可以对产生六自由度姿势的任意追踪目标使用鼠标模拟。以下函数允许应用设置并读取用于生成姿势的跟踪目标。第一种方法允许应用将鼠标模拟目标设置为Head或Primary。
void SetMouseEmulationTarget(TargetType targetType)
void SetMouseEmulationTarget(IntPtr targetHandle)
IntPtr GetMouseEmulationTarget()
计算虚拟射线与显示器的交点时,系统可以通过几种不同的方式将这些数据应用于鼠标。以下函数用于设置和读取当前模拟模式。
void SetMouseEmulationMovementMode(MovementMode movementMode)
MovementMode GetMouseEmulationMovementMode()
鼠标模拟模式可以是Absolute(绝对)或Relative(相对)。在绝对模式下,系统将把鼠标位置精确移动到交叉点位置。这种操作非常自然,但是如果鼠标在此过程中移动,则可能会出现光标干扰。如果采用相对模式,则对光标位置应用当前位置与最后位置之间的差异,这样不会干扰鼠标,但用户的直观体验将受到影响。 也可仅在触控笔靠近显示屏时才开启鼠标模拟。以下函数可以控制该功能。
void SetMouseEmulationMaxDistance(float maxDistance)
float GetMouseEmulationMaxDistance()
鼠标模拟的最大距离是指开始鼠标模拟时触控笔垂直于显示器的最大距离。如果触控笔距离超过最大距离,则无法进行鼠标模拟。距离以米为单位。 最后,应用可能需要触控笔按钮以特定方式映射至鼠标按钮。可以通过以下函数分配和检索触控笔按钮与鼠标按钮之间的映射关系。
void SetMouseEmulationButtonMapping(int buttonId, MouseButton mouseButton)
MouseButton GetMouseEmulationButtonMapping(int buttonId)
按钮ID为触控笔按钮编号,鼠标按钮可以是LEFT(左)、RIGHT(右)或CENTER(中)。
跟踪器事件
所有触控笔功能都以轮询方式呈现,因此应用需要不断检查系统以检测状态变化。有几种跟踪器目标功能可以生成异步事件,应用可以通过添加以下事件处理器来侦听这些事件。为这些事件的侦听者定义了以下方法签名。
delegate void EventHandler(ZCore sender);
delegate void TrackerEventHandler(ZCore sender, TrackerEventInfo info);
delegate void TrackerButtonEventHandler(ZCore sender, TrackerButtonEventInfo info);
以下事件可能为已知事件。PreUpdate()在插件开始Update()每帧处理之前被调用。PostUpdate()在插件完成Update()每帧处理之后被调用。
event EventHandler PreUpdate;
event EventHandler PostUpdate;
如果跟踪器目标移动,TrackerEventInfo将包含生成事件的跟踪器目标、目标类型以及事件发生时的世界坐标系姿势。
event TrackerEventHandler TargetMove;
可以使用以下函数检测跟踪器目标是否在显示器上被按压。
event TrackerEventHandler TargetTapPress;
可以使用以下函数检测跟踪器目标是否在显示器上被释放。
event TrackerEventHandler TargetTapRelease;
可以使用以下函数检测跟踪器目标按钮是否被按压。TrackerButtonEventInfo具有TrackerEventInfo中的所有信息以及按钮ID信息。
event TrackerButtonEventHandler TargetButtonPress;
可以使用以下函数检测跟踪器目标按钮是否被按压。
event TrackerButtonEventHandler TargetButtonRelease;
StylusEventSample脚本展示了这些事件处理器的示例。在Start()回调中,以下代码用于设置事件处理器。
void Start() {
_core = GameObject.FindObjectOfType<ZCore>();
if (_core == null) {
Debug.LogError("Unable to find reference to zSpace.Core.ZCore Monobehaviour.");
this.enabled = false;
return;
}
// Register event handlers.
_core.TargetMove += HandleMove;
_core.TargetButtonPress += HandleButtonPress;
_core.TargetButtonRelease += HandleButtonRelease;
// _core.TargetTapPress += HandleTapPress;
// _core.TargetTapRelease += HandleTapRelease;
}
所有事件回调都采用以下形式。
private void HandleMove(ZCore sender, ZCore.TrackerEventInfo info)
{
}
private void HandleButtonPress(ZCore sender, ZCore.TrackerButtonEventInfo info)
{
}
private void HandleButtonRelease(ZCore sender, ZCore.TrackerButtonEventInfo info)
{
}
private void HandleTapPress(ZCore sender, ZCore.TrackerEventInfo info)
{
}
private void HandleTapRelease(ZCore sender, ZCore.TrackerEventInfo info)
{
}
这些代码允许应用采用事件通知而非轮询方式。