ARCore之路:AugmentedFaceExample例子分析

项目效果

将一个三维物体附着到脸部,随着脸部的移动,三维物体也会跟踪脸部来进行移动。
在这里插入图片描述

下图是该项目的 GameObject 节点,下面将从各个节点分析该项目。
在这里插入图片描述

1、ARCore Device

该节点的介绍可移步到这里浏览传送门
下面分析该项目中的一些需要注意的参数:
该项目使用的是前置摄像头,并且定义了自己的ARCore 会话文件。
在这里插入图片描述
会话文件中,Plane Finding Mode 选项禁用了平面查找模式,并且 Augmented Face Mode 选项设为Mesh 模式
在这里插入图片描述

2、FaceAttachment

2.1、FaceTextureFaceOccluder节点

两个节点都是把相机捕捉到的脸部图像绘制到材质贴图中,如下图红色方框中的 Mesh Filter
在这里插入图片描述

2.1.1、ARCoreAugmentedFaceMeshFilter 脚本分析

namespace GoogleARCore.Examples.AugmentedFaces
{
    using System.Collections.Generic;
    using GoogleARCore;
    using UnityEngine;

    /// <summary>
    /// Helper component to update face mesh data.
    /// </summary>
    [RequireComponent(typeof(MeshFilter))]
    public class ARCoreAugmentedFaceMeshFilter : MonoBehaviour
    {
        /// <summary>
        /// If true, this component will update itself using the first AugmentedFace detected by ARCore.
        /// </summary>
        public bool AutoBind = false;

        private AugmentedFace _augmentedFace = null;
        private List<AugmentedFace> _augmentedFaceList = null;
        
        // 保留前一帧的网格多边形以避免每帧更新网格。
        private List<Vector3> _meshVertices = new List<Vector3>();
        private List<Vector3> _meshNormals = new List<Vector3>();
        private List<Vector2> _meshUVs = new List<Vector2>();
        private List<int> _meshIndices = new List<int>();
        private Mesh _mesh = null;
        private bool _meshInitialized = false;

        /// <summary>
        /// Gets or sets the ARCore AugmentedFace object that will be used to update the face mesh data.
        /// </summary>
        public AugmentedFace AumgnetedFace
        {
            get{
                return _augmentedFace;
            }

            set{
                _augmentedFace = value;
                Update();
            }
        }

        // 初始化数据
        public void Awake()
        {
            _mesh = new Mesh();	// 创建一个 Mesh
            GetComponent<MeshFilter>().mesh = _mesh;// 将创建的 Mesh 赋值当前对象的Mesh Filter 组件,即上图红色方框的组件
            _augmentedFaceList = new List<AugmentedFace>();	// 创建一个集合,存储相机捕捉到的人脸
        }

        /// <summary>
        /// The Unity Update() method.
        /// </summary>
        public void Update()
        {
            if (AutoBind)// 检测是否自动绑定
            {
            	// 把存储人脸的集合清空
                _augmentedFaceList.Clear(); 
                // 重新获取相机捕捉到的人脸
                Session.GetTrackables<AugmentedFace>(_augmentedFaceList, TrackableQueryFilter.All);
                // 相机捕捉到人脸,将第一张人脸作为 augmentedFace
                if (_augmentedFaceList.Count != 0)
                    _augmentedFace = _augmentedFaceList[0]; 
            }

            if (_augmentedFace == null)	// 如果
                return;

            // 更新贴图的位置和旋转,CenterPose 是中心点
            transform.position = _augmentedFace.CenterPose.position;
            transform.rotation = _augmentedFace.CenterPose.rotation;

            UpdateMesh();	// 更新 Mehs
        }

        // 通过获得人脸的顶点、法线、UV、三角片数据来更新 Mehs
        private void UpdateMesh()
        {
            _augmentedFace.GetVertices(_meshVertices);  // 获取人脸的顶点
            _augmentedFace.GetNormals(_meshNormals);    // 获取人脸的法线

            if (!_meshInitialized)
            {
                _augmentedFace.GetTextureCoordinates(_meshUVs);     // 获取人脸的UV
                _augmentedFace.GetTriangleIndices(_meshIndices);    // 获取人脸的三角片

                // Only update mesh indices and uvs once as they don't change every frame.
                _meshInitialized = true;    // UV 和 三角片只获取一次
            }

            _mesh.Clear();  // 清空贴图
            // 重新设置贴图
            _mesh.SetVertices(_meshVertices);
            _mesh.SetNormals(_meshNormals);
            _mesh.SetTriangles(_meshIndices, 0);
            _mesh.SetUVs(0, _meshUVs);

            _mesh.RecalculateBounds();  // 从顶点重新计算网格的包围体。
        }
    }
}

