SpriteKit - Adding a blur to the entire Scene - swift

I am trying to blur my entire GameScene when my pause button is pressed. I have a method called blurSceen() but it doesn't seem to be adding the effect to the scene. Is there a way I can accomplish this or am I doing something wrong? I have viewed other posts about this topic but haven't been able to accomplish the effect.
func blurScreen() {
let effectsNode = SKEffectNode()
let filter = CIFilter(name: "CIGaussianBlur")
let blurAmount = 10.0
filter!.setValue(blurAmount, forKey: kCIInputRadiusKey)
effectsNode.filter = filter
effectsNode.position = self.view!.center
effectsNode.blendMode = .Alpha
// Add the effects node to the scene
self.addChild(effectsNode)
}

From the SKEffectNode docs:
An SKEffectNode object renders its children into a buffer and optionally applies a Core Image filter to this rendered output.
The effect node applies a filter only to its child nodes. Your effect node has no children, so there's nothing to apply a filter to.
Probably what you want is to add an effect node to your scene early on--but don't set the filter on it yet--and put all the nodes that you'll later want to blur in as its children. When it comes time to apply a blur, set the filter on the (already existing, already with children) effect node.

I had the same issue trying to blur the whole SKScene and it just wasn't working. The missing piece of the puzzle was this line:
shouldEnableEffects = true

Swift 4:
from gameScene:
let blur = CIFilter(name:"CIGaussianBlur",withInputParameters: ["inputRadius": 10.0])
self.filter = blur
self.shouldRasterize = true
self.shouldEnableEffects = true

Related

How to zoom paused ARFaceTrackingSession

I think the title is self-explanatory. I am starting a session like this:
let configuration = ARFaceTrackingConfiguration()
sceneView.session.run(configuration)
Then I add some nodes to the scene.
Then I pause the scene:
self.sceneView.session.pause()
Then, I want to programmatically zoom into the face so that the screen is filled with the face.
But I cannot figure out how. I have tried
let cameraNode = sceneView.pointOfView!
faceNode.simdScale = cameraNode.simdWorldFront * 1
But that does not seem to take any effect.
I can do it with CGAffinTransform on the SceneView itself but that approach seems wrong.

How to swap from MTKView to UIView display seamlessly

I have an MTKView whose contents I draw into a UIView. I want to swap display from MTKView to UIView without perceptible changes. How to achieve?
Currently, I have
let strokeCIImage = CIImage(mtlTexture: metalTextureComposite...) // get MTLTexture
let imageCropCG = cicontext.createCGImage(strokeCIImage...) // convert to CGImage
let layerStroke = CALayer() // create layer
layerStroke.contents = imageCropCG // populate with CGImage
strokeUIView.layer.addSublayer(layerStroke) // add to view
strokeUIView.layerWillDraw(layerStroke) //heads up to strokeUIView
and a delegate method within layerWillDraw() that clears the MTKView.
strokeViewMetal.metalClearDisplay()
The result is that I'll see a frame drop every so often in which nothing is displayed.
In the hopes of cleanly separating the two tasks, I also tried the following:
let dispatchWorkItem = DispatchWorkItem{
print("lyr add start")
self.pageCanvasImage.layer.addSublayer(sublayer)
print("lyr add end")
}
let dg = DispatchGroup()
DispatchQueue.main.async(group: dg, execute: dispatchWorkItem)
//print message when all blocks in the group finish
dg.notify(queue: DispatchQueue.main) {
print("dispatch mtl clear")
self.strokeCanvasMetal.setNeedsDisplay() // clear MTKView
}
The idea being add the new CALayer to UIImageView, and THEN clear the MTKView.
Over many screen draws, I think this result in fewer frame drops during the View swap, but I'd like a foolproof solution with NO drops. Basically what I'm after is to only clear strokeViewMetal once strokeUIView is ready to display. Any pointers would be appreciated
Synchronicity issues between MTKView and UIView are resolved for 99% of my tests when I set MTKView's presentsWithTransaction property to true. According to Apple's documentation:
Setting this value to true changes this default behavior so that your
MTKView displays its drawable content synchronously, using whichever
Core Animation transaction is current at the time the drawable’s
present() method is called.
Once that is done, the draw loop has to be modified from:
commandEncoder.endEncoding()
commandBuffer.present(drawable)
commandBuffer.commit()
to:
commandEncoder.endEncoding()
commandBuffer.commit()
commandBuffer.waitUntilScheduled() // synchronously wait until the drawable is ready
drawable.present() // call the drawable’s present() method directly
This is done to prevent Core activities to end before we're ready to present MTKView's drawable.
With all of this set up, I can simply:
let strokeCIImage = CIImage(mtlTexture: metalTextureComposite...) // get MTLTexture
let imageCropCG = cicontext.createCGImage(strokeCIImage...) // convert to CGImage
let layerStroke = CALayer() // create layer
layerStroke.contents = imageCropCG // populate with CGImage
// the last two events will happen synchronously
strokeUIView.layer.addSublayer(layerStroke) // add to view
strokeViewMetal.metalClearDisplay() // empty out MTKView
With all of this said, I do see overlapping of the views every now and then, but at a much, much lower frequency

