ARFoundation入门教程10-平面检测和放置

Posted suelee_hm

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ARFoundation入门教程10-平面检测和放置相关的知识,希望对你有一定的参考价值。

示例源代码:

https://github.com/sueleeyu/ar-plane

从《ARFoundation从零开始3-arfoundation项目》复制项目,继续:

一、添加组件

1.添加AR RayCaset Manager 和AR Plane Manager:

选择左侧Hierarchy-AR Session Origin,Inspector下点击Add Component,依次输入并添加AR RayCaset Manager 和AR Plane Manager

2.创建平面prefabs:Hierarchy-‘+’-XR-AR Default Plane,Assets下新建Prefabs目录,将创建的对象拖动到Prefabs目录,删除Hieraychy下的对象。

3.将创建的plane预制件拖动到Plane Manager组件:

4.创建Button组件,命名BtnPlane,用于显示/隐藏平面:

二、编写代码

1.编写cs代码PlaceManager.cs,用于放置预制件。

using System.Collections.Generic;

using UnityEngine;

using UnityEngine.EventSystems;

using UnityEngine.XR.ARFoundation;

using UnityEngine.XR.ARSubsystems;



namespace FrameworkDesign.Example



  

    public class PlaceManager : MonoBehaviour

    

        [Header("AR Foundation")] 

        /// <summary>

        /// The active ARRaycastManager used in the example.

        /// </summary>

        public ARRaycastManager m_RaycastManager;





        [Header("UI")]

        [SerializeField]

        [Tooltip("Instantiates this prefab on a plane at the touch location.")]

        GameObject m_PlacedPrefab;//要放置的预制件



        /// <summary>

        /// The prefab to instantiate on touch.

        /// </summary>

        public GameObject placedPrefab

        

            get  return m_PlacedPrefab; 

            set  m_PlacedPrefab = value; 

        



        [HideInInspector]

        static List<ARRaycastHit> s_Hits = new List<ARRaycastHit>();//存放检测到的碰撞点

        /// <summary>

        /// The object instantiated as a result of a successful raycast intersection with a plane.

        /// </summary>

        public GameObject spawnedObject  get; private set; 



        void Awake()

        

            // m_RaycastManager = GetComponent<ARRaycastManager>();//也可以通过GetComponent获取到ARRaycastManager

        



        bool TryGetTouchPosition(out Vector2 touchPosition)

        

            if (Input.touchCount > 0)

            

                touchPosition = Input.GetTouch(0).position;

                return true;

            



            touchPosition = default;

            return false;

        



        void Update()

        

            if (!TryGetTouchPosition(out Vector2 touchPosition))

                return;



            var touch = Input.GetTouch(0);

            const TrackableType trackableTypes =

            TrackableType.FeaturePoint |

            TrackableType.PlaneWithinPolygon;



            if (Input.touchCount == 1 && touch.phase == TouchPhase.Moved)//移动已放置的对象

            



                if (m_RaycastManager.Raycast(touchPosition, s_Hits, trackableTypes))

                

                    // Raycast hits are sorted by distance, so the first one

                    // will be the closest hit.

                    var hitPose = s_Hits[0].pose;

                    if (spawnedObject != null)

                    

                        spawnedObject.transform.position = hitPose.position;

                                       

                

            

            if (Input.touchCount == 1 && touch.phase == TouchPhase.Began)//检测touch begin,在touch begin中做射线碰撞检测

            

                //---判断是否touch到UI组件----

                //#if IPHONE || android

                if (EventSystem.current.IsPointerOverGameObject(Input.GetTouch(0).fingerId))

                //#else

                // if (EventSystem.current.IsPointerOverGameObject())

                //#endif

                //Debug.Log("当前触摸在UI上");

                

                    Logger.Log($"当前触摸在UI上"+ touch.phase);

                    return;

                

                else

                

                    //Debug.Log("当前没有触摸在UI上");

                    Logger.Log($"当前没有触摸在UI上"+ touch.phase);

                



                if (m_RaycastManager.Raycast(touchPosition, s_Hits, trackableTypes))

                

                    // Raycast hits are sorted by distance, so the first one

                    // will be the closest hit.

                    var hitPose = s_Hits[0].pose;

                    if (spawnedObject == null)

                    

                        spawnedObject = Instantiate(m_PlacedPrefab, hitPose.position, hitPose.rotation);//实例化预制件对象

                    

                    else

                    

                        spawnedObject.transform.position = hitPose.position;//更新对象状态

                    

                

                          

              

    

