您不需要这么多数学运算——SceneKit 实际上已经在做这些数学运算了,所以您只需要向它询问正确的结果即可。
[如何]生成 SCNVector3,将节点放置在相机“前面”给定的距离?
在托管相机的节点的局部坐标系中,“前”是 -Z 方向。如果你把东西放在(0, 0, -10)
相对于相机,它直接位于相机视线的中心,距离 10 个单位。有两种方法可以做到这一点:
-
只需将文本节点设为相机的子节点即可。
cameraNode.addChildNode(textNode)
textNode.position = SCNVector3(x: 0, y: 0, z: -10)
请注意,这也会使其移动with相机:如果相机改变方向或位置,textNode
一起去兜风。 (想象一下反向的自拍杆——在一端旋转手机,另一端随之摆动。)
由于文本节点位于相机节点空间中,因此您可能也不需要任何其他内容即可使其面向相机。 (默认方向SCNText
相对于其父空间使文本面向相机。)
-
(iOS 11 / macOS 10.13 / Xcode 9 / 等中的新增功能)SceneKit 现在包含一些方便的访问器和方法,可以用更少的步骤执行各种相对几何数学。这些属性和方法中的大多数都以新的形式提供,支持系统范围的 SIMD 库,而不是使用 SCN 向量/矩阵类型 - 并且 SIMD 库具有内置的重载运算符等很好的功能。将它们放在一起,这样的操作就变成了单行:
textNode.simdPosition = camera.simdWorldFront * distance
(simdWorldFront
是一个长度为 1.0 的向量,因此您可以通过缩放来改变距离。)
-
如果您仍然支持 iOS 10 / macOS 10.12 / 等,您仍然可以使用节点层次结构进行坐标转换。文本节点可以是场景的子节点(场景的rootNode
,实际上),但你可以找到与你想要的相机空间坐标相对应的场景坐标空间位置。你实际上并不需要一个函数,但这可能可以解决问题:
func sceneSpacePosition(inFrontOf node: SCNNode, atDistance distance: Float) -> SCNVector3 {
let localPosition = SCNVector3(x: 0, y: 0, z: CGFloat(-distance))
let scenePosition = node.convertPosition(localPosition, to: nil)
// to: nil is automatically scene space
return scenePosition
}
textNode.position = sceneSpacePosition(inFrontOf: camera, atDistance: 3)
在这种情况下,文本节点最初是相对于相机定位的,但并不保持与其“连接”。如果移动相机,节点将保持在原来的位置。由于文本节点位于场景空间中,因此它的方向也未连接到相机。
[如何]生成 SCNVector4(或 SCNQuaternion)来定向节点,使其“面向”相机?
这部分您不需要做太多事情,也可以完全交给 SceneKit,具体取决于您使用的第一个问题的答案。
-
如果您执行了上面的 (1) - 使文本节点成为相机节点的子节点 - 使其面向相机是一步操作。正如我所指出的,随着SCNText
作为您的测试几何体,该步骤已经完成。在其局部坐标空间中,SCNText
是直立的,并且从默认相机方向可读(即,朝 -Z 方向看,+Y 向上,+X 向右)。
如果您有一些其他几何体想要面向相机,则只需将其定向一次,但您必须计算出适当的方向。例如,一个SCNPyramid
它的点在 +Y 方向,因此如果您希望该点面向相机(注意,它很锐利),您需要将其绕 X 轴旋转.pi/2
。 (你可以这样做eulerAngles
, rotation
, orientation
or pivot
.)
由于它是相机的子级,因此您对其进行的任何旋转都将相对于相机进行,因此如果相机移动,它将保持指向相机。
-
如果您执行了上述 (2),您could尝试从场景空间角度计算出面对相机需要什么旋转,并在节点或相机移动时应用该旋转。但有一个 API 可以为您做到这一点。实际上有两个API:SCNLookAtConstraint
使任何节点“面向”任何其他节点(由受约束节点的 -Z 方向确定),并且SCNBillboardConstraint
有点相同,但需要一些额外的考虑,假设您不仅要面对某个其他节点,而且要具体面对相机.
它仅使用默认选项就可以很好地工作:
textNode.constraints = [ SCNBillboardConstraint() ]
额外的警告
如果您正在使用sceneSpacePosition
上面的函数,并且你的相机节点的方向是在渲染时设置的 - 也就是说,你不设置transform
, eulerAngles
, rotation
, or orientation
直接对其进行操作,而是使用约束、动作、动画或物理来移动/定向相机 - 还有一件事需要考虑。
SceneKit 实际上在任何给定时间都有两个正在运行的节点层次结构:“模型”树和“演示”树。模型树是直接设置的transform
(等)确实如此。所有其他移动/定向节点的方法都使用表示树。因此,如果您想使用相机相对空间计算位置,则需要相机的呈现节点:
textNode.position = sceneSpacePosition(inFrontOf: camera.presentation, atDistance: 3)
~~~~~~~~~~~~~
(为什么会这样?除此之外,这样你就可以拥有隐式动画:setposition
在交易内部,它会从旧位置动画到新位置。在该动画期间,模型position
是你设置的,并且呈现节点不断改变它的position
.)