How to programmatically assign a material to a 3D SCNNode?

I'm trying to figure out how to programmatically assign a material to some object (SCNNode) in my scene for ARKit (XCode 9 / Swift 4). I'm trying to programmatically do this because I want the same shaped object to be rendered with way too many variants (or user-generated images) to be able to do it via the menu assignment in a scene. The object just a cube - for now, I'm just trying to get one side to display this material pulled from the Assets folder.
This is the current code that I've tried referencing prior Stack posts, but the object is just remaining white.
let material = SCNMaterial()
material.diffuse.contents = UIImage(named: "texture.jpg")
let nodeObject = self.lastUsedObject?.childNode(withName: "box", recursively: true)
// I believed this lets me grab the last thing I rendered in an ARKit scene - please
// correct me if I'm wrong. My object is also labeled "box".
nodeObject?.geometry?.materials
nodeObject?.geometry?.materials[0] = material // I wanted to grab the first face of the box
Thank you so much in advance! I've been fiddling with this for a while but I can't seem to get the grasp of programmatic methods for 3D objects / Scenes in Swift.
Overall, I was setting the materials of an object like this and it was working (to only grab one face of the box
var imageMaterial = SCNMaterial()
imageMaterial.isDoubleSided = false
imageMaterial.diffuse.contents = UIImage(named: "myImage")
var cube: SCNGeometry? = SCNBox(width: 1.0, height: 1.0, length: 1, chamferRadius: 0)
var node = SCNNode(geometry: cube)
node.geometry?.materials = [imageMaterial]
So it could possibly be that you haven't been able to grab the object, as stated in the comments.

How to create image of SKSpriteNode?

I have a SKSpriteNode with a lot of child nodes.
I use that node with all the children very frequently in my game.
A better approach would be to build this node once with all the children, create an image (SKTexture) and use the created texture, as opposed to recreating the SKSpriteNode and all its children.
Is there any way to do this?
There is actually a very easy way to do this.. I do this all the time to convert ShapeNodes to SpriteNodes (because shapes need 1 draw call per shape, and sprites dont)
let texture = SKView().texture(from: desiredNode)
let newSprite = SKSpriteNode(texture: texture)
or, in your gamescene for brevity:
let newSprite = SKSpriteNode(texture: view!.texture(from: desiredNode))
SKEffectNode & shouldRasterize
You can us SKEffectNode and set shouldRasterize to true.
let effectNode = SKEffectNode()
effectNode.shouldRasterize = true
scene.addChild(effectNode)
The SKEffectNode's output is rasterized and cached internally. This cache is reused when rendering.
Now just add your sprites to effectNode
effectNode.addChild(sprite0)
effectNode.addChild(sprite1)
effectNode.addChild(sprite2)
The first time the effectNode is rendered, the produced image will be automatically cached.
And the cached version will be used for the next frames.

Swift 3 (SpriteKit): Create a texture from a node tree

I have been trying to render a node tree into an SKTexture as a possible solution to another question here. I tried searching for an answer to this, and I came across one of Apple's Developer pages (view it here). It says the command which converts a node tree into an SKTexture is this:
func texture(from node: SKNode) -> SKTexture?
Although, the code is incomplete since there is no code which executes the conversion of a node tree to an SKTexture. Am I doing something wrong or what is the complete code that I should use to render a node tree into an SKTexture.
Thanks for any advice, help, and answers!
texture(from:) is an instance method of SKView that takes a node (with zero or more children) as a parameter and returns an optional SKTexture. Since the returned value is an optional, you'll need to unwrap it before using it.
From didMove(to:), you can use the view parameter to create a texture
if let texture = view.texture(from: node) {
let sprite = SKSpriteNode(texture:texture)
addChild(sprite)
}
From other methods in the SKScene subclass, use optional chaining
if let texture = self.view?.texture(from: node) {
let sprite = SKSpriteNode(texture:texture)
addChild(sprite)
}
You can also render the portion of the node-tree's contents inside of a rectangle with
let rect = CGRect(x:-width/2, y:-height/2, width:width, height:height)
if let texture = self.view?.texture(from: node, crop: rect) {
let sprite = SKSpriteNode(texture:texture)
addChild(sprite)
}