2.Hierarchy下Game下Create Empty,命名GameScene,将PlaneManager.cs挂载到其下:

选择ARScene,将AR Session Origin 和做好的预制件拖放到PlaceManager.cs的参数列:

3.编写ARManager.cs,用于显示/隐藏平面信息:

using System.Collections.Generic;

using UnityEngine;

using UnityEngine.XR.ARFoundation;



public class ARManager : MonoBehaviour



    [Header("AR Foundation")]

    /// <summary>

    /// The active ARRaycastManager used in the example.

    /// </summary>

    public ARPlaneManager m_ARPlaneManager;



    [HideInInspector]



    /// <summary>

    /// 当前识别出的平面

    /// </summary>

    List<ARPlane> detectPlanes = new List<ARPlane>();



    /// <summary>

    /// 当前是否要显示平面

    /// </summary>

    bool isShowPlane = true;



    #region MonoBehaviour CallBacks



    private void Awake()

         

        m_ARPlaneManager = FindObjectOfType<ARPlaneManager>();

    



    void Start()

    

        CheckDevice();



        m_ARPlaneManager.planesChanged += OnPlaneChanged;

    



    private void Update()

    

        SaveElePolicy();

    



    void OnDisable()

    

        m_ARPlaneManager.planesChanged -= OnPlaneChanged;

    



    #endregion



    // 启用与禁用平面检测

    // 程序默认启用,启用时一直不停地检测平面。关闭时则不会再检测新平面了。

    public void DetectionPlane(bool value)

    

        m_ARPlaneManager.enabled = value;

        if (m_ARPlaneManager.enabled)

        

            print("已启用平面检测");

        

        else

        

            print("已禁用平面检测");

        

    



    // 显示与隐藏检测到的平面 

    public void SwitchPlane()

    

        isShowPlane = !isShowPlane;



        for (int i = detectPlanes.Count - 1; i >= 0; i--)

        

            if (detectPlanes[i] == null || detectPlanes[i].gameObject == null)

                detectPlanes.Remove(detectPlanes[i]);

            else

                detectPlanes[i].gameObject.SetActive(isShowPlane);

        

    



    /// <summary>

    /// 得到当前AR会话是否正在运行,并被跟踪(即,该设备能够确定其在世界上的位置和方向)。

    /// </summary>

    public bool Skode_IsTracking()

    

        bool isTracking = false;



        if (ARSession.state == ARSessionState.SessionTracking)

        

            isTracking = true;

        



        return isTracking;

    





    //在ARFoundation新发现平面时,将平面添加进列表里,便于我们控制这些平面

    void OnPlaneChanged(ARPlanesChangedEventArgs arg)

    

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

        

            detectPlanes.Add(arg.added[i]);

            arg.added[i].gameObject.SetActive(isShowPlane);

        

    



    //检查设备运行环境

    void CheckDevice()

    

        if (ARSession.state == ARSessionState.NeedsInstall)

        

            ShowAndroidToastMessage("AR is supported, but requires an additional install. .");

            Invoke("Quit", 1);

        

        else if (ARSession.state == ARSessionState.Ready)

        

            Debug.Log("AR is supported and ready.");

        

        else if (ARSession.state == ARSessionState.Unsupported)

        

            ShowAndroidToastMessage("AR is not supported on the current device.");

            Invoke("Quit", 1);

        

    



    void ShowAndroidToastMessage(string message)

    

        AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");

        AndroidJavaObject unityActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");

        if (unityActivity != null)

        

            AndroidJavaClass toastClass = new AndroidJavaClass("android.widget.Toast");

            unityActivity.Call("runOnUiThread", new AndroidJavaRunnable(() =>

            

                AndroidJavaObject toastObject = toastClass.CallStatic<AndroidJavaObject>("makeText", unityActivity, message, 0);

                toastObject.Call("show");

            ));

        

    



    void Quit()

    

        Application.Quit();

    



    /// <summary>

    /// 一种省电设置,当设备没找到识别目标,允许屏幕在最后激活一段时间后变暗

    /// </summary>

    void SaveElePolicy()

    

        if (ARSession.state != ARSessionState.SessionTracking)

        

            const int lostTrackingSleepTimeout = 15;

            Screen.sleepTimeout = lostTrackingSleepTimeout;

        

        else

        

            Screen.sleepTimeout = SleepTimeout.NeverSleep;

        

    



