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

Mau*_*itz 12 scenekit swift3

我想在用户点击屏幕时创建一个新的SceneKit节点,并让它以预定距离直接出现在摄像机前面.对于测试,这将是一个SCNText读取"你在这里点击"的读数.它也应该与视线成直角 - 也就是说,"面向相机".

那么,鉴于self.camera.orientationSCNVector4(或类似),我该如何:

  1. 产生SCNVector3,将节点置于摄像机的"前方"给定距离?
  2. 生成SCNVector4(或SCNQuaternion),它定向节点,使其"面对"相机?

我怀疑(2)的答案是它与`self.camera.orientation相同?但是(1)让我有点失落.

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

ric*_*ter 28

你不需要这么多的数学 - 这是SceneKit实际上已经做的所有数学运算,所以你只需要问它正确的结果.

[我如何]生成SCNVector3,将节点置于摄像机的"前方"给定距离?

在托管摄像机的节点的本地坐标系中,"前"是-Z方向.如果你把东西放在(0, 0, -10)相机的相对位置,它就会直接位于相机的视线中心,距离10个单位.有两种方法可以做到这一点:

  1. 只需将文本节点作为摄像机的子节点即可.

    cameraNode.addChildNode(textNode)
    textNode.position = SCNVector3(x: 0, y: 0, z: -10)
    
    Run Code Online (Sandbox Code Playgroud)

    请注意,这也使其相机一起移动:如果相机改变方向或位置,textNode则为骑行而来.(想象一下反向自拍杆 - 在一端转动手机,另一端用它摆动.)

    由于文本节点位于摄像机节点空间,因此您可能不需要任何其他内容来使其面向摄像机.(SCNText相对于其父空间的相对默认方向使文本面向相机.)

  2. (iOS 11/macOS 10.13/Xcode 9等中的新功能)SceneKit现在包含一些便捷的访问器和方法,可以用更少的步骤进行各种相对几何数学运算.大多数这些属性和方法都有新的风格,支持系统范围的SIMD库,而不是使用SCN向量/矩阵类型 - 而SIMD库具有很好的功能,如内置的重载运算符.将它们放在一起,这样的操作就变成了一个班轮:

    textNode.simdPosition = camera.simdWorldFront * distance
    
    Run Code Online (Sandbox Code Playgroud)

    (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)
    
    Run Code Online (Sandbox Code Playgroud)

    在这种情况下,文本节点最初相对于摄像机定位,但不与其保持"连接".如果移动相机,节点将保持原样.由于文本节点位于场景空间中,因此其方向也未连接到摄像机.

[我如何]生成SCNVector4(或SCNQuaternion),它定向节点以使其"面向"相机?

这部分你要么不需要做太多工作,要么可以完全交给SceneKit,具体取决于你使用的第一个问题的答案.

  1. 如果你做了上述(1) - 使文本节点成为相机节点的子节点 - 使其面向相机是一步一步的事情.正如我所指出的那样,SCNText作为您的测试几何体,该步骤已经完成.在其局部坐标空间中,a SCNText是直立且可从默认摄像机方向读取(即,沿-Z方向查看,+ Y向上,+ X向右).

    如果您想要面向相机的某些其他几何体,则只需将其定向一次,但您必须确定适当的方向.例如,a SCNPyramid的点位于+ Y方向,因此如果您希望该点面向相机(注意,它是锐利的),您将需要围绕其X轴旋转它.pi/2.(你可以做到这一点eulerAngles,rotation,orientationpivot).

    因为它是相机的孩子,所以你给它的任何旋转都将相对于相机,所以如果相机移动它将保持指向相机.

  2. 如果您执行了上述(2),则可以尝试在场景空间中计算出面对摄像机所需的旋转,并在节点或摄像机移动时应用该旋转.但是有一个API可以帮助你.实际上,两个API:SCNLookAtConstraint使任何节点"面向"任何其他节点(由受约束节点的-Z方向确定),并且SCNBillboardConstraint有点相同,但有一些额外的考虑因素,假设您不想面对只是其他一些节点,但特别是相机.

    只有默认选项才能很好地工作:

    textNode.constraints = [ SCNBillboardConstraint() ]
    
    Run Code Online (Sandbox Code Playgroud)

一个额外的警告

如果您使用的sceneSpacePosition功能之上,你的相机节点的方向设置在渲染时-也就是说,你不设置transform,eulerAngles,rotation,或者orientation直接就可以了,而是使用限制,动作,动画,或物理移动/定位相机 - 还有一件事需要考虑.

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

textNode.position = sceneSpacePosition(inFrontOf: camera.presentation, atDistance: 3)
                                                        ~~~~~~~~~~~~~
Run Code Online (Sandbox Code Playgroud)

(为什么会这样?除此之外,你可以拥有隐式动画:设置position在一个事务中,它将从旧位置动画到新位置.在那个动画中,模型position是你设置的,并且演示节点不断改变它position.)


Pab*_*blo 14

(斯威夫特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)
}
Run Code Online (Sandbox Code Playgroud)

如果你想把'node'2m放在某个'cameraNode'前面,你可以这样称呼:

let position = SCNVector3(x: 0, y: 0, z: -2)
updatePositionAndOrientationOf(node, withPosition: position, relativeTo: cameraNode)
Run Code Online (Sandbox Code Playgroud)

编辑:获取摄像头节点

要获取摄像头节点,取决于您是否使用SCNKit,ARKit或其他框架.以下是ARKit和SceneKit的示例.

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

let cameraNode = sceneView.pointOfView
Run Code Online (Sandbox Code Playgroud)

对于SceneKit,您有一个SCNView渲染SCNScene的3D对象.您可以创建摄像机节点并将它们放置在任何您想要的位置,因此您可以执行以下操作:

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)
Run Code Online (Sandbox Code Playgroud)

设置摄像机节点后,您可以像访问ARKit一样访问当前摄像机:

let cameraNode = scnView.pointOfView
Run Code Online (Sandbox Code Playgroud)


Mau*_*itz 13

我通过拼凑其他几个答案得到它...从这些方法开始:

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)
}
Run Code Online (Sandbox Code Playgroud)

现在你可以像这样调用它:

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)
Run Code Online (Sandbox Code Playgroud)

距离18将其定位在半径为10的球体上的网格和25处的背景图像之间.它看起来非常好并且我的5s上的性能非常好.