Render off-screen SCNScene into UIImage
ios, opengl-es, scenekit
Solution
Swift 4 with SCNRenderer:
You can use SCNRenderer's snapshot method to render the off-screen SCNScene to a UIImage pretty easily.
Some caveats here, this uses Metal. I don't know where the device/iOS version cutoff is, but you'll need a newer device. You also won't be able to run it on the simulator.
Step 1 - Set up your scene like you normally would:
// Set up your scene which won't be displayed
let hiddenScene = SCNScene()
[insert code to set up your nodes, cameras, and lights here]
Step 2 - Set up the SCNRenderer -- renderer will be nil on simulator:
// Set up the renderer -- this returns nil on simulator
let renderer = SCNRenderer(device: MTLCreateSystemDefaultDevice(), options: nil)
renderer!.scene = hiddenScene
Step 3 - Render scene to UIImage:
// You can use zero for renderTime unless you are using animations,
// in which case, renderTime should be the current scene time.
let renderTime = TimeInterval(0)
// Output size
let size = CGSize(width:300, height: 150)
// Render the image
let image = renderer!.snapshot(atTime: renderTime, with: size,
antialiasingMode: SCNAntialiasingMode.multisampling4X)
If you are running animations, you'll need to increment renderTime or set it to the time index you want to render. For example, if you want to render the frame 4 seconds into the scene, you would set it to 4. This only affects animations -- it won't go back in time and show you a historical view of your scene.
For example, if you run are running animations with SCNNode.runAction, you may want to keep incrementing renderTime every 60th of a second (0.16667 seconds), so that whenever you decide to render, you've got an updated renderTime:
var timer : Timer
var renderTime = TimeInterval(0)
timer = Timer.scheduledTimer(withTimeInterval: 0.016667, repeats: true, block: { (t) in
self?.renderTime += 0.016667
}
})
Using CADisplayLink is probably a better solution for the timing though.
Here's a very quick and dirty implementation example.
Problem
How can I render render an off-screen `SCNScene` into a `UIImage`? I know that `SCNView` provides a `-snapshot` method, but unfortunately that doesn't work for off-screen views. A similar question have been asked before where one of the answers suggest reading the bitmap data from OpenGL using `glReadPixels`, but that approach doesn't work for me with an off-screen scene. I tried rendering into the context of an `GLKView` using `SCNRenderer` without success.