当用户点击屏幕时,我想创建一个新的SceneKit节点,并使其以设定的距离直接出现在相机前面。为了测试,这将是一个SCNText读取"你点击这里"。它也应该与视线成直角,也就是说,"正面对着"相机。
因此,给定self.camera.orientation
SCNVector4(或类似),我如何才能:
- 生成SCNVector3,将节点放在相机的"前面"给定距离处
- 生成SCNVector4(或SCNQuaternion),用于定向节点,使其"面向"摄影机
我怀疑(2)的答案是,它与"self.camera.orientation"相同?但是(1)让我有点不知所措。
我看到了很多类似的问题,比如这个,但没有答案。
您不需要太多的数学运算——SceneKit实际上已经在做所有的数学运算了,所以您只需要要求它给出正确的结果。
[如何]生成SCNVector3,将节点放在相机的"前面"给定距离处?
在承载摄影机的节点的局部坐标系中,"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实际上在任何给定时间都有两个节点层次结构:"模型"树和"表示"树。模型树是直接设置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!)
无论你走到哪里,节点都会跟随你的相机。
在这里