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

Posted

技术标签:

【中文标题】在 SCNCamera 的当前方向前面放置一个 SceneKit 对象【英文标题】:Position a SceneKit object in front of SCNCamera's current orientation 【发布时间】:2017-06-21 03:01:18 【问题描述】:

我想在用户点击屏幕时创建一个新的 SceneKit 节点,并让它以设定的距离直接出现在相机前面。为了进行测试,这将是一个 SCNText,上面写着“你点击了这里”。它还应该与视线成直角 - 即“面向”相机。

那么,鉴于self.camera.orientation SCNVector4(或类似),我该怎么做:

    生成 SCNVector3 以将节点置于相机“前面”的给定距离处? 生成 SCNVector4(或 SCNQuaternion)来定向节点,使其“面向”相机?

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

我看到许多类似的问题,例如 this one,但没有答案。

【问题讨论】:

【参考方案1】:

您不需要这么多数学 - SceneKit 实际上已经在做所有数学,所以您只需要询问它以获得正确的结果。

[如何] 生成 SCNVector3 以将节点置于相机“前面”的给定距离?

在承载相机的节点的局部坐标系中,“前”是-Z方向。如果您将某物放在相对于相机的(0, 0, -10) 处,则它直接位于相机视线的中心并且相距10 个单位。有两种方法可以做到这一点:

    只需将文本节点设为相机的子节点即可。

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

    请注意,这也会使其相机一起移动:如果相机改变方向或位置,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 轴旋转 @987654332 @。 (您可以使用 eulerAnglesrotationorientationpivot 执行此操作。)

    由于它是相机的子对象,因此您对其进行的任何旋转都将相对于相机,因此如果相机移动,它将始终指向相机。

    如果您执行了上述 (2),您可以尝试在场景空间术语中计算出面对相机需要什么旋转,并在节点或相机移动时应用该旋转。但是有一个 API 可以为您做到这一点。实际上,有两个 API:SCNLookAtConstraint 使任何节点“面向”任何其他节点(由受约束节点的 -Z 方向确定),SCNBillboardConstraint 是同一件事,但对假设有一些额外的考虑你想要面对的不仅仅是其他节点,而是相机

    仅使用默认选项就可以很好地工作:

    textNode.constraints = [ SCNBillboardConstraint() ]
    

一个额外的警告

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

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

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

(为什么会这样?除此之外,你可以有隐式动画:在事务中设置position,它会从旧位置动画到新位置。在动画期间,模型@ 987654348@是你设置的,表示节点不断的改变它的position。)

【讨论】:

啊,上面的第一个(2)是这样的帮助。至于旋转,我确实尝试简单地从相机节点复制矩阵,这确实满足了我的需要。幸运的是,几何体被定义为“面朝上”,我什至不必旋转它或其他任何东西。 最佳答案。谢谢! 我想在 Mac 催化剂中用鼠标移动相机,但是当相机旋转和我改变位置时,模型被剪裁(消失)你能帮我吗?【参考方案2】:

(斯威夫特 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)

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

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,您有一个渲染 SCNScene 的 3D 对象的 SCNView。您可以创建相机节点并将它们放置在您想要的任何位置,因此您可以执行以下操作:

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

【讨论】:

非常好!干净多了。 从哪里获取相机节点? @巴勃罗 @TalZion 我添加了一些关于如何获取相机节点的提示。 谢谢@Pablo。我测试了相机节点。我将如何设置与摄像机角度相关的位置?我设置了position = SCNVector3Make(cameraNode.position.x, cameraNode.position.y, cameraNode.position.z - 0.2),但它可以相对于相机指向的左侧或右侧。【参考方案3】:

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

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 上的性能出奇的好。

【讨论】:

这是我一直试图解决的问题的完美答案,方法是在相机前面放置一个固定距离的 SCNNode,而无需将此节点添加到相机的节点层次结构中。跨度> 这看起来很棒 Maury,但是 cameraNode 是什么,你从哪里得到它?此外,sceneView.session.currentFrame.camera 似乎没有上面使用的方向属性。 cameraNode 是您声明为相机的 SCNKit 节点。我将它放在我的 SCNKitView 子类的一个变量中,并在 viewDidLoad 期间设置它。方向上有一个完整的页面,谷歌“scnnode 方向” 这太有帮助了!对于其他人来说,我还发现这个 (***.com/questions/45185555/…) 提供了从 currentFrame.camera.transform 到相机方向的更短路径 Maury - 另一个快速的答案是对象的方向与相机的方向正好相反。因此,您可以获取相机方向的 x/y/z 分量,然后简单地将每个分量乘以 -1。这将为您提供一个完全朝向相反方向的方向。【参考方案4】:

让事情变得简单:

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

【讨论】:

对我来说最好的解决方案!但没有强制展开;-)【参考方案5】:

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

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

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

in here

【讨论】:

以上是关于在 SCNCamera 的当前方向前面放置一个 SceneKit 对象的主要内容,如果未能解决你的问题,请参考以下文章

SCNScene 中 SCNCamera 的默认位置

如何定位 SCNCamera 以使对象“刚好适合”视图?

OpenGL 方向。 (从负 z 看的身份旋转)

如何使用当前位置的纬度和经度知道一个地方的主要红线间方向(N,E,S,W)?

使用属性键路径为 SCNCamera.fStop 设置动画不起作用,仅在隐式 SCNTransaction 内

如何使用 WCF 公开 DCOM