项目效果
将一个三维物体附着到脸部,随着脸部的移动,三维物体也会跟踪脸部来进行移动。
下图是该项目的 GameObject 节点,下面将从各个节点分析该项目。
1、ARCore Device
该节点的介绍可移步到这里浏览:传送门。
下面分析该项目中的一些需要注意的参数:
该项目使用的是前置摄像头,并且定义了自己的ARCore 会话文件。
会话文件中,Plane Finding Mode
选项禁用了平面查找模式,并且 Augmented Face Mode
选项设为Mesh 模式
。
2、FaceAttachment
2.1、FaceTexture
和FaceOccluder
节点
两个节点都是把相机捕捉到的脸部图像绘制到材质贴图中,如下图红色方框中的 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");
}));
}
}