ARCore平面与空间点云实现
本文对ARCore如何实现平面显示和空间点云效果进行分析
平面显示步骤如下:
1)在DetectedPlaneGenerator脚本中,通过平面接口Session.GetTrackables获取所有接平面,根据平面实例化每一个平面prefab,每个prefab通过DetectedPlaneVisualizer脚本来展示平面。
2)DetectedPlaneVisualizer首先获取平面信息,包括平面中心位姿和边界点。继而可以得到平面法线:
m_PlaneCenter = m_DetectedPlane.CenterPose.position;
Vector3 planeNormal = m_DetectedPlane.CenterPose.rotation * Vector3.up;
3)通过中心位姿以及边界点即可通过三角形组成完整的平面,但是ARCore计算了额外的(内圈)边界点,目的是在一定范围内实现平面边界的逐渐过渡,即平面的边界处透明度为0,从中心到边界点0.8位置处开始逐渐透明。计算如下:
// The following code converts a polygon to a mesh with two polygons, inner polygon
// renders with 100% opacity and fade out to outter polygon with opacity 0%, as shown
// below. The indices shown in the diagram are used in comments below.
// _______________ 0_______________1
// | | |4___________5|
// | | | | | |
// | | => | | | |
// | | | | | |
// | | |7-----------6|
// --------------- 3---------------2
m_MeshColors.Clear();
// Fill transparent color to vertices 0 to 3.
for (int i = 0; i < planePolygonCount; ++i)
{
m_MeshColors.Add(Color.clear);
}
// Feather distance 0.2 meters.
const float featherLength = 0.2f;
// Feather scale over the distance between plane center and vertices.
const float featherScale = 0.2f;
// Add vertex 4 to 7.
for (int i = 0; i < planePolygonCount; ++i)
{
Vector3 v = m_MeshVertices[i];
// Vector from plane center to current point
Vector3 d = v - m_PlaneCenter;
float scale = 1.0f - Mathf.Min(featherLength / d.magnitude, featherScale);
m_MeshVertices.Add((scale * d) + m_PlaneCenter);
m_MeshColors.Add(Color.white);
}
所以如上述注释中所示,点0、1、2、3的顶点颜色为clear,而后续计算的4、5、6、7则为white.
4)设置三角形,并赋值mesh
根据点形成三角形的索引,并赋值给mesh(默认mesh是unity默认的cube)。
m_MeshIndices.Clear();
int firstOuterVertex = 0;
int firstInnerVertex = planePolygonCount;
// Generate triangle (4, 5, 6) and (4, 6, 7).
for (int i = 0; i < planePolygonCount - 2; ++i)
{
m_MeshIndices.Add(firstInnerVertex);
m_MeshIndices.Add(firstInnerVertex + i + 1);
m_MeshIndices.Add(firstInnerVertex + i + 2);
}
// Generate triangle (0, 1, 4), (4, 1, 5), (5, 1, 2), (5, 2, 6), (6, 2, 3), (6, 3, 7)
// (7, 3, 0), (7, 0, 4)
for (int i = 0; i < planePolygonCount; ++i)
{
int outerVertex1 = firstOuterVertex + i;
int outerVertex2 = firstOuterVertex + ((i + 1) % planePolygonCount);
int innerVertex1 = firstInnerVertex + i;
int innerVertex2 = firstInnerVertex + ((i + 1) % planePolygonCount);
m_MeshIndices.Add(outerVertex1);
m_MeshIndices.Add(outerVertex2);
m_MeshIndices.Add(innerVertex1);
m_MeshIndices.Add(innerVertex1);
m_MeshIndices.Add(outerVertex2);
m_MeshIndices.Add(innerVertex2);
}
m_Mesh.Clear();
m_Mesh.SetVertices(m_MeshVertices);
m_Mesh.SetTriangles(m_MeshIndices, 0);
m_Mesh.SetColors(m_MeshColors);
5)平面渲染
由于ARCore渲染平面时采用了一张三角形网格的贴图,所以需要uv来进行采样,但是前四步未计算uv。ARCore通过顶点着色器进行uv的计算:通过任意一条与法线既不平行也不会垂直的向量,计算两条相互垂直的单位方向向量(即计算出uv平面内的坐标系),通过顶点坐标向此两个向量投影即可算出uv。然后根据cpu传入的随机转角,计算旋转矩阵,计算一个uv值。后续计算则对贴图采样,并通过r通道值与顶点颜色的alpha值计算,得出像素的透明度。如下所示:
fixed cosr = cos(_UvRotation);
fixed sinr = sin(_UvRotation);
fixed2x2 uvrotation = fixed2x2(cosr, -sinr, sinr, cosr);
// Construct two vectors that are orthogonal to the normal.
// This arbitrary choice is not co-linear with either horizontal
// or vertical plane normals.
const float3 arbitrary = float3(1.0, 1.0, 0.0);
float3 vec_u = normalize(cross(_PlaneNormal, arbitrary));
float3 vec_v = normalize(cross(_PlaneNormal, vec_u));
// Project vertices in world frame onto vec_u and vec_v.
float2 plane_uv = float2(dot(v.vertex.xyz, vec_u), dot(v.vertex.xyz, vec_v));
float2 uv = plane_uv * _MainTex_ST.xy;
o.uv = mul(uvrotation, uv);
uv可在c#脚本中进行计算,然后传入shader进行计算。
点云的显示较为简单,通过底层获取点数据,在C#层是通过LinkedList存储点,通过uv数据通道传递点的大小,并估值PSIZE来来设置大小。通过LinkedList数据给mesh赋值,由于设置的mesh类型是point,所以只需要设置顶点和size(通过uv)。只不过在点设置是采用了随机大小以及在动画时间范围内点大小变化的效果。
不过在shader中做了一步特殊处理,即现在vs中计算在屏幕上的像素位置,然后通过vps传入的像素坐标位置计算,如果两者距离小于点的大小就discard掉。