2.2、fox_sample 节点

该节点是三维模型的节点,下图就是三维模型,当捕捉到人脸后,该模型会贴合到人脸上
在这里插入图片描述
该节点下的root节点是骨骼节点,分别是左耳、右耳、鼻子的骨骼节点,用来标记模型的位置。
捕捉到人脸后,就是调整骨骼节点的位置,从而导致模型也跟着骨骼节点位置移动。
在这里插入图片描述

2.2.1、ARCoreAugmentedFaceRig脚本分析

namespace GoogleARCore.Examples.AugmentedFaces
{
    using System.Collections.Generic;
    using GoogleARCore;
    using UnityEngine;

    /// <summary>
    /// Helper component to update face regions.
    /// </summary>
    [ExecuteInEditMode]
    public class ARCoreAugmentedFaceRig : MonoBehaviour
    {
        /// <summary>
        /// If true, this component will update itself using the first AugmentedFace detected by ARCore.
        /// </summary>
        public bool AutoBind = false;
		// AugmentedFaceRegion 是一个枚举
        private static readonly Dictionary<AugmentedFaceRegion, string> _regionTransformNames =
            new Dictionary<AugmentedFaceRegion, string>()
            {
                { AugmentedFaceRegion.NoseTip, "NOSE_TIP" },    // 枚举 和 root 节点下的骨骼名字对应
                { AugmentedFaceRegion.ForeheadLeft, "FOREHEAD_LEFT" },
                { AugmentedFaceRegion.ForeheadRight, "FOREHEAD_RIGHT" }
            };

        private AugmentedFace _augmentedFace;
        private List<AugmentedFace> _augmentedFaceList = new List<AugmentedFace>();
        // 枚举和相应骨骼节点的 Transform 绑定到字典
        private Dictionary<AugmentedFaceRegion, Transform> _regionGameObjects =
            new Dictionary<AugmentedFaceRegion, Transform>();

        /// <summary>
        /// Gets or sets the ARCore AugmentedFace object that will be used to update the face region.
        /// </summary>
        public AugmentedFace AumgnetedFace
        {
            get{
                return _augmentedFace;
            }

            set{
                _augmentedFace = value;
                Update();
            }
        }

        public void Awake()
        {
            _augmentedFaceList = new List<AugmentedFace>();
            InitializeFaceRegions();	// 获得骨骼节点和三维模型各个部件对象
        }

        public void Update()
        {
            if (!Application.isPlaying)	// 程序没有在运行,则退出
                return;

            if (AutoBind)// 检测是否自动绑定
            {
            	// 把存储人脸的集合清空
                _augmentedFaceList.Clear(); 
                // 重新获取相机捕捉到的人脸
                Session.GetTrackables<AugmentedFace>(_augmentedFaceList, TrackableQueryFilter.All);
                // 相机捕捉到人脸,将第一张人脸作为 augmentedFace
                if (_augmentedFaceList.Count != 0)
                    _augmentedFace = _augmentedFaceList[0]; 
            }

            if (_augmentedFace == null)
                return;

            UpdateRegions();	// 更新三维模型的位置
        }

        // 获得骨骼节点和三维模型各个部件对象
        private void InitializeFaceRegions()
        {
            foreach (AugmentedFaceRegion region in _regionTransformNames.Keys)
            {
                string name = _regionTransformNames[region];    // 获得骨骼节点的名字
                // 通过节点名字找到GameObject
                Transform regionTransform = FindChildTransformRecursive(transform, name);
                // 找不到就创建一个 name节点
                if (regionTransform == null)
                {
                    GameObject newRegionObject = new GameObject(name);
                    newRegionObject.transform.SetParent(transform);
                    regionTransform = newRegionObject.transform;
                }
				// 把骨骼节点的 Transform 与相应的枚举绑定,存储到字典
                _regionGameObjects[region] = regionTransform;   
            }
        }
        
