IOS 增强现实的 .NET 开发者指南(二)
十一、人脸跟踪和表情检测
万一你认为 ARKit 提供的内置增强现实功能不够惊人,你不会相信你可以用面部跟踪和面部表情检测来做什么。通过使用前置摄像头,它可以跟踪多张脸,甚至是脸上的表情。
跟踪人脸
现成的 ARKit 让我们能够在一个场景中跟踪多达三张不同的脸。明确地说,这意味着检测人脸并在场景中跟随他们。
请注意,如果没有额外的编码,ARKit 无法识别这些面孔属于谁。ARKit 只能检测到有在镜头前是张脸,而不是他们属于谁。
图 11-1
一个场景中最多可以跟踪三个面,并且可以检索和操纵它们的几何体
值得注意的是,较旧的 iOS 设备可能不支持面部跟踪。建议您检查一下ARFaceTrackingConfiguration。IsSupported属性在尝试调用面部跟踪功能之前,如果您的设备不支持该功能,应用将在尝试调用该功能时崩溃并退出。如果不支持面部跟踪,您可能希望向用户显示一条消息,告诉他们这一点。
在清单 11-1 中,我们正在运行我们的会话,这次使用的是ARFaceTrackingConfiguration,它默认使用手机上的前置摄像头,允许我们跟踪场景中的人脸,如图 11-1 所示。
然后,我们使用场景视图委托来处理当检测到、移动或更改人脸时触发的事件。更具体地说,在下面的代码示例中,当在场景中检测到一个新的面部时(在相关位置放置一个ARFaceAnchor),我们将检索面部几何图形,并将其设置为放置在ARFaceAnchor位置的节点的几何图形,并将其设置为 80%不透明。
public partial class ViewController : UIViewController
{
private readonly ARSCNView sceneView;
public ViewController(IntPtr handle) : base(handle)
{
this.sceneView = new ARSCNView
{
AutoenablesDefaultLighting = true,
Delegate = new SceneViewDelegate()
};
this.View.AddSubview(this.sceneView);
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
this.sceneView.Frame = this.View.Frame;
}
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
var faceTrackingConfiguration = new ARFaceTrackingConfiguration()
{
LightEstimationEnabled = true,
MaximumNumberOfTrackedFaces = 1
};
this.sceneView.Session.Run(faceTrackingConfiguration);
}
public override void ViewDidDisappear(bool animated)
{
base.ViewDidDisappear(animated);
this.sceneView.Session.Pause();
}
}
public class SceneViewDelegate : ARSCNViewDelegate
{
public override void DidAddNode(ISCNSceneRenderer renderer, SCNNode node, ARAnchor anchor)
{
if (anchor is ARFaceAnchor faceAnchor)
{
var faceGeometry = ARSCNFaceGeometry.Create(renderer.GetDevice());
node.Geometry = faceGeometry;
node.Opacity = 0.8f;
}
}
public override void DidUpdateNode(ISCNSceneRenderer renderer, SCNNode node, ARAnchor anchor)
{
if (anchor is ARFaceAnchor)
{
var faceAnchor = anchor as ARFaceAnchor;
var faceGeometry = node.Geometry as ARSCNFaceGeometry;
faceGeometry.Update(faceAnchor.Geometry);
}
}
}
Listing 11-1Tracking people’s faces in the scene
如果我们愿意的话,我们可以用一张图片来代替面部几何图形,而不是使用平面纯色。例如,我们可以在某人的脸上放一张图片,让他们看起来像一个戴面具的超级英雄。
人们很容易怀疑这种功能在现实世界中的应用是否有用,而只是把它当成一种乐趣;然而,有一些成功的企业已经实施了这种类型的面部跟踪,并取得了很好的效果。例如,能够跟踪面部的方向,一些企业通过将不同风格的眼镜的 3D 模型添加到用户的面部几何形状上,来显示用户佩戴不同眼镜的样子。令人印象深刻的东西。
识别面部表情
除了跟踪场景中人脸的存在,我们还能够检测这些人脸上数量惊人的不同面部表情(事实上,超过 50 种不同的面部表情)。
在图 11-2 中,我使用了我们在第 3 “节点、几何图形、材质和锚点”中首次提到的material.FillMode = SCNFillMode.Lines,默认颜色为白色,然后,当检测到嘴部漏斗时,将线条颜色改为黄色。
图 11-2
可以检测到 50 多种不同的面部表情
利用SCNFillMode.Lines,我们真的可以看到 ARKit 是如何检测人脸轮廓的。毫不奇怪,它可以推断出许多面部表情。
这里有一个完整的可检测面部表情列表(我告诉过你有很多):
-
左吊环,右吊环
-
左,右
-
eyeLookInLeft,eyeLookInRight
-
向左看,向右看
-
向上看,向上看
-
左眼睛,右眼睛
-
左睁大眼睛,右睁大眼睛
-
下巴向前
-
左颚,右颚
-
jawOpen
-
嘴巴闭上
-
漏斗嘴
-
噘嘴
-
向左,向右
-
嘴巴左,嘴巴右
-
左嘴巴,右嘴巴
-
左嘴巴,右嘴巴
-
左口拉伸,右口拉伸
-
下口辊,上口辊
-
口交怒视者,口交怒视者
-
口按左,口按右
-
左下下,右下下
-
左嘴巴,右嘴巴
-
浏览器左,浏览器右
-
浏览器升级
-
browOuterUpLeft
-
棕色直立
-
脸颊粉扑
-
左脸颊,右脸颊
-
鼻子左,鼻子右
-
伸出舌头
每个表情的描述可以在苹果官方文档这里找到: https://developer.apple.com/documentation/arkit/arfaceanchor/blendshapelocation 。
ARKit 甚至允许我们同时跟踪多个表情(例如,右眼闭上和舌头伸出),以及跟踪这些表情的相对存在。例如,每个表达式都有一个介于 0 和 1 之间的浮点值,以表示该表达式完全不存在或完全存在,也就是说,跟踪舌头是完全不出来、有点出来还是完全不出来。
清单 11-2 展示了当我们开始会话时,我们如何设置和使用 ARFaceTrackingConfiguration。
public partial class ViewController : UIViewController
{
private readonly ARSCNView sceneView;
public ViewController(IntPtr handle) : base(handle)
{
this.sceneView = new ARSCNView
{
AutoenablesDefaultLighting = true,
Delegate = new SceneViewDelegate()
};
this.View.AddSubview(this.sceneView);
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
this.sceneView.Frame = this.View.Frame;
}
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
var faceTrackingConfiguration = new
ARFaceTrackingConfiguration()
{
LightEstimationEnabled = true,
MaximumNumberOfTrackedFaces = 1
};
this.sceneView.Session.Run(faceTrackingConfiguration);
}
public override void ViewDidDisappear(bool animated)
{
base.ViewDidDisappear(animated);
this.sceneView.Session.Pause();
}
}
public class SceneViewDelegate : ARSCNViewDelegate
{
public override void DidAddNode(ISCNSceneRenderer renderer, SCNNode node, ARAnchor anchor)
{
if (anchor is ARFaceAnchor)
{
var faceGeometry = ARSCNFaceGeometry.Create(renderer.GetDevice());
node.Geometry = faceGeometry;
node.Geometry.FirstMaterial.FillMode =
SCNFillMode.Lines;
}
}
public override void DidUpdateNode(ISCNSceneRenderer renderer, SCNNode node, ARAnchor anchor)
{
if (anchor is ARFaceAnchor)
{
var faceAnchor = anchor as ARFaceAnchor;
var faceGeometry = node.Geometry as
ARSCNFaceGeometry;
var expressionThreshold = 0.5f;
faceGeometry.Update(faceAnchor.Geometry);
if (faceAnchor.BlendShapes.EyeBlinkLeft > expressionThreshold
|| faceAnchor.BlendShapes.EyeBlinkRight > expressionThreshold)
{
ChangeFaceColour(node, UIColor.Blue);
return;
}
if (faceAnchor.BlendShapes.MouthSmileLeft > expressionThreshold
|| faceAnchor.BlendShapes.MouthSmileRight > expressionThreshold)
{
ChangeFaceColour(node, UIColor.SystemPinkColor);
return;
}
if (faceAnchor.BlendShapes.EyeLookOutLeft > expressionThreshold
|| faceAnchor.BlendShapes.EyeLookOutRight > expressionThreshold)
{
ChangeFaceColour(node, UIColor.Magenta);
return;
}
if (faceAnchor.BlendShapes.TongueOut > expressionThreshold)
{
ChangeFaceColour(node, UIColor.Red);
return;
}
if (faceAnchor.BlendShapes.CheekPuff > expressionThreshold)
{
ChangeFaceColour(node, UIColor.Orange);
return;
}
ChangeFaceColour(node, UIColor.White);
}
}
private void ChangeFaceColour(SCNNode faceGeometry, UIColor colour)
{
var material = new SCNMaterial();
material.Diffuse.Contents = colour;
material.FillMode = SCNFillMode.Lines;
faceGeometry.Geometry.FirstMaterial = material;
}
}
Listing 11-2Recognizing a few of the facial expressions
注意在清单 11-2 中,我们使用SCNFillMode.Lines作为材质FillMode来更好地显示面部几何的轮廓。
要尝试的事情
向检测到的人脸添加新的节点和形状。
在 DidUpdateNode 方法中,为包含面部网格的节点创建并添加额外的节点(例如,形状或图像),例如,在某人的头上戴一顶基本的“帽子”,给他们留胡须,或显示包含此人信息的图像(在 2D 平面上)。
区别对待不同的被跟踪人脸。
如上所述,ARKit 可以同时跟踪多达三张脸。尝试为被跟踪的每张脸分配不同的颜色。
使用图像作为面部材质。
不要对检测到的面节点材质使用纯色,尝试对材质使用图像。有了正确的图像,你可以让用户的脸看起来像蜘蛛侠的面具或类似的东西。发挥你的想象力!
摘要
面部跟踪和面部表情检测允许我们通过在体验中涉及用户的面部来增强个性化。这种用例的范围可以从一点乐趣到预览可穿戴产品,这是增强现实应用的一个非常受欢迎的用例。
到目前为止,在本书中,我们已经看到了在场景中放置物品;在下一章中,当我们学习触摸手势和交互时,我们将看看我们如何与这些物体交互。
十二、触摸手势和交互
到目前为止,我们已经研究了向我们的增强现实场景添加虚拟对象的不同方法。如果你也能和他们互动,那不是很好吗?哦,等等。你可以,这就是我们将在本章中探讨的内容。
手势识别器
有许多预定义的触摸设备屏幕的方式可以自动转换为所谓的手势,并触发一个等效的UIGestureRecognizer。然后根据手势的类型,如果场景中的任何虚拟物品被触摸,它们可以被相应地操纵。
我们能够识别设备屏幕上的许多不同手势,在这一章中,我们将看看如何对它们做出反应。
-
龙头
-
辐状的
-
少量
-
偷窃
-
商标出版社
我们还将看看如何改变这些手势的默认行为并扩展它们。例如,轻松地将轻击手势更改为双击手势,或者更改长按手势所需的按下时间。
你必须记住,设备屏幕是二维的,因此我们的手势是在 2D,所以有时有必要在代码中定义你想要在哪个轴上操作你的虚拟物品。例如,当使用向上或向下或向左或向右的平移手势时,在 3D 空间中沿着 Y 和 X 轴移动对象是有意义的,但是如何使用户能够沿着 Z 轴移动对象以使其更近或更远呢?您可能希望使用多个手势识别器来实现您想要的体验。
连接手势识别器
为了让我们的应用响应不同类型的触摸,我们需要告诉我们的 SceneView 监听我们希望它识别的手势,如清单 12-1 所示。
然后在本章后面的清单中,我们可以看看为这些类型的手势运行的示例代码。
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
...
var panGesture = new UIPanGestureRecognizer(HandlePanGesture);
this.sceneView.AddGestureRecognizer(panGesture);
var rotateGesture = new UIRotationGestureRecognizer(HandleRotateGesture);
this.sceneView.AddGestureRecognizer(rotateGesture);
var pinchGesture = new UIPinchGestureRecognizer(HandlePinchGesture);
this.sceneView.AddGestureRecognizer(pinchGesture);
var tapGesture = new UITapGestureRecognizer(HandleTapGesture);
this.sceneView.AddGestureRecognizer(tapGesture);
var swipeGesture = new UISwipeGestureRecognizer(HandleSwipeGesture);
this.sceneView.AddGestureRecognizer(swipeGesture);
var longPressGesture = new UILongPressGestureRecognizer(HandleLongPressGesture);
this.sceneView.AddGestureRecognizer(longPressGesture);
...
}
Listing 12-1We can tell our app to respond to a number of different gestures
开孔
我们可以检测屏幕上的点击是否触摸了场景中的虚拟对象,并做出相应的反应。例如,如果我们想让它成为双击手势识别器,我们还可以坚持最少的点击次数。在清单 12-2 中,当点击一个节点时,我们将它的颜色改为黑色。
private void HandleTapGesture(UITapGestureRecognizer sender)
{
var areaTapped = sender.View as SCNView;
var location = sender.LocationInView(areaTapped);
var hitTestResults = areaTapped.HitTest(
location, new SCNHitTestOptions());
var hitTest = hitTestResults.FirstOrDefault();
if (hitTest == null)
return;
var node = hitTest.Node;
var material = new SCNMaterial();
material.Diffuse.Contents = UIColor.Black;
node.Geometry.FirstMaterial = material;
}
Listing 12-2Tap UIGestureRecognizer
如果您使用点击手势来“选择”场景中的虚拟对象,您可能想要做其他事情来表示它是“被选择的”,例如更改它的颜色或比例,例如,帮助表示被点击的对象具有焦点,并且您希望您的用户知道什么被点击/选择。
吝啬的
通过将两个手指放在屏幕上,将它们捏在一起或松开,我们可以放大或缩小你捏的虚拟物品。这可以通过使用清单 12-3 中所示的代码来实现。
Note
当处理如清单 12-2 所示的收缩手势和缩放节点时,有必要将发送者比例重置为 1,以避免异常行为。
private void HandlePinchGesture(UIPinchGestureRecognizer sender)
{
var areaPinched = sender.View as SCNView;
var location = sender.LocationInView(areaPinched);
var hitTestResults = areaPinched.HitTest(
location, new SCNHitTestOptions());
var hitTest = hitTestResults.FirstOrDefault();
if (hitTest == null)
return;
var node = hitTest.Node;
var scaleX = (float)sender.Scale * node.Scale.X;
var scaleY = (float)sender.Scale * node.Scale.Y;
var scaleZ = (float)sender.Scale * node.Scale.Z;
node.Scale = new SCNVector3(scaleX, scaleY, scaleZ);
sender.Scale = 1;
}
Listing 12-3Pinch UIGestureRecognizer
收缩是缩放场景中项目的一种很好的方式。它通常用于在其他流行的应用中缩放项目或放大/缩小,因此用户以这种方式使用挤压会感觉很自然。
轮流
通过将两个手指放在屏幕上的虚拟物体上,顺时针或逆时针旋转它们的位置,我们可以在给定的轴上旋转虚拟物体。
在清单 12-4 中,当我们检测到这个手势时,我们在 Z 轴上旋转被触摸对象的方向。
private void HandleRotateGesture(UIRotationGestureRecognizer sender)
{
var areaTouched = sender.View as SCNView;
var location = sender.LocationInView(areaTouched);
var hitTestResults = areaTouched.HitTest(
location, new SCNHitTestOptions());
var hitTest = hitTestResults.FirstOrDefault();
if (hitTest == null)
return;
var node = hitTest.Node;
newAngleZ = (float)(-sender.Rotation);
newAngleZ += currentAngleZ;
node.EulerAngles = new SCNVector3(node.EulerAngles.X,
node.EulerAngles.Y, newAngleZ);
}
Listing 12-4Rotate UIGestureRecognizer
您可能需要尝试平移旋转手势来更改对象在不同轴上的方向,以获得正确的结果。
装鱼箱
通过将手指放在屏幕上的虚拟对象上,并在屏幕上向任意方向拖动它,然后释放,我们可以将一个项目从其原始位置沿给定的轴移动到新的位置。清单 12-5 展示了如何响应平移手势。
private void HandlePanGesture(UIPanGestureRecognizer sender)
{
var areaPanned = sender.View as SCNView;
var location = sender.LocationInView(areaPanned);
var hitTestResults = areaPanned.HitTest(location,
new SCNHitTestOptions());
var hitTest = hitTestResults.FirstOrDefault();
if (hitTest == null)
return;
var node = hitTest.Node;
if (sender.State == UIGestureRecognizerState.Changed)
{
var translate = sender.TranslationInView(areaPanned);
// Only allow movement vertically or horizontally
// High values are used so that the movement is smooth
node.LocalTranslate(
new SCNVector3((float)translate.X / 10000f,
(float)-translate.Y / 10000, 0.0f));
}
}
Listing 12-5Pan UIGestureRecognizer
正如简介中提到的,我们只能使用触摸手势在二维空间(垂直和水平)与设备屏幕进行交互,因此当识别出平移手势时,我们需要选择要将对象移动到两个轴中的哪一个。
无论你是从侧面还是从上面看一个物体,都可以决定你想沿着哪个轴移动它们。
偷窃
通过将你的手指放在屏幕上的虚拟物体上,并在屏幕上垂直或水平滑动,我们可以让我们的虚拟物体对滑动做出反应。在清单 12-6 中,当在一个节点上检测到滑动手势时,它会将它变成粉红色。
private void HandleSwipeGesture(UISwipeGestureRecognizer sender)
{
var areaSwiped = sender.View as SCNView;
var location = sender.LocationInView(areaSwiped);
var hitTestResults = areaSwiped.HitTest(
location, new SCNHitTestOptions());
var hitTest = hitTestResults.FirstOrDefault();
if (hitTest == null)
return;
var node = hitTest.Node;
var material = new SCNMaterial();
material.Diffuse.Contents = UIColor.SystemPinkColor;
node.Geometry.FirstMaterial = material;
}
Listing 12-6Swipe UIGestureRecognizer
滑动手势类似于快速平移手势,通常用于移除或消除其他应用中的内容,所以如果你愿意,你可以对你的应用执行相同的操作。
商标出版社
通过将你的手指放在屏幕上的一个虚拟物体上并保持在那里,我们可以让我们的虚拟物体对长按手势做出响应。在清单 12-7 中,当在一个节点上检测到长按手势时,它会把它变成橙色。
private void HandleLongPressGesture(UILongPressGestureRecognizer sender)
{
var areaPressed = sender.View as SCNView;
var location = sender.LocationInView(areaPressed);
var hitTestResults = areaPressed.HitTest(
location, new SCNHitTestOptions());
var hitTest = hitTestResults.FirstOrDefault();
if (hitTest == null)
return;
var node = hitTest.Node;
var material = new SCNMaterial();
material.Diffuse.Contents = UIColor.Orange;
node.Geometry.FirstMaterial = material;
}
Listing 12-7Long Press UIGestureRecognizer
你可以使用长按作为一种“特殊选择”的方式,以区别于简单的“点击”手势。
可以更改MinimumPressDuration,这是被认为是长时间按下并发射所需要的秒数,默认为 0.5。
要尝试的事情
触摸互动是我们在增强现实体验中更多的触觉互动。你可以根据自己的需要对它们进行微调。
将触摸手势识别器添加到您的应用中。
尝试将点击、旋转、平移、滑动和长按触摸手势识别器添加到您的应用中,并让它们以不同的方式操纵场景中的对象。
更改长按手势的 MinimumPressDuration。
尝试将触发长按手势所需的MinimumPressDuration从默认的 0.5 秒更改为 2 秒。
更改手势所需的最少手指数。
尝试使用NumberOfTouchesRequired属性强制两个或更多手指参与到手势中。默认情况下,它是 1。
需要双击条件才能激活点击手势。
您可以将点击手势识别器上的NumberOfTapsRequired属性更改为 2,以将点击手势识别器更改为双击识别器。你不需要太多的想象力就能知道如何实现三击手势识别器。
摘要
您现在应该知道如何以几种不同的方式与您放入 AR 体验中的任何项目进行交互,包括在 3D 空间中移动它们。诀窍是让你的交互变得直观,并以用户期望的方式表现。
到目前为止,我们一直在场景中放置简单的形状和图像;在下一章,我们将看看如何在我们的场景中放置更多有趣的物体,3D 模型。
十三、三维模型
在这一章中,我们将看看如何在你的增强现实场景中使用现有的 3D 模型,并讨论流行的免费 3D 工具“Blender”以及如何使用它来创建你自己的 3D 模型。
我们已经看到,SceneKit 允许我们使用七八种不同的原始 3D 模型,如盒子、球体、圆柱体、平面等,但它们相当有限,也不令人兴奋。通过使用现有的 3D 模型,甚至创建我们自己的模型,我们可以使我们的增强现实体验更加令人印象深刻和迷人。
导入 3D 模型
幸运的是,很容易将现有的 3D 模型导入到场景中,并且 SceneKit/ARKit 支持几种 3D 文件格式。
我们的场景中可以使用以下 3D 模型格式:
-
。航空学博士(doctorofaeronautics 的缩写)
-
. usz
-
。美国农业部
-
。美元和。美国显示器联盟
-
。rcproject 和。现实
-
。obj 和。合并晶体管逻辑
-
。字母表
-
。使用
-
。标准模板库(Standard Template Library 的缩写)
-
。视交叉上核(Suprachiasmatic Nucleus 的缩写)
越来越多的网站和创作者专注于预制 3D 模型。我发现 free3d.com 是一个寻找免费和便宜的预制 3D 模型的好地方。
在清单 13-1 中,我们可以看到将 3D 模型导入场景是多么简单。
值得注意的是,一旦 3D 模型作为SCNNode添加到场景中,它就像任何其他的SCNNode一样,因此我们可以更改它的位置、比例、方向、材质等等。事实上,有时导入的 3D 模型对于我们的场景来说太大了,所以我们需要在它们适合我们的场景之前改变节点的比例。
当然,你可以将我们在前面章节中提到的其他效果与你的 3D 模型结合起来。例如,您可以使用动画让 3D 模型缓慢旋转,或设置其不透明度,使其略微透明或淡入场景。
从文件中检索 3D 模型时,您必须记住的一件事是,您需要通过名称从文件中检索您希望检索的特定节点,如清单 13-1 所示。幸运的是,Xcode 在确定你想要添加的节点名称时非常有用,如图 13-1 所示。
图 13-1
如果您不知道根节点的名称,Xcode 对于查找它非常有用
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
this.sceneView.Session.Run(
new ARWorldTrackingConfiguration());
SCNScene sceneFromFile = SCNScene.FromFile(
"art.scnassets/tree.dae");
SCNNode model = sceneFromFile.RootNode.FindChildNode(
childName:"SomeChildName", recursively: true);
// How to scale or position the node model if needed
model.Scale = new SCNVector3(0.2f, 0.2f, 0.2f);
model.Position = new SCNVector3(0, -0.2f, 0);
this.sceneView.Scene.RootNode.AddChildNode(model);
}
Listing 13-1Adding a 3D model to a scene
再次重申,如果您拥有 3D 模型文件,但不知道根节点的名称,那么如果您在 Xcode 中打开该文件,您应该能够在模型的各个部分周围单击,并导航场景图形以找到根节点名称。
在 Blender 中创建您自己的 3D 模型
如果你想在增强现实体验中创建自己的 3D 模型,我强烈建议你考虑学习如何使用一种叫做 Blender 的 3D 建模工具。这是我自己也在慢慢学习的东西。
首先,它是一个免费的工具,不仅功能强大,同时对于愿意花时间学习它的初学者来说也很容易使用,并且越来越受欢迎。事实上,许多电影工作室已经开始使用 Blender 来创建 3D 模型和效果,而不是使用昂贵的行业标准替代品。网上也有很多关于如何创建各种 3D 模型的教程,从甜甜圈到家具到城堡和汽车。
例如,如图 13-2 所示,使用 Blender 和一个名为“BlenderGIS”的插件,我们可以生成从谷歌地图返回的任何地形的 3D 模型,然后将其导出并在我们的 AR 体验中使用。
图 13-2
在我们的 AR 体验中使用 Blender 的 3D 模型会给人留下非常深刻的印象
你可以在图 13-2 中看到,这个例子也使用了阴影(来自第七章“照明”)来帮助用户理解它离地面有多高,让它看起来更真实。
Note
无论您是创建、导出和导入自己的 3D 模型,还是获取和使用预先制作的 3D 模型,如果该模型带有纹理(通常是一个或多个图像文件),您都需要确保将其与 3D 模型一起打包。3D 模型文件通常会相对引用图像纹理文件的位置,因此它们通常需要存储在同一文件夹中,或者至少相对于 3D 模型文件所在的位置。
添加阴影,动画,并使互动
至此,我们已经介绍了一些可以与 3D 模型结合使用的其他概念。我们可以添加照明和阴影,使三维模型看起来更真实。我们可以使用动画来使 3D 模型更加动态。
要尝试的事情
你可以整天在你的增强现实场景中摆弄 3D 模型;然而,这里有一些你可以尝试的方法。
将预先制作的 3D 模型添加到您的应用中。
获取受支持的 3D 模型文件,将其添加到项目中,并将其放置在场景中。
在 Blender 中创建一个简单的模型,并在你的应用中使用它。
在 Blender 中创建一个基本模型;不一定要复杂。然后将其导出为支持的文件类型,添加到项目中,然后在场景中使用。
使用触摸手势与场景中的 3D 模型互动。
让您的 3D 模型响应第十二章“触摸手势和交互”中讨论的触摸交互
向您的 3D 模型添加动画。
使用第五章“动画”中讨论的一些动画(动作)来制作场景中 3D 模型的比例、位置或不透明度的动画。
使用带有图像检测的 3D 模型。
尝试将 3D 模型添加到在场景中检测到的图像。您会注意到,如果您旋转检测到的图像的方向,3D 模型的方向也会发生类似的变化。
摘要
除了我们之前了解的基本 3D 形状,您现在应该知道如何在 AR 体验中添加和使用更复杂的 3D 模型。你可以获得预制的,甚至可以使用像 Blender 这样的 3D 建模工具来构建和使用你自己的。
现在我们的场景中有了各种各样的模型和形状,我们应该想办法让它们通过使用模拟物理来模拟彼此之间的交互以及它们的物理环境。如果你认为这听起来很复杂,不要担心。ARKit 有一些内置的物理能力,所以我们不必担心数学和复杂性,我们将在下一章“物理”中看到
十四、物理学
SceneKit 为我们提供的另一个惊人的东西是物理引擎,我们可以在增强现实体验中使用它。这意味着我们可以给场景中放置的物品赋予相互交互的能力,就像它们是真实物体时你所期望的那样。
我们可以通过在 SCNNodes 上设置SCNNode.PhysicsBody属性来做到这一点。
给物品一个坚硬的结构
我们可以给我们的节点一些视觉外观之外的虚拟物质,这样东西就可以和它碰撞,就像它是固体一样,如清单 14-1 所示。
我们通过调用PhysicsBody = SCNPhysicsBody.CreateKinematicBody()将节点的物理实体设置为实体。
var material = new SCNMaterial();
material.Diffuse.Contents = UIColor.DarkGray;
var geometry = SCNPlane.Create(width, length);
geometry.Materials = new[] { material };
var planeNode = new SCNNode
{
Geometry = geometry,
PhysicsBody = SCNPhysicsBody.CreateKinematicBody(),
EulerAngles = new SCNVector3((float)(-Math.PI / 2), 0, 0)
};
Listing 14-1Making a 2D plane rigid
一旦我们做到了这一点,如果其他具有实体的节点试图占据相同的空间,就会与它发生冲突,更重要的是,我们可以在这个固体平面的顶部放置物品。
对物体施加重力
我们可以让物品模拟受重力影响,也就是说,被直接拉下来,直到它们碰到另一个虚拟物品而停止,这个虚拟物品已经被赋予了一个刚体,就像我们之前在图 14-1 中所做的那样。
为了让一个节点像重力一样被拉下来,我们可以将它的PhysicsBody设置为一个动态体PhysicsBody = SCNPhysicsBody.CreateDynamicBody(),如清单 14-2 所示。
如果你在你的场景和节点中使用重力,我建议你也使用一个运动体作为一种物理表面或地板;否则,你会发现你的节点会从屏幕上掉到地球的中心!事实上,当它们远离视野时,它们会继续下降(但仍会使用应用内存!).
通过在下面放置一个动力学平面,我们可以阻止这种不寻常的和不希望的行为,并模仿一些更像真实世界的体验。
var material = new SCNMaterial();
material.Diffuse.Contents = UIColor.Green;
var size = 0.05f;
var geometry = SCNBox.Create(size, size, size, 0);
geometry.Materials = new[] { material };
var cubeNode = new SCNNode
{
Geometry = geometry,
PhysicsBody = SCNPhysicsBody.CreateDynamicBody(),
};
Listing 14-2Making a cube effected by gravity
结合重力和固体物体
在下面的例子中(列表 14-3 ),我们在场景中放置一个 2D 平面,并赋予它一个坚固的物理属性。然后,我们在 2D 平面上方产卵,它们受到重力的影响,被拉向下方,直到它们撞击并停在坚固的 2D 平面上。
public partial class ViewController : UIViewController
{
private readonly ARSCNView sceneView;
public ViewController(IntPtr handle) : base(handle)
{
this.sceneView = new ARSCNView();
this.View.AddSubview(this.sceneView);
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
this.sceneView.Frame = this.View.Frame;
}
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
this.sceneView.Session.Run(new ARWorldTrackingConfiguration
{
LightEstimationEnabled = true,
WorldAlignment = ARWorldAlignment.Gravity
});
var planeNode = new PlaneNode(width:0.5f, length:0.5f, UIColor.DarkGray);
this.sceneView.Scene.RootNode.AddChildNode(planeNode);
}
public override void TouchesEnded(NSSet touches, UIEvent evt)
{
base.TouchesEnded(touches, evt);
if (!(touches.AnyObject is UITouch touch))
return;
var point = touch.LocationInView(this.sceneView);
var hits = this.sceneView.HitTest(point, new SCNHitTestOptions());
var hit = hits.FirstOrDefault();
if (hit == null)
return;
var node = hit.Node;
if (node == null)
return;
var cubeNode = new CubeNode(0.05f, UIColor.Green)
{
Position = new SCNVector3(
hit.WorldCoordinates.X,
hit.WorldCoordinates.Y + 0.1f,
hit.WorldCoordinates.Z
)
};
this.sceneView.Scene.RootNode.AddChildNode(cubeNode);
}
public override void ViewDidDisappear(bool animated)
{
base.ViewDidDisappear(animated);
this.sceneView.Session.Pause();
}
public override void DidReceiveMemoryWarning()
{
base.DidReceiveMemoryWarning();
}
}
public class PlaneNode : SCNNode
{
public PlaneNode(float width, float length, UIColor color)
{
Geometry = CreateGeometry(width, length, color);
PhysicsBody = SCNPhysicsBody.CreateKinematicBody();
EulerAngles = new SCNVector3((float)(-Math.PI / 2), 0, 0);
}
private static SCNGeometry CreateGeometry(float width, float length, UIColor color)
{
var material = new SCNMaterial();
material.Diffuse.Contents = color;
material.DoubleSided = true;
var geometry = SCNPlane.Create(width, length);
geometry.Materials = new[] { material };
return geometry;
}
}
public class CubeNode : SCNNode
{
public CubeNode(float size, UIColor color)
{
Geometry = CreateGeometry(size, color);
Position = new SCNVector3(0, size / 2, 0);
PhysicsBody = SCNPhysicsBody.CreateDynamicBody();
}
private static SCNGeometry CreateGeometry(float size, UIColor color)
{
var material = new SCNMaterial();
material.Diffuse.Contents = color;
var geometry = SCNBox.Create(size, size, size, 0);
geometry.Materials = new[] { material };
return geometry;
}
}
Listing 14-3Using gravity to drop solid cubes onto a solid 2D plane
图 14-1
将固体立方体放到固体平面上
施加力
除了将基础物理应用于我们的节点,如重力,给它们一个坚固的结构,并允许它们相互接触,我们还可以对它们施加一个力。
在清单 14-4 中,我们将一个单独的盒子放在一个平面上,当接触盒子节点时,对它施加一个大的力,推动它向前离开平面。您可以尝试应用力的大小,看看节点在被触摸时会受到怎样的影响。
public partial class ViewController : UIViewController
{
private readonly ARSCNView sceneView;
public ViewController(IntPtr handle) : base(handle)
{
this.sceneView = new ARSCNView();
this.View.AddSubview(this.sceneView);
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
this.sceneView.Frame = this.View.Frame;
}
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
this.sceneView.Session.Run(new ARWorldTrackingConfiguration
{
LightEstimationEnabled = true,
WorldAlignment = ARWorldAlignment.Gravity,
});
var planeNode = new PlaneNode(width: 0.3f, length: 0.3f, UIColor.LightGray);
this.sceneView.Scene.RootNode.AddChildNode(planeNode);
SCNNode boxNode = new SCNNode();
var boxMaterial = new SCNMaterial();
boxMaterial.Diffuse.Contents = UIColor.Blue;
var boxGeometry = SCNBox.Create(0.04f, 0.06f, 0.04f, 0f);
boxNode.Geometry = boxGeometry;
boxNode.Geometry.FirstMaterial = boxMaterial;
boxNode.PhysicsBody = SCNPhysicsBody.CreateDynamicBody();
boxNode.Position = new SCNVector3(0.0f, 0.05f, 0.0f);
this.sceneView.Scene.RootNode.AddChildNode(boxNode);
}
public override void TouchesEnded(NSSet touches, UIEvent evt)
{
base.TouchesEnded(touches, evt);
if (!(touches.AnyObject is UITouch touch))
return;
var point = touch.LocationInView(this.sceneView);
var hits = this.sceneView.HitTest(point, new SCNHitTestOptions());
var hit = hits.FirstOrDefault();
if (hit == null)
return;
var node = hit.Node;
if (node == null)
return;
var forcePower = 10;
var pointOfView = this.sceneView.PointOfView;
var transform = pointOfView.Transform;
var orientation = new SCNVector3(-transform.M31, -transform.M32, -transform.M33);
node.PhysicsBody.ApplyForce(
new SCNVector3(
orientation.X * forcePower,
orientation.Y * forcePower,
orientation.Z * forcePower), true);
}
public override void ViewDidDisappear(bool animated)
{
base.ViewDidDisappear(animated);
this.sceneView.Session.Pause();
}
public override void DidReceiveMemoryWarning()
{
base.DidReceiveMemoryWarning();
}
}
public class PlaneNode : SCNNode
{
public PlaneNode(float width, float length, UIColor color)
{
Geometry = CreateGeometry(width, length, color);
PhysicsBody = SCNPhysicsBody.CreateKinematicBody();
EulerAngles = new SCNVector3((float)(-Math.PI / 2), 0, 0);
}
private static SCNGeometry CreateGeometry(float width, float length, UIColor color)
{
var material = new SCNMaterial();
material.Diffuse.Contents = color;
material.DoubleSided = true;
var geometry = SCNPlane.Create(width, length);
geometry.Materials = new[] { material };
return geometry;
}
}
public class CubeNode : SCNNode
{
public CubeNode(float size, UIColor color)
{
Geometry = CreateGeometry(size, color);
Position = new SCNVector3(0, size / 2, 0);
PhysicsBody = SCNPhysicsBody.CreateDynamicBody();
}
private static SCNGeometry CreateGeometry(float size, UIColor color)
{
var material = new SCNMaterial();
material.Diffuse.Contents = color;
var geometry = SCNBox.Create(size, size, size, 0);
geometry.Materials = new[] { material };
return geometry;
}
}
Listing 14-4Apply force to an object in Augmented Reality
在 SceneKit 中有很多与物理相关的变量可以改变,包括质量和摩擦力。通过改变这些值,您将改变场景中的项目受物理影响的方式。
Note
同样,我们可以对一个物体施加力,我们也可以对一个物体施加力矩,也就是说,使一个物体绕轴旋转。你可以通过调用SCNPhysicsBody.ApplyTorque()来实现。
要尝试的事情
这里有一些不同的东西,你可以在 ARKit 中尝试和学习物理。
尝试改变摩擦力、质量和其他物理属性 .
尝试改变场景中对象的一些属性,包括它们的摩擦力和质量,看看这会如何影响它们在场景中的行为。
使用 ApplyForce()来发射物体。
在不同的方向和其他物体上玩射击游戏。看看能不能打翻其他物体。
使用不同形状的物体。
不要只使用立方体,例如,看看球体如何从斜面滚下。
使用 ApplyTorque()对物体施加扭矩。
查看对具有不同物理属性的不同形状的物体施加扭矩时它们的行为。
摘要
SceneKit 为我们提供了一个非常复杂的物理引擎。让场景中的项目像真实物体一样响应交互,可以为您的 AR 体验增加另一个层次的真实感。可以看到一些游戏是如何很好的利用 ARKit 内置的物理引擎的。
在前面的章节中,我们已经看到了如何使用 ARKit 进行图像检测、人脸检测和平面检测。在下一章,我们将看看如何识别场景中的 3D 物体。听起来不可能?好吧,让我们用物体检测来找出答案。
十五、目标检测
之前,在第十章“图像检测”中,我们讨论了如何让我们的 AR 移动应用在我们的场景中检测到预定义的 2D 图像时识别并做出响应。同样,我们可以让我们的应用响应预定义的 3D 对象。这是一个比 2D 图像识别更复杂的过程;然而,ARKit 使这成为可能。我们需要做的就是将功能整合在一起。
这个过程需要两个部分,第一部分是让用户能够使用应用扫描 3D 对象并存储其一些“空间数据”,第二部分是再次使用空间数据来检测场景中的对象。
虽然本章给出了这个概念的概述,但是演示这个概念所需的代码太长了,无法完整地包括进来。幸运的是,微软已经创建了一个开源的 Xamarin 身体检测样本应用,我们可以下载并试用。
本章中讨论和显示的示例应用和屏幕截图来自以下 Microsoft Xamarin.iOS 扫描应用示例:
https://docs.microsoft.com/en-us/samples/xamarin/ios-samples/ios12-scanninganddetecting3dobjects/
扫描和保存对象空间数据
在扫描期间,会话使用了一个ARObjectScanningConfiguration配置实例,如清单 15-1 所示。
var configuration = new ARObjectScanningConfiguration();
sceneView.Session.Run(configuration);
Listing 15-1Using ARObjectScanningConfiguration
运行示例应用时,您会看到在扫描阶段,一个边界框被用来表示我们希望扫描的 3D 对象应位于的区域,如图 15-1 所示。默认情况下,它会检测一个水平面,并将边界框的底部放在它的顶部。使用捏合和平移触摸手势可以增加边界框的大小和位置。
图 15-1
将边界框放置在要扫描的对象周围
如果您对 3D 对象位于边界框内感到满意,请按“扫描”按钮存储空间数据以备后用。在扫描过程中,该应用要求你在物体周围移动,以便从不同角度进行扫描和后续识别。这个从不同角度扫描的过程使得包围盒的壁变得坚固,如图 15-2 所示。当您对从足够多的不同角度扫描了对象感到满意时,请按“完成”。
图 15-2
从多个方向扫描物体
扫描完成后,扫描的对象将作为一个ARReferenceObject保存在应用中,供以后参考。
识别扫描的对象
为了识别场景中的 3D 对象,我们需要检索(或至少引用)之前扫描和保存的 3D 对象的空间数据,并使用它来允许应用检测任何与之匹配的对象。
当你准备好了,按下应用中的“测试”按钮,这将开始检测你在场景中扫描的 3D 对象。
如果在场景中检测到物体(使用清单 15-2 中的代码),应用会通知你并告诉你检测它花了多长时间(在我看来相当快),如图 15-3 所示。
public override void DidAddNode(ISCNSceneRenderer renderer, SCNNode node, ARAnchor anchor)
{
if (anchor != null && anchor is ARObjectAnchor)
{
var objectAnchor = anchor as ARObjectAnchor;
if (objectAnchor.ReferenceObject == referenceObject)
{
// Successful detection, do something
}
}
}
Listing 15-2The code that fires when the object is detected
一旦成功检测到对象,我们可以做任何事情,我们可以显示如图 15-3 所示的消息,或者我们可以在检测到的对象上或旁边添加额外的节点。
图 15-3
成功检测到对象
要尝试的事情
这里有一些使用对象检测的想法。
扫描并存储多个对象。
看看你能否扫描和存储多个不同的对象。
扫描产品,并在成功检测后检索/显示产品信息。
扫描并保存产品(如毛绒玩具)的 3D 特征;然后,当检测到它时,在它旁边显示附加信息,如产品详细信息、描述、价格等。
扫描某人的头部,看看识别的准确度如何。
尝试扫描某人的头部,看看物体检测是否可以识别它。
看看你能扫描和探测多大/多小的物体。
尝试扫描非常小或非常大的对象,看看对象检测在处理非常小或非常大的对象时是否有约束。
改变边框的颜色。
尝试更改用于扫描和检测的边界框的颜色或其他方面。
摘要
ARKit 中内置的对象检测功能继续显示 ARKit 是多么多样和强大,并为我们之前看到的 2D 图像检测增加了另一个维度,打开了一系列有趣的用例。
继续在我们的场景中检测有趣的主题,在下一章,我们将看看身体检测,我们将看到 ARKit 如何确定一个人在场景中的位置和方向。
十六、身体跟踪
谈到人,以及我们在第十一章“面部跟踪和表情检测”中看到的检测和跟踪面部,我们还可以使用 ARKit 实时检测场景中的身体,包括身体不同部位的方向。这被称为身体跟踪,它使我们能够不跟踪主要身体关节的位置,达到很高的精确度。
检测场景中的身体
我们将看看 ARKit 如何能够检测场景中人体及其各种关节的存在,然后将它们覆盖在 3D 空间中检测到的身体上。但是你问我们到底在追踪什么?
跟踪以下关节的位置:
-
根部(臀部中心)
-
头
-
左手
-
右手
-
勒弗福德
-
右脚
-
左肩膀
-
右肩
这些值来自枚举ARSkeletonJointName。
在被检测的ARSkeleton3D对象中可以引用许多其他关节(图 16-1 共显示 92 个)。但是,只跟踪前面的关节,因此其他关节是根据这些跟踪关节的位置推断出来的。
事实上,为了获得我们将迭代的所有 92 个联合名称的完整列表,我们将使用从调用ARSkeletonDefinition.DefaultBody3DSkeletonDefinition.JointNames返回的string[]。
图 16-1
组成一个骨骼的所有 92 个关节的名称
为了在我们的场景中启用身体跟踪,我们在运行 ARSession 时使用了一个ARBodyTrackingConfiguration,如清单 16-1 所示。
Note
除了这些关节,如果我们愿意,我们还可以推断这些关节之间的路径,并绘制直线,从而创建一个骨骼的可视化。
public BodyDetectionViewController()
{
this.sceneView = new ARSCNView
{
AutoenablesDefaultLighting = true,
Delegate = new SceneViewDelegate()
};
this.View.AddSubview(this.sceneView);
}
...
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
var bodyTrackingConfiguration
= new ARBodyTrackingConfiguration()
{
WorldAlignment = ARWorldAlignment.Gravity
};
this.sceneView.Session.Run(bodyTrackingConfiguration);
}
Listing 16-1Using ARBodyTrackingConfiguration and declaring the SceneViewDelegate
当在场景中检测到尸体时,会在相关位置放置一个ARBodyAnchor。我们可以将我们的自定义代码添加到ARSCNViewDelegate上的DidAddNode和DidUpdateNode方法中,如清单 16-2 所示。
正如你在清单 16-2 中看到的,我们已经声明了一个从SCNNode继承而来的JointNode类来表示我们想要放置在场景中的关节节点。当我们在DidAddNode中检测到这些关节节点时,我们使用关节名称作为关键字将它们存储在字典中。当DidUpdateNode被解雇时,如果我们检测到他们的位置已经改变,我们就通过调用.Update(SCNVector3 position)来更新他们的位置。
我们有一个创建球体来表示关节的方法,叫做MakeJoint(string jointName),这个方法非常简单,类似于我们之前看到的创建基本颜色形状的例子。
更复杂的方法GetJointPosition(ARBodyAnchor bodyAnchor, string jointName)是获取检测到的ARBodyAnchor并计算,然后返回 jointName 引用的关节的位置。它通过确定从身体锚点的根位置(始终是臀部的中心)偏移的请求关节来实现这一点。我们还使用了一个扩展方法,将一个NMatrix4转换成一个SCNMatrix4。
最终结果显示场景中有 92 个球体排列在与检测到的身体相同的方向上。这些球体的方向和位置随着被跟踪物体的方向和位置的实时变化而变化。
public class SceneViewDelegate : ARSCNViewDelegate
{
Dictionary<string, JointNode> joints
= new Dictionary<string, JointNode>();
public override void DidAddNode(
ISCNSceneRenderer renderer, SCNNode node,
ARAnchor anchor)
{
if (!(anchor is ARBodyAnchor bodyAnchor))
return;
foreach (var jointName in ARSkeletonDefinition.DefaultBody3DSkeletonDefinition.JointNames)
{
JointNode jointNode = MakeJoint(jointName);
var jointPosition = GetJointPosition(bodyAnchor, jointName);
jointNode.Position = jointPosition;
if (!joints.ContainsKey(jointName))
{
node.AddChildNode(jointNode);
joints.Add(jointName, jointNode);
}
}
}
public override void DidUpdateNode(
ISCNSceneRenderer renderer, SCNNode node,
ARAnchor anchor)
{
if (!(anchor is ARBodyAnchor bodyAnchor))
return;
foreach (var jointName in ARSkeletonDefinition.DefaultBody3DSkeletonDefinition.JointNames)
{
var jointPosition = GetJointPosition(bodyAnchor, jointName);
if (joints.ContainsKey(jointName))
{
joints[jointName].Update(jointPosition);
}
}
}
private SCNVector3 GetJointPosition(
ARBodyAnchor bodyAnchor, string jointName)
{
NMatrix4 jointTransform = bodyAnchor.Skeleton.GetModelTransform((NSString)jointName);
return new SCNVector3(jointTransform.Column3);
}
private JointNode MakeJoint(string jointName)
{
var jointNode = new JointNode();
var material = new SCNMaterial();
material.Diffuse.Contents =
GetJointColour(jointName);
var jointGeometry =
SCNSphere.Create(GetJointRadius(jointName));
jointGeometry.FirstMaterial = material;
jointNode.Geometry = jointGeometry;
return jointNode;
}
private UIColor GetJointColour(string jointName)
{
switch (jointName)
{
case "root":
case "left_foot_joint":
case "right_foot_joint":
case "left_leg_joint":
case "right_leg_joint":
case "left_hand_joint":
case "right_hand_joint":
case "left_arm_joint":
case "right_arm_joint":
case "left_forearm_joint":
case "right_forearm_joint":
case "head_joint":
return UIColor.Green;
}
return UIColor.White;
}
private float GetJointRadius(string jointName)
{
switch (jointName)
{
case "root":
case "left_foot_joint":
case "right_foot_joint":
case "left_leg_joint":
case "right_leg_joint":
case "left_hand_joint":
case "right_hand_joint":
case "left_arm_joint":
case "right_arm_joint":
case "left_forearm_joint":
case "right_forearm_joint":
case "head_joint":
return 0.04f;
}
if (jointName.Contains("hand"))
return 0.01f;
return 0.02f;
}
}
public class JointNode : SCNNode
{
public void Update(SCNVector3 position)
{
this.Position = position;
}
}
}
Listing 16-2Detecting and updating body joint positions
结果如图 16-2 所示。被跟踪身体的主要关节被跟踪并显示为绿色球体,其他推断的次要关节显示为白色节点。
像往常一样,ARKit 在现实世界中跟踪事物的准确性取决于充足的照明。为了让 ARKit 有最好的机会跟踪场景中的尸体,请确保环境光线充足。
图 16-2
使用节点显示被跟踪实体的方向
捕捉身体动作
身体跟踪的一个用途是转换被跟踪身体的检测到的运动和位置,并在人形 3D 模型(称为装备)上模拟它们,以便如果您移动手臂,3D 模型的手臂也以相同的方式移动。这需要创建一个具有各种活动关节的 3D 模型,并将其导入到应用中,这超出了本书的范围,但可以在图 16-3 中看到。
要了解有关使用身体跟踪的模型装配的更多信息,请参见 Apple 的文档( https://developer.apple.com/documentation/arkit/rigging_a_model_for_motion_capture )。
图 16-3
身体跟踪示例的索具
潜在应用
因为我们可以检测主要关节的位置以及它们彼此之间的相对位置,所以我们可以推断场景中身体各个部分的角度。我见过这种技术用于自动检测用户坐在办公桌前时是否无精打采,以帮助防止脊椎受到不必要的压力,并帮助避免背痛。
能够检测重复的身体运动使身体检测成为跟踪俯卧撑和深蹲等运动的一种很好的方式。
要尝试的事情
这里有一些你可以在实施身体追踪时自己尝试的事情。
改变代表关节的节点的颜色、大小和不透明度。
尝试用不同的方式表示关节节点。
添加触摸手势,帮助识别按压时的关节。
使用你的触摸手势知识,这样当你触摸一个节点时,它会在屏幕上显示它的名称。
尝试装配一个 3D 模型来复制你的动作。
查找如何使用适当的 3D 骨架模型,并将其装配到被跟踪的身体上,使其模拟场景中身体的运动。
向被跟踪的主体添加额外的节点。
使用现成的几何形状、图像或 3D 模型的组合来将附加节点添加到被跟踪的身体。例如,你可以在头部节点的位置放置一个球形的表情头像。
在关节间添加直线,打造骨架效果。
当您知道主要关节和次要关节的位置和名称时,您可以尝试在它们之间创建线条(或细长的盒子/圆柱体)。
摘要
如果你已经做到了这一步,那么你现在可能已经知道如何利用 arKit 的大量增强现实功能,并能够做出一些相当出色的 AR 体验。
一旦你制作了你的杀手级 AR 应用,你可能希望通过应用商店与世界分享它,所以在下一章也是最后一章,我们将看看“发布到应用商店”
十七、发布到应用商店
正如一开始所承诺的,我们在本书中看到的一切都可以在没有苹果开发者帐户的情况下进行实验,并放入你的应用和部署到你的手机上。
也就是说,如果你创造的东西准备好与世界其他地方分享,你会想把它放在 App Store 里让其他人下载和安装。要做到这一点,你需要一个苹果开发者账户,你需要按照本章概述的步骤操作。
App Store 提交待办事项列表
在这一章中,我们将介绍将您的应用放入 App Store 的过程。该过程由多个阶段组成:
-
为应用设置图标。
-
设置启动屏幕(可选)。
-
设置应用 ID 和授权。
-
创建并安装 App Store 预置描述文件。
-
更新内部版本配置。
-
构建您的应用并提交给 Apple。
为应用设置图标
因为您的应用的图标将在各种不同的地方使用,所以您需要提供几种不同大小的图标。
您的图标将以不同尺寸出现在以下位置:
-
应用商店
-
通知
-
设置
-
聚光灯
要提供不同大小的图标,打开Assets.xcassets并为 IconImage 资源提供图像。见图 17-1 。
图 17-1
为 Assets.xcassets 文件夹中的 AppIcon 资源提供图像
设置启动屏幕图像
应用的启动屏幕是在启动应用后立即看到的屏幕,但在您最初看到应用的主页之前,默认情况下它是一个空白的白色屏幕。幸运的是,如果你选择这样做,改变是非常容易的。这是我推荐的,因为它相对简单,可以帮助用户体验你的应用。
如上所述,你可以选择覆盖应用的默认空白启动屏幕(LaunchScreen.storyboard)。一旦你打开LaunchScreen.storyboard,你就可以改变它的背景颜色,给它添加标签和图像,如图 17-2 所示。如果您选择更改默认设置,当您的应用启动时,更新后的启动屏幕将显示在您的主应用之前。
图 17-2
您可以自定义应用的启动屏幕
设置应用 ID 和授权
在进一步操作之前,您需要为您的应用创建一个应用 ID。你可以在苹果开发者门户网站 https://developer.apple.com 上这样做,为了能够做到这一点,你需要一个苹果开发者账户,在撰写本文时这个账户的价格是 79 英镑。
此外,如果您还没有 Apple ID,您需要先在 https://appleid.apple.com/account 创建一个。
当你登录你的苹果开发者账户时,你应该会看到如图 17-3 所示的页面。
图 17-3
您的 Apple 开发者帐户
好的,假设你现在已经有了你的苹果开发者账户并且已经登录,进入如图 17-4 所示的证书、id 和档案。
图 17-4
开发人员帐户的标识符部分
我们将以应用 ID 的形式为我们的应用创建一个新的标识符,因此单击标识符标题旁边的+按钮开始为我们的应用创建一个新的标识符。
如图 17-5 所示,从标识符列表中选择应用 id,然后按继续。
图 17-5
开始注册新的标识符
在下一个屏幕上,选择–的应用 ID,在我们的例子中是一个应用,因此从如图 17-6 所示的选项中选择应用,然后按继续。
图 17-6
选择我们使用应用 ID 的目的
在下一个屏幕中,提供描述和捆绑包 ID ,然后从列表中选择您的应用使用的任何设备功能,如图 17-7 所示,然后按继续。
图 17-7
提供您的应用 ID 信息
在下一个屏幕上,您有机会在注册前确认应用 ID 详情,如图 17-8 所示。准备就绪后,按继续,然后按注册。
图 17-8
注册前请确认您的应用 ID 详细信息
恭喜你!您已经创建了您的第一个应用!好吧,反正是 App ID。别担心。我们很快就会好好利用它。
创建并安装 App Store 预置描述文件
为了将您的应用发布到 App Store,您需要在电脑上创建、安装和使用适当的分发预置描述文件。这些预置描述文件包含用于签署您的应用的证书、应用 ID 及其安装位置的相关信息。
要为您的应用创建和安装预置描述文件,请再次前往 Apple Developer Portal 中的证书、id&描述文件部分。
这一次,转到配置文件部分。从这里,您将看到任何现有的开发或发布概要文件,并且可以创建新的。
在 Profiles 部分,点击 Profiles 标题旁边的+按钮,如图 17-9 所示。
图 17-9
开发和分布概况
然后在注册一个新的预置描述文件页面,在分发部分,选择如图 17-10 所示的 App Store,点击继续。
图 17-10
注册新的分发预置描述文件
在下一个屏幕上,从下拉列表中选择您的应用 ID,如图 17-11 所示,然后按继续。
图 17-11
选择预置描述文件适用的应用
如图 17-12 所示,从下一个屏幕中选择证书,然后按继续。
图 17-12
选择证书
如图 17-13 所示,在下一个屏幕上为配置文件提供一个名称,然后按生成。
图 17-13
提供预配概要文件的名称
最后,如图 17-14 所示,下载并双击您生成的预置描述文件,将其安装到您的电脑上。
图 17-14
下载并安装预置描述文件
唷,现在您已经成功地将一个分发预置描述文件安装到您的计算机上,该描述文件可用于将您的应用放入 App Store。
现在,让我们开始构建我们希望在下一部分中上传的应用版本。
更新内部版本发布配置
在我们构建提交到 App Store 的应用之前,我们还需要做一些事情,包括分配我们在上一节中创建的预置描述文件。
打开Info.plist文件并转到应用选项卡。它可能看起来有点像这样。如图 17-15 所示,确定手动提供作为签约方案。
图 17-15
确保签名使用手动设置
接下来,打开你的项目选项,进入构建 ➤ IOS 构建。在此页面上,将配置更改为发布并将平台更改为电话,并确保所有其他设置如下图 17-16 所示。
图 17-16
设置 iOS 构建设置
接下来进入 iOS 捆绑签名部分,如图 17-17 所示。
图 17-17
设置 iOS 捆绑包签名设置
-
将配置设置为发布,将平台设置为 iPhone 。
-
签约身份应该是分发(自动)。
-
配置文件应该是您在上一步中创建的文件。
注意您将只能在
Info.plist文件中看到捆绑包 ID 与应用捆绑包 ID 相匹配的预置描述文件。
您的项目现在应该可以构建和发布了。但首先,我们需要准备好应用商店方面的东西,以接收应用的上传。
在 App Store Connect 中设置应用
您必须先在 App Store Connect 中配置应用,然后才能将应用提交给 Apple 进行审查。App Store Connect 是一个在线门户,用于管理您在 App Store 中的 iOS 应用,可在 https://appstoreconnect.apple.com/ 找到。
我们需要在 App Store Connect 中做很多事情,包括
-
提供将出现在商店中的应用名称
-
选择捆绑 ID
-
提供描述、关键字、类别
-
提供截图
-
申报价格和供货情况
App Store Connect 的主屏幕如下图 17-18 所示。
图 17-18
应用商店连接
进入我的应用,点击应用标题旁边的蓝色圆圈+按钮创建一个新的应用,并提供你的应用的详细信息,如图 17-19 所示。
图 17-19
从应用部分创建新应用
一旦您在 App Store Connect 中创建了一个应用,您应该会看到如图 17-20 所示的屏幕,您可以在其中提供更多详细信息。
图 17-20
您未发布的应用草稿
在图 17-21 所示的定价和可用性部分,您可以设置您希望对您的应用收取多少费用。
图 17-21
提供定价信息
在一般信息部分,你应该提供你的应用的主要类别和次要类别以及副标题,以帮助人们搜索像你这样的应用,并给他们最好的机会偶然发现你的应用。参见图 17-22 。
您还需要为应用设置内容权限,确认您对应用中的任何内容拥有权限。
图 17-22
提供一般应用信息
但是不要按提交以供审查,因为您将需要创建和上传一个构建并将其与您的初始发布相关联。为此,我们需要回到 Visual Studio for Mac,我们将在下一节看到。
构建应用并提交给苹果
现在您已经在 App Store Connect 中设置了您的应用,您需要最终构建并提交您的应用。
在 Visual Studio for Mac 中选择发布版本配置,如图 17-23 所示。
图 17-23
发布生成配置的设置
然后从构建菜单中选择归档发布,如图 17-24 所示。这会将您的应用打包到一个归档文件中,以备上传。
图 17-24
存档您的应用以进行发布
一旦创建了档案,点击图 17-25 所示的签署并分发按钮。
图 17-25
归档创建后
在选择 iOS 分发渠道屏幕上,选择 App Store 并按下一步,如图 17-26 所示。
图 17-26
选择分销渠道
在下一个界面中,当选择目的地时,选择上传,然后下一步,如图 17-27 所示。
图 17-27
选择目的地
在如图 17-28 所示的下一个预置描述文件屏幕中,选择所需的预置描述文件(如果您有多个预置描述文件),然后按下一步。
图 17-28
选择相关的预配配置文件
在下一个屏幕上,您将被要求提供一些凭证以启用与 App Store Connect 的通信,如图 17-29 所示。
图 17-29
提供 App Store 的通信细节
现在你可能想知道这个应用特定的密码到底是什么。我当然有。
原来你必须在 https://appleid.apple.com 创建一个专用的 app 密码,如图 17-30 所示。
图 17-30
提供 App Store 的通信细节
生成特定于应用的密码后,输入您的 Apple ID 用户名和密码,然后按下一步。
之后,正如你可以从图 17-31 所示的下一个屏幕中猜到的,你终于准备好发布应用了。按下出版。
图 17-31
准备发布您的应用
点击发布后,系统会要求您选择保存 ipa 文件的位置,之后,您的应用将被上传到 App Store Connect,如果成功,系统会通知您发布成功,如图 17-32 所示。
图 17-32
成功将您的应用发布到应用商店
您会注意到您的应用的状态将变为“等待审查”你现在只需要等待苹果公司的审查团队对你的应用进行自动和手动检查。如果苹果有任何方式拒绝你的应用,如侵犯版权或不明确的许可请求,你的应用将被拒绝,你会得到反馈。如果发生这种情况,您将能够对您的应用进行相关更改,并重新提交以获得批准。
一旦苹果公司成功批准你的应用,它将很快出现在应用商店。
摘要
嗯,就是这样。现在,您已经拥有了所需的一切,不仅可以开发一些令人印象深刻且有用的增强现实体验,还可以与世界分享它们。接下来你选择做什么取决于你自己。
增强现实在未来几年将变得越来越受欢迎,arKit 允许我们利用开箱即用的丰富功能来提供令人惊叹的 AR 体验,这一点现在应该很明显。
你所能创造的经历只受到你想象力的约束。
祝你好运,玩得开心。