一、平面检测设置
1. 新建场景,场景中加入【AR Session Origin】和【AR Session】;
2. 【AR Session Origin】挂在【ARPlaneManager】组件;
3. 创建平面检测预制体;
4. 将创建的平面检测预制体置于【ARPlaneManager】组件中;
5. 打包,发布即可;
二、个性化渲染
1. 将【AR Default Plane】中的【LineRender】组件移除;
2. 在【AR Default Plane】上增加【ARFeatheredPlaneMeshVisualizer】组件和【FeatheredPlaneShader】材质球;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
/// <summary>
/// 创建人:
/// 创建时间:2022-2-11
/// 功能描述:平面可视化工具演示了在被检测平面的边缘使用羽化效果,它减少了硬边的视觉印象
/// </summary>
[RequireComponent(typeof(ARPlaneMeshVisualizer), typeof(MeshRenderer), typeof(ARPlane))]
public class ARFeatheredPlaneMeshVisualizer : MonoBehaviour
{
[Tooltip("The width of the texture feathering (in world units).")]
[SerializeField]
float m_FeatheringWidth = 0.2f;
/// <summary>
/// 纹理羽化的宽度(世界单位)
/// </summary>
public float featheringWidth
{
get { return m_FeatheringWidth; }
set { m_FeatheringWidth = value; }
}
void Awake()
{
m_PlaneMeshVisualizer = GetComponent<ARPlaneMeshVisualizer>();
m_FeatheredPlaneMaterial = GetComponent<MeshRenderer>().material;
m_Plane = GetComponent<ARPlane>();
}
void OnEnable()
{
m_Plane.boundaryChanged += ARPlane_boundaryUpdated;
}
void OnDisable()
{
m_Plane.boundaryChanged -= ARPlane_boundaryUpdated;
}
void ARPlane_boundaryUpdated(ARPlaneBoundaryChangedEventArgs eventArgs)
{
GenerateBoundaryUVs(m_PlaneMeshVisualizer.mesh);
}
/// <summary>
/// 生成uv2来标记边界顶点和羽状UV坐标
/// </summary>
void GenerateBoundaryUVs(Mesh mesh)
{
int vertexCount = mesh.vertexCount;
// 重用uv列表
s_FeatheringUVs.Clear();
if (s_FeatheringUVs.Capacity < vertexCount) { s_FeatheringUVs.Capacity = vertexCount; }
mesh.GetVertices(s_Vertices);
Vector3 centerInPlaneSpace = s_Vertices[s_Vertices.Count - 1];
Vector3 uv = new Vector3(0, 0, 0);
float shortestUVMapping = float.MaxValue;
// 假设最后一个顶点是中心顶点
for (int i = 0; i < vertexCount - 1; i++)
{
float vertexDist = Vector3.Distance(s_Vertices[i], centerInPlaneSpace);
// 重新映射UV,使“1”的UV标记羽化边界
// featherBoundaryDistance/edgeDistance的比值与featherUV/edgeUV相同
// 重新排列得到边缘UV
float uvMapping = vertexDist / Mathf.Max(vertexDist - featheringWidth, 0.001f);
uv.x = uvMapping;
// 所有的UV映射都是不同的。 在着色器中,我们需要知道我们需要淡出的UV值
// 选择最短的UV,以确保我们在边界前淡出
// 这意味着羽化宽度将略有不同,我们再次依赖于一个相当均匀的平面
if (shortestUVMapping > uvMapping) { shortestUVMapping = uvMapping; }
s_FeatheringUVs.Add(uv);
}
m_FeatheredPlaneMaterial.SetFloat("_ShortestUVMapping", shortestUVMapping);
// 添加中心顶点UV
uv.Set(0, 0, 0);
s_FeatheringUVs.Add(uv);
mesh.SetUVs(1, s_FeatheringUVs);
mesh.UploadMeshData(false);
}
static List<Vector3> s_FeatheringUVs = new List<Vector3>();
static List<Vector3> s_Vertices = new List<Vector3>();
ARPlaneMeshVisualizer m_PlaneMeshVisualizer;
ARPlane m_Plane;
Material m_FeatheredPlaneMaterial;
}
Shader "Unlit/FeatheredPlaneShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_TexTintColor("Texture Tint Color", Color) = (1,1,1,1)
_PlaneColor("Plane Color", Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Transparent" "Queue"="Transparent" }
LOD 100
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 uv2 : TEXCOORD1;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
float3 uv2 : TEXCOORD1;
};
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _TexTintColor;
fixed4 _PlaneColor;
float _ShortestUVMapping;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.uv2 = v.uv2;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv) * _TexTintColor;
col = lerp( _PlaneColor, col, col.a);
// Fade out from as we pass the edge.
// uv2.x stores a mapped UV that will be "1" at the beginning of the feathering.
// We fade until we reach at the edge of the shortest UV mapping.
// This is the remmaped UV value at the vertex.
// We choose the shorted one so that ll edges will fade out completely.
// See ARFeatheredPlaneMeshVisualizer.cs for more details.
col.a *= 1-smoothstep(1, _ShortestUVMapping, i.uv2.x);
return col;
}
ENDCG
}
}
}
3. 在【AR Default Plane】上的材质球中替换自己想要显示的图片即可;
三、开启/禁用平面检测、隐藏/显示平面
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using UnityEngine.XR.ARFoundation;
/// <summary>
/// 创建人:
/// 创建时间:2022-2-11
/// 功能描述:平面检测以及个性化渲染
/// </summary>
[RequireComponent(typeof(ARPlaneManager))]
public class CheckPlaneDetection : MonoBehaviour
{
#region UI
[Tooltip("显示/隐藏平面")]
public Button showandhideBtn;
[Tooltip("开启/禁用平面检测")]
public Button openandcloseBtn;
#endregion
#region 管理器
public ARPlaneManager mARPlaneManager;
#endregion
void Start()
{
mARPlaneManager.planesChanged += OnPlaneChanged;
InitUI();
}
// 当行为被禁用或处于非活动状态时调用此函数
private void OnDisable()
{
mARPlaneManager.planesChanged -= OnPlaneChanged;
}
/// <summary>
/// 初始化UI
/// </summary>
private void InitUI()
{
openandcloseBtn.onClick.AddListener(TogglePlaneDetection);
showandhideBtn.onClick.AddListener(TogglePlaneDisplay);
}
#region 开启/禁用平面检测
/// <summary>
/// 开启禁用平面检测
/// </summary>
private void TogglePlaneDetection()
{
mARPlaneManager.enabled = !mARPlaneManager.enabled;
if (mARPlaneManager.enabled)
{
openandcloseBtn.transform.Find("Text").GetComponent<Text>().text = "禁用平面检测";
SetAllPlaneAction(true);
}
else
{
openandcloseBtn.transform.Find("Text").GetComponent<Text>().text = "启用平面检测";
SetAllPlaneAction(false);
}
}
/// <summary>
/// 设置已检测到的平面
/// </summary>
private void SetAllPlaneAction(bool value)
{
foreach (var item in mARPlaneManager.trackables)
{
item.gameObject.SetActive(value);
}
}
#endregion
#region 显示/隐藏平面
[Tooltip("存储已检测的平面")]
private List<ARPlane> planeList = new List<ARPlane>();
[Tooltip("已检测平面的显隐")]
private bool isShow = true;
/// <summary>
/// 显示/隐藏检测的平面
/// </summary>
private void TogglePlaneDisplay()
{
if (isShow)
{
showandhideBtn.transform.Find("Text").GetComponent<Text>().text = "隐藏平面";
}
else
{
showandhideBtn.transform.Find("Text").GetComponent<Text>().text = "显示平面";
}
for (int i = planeList.Count - 1; i >= 0; i--)
{
if (planeList[i] == null || planeList[i].gameObject == null)
{
planeList.Remove(planeList[i]);
}
else
{
planeList[i].gameObject.SetActive(false);
}
}
isShow = !isShow;
}
/// <summary>
/// 平面检测事件回调(增加、删除、更新)
/// </summary>
/// <param name="obj"></param>
private void OnPlaneChanged(ARPlanesChangedEventArgs obj)
{
for (int i = 0; i < obj.added.Count; i++)
{
planeList.Add(obj.added[i]);
obj.added[i].gameObject.SetActive(isShow);
}
}
#endregion
}
四、平面检测分类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
/// <summary>
/// 创建人:
/// 创建时间:2022-2-11
/// 功能描述:平面检测分类
/// </summary>
[RequireComponent(typeof(ARPlane))]
[RequireComponent(typeof(MeshRenderer))]
public class PlaneClassificationLabeler : MonoBehaviour
{
ARPlane m_ARPlane;
MeshRenderer m_PlaneMeshRenderer;
TextMesh m_TextMesh;
GameObject m_TextObj;
Vector3 m_TextFlipVec = new Vector3(0, 180, 0);
void Awake()
{
m_ARPlane = GetComponent<ARPlane>();
m_PlaneMeshRenderer = GetComponent<MeshRenderer>();
m_TextObj = new GameObject();
m_TextMesh = m_TextObj.AddComponent<TextMesh>();
m_TextMesh.characterSize = 0.05f;
m_TextMesh.color = Color.black;
}
void Update()
{
UpdateLabel();
UpdatePlaneColor();
}
void UpdateLabel()
{
m_TextMesh.text = m_ARPlane.classification.ToString();
m_TextObj.transform.position = m_ARPlane.center;
m_TextObj.transform.LookAt(Camera.main.transform);
m_TextObj.transform.Rotate(m_TextFlipVec);
}
void UpdatePlaneColor()
{
Color planeMatColor = Color.cyan;
switch (m_ARPlane.classification)
{
case PlaneClassification.None:
planeMatColor = Color.cyan;
break;
case PlaneClassification.Wall:
planeMatColor = Color.white;
break;
case PlaneClassification.Floor:
planeMatColor = Color.green;
break;
case PlaneClassification.Ceiling:
planeMatColor = Color.blue;
break;
case PlaneClassification.Table:
planeMatColor = Color.yellow;
break;
case PlaneClassification.Seat:
planeMatColor = Color.magenta;
break;
case PlaneClassification.Door:
planeMatColor = Color.red;
break;
case PlaneClassification.Window:
planeMatColor = Color.clear;
break;
}
planeMatColor.a = 0.33f;
m_PlaneMeshRenderer.material.color = planeMatColor;
}
void OnDestroy()
{
Destroy(m_TextObj);
}
}