我想在用户点击屏幕时创建一个新的SceneKit节点,并让它以预定距离直接出现在摄像机前面。对于测试,这将是一个SCNText读取“你在这里点击”的读数。它也应该与视线成直角 - 也就是说,“面朝镜头”。
所以,鉴于self.camera.orientation
SCNVector4(或类似),我该怎么做:
我怀疑(2)的答案是它与`self.camera.orientation相同?但是(1)让我有点迷失。
我看到了许多类似的问题,例如this one,但没有答案。
答案 0 :(得分:27)
你不需要这么多的数学 - 这是SceneKit实际上已经在做的所有数学,所以你只需要问它是否有正确的结果。
[我如何]生成SCNVector3,将节点置于" front"在给定距离的相机?
在托管摄像机的节点的本地坐标系中," front"是-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 / 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) - 使文本节点成为相机节点的子节点 - 使其面向相机是一步一步的事情。正如我所指出的那样,SCNText
作为您的测试几何体已经完成了步骤。在其局部坐标空间中,SCNText
直立且可从默认摄像机方向读取(即,沿-Z方向查看,+ Y向上,+ X向右)。
如果您想要面向相机的某些其他几何图形,则只需将其定向一次,但您必须确定适当的方向。例如,SCNPyramid
的点位于+ Y方向,因此如果您希望该点面向相机(注意,它是锋利的),您将要旋转它围绕其X轴.pi/2
。 (您可以使用eulerAngles
,rotation
,orientation
或pivot
执行此操作。)
因为它是相机的孩子,所以你给它的任何旋转都是相对于相机的,所以如果相机移动它会一直指向相机。
如果您执行了上述(2),则可以尝试在场景空间中计算出面对相机所需的旋转,并在节点或相机时应用该旋转移动。但是有一个API可以帮助你做到这一点。实际上,两个API:SCNLookAtConstraint
使任何节点"面向"任何其他节点(由受约束节点的-Z方向确定),SCNBillboardConstraint
有点相同但有一些额外的考虑因素假设您不仅要面对其他节点,而是具体相机。
只有默认选项才能正常工作:
textNode.constraints = [ SCNBillboardConstraint() ]
如果您正在使用上面的sceneSpacePosition
功能,并且您的相机节点的方向设置为渲染时间 - 也就是说,您没有设置transform
,直接使用eulerAngles
,rotation
或orientation
,而是使用约束,动作,动画或物理来移动/定位相机 - 还有一件事需要考虑。
SceneKit实际上在任何给定时间都有两个节点层次结构:"模型"树和#34;演示"树。模型树是直接设置transform
(等)的工具。所有其他移动/定向节点的方法都使用表示树。因此,如果您想使用相机相对空间计算位置,您需要相机的演示节点:
textNode.position = sceneSpacePosition(inFrontOf: camera.presentation, atDistance: 3)
~~~~~~~~~~~~~
(为什么会这样呢?除此之外,它还可以隐藏动画:在事务中设置position
,然后从旧位置动画到新位置在该动画期间,模型position
是您设置的模型,并且演示节点不断更改其position
。)
答案 1 :(得分: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)
}
现在你可以这样称呼它:
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的表现非常好。
答案 2 :(得分:12)
(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)
}
如果你想放置节点'在某个摄像机节点前面2米处,您可以这样称呼它:
let position = SCNVector3(x: 0, y: 0, z: -2)
updatePositionAndOrientationOf(node, withPosition: position, relativeTo: cameraNode)
修改:获取相机节点
要获取摄像机节点,取决于您是否使用SCNKit,ARKit或其他框架。以下是ARKit和SceneKit的示例。
使用ARKit,您可以使用ARSCNView渲染与摄像机内容重叠的SCNScene的3D对象。您可以从ARSCNView的pointOfView属性中获取摄像机节点:
let cameraNode = sceneView.pointOfView
对于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)
设置摄像机节点后,您可以像访问ARKit一样访问当前摄像机:
let cameraNode = scnView.pointOfView
答案 3 :(得分:0)
ARSCNView具有属性“ pointOfView”。您可以在其上附加一个子节点。
let ball = SCNSphere(radius: 0.02)
ballNode = SCNNode(geometry: ball)
ballNode?.position = SCNVector3Make(0, 0, -0.2)
sceneView.pointOfView?.addChildNode(ballNode!)
该节点将随身携带摄像机。
答案 4 :(得分:-1)
简单一点:
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
}