ARKit 中用好 lookAt: 效率提升一倍

1,550

角度计算

在 AR 中,我们经常看到夹角的计算,常见的有让物体朝向手机,或者箭头从 A 点指向 B 点,一般都需要用到 atan2 等函数:
atan和atan2都是反正切函数,如:有两个点 point(x1,y1), 和 point(x2,y2);
那么这两个点形成的斜率的弧度计算方法分别是:
float radian = atan( (y2-y1)/(x2-x1) );

float radian = atan2( y2-y1, x2-x1 );

转化为角度:
float degree = arc / pi * 180

atan 和 atan2 区别在于:
1,参数的填写方式不同;
2,atan2 的优点在于x2-x1等于0时依然可以计算,但是atan函数除零会出错;

但实际上,我们可以不用这么麻烦,使用 ARKit 提供的 lookAt 方法可以让我们方便地完成“指向”操作。

lookAt 与 simdLookAt

lookAt操作可以让一个 node “指向”某个位置,默认的情况下,就是以 z 轴的正方向来对着目标位置。 如下图,飞机的头部指向了 z 轴的正方向。

这里,我们让飞机指向手机的位置,它只会执行一次,当手机移动时,飞机会保持原位
localFront可以控制自身的旋转,比如让自己(SCNNode 对象)的 x 轴正方向朝向目标,只需要设置 localFront为 (1,0,0) 就可以了。
那么 worldUp是干什么的呢?其实它是用来限制意外旋转的。就是说,当物体 A lookAt 物体 B 的时候,物体 A 有可能会绕着 A-B 点的连线旋转,所以需要指定worldUp来限制。指定后在旋转时会尽可能减小与worldUp方向的夹角,所以就不会绕 A-B 连线随意旋转了。

比如,这种情况,也是满足 lookAt 条件的,但是飞机却歪了,这不是我们想要的,所以worldUp就是为了防止出现这种情况的。

如果采用下面的代码,指定了worldUp,就不会有这种情况了:

// 延迟 2 秒,将飞机的(0,0,-1)即尾部朝向手机,同时指定 up 为世界坐标的 y 轴正方向(0,1,0),这样飞机就不会歪了
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self.shipNode simdLookAt:self.sceneView.pointOfView.simdPosition up:simd_make_float3(0, 1, 0) localFront:simd_make_float3(0, 0, -1)];
    });

simd 版本作用也是一样的。

- (void)lookAt:(SCNVector3)worldTarget;
- (void)lookAt:(SCNVector3)worldTarget up:(SCNVector3)worldUp localFront:(SCNVector3)localFront;

// 对应的 simd 版本
- (void)simdLookAt:(simd_float3)worldTarget;
- (void)simdLookAt:(simd_float3)worldTarget up:(simd_float3)worldUp localFront:(simd_float3)localFront;

SCNLookAtConstraint 与 SCNBillboardConstraint

这两个操作是约束,类似 AutoLayout 的效果,可以实时更新,不管目标怎么动,也不管自身怎么动,都一直起作用。

SCNBillboardConstraint 就是俗称的“广告牌”效果,让物体始终正面对着相机(即手机)的位置,同时保持 y 轴不变(竖直方向不变,只水平旋转)。当然,也可以调整freeAxes让它保持 x 轴不变,绕 x 轴旋转。

// 始终指向相机位置
SCNBillboardConstraint *con = [SCNBillboardConstraint billboardConstraint];
con.freeAxes = SCNBillboardAxisY;
self.shipNode.constraints = @[con];

可以看到,当左右水平移动时,飞机头部始终朝向相机方向;当手机从高处或低处看时,飞机保持竖直方向,这就是广告牌效果:

需要注意的是,广告牌会让旋转轴自动变竖直。如下图:

SCNLookAtConstraint 就没有那么多限制,可以自由旋转。同时,它也有localFrontworldUp,可以调整朝向的姿态并防止意外的旋转;另外它还有targetOffset来让你指向目标的某一侧;此外gimbalLockEnabled可以用来处理万向结锁问题。

惟一不同的是,SCNLookAtConstraint 默认是用 z 轴负方向(即飞机尾部)来指向目标的。