ARSCNView
- ARSCNView 类提供了创建增强现实体验的最简单方法,该体验将虚拟3D内容与现实世界的设备相机视图相结合。当我们运行 ARSCNView 提供的 ARSession 对象时:
- ARSCNView 会自动将设备摄像头的实时视频输入渲染为场景背景。
- ARSCNView 的属性 scene 的世界坐标系直接响应由会话配置建立的AR世界坐标系。
- ARSCNView 会自动移动它的 SceneKit 相机来匹配设备的实际移动。
- 因为 ARKit 会自动将 SceneKit 空间与现实世界匹配,所以放置虚拟对象的时候如果想调整它在真实世界的位置,那只需要适当地设置该对象(SCNNode对象)的 position 属性就可以了。
SCNNode
- SCNNode 是 scene 的结构元素,表示3D坐标空间中的位置和变换,可以附加几何,灯光,相机或其他可显示内容。
- SCNNode 只能用来确定 scene 的空间的坐标系和虚拟内容的逻辑结构,如果想要呈现2D或者3D的虚拟内容,还需要添加 SCNGeometry 对象。
- 我们可以用代码的方式创建 SCNNode 来进行建模,也可以从3D模型文件中加载一个 SCNNode,或者组合这两种方法。
ARFaceTrackingConfiguration
- 由于面部跟踪仅适用于具有有前置TrueDepth相机的iOS设备。所以在向用户提供需要面部跟踪的任何功能之前,需要先使用 ARFaceTrackingConfiguration 的 isSupported 属性来判断当前设备上是否可以使用面部跟踪。
if (!ARFaceTrackingConfiguration.isSupported) { return; }
- ARSession 使用 ARFaceTrackingConfiguration 来运行时,就会使用设备的前置摄像头来检测用户的面部,并将表示面部的 ARFaceAnchor 对象添加到锚点列表中。此外,当我们启用 lightEstimationEnabled 设置时,ARFaceTrackingConfiguration 会把检测到的面部作为光探针,来估算出当前环境光的照射方向和亮度信息。
ARFaceAnchor
ARFaceAnchor 包含有相关面部的位置和方位,还有面部拓扑结构以及描述面部表情特征的信息。
-
从父类 ARAnchor 继承的 transform 属性是一个4*4的矩阵,它在世界坐标中描述了脸部的当前位置、方位、比例。transform 矩阵会创建一个面部坐标系,用于定位相对于面部的其他元素。该坐标系是一个右手坐标系,正 X 方向指向面部自身的左边,正 Y 方向指向面部自身的上方,正 Z 方向从面部指向外面。面部坐标空间的单位为米,原点位于鼻子后方几厘米处,如下图所示。
ARFaceGeometry
ARFaceAnchor 的 geometry 属性封装了人脸具体的拓扑结构信息,包括顶点坐标缓存、纹理坐标缓存、以及三角形索引缓存。经过测试,人脸拓扑结构中包含1220个顶点坐标、1220个纹理坐标、2304个三角形。
当面部的形状或者表情改变时,ARSession 只提供在面部拓扑网格之间变化的顶点缓冲,表明顶点位置的变化,因为 ARKit 会根据用户面部的形状和表情来使面部拓扑的网格适应变化。
面部表情追踪
- blendShapes 属性提供了当前人脸面部表情的一个高阶模型,表示了一系列的面部特征相对于无表情时的偏移系数。它是一个 NSDictionary,其 key 有多种具体的面部表情参数可选,比如 ARBlendShapeLocationEyeBlinkLeft 代表左眼的眨眼系数,而 ARBlendShapeLocationJawOpen 表示下巴开口系数。每个key对应的value是一个取值范围为0.0 - 1.0的浮点数,0.0表示中立情况的取值(面无表情时),1.0表示最大程度(比如下巴开口到最大值)。ARKit里提供了52种非常具体的面部表情形变参数,我们可以自行选择采用较多的或者只是采用某几个参数来达成我们的目标,比如,用“张嘴”、“眨左眼”、“眨右眼”来驱动一个卡通人物。
创建项目
首先需要添加一个 ARSCNView,在这个项目里面 ARSCNView 是懒加载的,代码如下:
- (ARSCNView *)sceneView {
if (!_sceneView) {
_sceneView = [[ARSCNView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)];
_sceneView.delegate = self;
_sceneView.automaticallyUpdatesLighting = YES;
}
return _sceneView;
}
然后根据 SCNGeometry 对象来设置 ARSCNView 的 scene 的节点。这里有3种节点,分别是网格状的人脸、面具人脸、机器人头部。机器人头部节点使用的是创建好的3D模型来进行初始化,所以不用传入代码创建的 SCNGeometry 对象。此外,我们可以根据需要来设置 scene 的背景内容,默认是以现实世界为背景。
/** 虚拟面部节点的类型 */
typedef NS_ENUM(NSInteger, XHBVirtualFaceType) {
XHBVirtualFaceTypeMesh, /**< 面部拓扑的网格 */
XHBVirtualFaceTypeMask, /**< 面具 */
XHBVirtualFaceTypeRobot /**< 机器人 */
};
/** 设置面部节点 */
- (void)p_setupFaceNode {
// fillMesh 设置为NO,会空出眼睛和嘴巴的区域
ARSCNFaceGeometry *faceGeometry = [ARSCNFaceGeometry faceGeometryWithDevice:self.sceneView.device fillMesh:NO];
switch (self.virtualFaceType) {
case XHBVirtualFaceTypeMesh:
self.meshFaceNode = [[XHBMeshFaceNode alloc] initWithFaceGeometry:faceGeometry];
self.virtualFaceNode = (SCNNode *)self.meshFaceNode;
// 更改场景的背景内容
self.sceneView.scene.background.contents = [UIColor blackColor];
// self.sceneView.scene.background.contents = [UIImage imageNamed:@"head"];
break;
case XHBVirtualFaceTypeMask:
self.maskFaceNode = [[XHBMaskFaceNode alloc] initWithFaceGeometry:faceGeometry];
self.virtualFaceNode = (SCNNode *)self.maskFaceNode;
break;
case XHBVirtualFaceTypeRobot:
self.robotFaceNode = [[XHBRobotFaceNode alloc] init];
self.virtualFaceNode = (SCNNode *)self.robotFaceNode;
self.sceneView.scene.background.contents = [UIColor lightGrayColor];
break;
}
}
节点初始化的代码如下,这里只列举面部网格节点的代码:
@interface XHBMeshFaceNode : SCNNode
- (instancetype)initWithFaceGeometry:(ARSCNFaceGeometry *)faceGeometry;
- (void)updateWithFaceAnchor:(ARFaceAnchor *)faceAnchor;
@end
@implementation XHBMeshFaceNode
#pragma mark -
#pragma mark -------------------- Life Cycle --------------------
- (instancetype)initWithFaceGeometry:(ARSCNFaceGeometry *)faceGeometry {
self = [super init];
if (self) {
SCNMaterial *material = faceGeometry.firstMaterial;
// 更改材料的填充模型为线条
material.fillMode = SCNFillModeLines;
// 漫反射的内容颜色
material.diffuse.contents = [UIColor whiteColor];
// 基于物理的阴影,包含了真实世界灯光和材料之间相互作用的光照模型
material.lightingModelName = SCNLightingModelPhysicallyBased;
self.geometry = faceGeometry;
}
return self;
}
#pragma mark -
#pragma mark -------------------- Public Method --------------------
- (void)updateWithFaceAnchor:(ARFaceAnchor *)faceAnchor {
[(ARSCNFaceGeometry *)self.geometry updateFromFaceGeometry:faceAnchor.geometry];
}
@end
接着使用 ARSCNView 的 ARSession 属性配置 ARFaceTrackingConfiguration 运行起来,代码如下:
/** 设置追踪会话 */
- (void)p_setupTrackingSession {
// 是否可以使用面部追踪
if (!ARFaceTrackingConfiguration.isSupported) { return; }
// 禁用系统的“空闲计时器”,防止屏幕进入屏幕变暗的睡眠状态
[[UIApplication sharedApplication] setIdleTimerDisabled:YES];
ARFaceTrackingConfiguration *configuration = [[ARFaceTrackingConfiguration alloc] init];
// YES,为 trackingSession 中捕获的 ARFrame 对象的 lightEstimate 属性提供场景照明信息
[configuration setLightEstimationEnabled:YES];
[self.trackingSession runWithConfiguration:configuration
options:ARSessionRunOptionResetTracking | ARSessionRunOptionRemoveExistingAnchors];
}
- (ARSession *)trackingSession {
return self.sceneView.session;
}
当 ARSCNView 首次捕捉到脸部时,会添加一个和 ARAnchor 对应的 SCNNode,然后后就会调用 ARSCNView 的代理方法,我们可以在这里让之前创建的自定义 SCNNode 成为系统创建的 SCNNode 的子节点,子节点会继承父节点的坐标系。代码如下:
- (void)p_setupFaceNodeContent {
if (!self.faceNode) { return; }
if (self.virtualFaceNode) {
[self.faceNode addChildNode:self.virtualFaceNode];
}
}
- (void)renderer:(id<SCNSceneRenderer>)renderer didAddNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor {
self.faceNode = node;
[self p_setupFaceNodeContent];
}
之后 ARSCNView 会以一定的频率来更新 ARAnchor,然后调用代理方法。我们可以在代理方法里面把新的 ARAnchor 传给自定义节点,让节点更新它的顶点数据。代码如下:
/** 节点实现的方法 */
- (void)updateWithFaceAnchor:(ARFaceAnchor *)faceAnchor {
[(ARSCNFaceGeometry *)self.geometry updateFromFaceGeometry:faceAnchor.geometry];
}
- (void)renderer:(id<SCNSceneRenderer>)renderer didUpdateNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor {
if (!anchor) { return; }
switch (self.virtualFaceType) {
case XHBVirtualFaceTypeMesh:
[self.meshFaceNode updateWithFaceAnchor:(ARFaceAnchor *)anchor];
break;
case XHBVirtualFaceTypeMask:
[self.maskFaceNode updateWithFaceAnchor:(ARFaceAnchor *)anchor];
break;
case XHBVirtualFaceTypeRobot:
[self.robotFaceNode updateWithFaceAnchor:(ARFaceAnchor *)anchor];
break;
}
}
到这里,就可以让虚拟世界的面部跟着我们的现实世界的面部做动作了。完整的项目在这里。
参考资料
- https://mp.weixin.qq.com/s/YJ82vQYHAMmtueDgHgKcNA
- https://developer.apple.com/documentation/arkit/creating_face-based_ar_experiences?language=objc
- https://developer.apple.com/documentation/scenekit/scnnode?language=objc
- https://developer.apple.com/documentation/arkit/arfacetrackingconfiguration?language=objc
- https://developer.apple.com/documentation/arkit/arfaceanchor?language=objc
- https://developer.apple.com/documentation/arkit/arfacegeometry?language=objc
- https://developer.apple.com/documentation/arkit/arblendshapelocation?language=objc