4.添加ARManager.cs到Hierarchy-Game下,挂载PlaneManager组件:

5.添加Button的click事件。选择BtnPlane组件,添加Onclick事件,拖动Game,选择Function函数:

二、unity知识点

1.射线检测ARRaycastManager:

ARRaycastManager.Raycast(Vector2, List<ARRaycastHit>, TrackableType) 。

API:Class ARRaycastManager | AR Foundation | 4.2.3

注意:并非所有 TrackableType 都受 ARCore 和 ARKit 提供程序支持。ARCore 提供程序目前仅支持 PlaneEstimatedPlaneWithinBoundsPlaneWithinPolygonFeaturePointImage 和 Depth

三、android打包运行

如未配置,参看《ARFoundation从零开始3-arfoundation项目》。

1.安装运行

四、常见问题

五、参考资料

1. Unity api:

Class ARRaycastManager | AR Foundation | 4.2.3

2.ARFoundation示例:

GitHub - Unity-Technologies/arfoundation-samples: Example content for Unity projects based on AR Foundation

3.ARCore文档:

在 Unity (AR Foundation) 应用中执行光线投射  |  ARCore  |  Google Developers

4.本项目示例源代码:

https://github.com/sueleeyu/ar-plane

ARFoundation入门——平面检测(Android)

开发环境及一些说明

Unity:2021.1.14flc1
导入Package:
AR Foundation:4.1.7
ARCore XR Plugin:4.1.7
(在进行配置前需要先将Unity中的Android环境配置好)

对ARFoundation的使用学习可以参考unity官网的文档:
http://docs.unity3d.com/Packages/com.unity.xr.arfoundation@4.1/manual/index.html

也可以到ARCore的官网:https://developers.google.cn/ar对这部分SDK的功能和环境配置有个更好的了解。

两个网站的中文翻译我觉得都挺令人头疼的,看不懂的部分地方建议可以用英文文档理解一下。

步骤

  1. AR 平面管理器 AR Plane Manager
    在"AR Session Orign"中添加"AR Plane Manager"控件
    平面管理器可以为环境中检测到的每个平面创建游戏对象
    Plane Prefab 放置的是平面预制件,也就是检测到平面后生成的游戏对象
    Detection Mode 中设置检测平面类别,检测的平面类型包括水平屏幕和垂直平面
  2. 平面预制件
    在Hierarchy面板中创建"XR" -> “AR Default Plane”,
    将它拖入Assets中保存为预制件,
    然后在Hierarchy面板中将其删除,
    再将预制件中的"AR Default Plane"拖入到"AR Session Origin"中的"AR Plane Manager"控件下的"Plane Prefab"。
    (不懂预制件的可以补一下unity的一些基本知识,但这里先不知道也没关系)
  3. 导出apk到测试机进行效果测试

    淡黄色的范围即为平面检测结果生成的平面

以上是关于ARFoundation入门教程10-平面检测和放置的主要内容,如果未能解决你的问题,请参考以下文章

ARFoundation之路_很全面的

ARFoundation系列讲解 - 70 HumanBodyTracking3D

ARFoundation系列讲解 - 70 HumanBodyTracking3D

ARFoundation系列讲解 - 69 HumanBodyTracking2D

ARFoundation系列讲解 - 69 HumanBodyTracking2D

ARFoundation系列讲解-教程目录