项目效果
通过相机扫描环境中的平面,它会生成上图中的白色网格。可通过点击白色网格来生成三维模型。
下图是项目中的节点,下面将从挑选一些节点来分析,其中 Environmental Light
节点是灯光、EventSystem
节点是控制输入输出事件的、PlaneDiscovery
和 Canvas
节点 是UI界面,这些就不讲解了。
1、ARCore Device
该节点的介绍可去这里(传送门)浏览。
该节点下的 First Person Camera
节点还有一个 DepthPreview
节点,主要是用来生成深度图的(如下图所示)。
该项目中的 Session Config 文件配置如下图,灯光估计是 HDR 带有反射的模式、Instant Placement Mode 是 Local Y Up 模式。
2、HelloAR Controller
该节点只有两个脚本,其中一个是 HelloARController
,它是用来检测用户是否触摸了设备,并在触摸点处生成相应的模型;另一个是 SettingsMenu
,它是控制用户与UI界面的交互,如显示和隐藏UI界面。
2.1、HelloARController
脚本
该脚本中的配置从上到下分别是:
- 深度功能的菜单界面的控制脚本
- 放置模型的设置界面的控制脚本
- 放置模型功能的生成模型
- 第一人称相机
- 点击垂直平面时的生成模型
- 点击水平平面时的生成模型
- 点击点云时的生成模型
- 点击深度点云时的模型
namespace GoogleARCore.Examples.HelloAR
{
using System.Collections.Generic;
using GoogleARCore;
using GoogleARCore.Examples.Common;
using UnityEngine;
using UnityEngine.EventSystems;
#if UNITY_EDITOR
// Set up touch input propagation while using Instant Preview in the editor.
using Input = InstantPreviewInput;
#endif
public class HelloARController : MonoBehaviour
{
public DepthMenu DepthMenu;
public InstantPlacementMenu InstantPlacementMenu;
public GameObject InstantPlacementPrefab;
public Camera FirstPersonCamera;
public GameObject GameObjectVerticalPlanePrefab;
public GameObject GameObjectHorizontalPlanePrefab;
public GameObject GameObjectPointPrefab;
public GameObject GameObjectDepthPointPrefab;
// 生成模型时设置的旋转参数
private const float _prefabRotation = 180.0f;
// 标记程序是否退出
private bool _isQuitting = false;
public void Awake()
{
// 注意:当 QualitySettings.vSyncCount != 0 时,Application.targetFrameRate 会被忽略
Application.targetFrameRate = 60; // 帧率设为 60 帧
}
}
}
Update() 函数
该函数中主要是检测用户是否触摸了设备,并根据点击到的GameObject 对象在点击位置处生成相应的模型。
UpdateApplicationLifecycle() 函数讲解
public void Update()
{
UpdateApplicationLifecycle(); // 可点击上方的链接浏览该函数的功能
// 检测是否点击了设备的屏幕.
// Input.touchCount 获取当前帧触摸屏幕的次数,没有触摸就返回
// Input.GetTouch(0)).phase 获取手指触摸屏幕的状态,Began 是 手指触摸了屏幕
Touch touch;
if (Input.touchCount < 1 || (touch = Input.GetTouch(0)).phase != TouchPhase.Began)
return;
// 如果触摸了 UI,那么也返回,因为触摸UI不会放置模型
if (EventSystem.current.IsPointerOverGameObject(touch.fingerId)){
return;
// 对玩家触摸的位置进行射线投射以搜索平面。
TrackableHit hit;
bool foundHit = false;
// raycastFilter 是射线的过滤器
// PlaneWithinPolygon:碰撞发生在 DetectedPlane 的凸边界多边形内。
// FeaturePointWithSurfaceNormal:碰撞发生在当前帧的点云中具有表面法线估计(方向)的特征点上
// Depth:碰撞发生在当前帧的深度图上。
TrackableHitFlags raycastFilter = TrackableHitFlags.PlaneWithinPolygon |
TrackableHitFlags.FeaturePointWithSurfaceNormal;
// Allows the depth image to be queried for hit tests.
raycastFilter |= TrackableHitFlags.Depth;
// 检测碰撞点,如果检测到就返回 true
foundHit = Frame.Raycast(touch.position.x, touch.position.y, raycastFilter, out hit);
if (!foundHit && InstantPlacementMenu.IsInstantPlacementEnabled())
{
foundHit = Frame.RaycastInstantPlacement(
touch.position.x, touch.position.y, 1.0f, out hit);
}
if (foundHit)
{
// 检测触摸点中的物体是不是平面且使用触摸点和相机的位置做一个点乘运算,
// 检测是否点击到平面的背面,如果是,不需要创建锚点
if ((hit.Trackable is DetectedPlane) &&
Vector3.Dot(FirstPersonCamera.transform.position - hit.Pose.position,
hit.Pose.rotation * Vector3.up) < 0)
{
Debug.Log("Hit at back of the current DetectedPlane");
}
else
{
if (DepthMenu != null)
DepthMenu.ConfigureDepthBeforePlacingFirstAsset();// 显示深度图菜单
// 根据点击到的物体生成相应的三维模型
GameObject prefab;
if (hit.Trackable is InstantPlacementPoint)
{
prefab = InstantPlacementPrefab;
}
else if (hit.Trackable is FeaturePoint)
{
prefab = GameObjectPointPrefab;
}
else if (hit.Trackable is DepthPoint)
{
prefab = GameObjectDepthPointPrefab;
}
else if (hit.Trackable is DetectedPlane)
{
// 如果是平面,还需要根据平面是垂直还是水平来选择生成的模型
DetectedPlane detectedPlane = hit.Trackable as DetectedPlane;
if (detectedPlane.PlaneType == DetectedPlaneType.Vertical)
prefab = GameObjectVerticalPlanePrefab;
else
prefab = GameObjectHorizontalPlanePrefab;
}
else
{
prefab = GameObjectHorizontalPlanePrefab;
}
// 在点击的位置实例化一个前面选择的预制体
var gameObject = Instantiate(prefab, hit.Pose.position, hit.Pose.rotation);
// 设置预制体的选择参数
gameObject.transform.Rotate(0, _prefabRotation, 0, Space.Self);
// 在点击的位置创建一个锚点
var anchor = hit.Trackable.CreateAnchor(hit.Pose);
// 设置锚点为生成预制体的父节点
gameObject.transform.parent = anchor.transform;
// 点击到的对象是InstantPlacementPoint, 生成一个放置物体时的是假的效果
if (hit.Trackable is InstantPlacementPoint)
{
gameObject.GetComponentInChildren<InstantPlacementEffect>()
.InitializeWithTrackable(hit.Trackable);
}
}
}
}
2.2、SettingsMenu
脚本
该脚本主要是用来管理项目中的 UI 界面,其中总共有三种功能类型的脚本,分别是:
- 公共的设置界面
- 是否启用深度功能的设置界面
- 放置模型的设置界面
3、Plane Generator
该节点主要是用来生成白色的网格的,并可以在这些网格中生成模型。其中 Detected Plane Prefab
就是网格的预制体。
namespace GoogleARCore.Examples.Common
{
using System.Collections.Generic;
using GoogleARCore;
using UnityEngine;
public class DetectedPlaneGenerator : MonoBehaviour
{
public GameObject DetectedPlanePrefab;
// 在 ARCore开始追踪起到当前帧,生成的平面保存在下面的集合中
private List<DetectedPlane> _newPlanes = new List<DetectedPlane>();
public void Update()
{
// 检查运动跟踪是否正在跟踪。
if (Session.Status != SessionStatus.Tracking)
return;
// 遍历在此帧中找到的平面并实例化相应的游戏对象以可视化它们
// 过滤器中实现了新检测的平面才放进去,即在集合中的平面就不用放进去了
Session.GetTrackables<DetectedPlane>(_newPlanes, TrackableQueryFilter.New);
for (int i = 0; i < _newPlanes.Count; i++)
{
// 实例化一个平面可视化预制体并将其设置为跟踪新平面。
// 由于我们的预制体的网格在 Unity World 坐标中更新,因此变换设置为具有标识旋转的原点
GameObject planeObject =
Instantiate(DetectedPlanePrefab, Vector3.zero, Quaternion.identity, transform);
planeObject.GetComponent<DetectedPlaneVisualizer>().Initialize(_newPlanes[i]);
}
}
}
}
4、Point Cloud
该节点主要是生成点云的,点云就是下图中那些青蓝色的点,如何生成这些点的脚本就不分析了。