将 SceneKit 对象放置在 SCNCamera 的当前方向前面



当用户点击屏幕时,我想创建一个新的SceneKit节点,并使其以设定的距离直接出现在相机前面。为了测试,这将是一个SCNText读取"你点击这里"。它也应该与视线成直角,也就是说,"正面对着"相机。

因此,给定self.camera.orientationSCNVector4(或类似),我如何才能:

  1. 生成SCNVector3,将节点放在相机的"前面"给定距离处
  2. 生成SCNVector4(或SCNQuaternion),用于定向节点,使其"面向"摄影机

我怀疑(2)的答案是,它与"self.camera.orientation"相同?但是(1)让我有点不知所措。

我看到了很多类似的问题,比如这个,但没有答案。

您不需要太多的数学运算——SceneKit实际上已经在做所有的数学运算了,所以您只需要要求它给出正确的结果。

[如何]生成SCNVector3,将节点放在相机的"前面"给定距离处?

在承载摄影机的节点的局部坐标系中,"front"是-Z方向。如果将某个东西放置在相对于相机的(0, 0, -10)处,它将直接位于相机视线的中心,距离相机10个单位。有两种方法:

  1. 只需将文本节点设置为摄影机的子节点。

    cameraNode.addChildNode(textNode)
    textNode.position = SCNVector3(x: 0, y: 0, z: -10)
    

    请注意,这也会使其随摄像头移动:如果摄像头改变方向或位置,则textNode会随之移动。(想象一下,一根自拍杆反过来——一端转动手机,另一端随之摆动。)

    由于文本节点位于摄影机节点空间中,您可能也不需要任何其他操作来使其面向摄影机。(SCNText相对于其父空间的默认方向使文本面向相机。)

  2. (iOS 11/macOS 10.13/Xcode 9/等中的新增功能)SceneKit现在包括一些方便的访问器和方法,可以用更少的步骤进行各种相对几何数学运算。这些属性和方法中的大多数都有新的版本,支持系统范围的SIMD库,而不是使用SCN矢量/矩阵类型。SIMD库具有良好的功能,如内置重载运算符

    textNode.simdPosition = camera.simdWorldFront * distance
    

    (simdWorldFront是一个长度为1.0的矢量,因此您可以通过缩放来更改距离。)

  3. 如果你仍然支持iOS 10/macOS 10.12/etc,你仍然可以使用节点层次结构进行坐标转换。文本节点可以是场景(实际上是场景的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. 如果您执行了上面的(1)--使文本节点成为相机节点的子节点--使其面向相机是一步操作。正如我所指出的,使用SCNText作为测试几何体,这一步骤已经完成。在其局部坐标空间中,SCNText是垂直的,并且可以从默认相机方向读取(即,沿-Z方向看,+Y向上,+X向右)。

    如果有其他几何体要面向摄影机,则只需要将其定向一次,但必须确定适当的方向。例如,SCNPyramid的点位于+Y方向,因此,如果希望该点面向摄影机(注意,它很锋利),则需要将其绕X轴旋转.pi/2。(您可以使用eulerAnglesrotationorientationpivot进行此操作。)

    由于它是摄影机的子对象,所以您给它的任何旋转都将相对于摄影机,因此如果摄影机移动,它将保持指向摄影机。

  2. 如果您执行了上面的(2),则可以尝试在场景空间中计算出面向摄影机所需的旋转,并在节点或摄影机移动时应用该旋转。但有一个API可以为您做到这一点。事实上,有两个API:SCNLookAtConstraint使任何节点"面向"任何其他节点(由受约束节点的-Z方向确定),SCNBillboardConstraint有点相同,但有一些额外的考虑,因为假设您不仅要面向其他节点,还要面向摄影机

    它只与默认选项配合使用效果很好:

    textNode.constraints = [ SCNBillboardConstraint() ]
    

一个额外的警告

如果使用上面的sceneSpacePosition函数,并且摄影机节点的方向是在渲染时设置的——也就是说,不直接在其上设置transformeulerAnglesrotationorientation,而是使用约束、动作、动画或物理来移动/定向摄影机——那么还有一件事需要考虑。

SceneKit实际上在任何给定时间都有两个节点层次结构:"模型"树和"表示"树。模型树是直接设置transform(etc)所做的。所有其他移动/定向节点的方式都使用表示树。因此,如果你想使用相机的相对空间来计算位置,你需要相机的表示节点:

textNode.position = sceneSpacePosition(inFrontOf: camera.presentation, atDistance: 3)
~~~~~~~~~~~~~

(为什么是这样?除其他外,它是为了让你可以有隐含的动画:在事务中设置position,它将从旧位置动画到新位置。在动画过程中,模型position就是你设置的,演示节点不断更改其position。)

(Swift 4)

嘿,如果你想把对象放在相对于另一个节点(例如相机节点)的某个位置,并且与参考节点的方向相同,你可以使用这个更简单的功能:

func updatePositionAndOrientationOf(_ node: SCNNode, withPosition position: SCNVector3, relativeTo referenceNode: SCNNode) {
let referenceNodeTransform = matrix_float4x4(referenceNode.transform)
// Setup a translation matrix with the desired position
var translationMatrix = matrix_identity_float4x4
translationMatrix.columns.3.x = position.x
translationMatrix.columns.3.y = position.y
translationMatrix.columns.3.z = position.z
// Combine the configured translation matrix with the referenceNode's transform to get the desired position AND orientation
let updatedTransform = matrix_multiply(referenceNodeTransform, translationMatrix)
node.transform = SCNMatrix4(updatedTransform)
}

如果你想把"节点"放在某个"cameraNode"正前方2米处,你可以这样称呼它:

let position = SCNVector3(x: 0, y: 0, z: -2)
updatePositionAndOrientationOf(node, withPosition: position, relativeTo: cameraNode)

编辑:获取相机节点

要获得相机节点,这取决于您是使用SceneKit、ARKit还是其他框架。以下是ARKit和SceneKit的示例。

使用ARKit,您可以使用ARSCNView来渲染与相机内容重叠的SCNScene的3D对象。您可以从ARSCNView的pointOfView属性中获取相机节点:

let cameraNode = sceneView.pointOfView

对于SceneKit,您有一个SCNView,用于渲染SCNScene的三维对象。您可以创建相机节点,并将其定位在任何您想要的位置,因此您可以执行以下操作:

let scnScene = SCNScene()
// (Configure scnScene here if necessary)
scnView.scene = scnScene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(0, 5, 10)  // For example
scnScene.rootNode.addChildNode(cameraNode)

一旦设置了相机节点,您就可以以与ARKit相同的方式访问当前相机:

let cameraNode = scnView.pointOfView

我把其他几个答案拼凑在一起得到了。。。从以下方法开始:

func pointInFrontOfPoint(point: SCNVector3, direction: SCNVector3, distance: Float) -> SCNVector3 {
var x = Float()
var y = Float()
var z = Float()
x = point.x + distance * direction.x
y = point.y + distance * direction.y
z = point.z + distance * direction.z
let result = SCNVector3Make(x, y, z)
return result
}
func calculateCameraDirection(cameraNode: SCNNode) -> SCNVector3 {
let x = -cameraNode.rotation.x
let y = -cameraNode.rotation.y
let z = -cameraNode.rotation.z
let w = cameraNode.rotation.w
let cameraRotationMatrix = GLKMatrix3Make(cos(w) + pow(x, 2) * (1 - cos(w)),
x * y * (1 - cos(w)) - z * sin(w),
x * z * (1 - cos(w)) + y*sin(w),
y*x*(1-cos(w)) + z*sin(w),
cos(w) + pow(y, 2) * (1 - cos(w)),
y*z*(1-cos(w)) - x*sin(w),
z*x*(1 - cos(w)) - y*sin(w),
z*y*(1 - cos(w)) + x*sin(w),
cos(w) + pow(z, 2) * ( 1 - cos(w)))
let cameraDirection = GLKMatrix3MultiplyVector3(cameraRotationMatrix, GLKVector3Make(0.0, 0.0, -1.0))
return SCNVector3FromGLKVector3(cameraDirection)
}

现在你可以这样称呼它:

let rectnode = SCNNode(geometry: rectangle)
let dir = calculateCameraDirection(cameraNode: self.camera)
let pos = pointInFrontOfPoint(point: self.camera.position, direction:dir, distance: 18)
rectnode.position = pos
rectnode.orientation = self.camera.orientation
sceneView.scene?.rootNode.addChildNode(rectnode)

距离18将其定位在半径为10的球体上的网格和25的背景图像之间。它看起来真的很好,在我的5s上的表现也出奇地好。

让它变得简单:

let constraint = SCNTransformConstraint(inWorldSpace: true) { node, transform -> SCNMatrix4 in
let distance: Float = 1.0
let newNode = SCNNode()
let worldFront = self.sceneView!.pointOfView!.simdWorldFront * distance
newNode.transform = self.sceneView!.pointOfView!.transform
newNode.transform = SCNMatrix4Translate(newNode.transform, worldFront.x, worldFront.y, worldFront.z)
return newNode.transform
}

ARSCNView有一个属性"pointOfView"。您可以将子节点附加到它。

let ball = SCNSphere(radius: 0.02)
ballNode = SCNNode(geometry: ball)
ballNode?.position = SCNVector3Make(0, 0, -0.2)
sceneView.pointOfView?.addChildNode(ballNode!)

无论你走到哪里,节点都会跟随你的相机。

在这里

最新更新