		// 通过节点名字找到GameObject
        private Transform FindChildTransformRecursive(Transform target, string name)
        {
            if (target.name == name){
                return target;
                
            // 在 target 节点的子孩子中查找 name 节点
            foreach (Transform child in target)
            {
                if (child.name.Contains(name))
                    return child;
                // 在子节点的孩子节点继续找
                Transform result = FindChildTransformRecursive(child, name);
                if (result != null)
                    return result;
            }

            return null;
        }

        // 更新三维模型的位置
        private void UpdateRegions()
        {
            // 判断当前是否正在进行脸部跟踪
            bool isTracking = _augmentedFace.TrackingState == TrackingState.Tracking;
            // 如果正在进行跟踪,那么 3D 模型也得跟随脸部移动,即需要更新当前节点位置
            if (isTracking)
            {
                transform.position = _augmentedFace.CenterPose.position;
                transform.rotation = _augmentedFace.CenterPose.rotation;
            }

            foreach (AugmentedFaceRegion region in _regionGameObjects.Keys)
            {
            	// 获得骨骼节点的 Transform 
                Transform regionTransform = _regionGameObjects[region];
                // 如果没有正在跟踪,那就隐藏 3D 物体
                regionTransform.gameObject.SetActive(isTracking);
                // 如果当前处于跟踪状态,更新骨骼节点的位置
                if (isTracking)
                {
                    Pose regionPose = _augmentedFace.GetRegionPose(region);
                    regionTransform.position = regionPose.position;
                    regionTransform.rotation = regionPose.rotation;
                }
            }
        }
    }
}

3、AugmentedFaceExampleController

该节点下只有 AugmentedFacesExampleController 脚本。

namespace GoogleARCore.Examples.AugmentedFaces
{
    using System.Collections.Generic;
    using GoogleARCore;
    using UnityEngine;
    using UnityEngine.UI;

    /// <summary>
    /// Controller for the AugmentedFaces sample scene.
    /// </summary>
    public class AugmentedFacesExampleController : MonoBehaviour
    {
        /// <summary>
        /// The game object that renders the face attachment on an Augmented Face.
        /// </summary>
        public GameObject FaceAttachment;

        /// <summary>
        /// True if the app is in the process of quitting due to an ARCore connection error,
        /// otherwise false.
        /// </summary>
        private bool _isQuitting = false;

        private List<AugmentedFace> _tempAugmentedFaces = new List<AugmentedFace>();

        public void Awake()
        {
            Application.targetFrameRate = 60;   // 将程序的帧率设为 60 fps
        }

        public void Update()
        {
            UpdateApplicationLifecycle();	// 该函数的解析在 3.1小节讲解 

            // 获得相机中捕捉到的人脸放到 _tempAugmentedFaces 集合中
            Session.GetTrackables<AugmentedFace>(_tempAugmentedFaces, TrackableQueryFilter.All);

            // 如果检测不到人脸,就把设备的熄屏时间设置回原样.
            if (_tempAugmentedFaces.Count == 0)
            {
                Screen.sleepTimeout = SleepTimeout.SystemSetting;   // 把手机的熄屏时间设为手机中设置的熄屏时间
                FaceAttachment.SetActive(false);                    // 隐藏 3D 模型
            }
            else
            {
                Screen.sleepTimeout = SleepTimeout.NeverSleep;      // 把手机设置为永不熄屏
                FaceAttachment.SetActive(true);                     // 显示 3D 模型
            }
        }
    }
}

3.1、UpdateApplicationLifecycle() 函数分析

该函数是一个检测应用是否退出或是否获得相应权限的函数。

		/// <summary>
        /// Check and update the application lifecycle.
        /// </summary>
        private void UpdateApplicationLifecycle()
        {
            // 检查手机是否按了返回键,按了返回键,程序退出
            if (Input.GetKey(KeyCode.Escape))
                Application.Quit();

            if (_isQuitting)
                return;

            // Quit if ARCore was unable to connect and give Unity some time for the toast to
            // appear.(Session.Status 可以获取 AR 当前的状态)
            if (Session.Status == SessionStatus.ErrorPermissionNotGranted)
            {
                // 应用未获取手机相机的权限
                ShowAndroidToastMessage("Camera permission is needed to run this application.");
                _isQuitting = true;
                Invoke("DoQuit", 0.5f); // 退出程序
            }
            else if (Session.Status.IsError())
            {
                // AR 程序初始化失败
                ShowAndroidToastMessage(
                    "ARCore encountered a problem connecting.  Please start the app again.");
                _isQuitting = true;
                Invoke("DoQuit", 0.5f); // 退出程序
            }
        }

        // 退出程序
        private void DoQuit()
        {
            Application.Quit();
        }

        // 在 Android 设备上显示一条弹窗消息
        private 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");
                }));
            }
        }
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值