Search code examples
iosscenekitarkitmetal

SceneKit + ARKit: Billboarding without rolling with camera


I'm trying to draw a billboarded quad using SceneKit and ARKit. I have basic billboarding working, however when I roll the camera the billboard also rotates in place. This video shows this in action as I roll the camera to the left (the smily face is the billboard):

Rolling the camera to the left also rotates the billboard :(

Instead I'd like the billboard to still face the camera but keep oriented vertically in the scene, no matter what the camera is doing

Here's how I compute billboarding:

// inside frame update function

struct Vertex {
    var position: SIMD3<Float>
    var texCoord: SIMD2<Float>
}

let halfSize = Float(0.25)

let cameraNode = sceneView.scene.rootNode.childNodes.first!

let modelTransform = self.scnNode.simdWorldTransform
let viewTransform = cameraNode.simdWorldTransform.inverse
let modelViewTransform = viewTransform * modelTransform

let right = SIMD3<Float>(modelViewTransform[0][0], modelViewTransform[1][0], modelViewTransform[2][0]);
let up = SIMD3<Float>(modelViewTransform[0][1], modelViewTransform[1][1], modelViewTransform[2][1]);

// drawBuffer is a MTL buffer of vertex data
let data = drawBuffer.contents().bindMemory(to: ParticleVertex.self, capacity: 4)
data[0].position = (right + up) * halfSize
data[0].texCoord = SIMD2<Float>(0, 0)

data[1].position = -(right - up) * halfSize
data[1].texCoord = SIMD2<Float>(1, 0)

data[2].position = (right - up) * halfSize
data[2].texCoord = SIMD2<Float>(0, 1)

data[3].position = -(right + up) * halfSize
data[3].texCoord = SIMD2<Float>(1, 1)

Again this gets the billboard facing the camera correctly, however when I roll the camera, the billboard rotates along with it.

What I'd like instead is for the billboard to point towards the camera but keep its orientation in the world. Any suggestions on how to fix this?

Note that my code example is simplified so I can't use SCNBillboardConstraint or anything like that; I need to be able to compute the billboarding myself


Solution

  • Here's the solution I came up with: create a new node that matches the camera's position and rotation, but without any roll:

    let tempNode = SCNNode()
    tempNode.simdWorldPosition = cameraNode.simdWorldPosition
    
    // This changes the node's pitch and yaw, but not roll
    tempNode.simdLook(at: cameraNode.simdConvertPosition(SIMD3<Float>(0, 0, 1), to: nil))
    
    let view = tempNode.simdWorldTransform.inverse
    let modelViewTransform = view * node.simdWorldTransform
    

    This keeps the billboard pointing upwards in world space, even as the camera rolls.

    I had actually tried doing this earlier by setting tempNode.eulerAngles.z = 0, however that seems to effect the rest of the transform matrix in unexpected ways

    There's probably a way to do this without creating a temporary node too but this works well enough for me