I am trying to render an SCNRenderer in a MTKView so I have handles on the color / depth buffers - and I am trying to match the transforms that ARSCNView does via ARKit so my SCNRenderer behaves like an ARSCNView.
Does anyone have an example of matching ARSCNViews world and face tracking configurations via the ARFrame/ARCamera and info provided by ARKit - and properly modifying the transforms of SCNSCamera appropriately?
For various reasons (not having access to a MTLPassDescriptor for ARSCNView / SCNView and SCNTechnique having its own weird limitations) I'm trying to re-implement some of ARSCNView via MTKView + ARKit + SCNRenderer myself, which allows me to have more control over the rendering pipeline. I am basing my code off of Apples ARKit + Metal example.
I'd like to match what ARKit is doing - but when I attempt to use the ARFrame / ARCamera information to modify my SCNScene and SCNCamera as I show below, my scene moves, but not with the camera in the same way that either ARSCNView or the ARKit with Metal demo does.
I'm at a loss as to what is incorrectly being passed to my scene from ARKit.
My Scene:
self.sceneKitRenderer = SCNRenderer(device: self.device, options: nil)
self.sceneKitRenderer?.delegate = self as SCNSceneRendererDelegate
let scene = SCNScene()
scene.background.contents = nil
let sphere = SCNSphere(radius: 1.0)
sphere.firstMaterial?.diffuse.contents = UIColor.red
sphere.firstMaterial?.isDoubleSided = true
let sphereNode = SCNNode(geometry: sphere)
sphereNode.position = SCNVector3(0, 0.0, -5)
scene.rootNode.addChildNode(sphereNode)
let camera = SCNCamera()
let cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 10)
cameraNode.look(at: scene.rootNode.worldPosition)
scene.rootNode.addChildNode(cameraNode)
self.sceneKitRenderer?.scene = scene
self.sceneKitRenderer?.pointOfView = cameraNode
I am updating my Scene's point of view and its projection matrix when I receive an updated ARFrame like so:
// Match clipping
self.sceneKitRenderer?.pointOfView?.camera?.zNear = 0.001
self.sceneKitRenderer?.pointOfView?.camera?.zFar = 1000
// Match projection
let projection = SCNMatrix4( frame.camera.projectionMatrix(for: .landscapeRight, viewportSize: viewportSize, zNear: 0.001, zFar: 1000))
self.sceneKitRenderer?.pointOfView?.camera?.projectionTransform = projection
// Match transform
self.sceneKitRenderer?.pointOfView?.simdTransform = frame.camera.transform
While this appears to do something, it isn't matching ARKits output.
Questions:
Is assigning the simdTransform or the SCNRenderer's pointOfView from the camera's translation correct?
Do I need to do something to my scene to set up the proper coordinate system (ie, a root node scale?)
Any guidance is appreciated!
The code referenced in the question for matching the ARCamera with the SCNScene camera won't work for different orientations, as the transform will differ depending on the orientation. There is a function on ARCamera to get the view matrix for a particular orientation(the view matrix being the inverse of the camera transform). So the following can be used to correctly take orientation in to account:
// Match clipping
self.sceneKitRenderer?.pointOfView?.camera?.zNear = 0.001
self.sceneKitRenderer?.pointOfView?.camera?.zFar = 1000
// Match projection
let projection = SCNMatrix4( frame.camera.projectionMatrix(for: .portrait, viewportSize: viewportSize, zNear: 0.001, zFar: 1000))
self.sceneKitRenderer?.pointOfView?.camera?.projectionTransform = projection
// Match transform
self.sceneKitRenderer?.pointOfView?.simdTransform = currentFrame.camera.viewMatrix(for: .portrait).inverse
I did two things to get this working.
First you need to get the camera node from ARKit's scene and use this to set the camera node's transform and the camera's projectionTransform in your other scene:
// get the first node that has a camera attached
if let arkitCameraNode = arkitScene.rootNode.childNodes(passingTest: { (node, stop) -> Bool in
return node.camera != nil
}).first {
// SCNNode
otherSceneCameraNode.transform = arkitCameraNode.transform
// SCNCamera
otherSceneCamera.projectionTransform = arkitCameraNode.camera!.projectionTransform
}
After this you need to sync the cloned nodes in your other scene by getting its corresponding ARAnchor transform:
if let anchor = arkitSceneView.anchor(for: nodeFromARKitScene) {
clonedNodeInOtherScene.simdTransform = anchor.transform
}
Hope this helps!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With