一、VR運行環境配置:
- 安裝steam,在steam上安裝SteamVR驅動。
- 在Unity項目中需要導入VRTool插件包(已上傳服務器),里面包含兩個插件一個是SteamVR插件,一個是VRTK插件,這兩個插件也可以直接在Unity的商店中進行下載。這兩個插件要求的Unity最低版本要5.6。
二、VR項目開發:
1. 基礎組件:
VR要實現人物在場景中的初始化需要以下物體:
這兩個物體可以在VRTK中的任意示例場景中找到:
建議第二個例子中的物體。將這兩個物體直接做成預制體,然后再將預制體放到我們自己的場景中即可。
這兩個物體的結構如下:
VRTK_SDKManager下的StemVR對應的便是我們在場景中的人物。
在CameraRig上有 這個組件,這是人物游玩區域域的配置,就是人腳底圓圈的配置。一般使用默認配置即可
SteamVR_PlayArea屬性:
Border Thickness:游玩區域底部顏色區域的寬度
Wireframe Height:游玩區域高度
Draw Wireframe ...:當被選中時繪制線框圖
Draw In Game:是否在游戲中繪制
Size和Color:調整尺寸和顏色
基礎組件中最主要的便是VRTK_Scripts這個物體下的兩個物體,基本上所有交互腳本都會在這幾個空物體上,這里你也可以不用預制體自己隨意創建空物體即可,不過必須要與VRTK_SDKMangager建立如下關系:
在運行時,這兩個腳本會成為 Controller(left)和Controller(Right)的子物體。
在這兩個物體上都有一個事件響應腳本:
有了這個便可以給手柄添加各種事件。
VRTK_ControllerEvents這個腳本是使用VRTK手柄事件機制必備的腳本,需要在左右手柄控制器都添加上
Action Aias Button: 設定一些事件的觸發按鈕
Axis Refinement:設定按鍵點擊事件的臨界值,以Trigger(扳機鍵為例),當我們輕輕扣動扳機鍵時,會產生一個按鍵值,這邊可以設定當按鍵值達到多少的時候表示扳機輕按事件的觸發(扳機鍵有三種基本事件:輕按,觸底,按下)
添加了這個腳本之后我們就可以在自己的腳本里面使用VRTK的手柄事件了,使用方式可以分為三步:
第一:去獲取左右控制器上面的VRTK_ControllerEvents腳本
var controlEvents = GetComponent<VRTK_ControllerEvents>();
這個是以該腳本正好掛載在手柄控制器上為例,可以直接去獲取。
第二:為相應按鈕的相應事件設定觸發時的函數
controlEvents.TriggerClicked += new ControllerInteractionEventHandler(DoTriggerClicked);
這邊主要是這個腳本ControllerInteractionEventHandler,參數是我們要設定的監聽函數的函數名
第三:完成監聽函數,參數是固定的寫法
private void DoTriggerClicked(object sender, ControllerInteractionEventArgs e){}
sender:觸發事件的是哪個手柄
e:有四個可以獲得數屬性
buttonPressure:按鈕被按下的力度
controllerIndex:設備的ID。
touchpadAxis:touchpad被按下的位置點的坐標
touchpadAngle:touchpad被按下位置點的角度(順時針,其他按鈕被按下都是90)
手柄上按鈕對應在代碼中的名字:
2. 人物移動:
人物移動有三種實現方式:1.是利用射線進行移動2.是利用觸摸板進行人物移動3.是人在原地踏步做出走的動作人物移動,這種移動方式主要配合萬向跑步機使用,在此主要講解利用射線進行人物移動。
射線:
如果想要做手柄上射出線則將組建掛載左手柄上,如果想要右手柄上射出線則將組建掛在右手柄上。具體示例可以查看VRTK中的場景 002;
投射射線需要用到兩個腳本,一個是基本的VRTK_Pointer腳本,線控制器:
這個腳本需要掛載在手柄控制器上面,主要屬性說明:
Enable Teleport:是否啟用瞬移
Point Renderer:線渲染器(不能為空),需要指定一個掛載了線渲染器腳本的物體,這邊使用的是射線,我們使用VRTK_StraightPointerRenderer腳本作為線渲染器(插件還提供一種貝塞爾曲線的線渲染器,后面會提到)
Target List Poslicy:可以選取一些點設置為可以移動或不能移動。需要配合
這個組件使用 Operation :Ingore時則下面的物體為不能移動的點,Include時則下面的物體為能移動的點。Check為控制的模式, 可以根據標簽,腳本,層進行控制
Activation Button:發射射線的按鈕
Activation Delay:發射延遲
Selection Button:選中的按鈕
Point Interaction Settings:與物體交互的選項
線渲染器,這個渲染的是直線
射線腳本主要屬性:
Layer To Ignore:被射線忽略的層
Valid Collision Volor:當射線選中有效物體時的顏色
Invalid Collistion Volor:當射線選中無效點時的顏色
Maximum Length:射線的最大長度
腳本為射線事件設定監聽,射線一共有三種事件:射線有效、射線無效、選中(即按鈕松開)
分為三步:
一:獲取相應手柄上VRTK_DestinationMarker腳本,這個腳本是VRTK_Pointer父類
var destinationMarker = GetComponent<VRTK_DestinationMarker>();
二:設定監聽函數
destinationMarker.DestinationMarkerEnter += new DestinationMarkerEventHandler(DoPointerIn);
DestinationMarkerEnter射線有效,這個事件會在選中點有效的時候一直被觸發
DestinationMarkerExit 射線無效,這個事件會在選中點從有效點到無效點和選中事件觸發后被觸發一次
DestinationMarkerSet 選中,按鈕被松開是執行一次
三:完成監聽函數,同樣是固定寫法
private void DoPointerIn(object sender, DestinationMarkerEventArgs e){}
sender:產生事件的手柄
e:有7中屬性值可以獲取
public float distance;距離
public Transform target;被選中點的Transform
public RaycastHit raycastHit;射線
public Vector3 destinationPosition;被選中點的坐標
public bool forceDestinationPosition;被選中點力的坐標
public bool enableTeleport;是否瞬移
public uint controllerIndex;當前設備的ID
線渲染器,曲線:
一般在瞬移時需要用曲線,在操作UI界面時需要用的直線,所以兩個線渲染器需要同時使用,但是線控制器只有一個,這就需要在適當的時候對 當前線控制器控制的渲染器進行修改,對是否啟動射線瞬移進行修改 直線時設為false,曲線時設為true。但是直線和曲線切換時一定要在射線繪制按鈕抬起之后也就是不在繪制線時進行切換,否則會造成當前線渲染器渲染的線一直存在於場景中。現在先介紹利用曲線實現人物瞬移,后面會介紹利用直線進行UI操作。
利用曲線實現人物瞬移
利用曲線的示例場景為:VRTK下022場景。
需要在VRTK_Scripts下新建一空物體,上面配置一下腳本:
這些腳本主要用來處理人體碰撞,人物頭部和場景中物體是否發生碰撞,為了防止穿模,也就是人物頭部進入到物體內部時會讓人物眼前景象變黑。對於人物瞬移位置限制,還有人物往高處,和高處墜落模擬實現
這個腳本上的數值一般不用改動,改動后可能造成眩暈, 其中Nav MesLimit Distance這個屬性是用來控制在導航網格上瞬移的,到導航網格邊緣多少距離時禁止瞬移,當值為0時,則停止使用導航網格控制瞬移。默認人物瞬移按鍵就是射線繪制按鍵,當按鍵抬起時就會瞬移。可以在線控制器中對按鍵進行修改。其余腳本的屬性值直接利用即可,修改 后會造成眩暈。可以根據需求修改曲線的繪制距離,角度,以達到控制瞬移距離。
3. 物體抓取:
- 基礎抓取:
物體抓取需要在控制器上掛載一下腳本:
Touch:觸摸交互,Grab抓取物體,Use使用物體。
controllerAttachPoint: 被抓的物體,被附加在哪個物體上, 默認是手柄的圓環處
Grab Precognition 提前預判抓取物體 對應快速運動的物體,我們可能需要提早按下抓取按鍵才能抓住物體, 數值是提前的時間值,值越大, 可提前的抓取時間越長
Throw Multiplier: 把物體扔出去時,速度的倍增值
Create Rigid Body When Not Touch : 在碰到物體時才創建RigidBody 默認情況下手柄也創建Rigidbody,這就可以和物體在物理上產生碰撞
需要在被抓取物體上掛載以下腳本:
VRTK_InteractableObject 基本的腳本,一個物體只要掛載上這個腳本,就可以與手柄控制器交互
Touch Height Color:觸碰高亮,默認為黑色,觸碰時不高亮
Allowed Touch Controllers:允許觸摸的手柄
Is Grabbable:是否允許抓取,配合物體上的抓取腳本使用,后面會提到
Stay Grabbed On Teleport:當抓取物體時是否可以瞬移
Valid Drop:有效的扔下的位置
Grab Override Button:指定抓取的按鍵事件,默認是側邊鍵
Allowed Grab Controller:允許抓取的手柄
Grab Attach Mechanic Script:指定才用抓取的方式(固定關節、父子關系等等),默認采用固定關節的方式
Use Options:物體使用的一些選項設置
Is Usable:是否可以被使用
Hold Button To Use:需要長按手柄按鍵使用
Use Only if Grabbled:在被抓取時才能使用
Point Activates Use Action:遠程激活選項,如果被勾選,手柄激光選中物體時可以被使用(激活)
Use Override Button:指定使用物體的按鍵
Allowed Use Controllers:允許物體被使用的控制器
這些屬性設置只有在初始化之前的設置有效,一但初始化完成在改變屬性沒有任何作用,所以一般會根據不同的物體,繼承這個類,然后呢 在Awake中進行屬性初始化,里面用大量的方法允許重寫,最常用的有一下幾種:
public class Test : VRTK_InteractableObject {
public override void StartTouching(VRTK_InteractTouch currentTouchingObject = null)
{
//當手柄和物體接觸時執行一次,注意如果兩個手柄都能和物體進行接觸,則兩個物體和手柄接觸時會分別各執行一次
base.StartTouching(currentTouchingObject);
}
public override void StopTouching(VRTK_InteractTouch previousTouchingObject = null)
{
//當手柄和物體停止接觸時執行一次。
base.StopTouching(previousTouchingObject);
}
public override void StartUsing(VRTK_InteractUse currentUsingObject = null)
{
//當物體允許使用時,手柄按下扳機鍵時會執行一次
base.StartUsing(currentUsingObject);
}
public override void StopUsing(VRTK_InteractUse previousUsingObject=null)
{
//當停止使用物體時會執行一次
base.StopUsing(previousUsingObject);
}
public override void Grabbed(VRTK_InteractGrab currentGrabbingObject = null)
{
//當物體被抓取時,會一直在Update中執行此函數
base.Grabbed(currentGrabbingObject);
}
}
注意:VRTK_InteractableObject這個腳本中的Update不會一直執行,在初始化完成之后會停止執行。
以固定關節實現物體抓取的腳本,抓取物體的位置一直在物體的中心點。
繼承自VRTK_BaseJointGrabAttach
Right Snap Handle:如果設置了這一項 ,如果用右手柄抓取當前物體,則抓取的點為這個屬性中物體的中心點。如果不設置抓取物體的位置一直在被抓取物體的中心點。
Destory Immediately On Throw:扔下物體時立刻銷毀關節
Break Force:銷毀時的力
可以作為抓取的方式腳本,這個腳本意味着使用的固定關節的方式實現抓取
VRTK_SwapControllerGrabAction
掛載這個腳本的物體就可以被控制器手柄抓取
VRTK_OutlineObjectCopyHighlighter
物體上掛載這個腳本之后就能進行描邊高亮提示。
- 手柄抓取物體擴展進階(示例場景VRTK下008)
VRTK_TrackObjectGrabAttach:
繼承自VRTK_BaseGrabAttach,這個腳本的作用是抓取物體時的一些設置,使用這個腳本,抓取的位置是固定的
Detach Distance:分離再次抓取的距離
Velocity Limit:物體的最大速度
Andular Velocity Limit:旋轉的最大速度
VRTK_ChildOfControllerGrabAttach
這個腳本表示采用父子關系的方式,即將被抓取的物體變成手柄模型的子物體
VRTK_InteractControllerAppearance
這個腳本用來設定手柄是否隱藏,隱藏的時機等
Touch Visibility:觸碰的時候是否隱藏手柄模型
Grab Visibility:抓取的時候是否隱藏
Use Visibility:使用的時候是否隱藏
4.開關按鈕實現:
原理:開關閥主要是利用了手柄與物體接觸之后是否使用物體,如果使用了物體則代表按下了閥門。主要重寫了 VRTK_Interactable Object腳本中的以下方法:
public override void StartTouching(VRTK_InteractTouch currentTouchingObject = null)
{
//當手柄和物體接觸時執行一次,注意如果兩個手柄都能和物體進行接觸,則兩個物體和手柄接觸時會分別各執行一次
base.StartTouching(currentTouchingObject);
}
public override void StopTouching(VRTK_InteractTouch previousTouchingObject = null)
{
//當手柄和物體停止接觸時執行一次。
base.StopTouching(previousTouchingObject);
}
public override void StartUsing(VRTK_InteractUse currentUsingObject = null)
{
//當物體允許使用時,手柄按下扳機鍵時會執行一次
base.StartUsing(currentUsingObject);
}
public override void StopUsing(VRTK_InteractUse previousUsingObject=null)
{
//當停止使用物體時會執行一次
base.StopUsing(previousUsingObject);
}
第二種實現方式則是檢測 VRTK_InteractableObject.Touched屬性,如果為true則表示手柄與物體接觸,然后檢測手柄是否按下了扳機鍵。
5.物體旋轉:
開關閥和旋轉閥理論上都用兩種實現方式,第一種是直接利用內部自帶的旋轉方法 VRTK_Wheel,也就是利用Unity的物理引擎中鉸鏈剛體等組件;第二種則是計算向量。
第一種 利用VRTK_Wheel:
VRTK_Wheel屬性簡介:
Default Events 在物體旋轉時的回調事件,在當前版本中已經不再利用這個屬性添加事件,在下面將會具體介紹添加事件監聽的方法。
Interact Without Grab :是否允許不用抓住物體就可以旋轉物體。
Grab Type:旋轉方式。TrackObject:模擬的是類似於擰瓶蓋的旋轉方式,手柄在一個地方360度轉動,物體跟着轉動Rotaor Track:模擬閥門旋轉。
Detatch Distance:手柄離與物體的距離超過這個距離則沒法旋轉物體。
Minimun Value :物體旋轉的最小角度對應的最下值。
Maximum Value:物體旋轉的最大角度對應的最大值。
Step Size: 值改變的差值。
Snap To Step :如果選中,則限制旋轉物體的角度不能超過最大值,不能小於最小值。
Max Angle: 可以旋轉的角度范圍,最大旋轉角度為359度。因為在Unity獲取物體角度時,如果物體旋轉超過360度,在獲取歐拉角時會自動置為0度,暫時沒有找到改進辦法。
Rotate Axis 和RotateAnchor為腳本改進之后的新增屬性,在VRTK原版的旋轉中並沒有這兩個屬性。
如果物體上掛載了這個腳本則不用掛載其他交互腳本,這個腳本會自動初始化其他交互腳本,這個腳本一旦初始化完成則無法再次更改其屬性,所以屬性的初始化通常繼承VRTK_Wheel后重寫InitWheel()方法,在初始化之前先進行屬性修改,這就要求在代碼動態加載VRTK_Wheel組件之前首先加載一個存儲數據的腳本。然后再InitWheel中增添獲取屬性數據的方法。
VRTK_Wheel的深度改進
在實際項目開發中VRTK_ 並不能直接使用,因為這個組件只有圍繞物體Y軸旋轉的方法,我們需要給他自定義旋轉軸,而其中有控制旋轉軸向的隱藏屬性,就是
這個HingeJoint中有兩個屬性,分別為 wheelHinge.anchor,wheelHinge.axis; 這兩個屬性設置同一個方向向量即可,即都是Vector3.up,或Vector3.right或Vector3.left。要實現物體圍繞任意軸都能旋轉則需要更改兩個地方,一個是將 DetectSetup()方法更改。原本內容如下:
需要根據所旋轉的軸向給予wheelHinge.anchor和wheelHinge.axi不同的方向向量。一般更改為如下:
第二個需要修改的地方為SetupHingeRestrictions()這個方法,
腳本在這個地放計算了一個角度值,這個角度值的具體作用暫時未搞懂,不過這個地方的角度值需要根據不同的旋轉軸進行不同的計算,官方原腳本只有計算圍繞Y軸的角度值。圍繞x軸角度值的計算方法如下:
switch (Mathf.RoundToInt(initialLocalRotation.eulerAngles.y))
{
case 0:
adjustedLimitsAngle = new Vector3(transform.localEulerAngles.x - minJointLimit, transform.localEulerAngles.y, transform.localEulerAngles.z);
break;
case 90:
adjustedLimitsAngle = new Vector3(transform.localEulerAngles.x , transform.localEulerAngles.y, transform.localEulerAngles.z + minJointLimit);
break;
case 180:
adjustedLimitsAngle = new Vector3(transform.localEulerAngles.x + minJointLimit, transform.localEulerAngles.y , transform.localEulerAngles.z);
break;
}
圍繞z軸的計算方法如下:
switch (Mathf.RoundToInt(initialLocalRotation.eulerAngles.x))
{
case 0:
adjustedLimitsAngle = new Vector3(transform.localEulerAngles.x, transform.localEulerAngles.y , transform.localEulerAngles.z - minJointLimit);
break;
case 90:
adjustedLimitsAngle = new Vector3(transform.localEulerAngles.x , transform.localEulerAngles.y + minJointLimit, transform.localEulerAngles.z);
break;
case 180:
adjustedLimitsAngle = new Vector3(transform.localEulerAngles.x, transform.localEulerAngles.y , transform.localEulerAngles.z + minJointLimit);
break;
}
至此,這個腳本已經能夠實現任意軸向物體的旋轉。因為這個腳本上的屬性只有在初始化之前更改才會有效,所以一般會結合一個數據類來記錄當前腳本初始化時需要的設置。在使用這個腳本時,我們需要自定義一個類來繼承這個腳本,然后重寫腳本的InitWheel()方法,在腳本初始化之前獲取數據類中的相關屬性設置。模式基本如下所示:
protected override void InitWheel(){
//在此完成屬性設置
base.InitWheel()
}
至此第一種閥門旋轉方式完成。
添加旋轉監聽事件:
在代碼里面設置監聽函數:
VRTK_Control_UnityEvents這個腳本用於給控制事件設置監聽,掛載繼承自VRTK_Control的腳本后會在場景允許后自動掛載上這個腳本,也可以自己去手動在場景還沒有開始前添加這個腳本,需要注意的是監聽函數的參數是固定寫法,使用這個腳本去設置監聽函數可以獲取一些詳細的數值
注意: 在使用這種旋轉方式時,VRTK中有一個錯誤,在VRTK_Control類中
這個地方需要先判斷是否為空。
第二種計算向量實現閥門旋轉的方法:
計算向量實現閥門旋轉的大體思路:
第一步:初始化基礎物體交互腳本,設置其屬性為不可抓取,可以觸摸。
第二步:檢測手柄是否觸摸到物體,如果為true,則檢測手柄是否按住兩邊的側鍵,如果按下則記錄當前手柄到物體的方向向量。這期間如果松開了兩邊側鍵或手柄離開了物體則判定停止旋轉物體,如果沒有則計算對應閥門方向。具體計算方式有待研究。
注意:這兩種旋轉閥門方式只適用於 0---360度之內的旋轉,主要原因在於在unity中獲取物體旋轉角度只有 0—360度,當一個物體正向選裝到 360度時獲取到的是0度,逆向旋轉到0度時再旋轉獲取到的是359度。
簡單UI介紹
Canvas的渲染方式需要修改成World Space,為Canvas添加VRTK_UICanvas腳本即可
手柄上需要掛載 VRTK_UI Pointer
與UI的交互必須配合射線使用,所以必須掛載射線相關的腳本(見人物移動中射線介紹),這樣就可以將射線當做鼠標來使用。默認的使用方式為,按住TouchPad鍵出現射線,將射線指向要交互的UI,點擊Trigger按鍵==鼠標左擊。
VR中2DUI的實現方式。
VR中2DUI的實現方式類似於NGUI的實現方式,實際上,VR中是將UI Canvas當做一個3D物體來進行處理的,然后將普通UGUI的鼠標點擊事件改為射線輸入點擊。2DUI在VR中實現方式就是利用了 這一屬性, 將ui的Render Camera改成主相機。這樣Canvas永遠會在相機的正前方,並且會自動縮放並填充整個視野,注意,在VR中的視野范圍有點類似於屏幕視野的內切圓,在擺放UI時不能太靠近邊緣。在相機移動時會出現UI抖動的現象,當PlaneDistance值越大時,UI的抖動越小,但不能超出視野的范圍,這樣就會有一個問題,3D物體有可能會遮擋住UI,這里就用到了組件上的材質:
材質的shader如下:
Shader "LZLUI/Canvas"
{
Properties
{
[PerRendererData] _MainTex("Font Texture", 2D) = "white" {}
_Color("Tint", Color) = (1,1,1,1)
_StencilComp("Stencil Comparison", Float) = 8
_Stencil("Stencil ID", Float) = 0
_StencilOp("Stencil Operation", Float) = 0
_StencilWriteMask("Stencil Write Mask", Float) = 255
_StencilReadMask("Stencil Read Mask", Float) = 255
_ColorMask("Color Mask", Float) = 15
}
SubShader
{
LOD 100
Tags
{
"Queue" = "Transparent"
"IgnoreProjector" = "True"
"RenderType" = "Transparent"
"PreviewType" = "Plane"
"CanUseSpriteAtlas" = "True"
}
Stencil
{
Ref[_Stencil]
Comp[_StencilComp]
Pass[_StencilOp]
ReadMask[_StencilReadMask]
WriteMask[_StencilWriteMask]
}
Cull Off
Lighting Off
ZWrite Off
ZTest Always
Offset -1,-1
Blend SrcAlpha OneMinusSrcAlpha
ColorMask[_ColorMask]
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "UnityUI.cginc"
struct appdata_t
{
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
float4 color : COLOR;
};
struct v2f
{
float4 vertex : SV_POSITION;
half2 texcoord : TEXCOORD0;
fixed4 color : COLOR;
};
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Color;
fixed4 _TextureSampleAdd;
v2f vert(appdata_t v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
o.color = v.color * _Color;
#ifdef UNITY_HALF_TEXEL_OFFSET
o.vertex.xy += (_ScreenParams.zw - 1.0)*float2(-1,1);
#endif
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed4 col = (tex2D(_MainTex, i.texcoord) + _TextureSampleAdd) * i.color;
clip(col.a - 0.01);
return col;
}
ENDCG
}
}
}
創建一個Material,並將Material賦給相應的組件即可,一般為Image和Text組件這樣這些組件就可以永遠顯示在3D物體的前面了,以實現類似於PC的2DUI。注意要實現射線在2Dui上的點擊事件,要讓射線忽略所有的3D物體, 利用線渲染器的層控制即可。
VR2DUI中實現物體提示標簽
原理:原理就是利用相機到物體形成的向量和Canvas相交形成的交點,也就是標簽的位置。
需要用的向量: 相機到物體的方向向量,Canvas的法線(取transform.forward即可),Canvas上的一個點(即Canvas的postion即可)。
假設Canvas的法線為(A,B,C),Canvas上一個點的坐標為(x0,y0,z0);
則根據點法公式可以得出Canvas的面方程為:
A(x-x0)+B(y-y0)+C(z-z0)=0 ----------------------①
假設相機到物體的方向向量為(a1,b1,c1)取線上一點取目標物體的位置點即可假設為(x1,y2,z2);
這樣根據直線的點向式可以求出直線方程為:
(x-x1)/a1=(y-y2)/b1=(z-z2)/c1 --------------------------------②
方程①②組成方程組,解方程組為
x=(A*a1*x0 + B*a1*y0 + B*b1*x1 - B*a1*y2 + C*a1*z0 + C*c1*x1 - C*a1*z2)/(A*a1 + B*b1 + C*c1)
y=(A*b1*x0 - A*b1*x1 + A*a1*y2 + B*b1*y0 + C*b1*z0 - C*b1*z2 + C*c1*y2)/(A*a1 + B*b1 + C*c1)
z=(A*c1*x0 - A*c1*x1 + A*a1*z2 + B*c1*y0 + B*b1*z2 - B*c1*y2 + C*c1*z0)/(A*a1 + B*b1 + C*c1)
(x,y,z)即為提示標簽的位置點。最后一步要獲取提示標簽的anchoredPosition3D將其中的z設為0。