SpriteKit fast fullscreen vignette / lighting with alpha blending - ios

I am making a platforming game with SpriteKit. I want to achieve the effect that only the area around the player is lit and everything else fades into darkness. Imagine something like a fake light source or a strong vignette. For testing purpose I created the following code:
let effect = SKEffectNode()
effect.zPosition = 100
effect.position = CGPoint(x: 0, y: 0)
effect.shouldRasterize = false
effect.blendMode = SKBlendMode.Multiply
let gray = SKSpriteNode()
gray.color = SKColor.grayColor()
gray.size = self.frame.size
gray.blendMode = SKBlendMode.Multiply
gray.position = CGPoint(x: 0, y: 0)
let shine = SKSpriteNode(imageNamed: "Shine")
shine.position = CGPoint(x: 0, y: 0)
shine.blendMode = SKBlendMode.Screen
shine.setScale(3.0)
effect.addChild(gray)
effect.addChild(shine)
self.world.addChild(effect)
This works as desired but has a somewhat significant impact on the frame rate. Considering that there might be additional effects like these from e. g. torches or other light sources on the map, I expect the frame rate to drop even more.
Next, I wanted to make the light flicker, so I assigned a random alpha value to the shine sprite in the update function. This really killed the frame rate, letting it drop instantly to about 4 frames.
I have read about SKEffectNodes and their heavy impact on the frame rate. Is there any other way to achieve this sort of fullscreen alpha blending, that is reasonably fast, even with multiple "lights"?
Any advice is truly appreciated.

Could this be solved with a SKLightNode perhaps?
https://developer.apple.com/library/prerelease/ios/documentation/SpriteKit/Reference/SKLightNode_Ref/index.html
Here's an article about how to use the SKLightNode:
http://www.ymc.ch/en/playing-with-ios-8-sprite-kit-and-sklightnode
Hope that's of any usage to you

Related

SKSpriteNode not responding to set color & blendModeFactor (SpriteKit & Swift)

I know there are several other posts about this, but my case is kinda specific, i haven't seen this one yet.
I have in my game a ball-shaped sprite, that whenever I tap on it, I would like to add a colorised version of the very same sprite but with an effect of fadeIn and fadeOut.
Going to give you an example code:
self.ball = SKSpriteNode(imageNamed: "ball")
self.ball.position = CGPoint(x: midX, y: midY)
self.ball.zPosition = 1
self.ball.size = CGSize(width: 100, height: 100)
self.touchEffect = SKSpriteNode(imageNamed: "ball")
self.touchEffect.position = CGPoint(x: 0, y: 0)
self.touchEffect.zPosition = 2
self.touchEffect.size = CGSize(width: 100, height: 100)
self.touchEffect.color = UIColor.whiteColor()
self.touchEffect.blendColorFactor = 1
self.touchEffect.alpha = 0
self.ball.addChild(self.touchEffect)
self.addChild(self.ball)
Now, up to this point... I can't even see the touchEffect sprite colorised (if I put alpha to 1), but the same color of the original sprite. Why is this?
At the touchesBegan I do something like this:
func showTapEffect() {
let fadeIn = SKAction.fadeInWithDuration(0.3)
let fadeOut = SKAction.fadeOutWithDuration(0.3)
let sequence = SKAction.sequence([fadeIn,fadeOut])
self.touchEffect.runAction(sequence)
}
I also used it in different scenarios within my very same game, and it worked. Don't know why this unique case doesn't. Any hint? If you need more example code, let me know, is not a copy paste of my current code tho, I just typed it from my memory so you might see a typo error in there. But the code is very alike.
(And my sprite isn't dark or black)
Thanks in advance.
I am posting this an an answer because it is too large for comments, and I can post code in here if need be.
I just reread your thing like 3 times over, You are blending with White, what color are you expecting to get? if you blend Blue and White, you get Blue, if you blend Purple and Blue, You would get Blue. If you blend Blue and Gray, you get a darker Blue. It is all percentage multiplication. I do not believe you get a blend mode to pick from with colors. How it works is it takes the color of each pixel, and on the pixel, breaks it up into R,G,B, then it takes your color, and breaks that up into R G B (lets call it CR CG CB). The math becomes (R * CR,G * CG,B * CB) on a per pixel level.
You are doing (R * 1,G * 1,B * 1) which is (R,G,B)
If you want to colorize your sprite, then you need your sprite to be a gray scale image, and use colors only when you want them to stay that color (To a degree, because blending will still apply to them, you need to figure out the math on how you want it to blend)

SceneKit: Make blocks more lifelike or 3D-like

The code below is used to create a scene and create blocks in SceneKit. The blocks come out looking flat and not "3D enough" according to our users. Screenshots 1-2 show our app.
Screenshots 3-5 show what users expect the blocks to look like, that is more 3D-like.
After speaking to different people, there are different opinions about how to render blocks that look more like screenshots 3-5. Some people say use ambient occlusion, others say voxel lighting, some say use spot lighting and use shadows, or directional lighting.
We previously tried adding omni lighting, but that didn't work so it was removed. As you can see in the code, we also experimented with an ambient light node but that also didn't yield the right results.
What is the best way to render our blocks and achieve a comparable look to screenshots 3-5?
Note: we understand the code is not optimized for performance, i.e., that polygons are shown that should not be shown. That is okay. The focus is not on performance but rather on achieving more 3D-like rendering. You can assume some hard limit on nodes, like no more than 1K or 10K in a scene.
Code:
func createScene() {
// Set scene view
let scene = SCNScene()
sceneView.jitteringEnabled = true
sceneView.scene = scene
// Add camera node
sceneView.pointOfView = cameraNode
// Make delegate to capture screenshots
sceneView.delegate = self
// Set ambient lighting
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = SCNLightTypeAmbient
ambientLightNode.light!.color = UIColor(white: 0.50, alpha: 1.0)
//scene.rootNode.addChildNode(ambientLightNode)
//sceneView.autoenablesDefaultLighting = true
// Set floor
setFloor()
// Set sky
setSky()
// Set initial position for user node
userNode.position = SCNVector3(x: 0, y: Float(CameraMinY), z: Float(CameraZoom))
// Add user node
scene.rootNode.addChildNode(userNode)
// Add camera to user node
// zNear fixes white triangle bug while zFar fixes white line bug
cameraNode.camera = SCNCamera()
cameraNode.camera!.zNear = Double(0.1)
cameraNode.camera!.zFar = Double(Int.max)
cameraNode.position = SCNVector3(x: 0, y: 0, z: 0) //EB: Add some offset to represent the head
userNode.addChildNode(cameraNode)
}
private func setFloor() {
// Create floor geometry
let floorImage = UIImage(named: "FloorBG")!
let floor = SCNFloor()
floor.reflectionFalloffEnd = 0
floor.reflectivity = 0
floor.firstMaterial!.diffuse.contents = floorImage
floor.firstMaterial!.diffuse.contentsTransform = SCNMatrix4MakeScale(Float(floorImage.size.width)/2, Float(floorImage.size.height)/2, 1)
floor.firstMaterial!.locksAmbientWithDiffuse = true
floor.firstMaterial!.diffuse.wrapS = .Repeat
floor.firstMaterial!.diffuse.wrapT = .Repeat
floor.firstMaterial!.diffuse.mipFilter = .Linear
// Set node & physics
// -- Must set y-position to 0.5 so blocks are flush with floor
floorLayer = SCNNode(geometry: floor)
floorLayer.position.y = -0.5
let floorShape = SCNPhysicsShape(geometry: floor, options: nil)
let floorBody = SCNPhysicsBody(type: .Static, shape: floorShape)
floorLayer.physicsBody = floorBody
floorLayer.physicsBody!.restitution = 1.0
// Add to scene
sceneView.scene!.rootNode.addChildNode(floorLayer)
}
private func setSky() {
// Create sky geometry
let sky = SCNFloor()
sky.reflectionFalloffEnd = 0
sky.reflectivity = 0
sky.firstMaterial!.diffuse.contents = SkyColor
sky.firstMaterial!.doubleSided = true
sky.firstMaterial!.locksAmbientWithDiffuse = true
sky.firstMaterial!.diffuse.wrapS = .Repeat
sky.firstMaterial!.diffuse.wrapT = .Repeat
sky.firstMaterial!.diffuse.mipFilter = .Linear
sky.firstMaterial!.diffuse.contentsTransform = SCNMatrix4MakeScale(Float(2), Float(2), 1);
// Set node & physics
skyLayer = SCNNode(geometry: sky)
let skyShape = SCNPhysicsShape(geometry: sky, options: nil)
let skyBody = SCNPhysicsBody(type: .Static, shape: skyShape)
skyLayer.physicsBody = skyBody
skyLayer.physicsBody!.restitution = 1.0
// Set position
skyLayer.position = SCNVector3(0, SkyPosY, 0)
// Set fog
/*sceneView.scene?.fogEndDistance = 60
sceneView.scene?.fogStartDistance = 50
sceneView.scene?.fogDensityExponent = 1.0
sceneView.scene?.fogColor = SkyColor */
// Add to scene
sceneView.scene!.rootNode.addChildNode(skyLayer)
}
func createBlock(position: SCNVector3, animated: Bool) {
...
// Create box geometry
let box = SCNBox(width: 1.0, height: 1.0, length: 1.0, chamferRadius: 0.0)
box.firstMaterial!.diffuse.contents = curStyle.getContents() // "curStyle.getContents()" either returns UIColor or UIImage
box.firstMaterial!.specular.contents = UIColor.whiteColor()
// Add new block
let newBlock = SCNNode(geometry: box)
newBlock.position = position
blockLayer.addChildNode(newBlock)
}
Screenshots 1-2 (our app):
Screenshots 3-5 (ideal visual representation of blocks):
I still think there's a few easy things you can do that will make a big difference to how your scene is rendered. Apologies for not using your code, this example is something I had lying around.
Right now your scene is only lit by an ambient light.
let aLight = SCNLight()
aLight.type = SCNLightTypeAmbient
aLight.color = UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 1.0)
let aLightNode = SCNNode()
aLightNode.light = aLight
scene.rootNode.addChildNode(aLightNode)
If I use only this light in my scene I see the following. Note how all faces are lit the same irrespective of the direction they face. Some games do pull off this aesthetic very well.
The following block of code adds a directional light to this scene. The transformation applied in this light won't be valid for your scene, it's important to orientate the light according to where you want the light coming from.
let dLight = SCNLight()
dLight.type = SCNLightTypeDirectional
dLight.color = UIColor(red: 0.6, green: 0.6, blue: 0.6, alpha: 1.0)
let dLightNode = SCNNode()
dLightNode.light = dLight
var dLightTransform = SCNMatrix4Identity
dLightTransform = SCNMatrix4Rotate(dLightTransform, -90 * Float(M_PI)/180, 1, 0, 0)
dLightTransform = SCNMatrix4Rotate(dLightTransform, 37 * Float(M_PI)/180, 0, 0, 1)
dLightTransform = SCNMatrix4Rotate(dLightTransform, -20 * Float(M_PI)/180, 0, 1, 0)
dLightNode.transform = dLightTransform
scene.rootNode.addChildNode(dLightNode)
Now we have shading on each of the faces based on their angle relative to the direction of the light.
Currently SceneKit only supports shadows if you're using the SCNLightTypeSpot. Using a spotlight means we need to both orientate (as per directional light) and position it. I use this as a replacement for the directional light.
let sLight = SCNLight()
sLight.castsShadow = true
sLight.type = SCNLightTypeSpot
sLight.zNear = 50
sLight.zFar = 120
sLight.spotInnerAngle = 60
sLight.spotOuterAngle = 90
let sLightNode = SCNNode()
sLightNode.light = sLight
var sLightTransform = SCNMatrix4Identity
sLightTransform = SCNMatrix4Rotate(sLightTransform, -90 * Float(M_PI)/180, 1, 0, 0)
sLightTransform = SCNMatrix4Rotate(sLightTransform, 65 * Float(M_PI)/180, 0, 0, 1)
sLightTransform = SCNMatrix4Rotate(sLightTransform, -20 * Float(M_PI)/180, 0, 1, 0)
sLightTransform = SCNMatrix4Translate(sLightTransform, -20, 50, -10)
sLightNode.transform = sLightTransform
scene.rootNode.addChildNode(sLightNode)
In the above code we first tell the spotlight to cast a shadow, by default all nodes in your scene will then cast a shadow (this can be changed). The zNear and zFar settings are also important and must be specified so that the nodes casting shadows are within this range of distance from the light source. Nodes outside this range will not cast a shadow.
After shading/shadows there's a number of other effects you can apply easily. Depth of field effects are available for the camera. Fog is similarly easy to include.
scene.fogColor = UIColor.blackColor()
scene.fogStartDistance = 10
scene.fogEndDistance = 110
scenekitView.backgroundColor = UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 1.0)
Update
Turns out you can get shadows from a directional light. Modifying the spotlight code from above by changing its type and setting the orthographicScale. Default value for orthographicScale seems to be 1.0, obviously not suitable for scenes much larger than 1.
let dLight = SCNLight()
dLight.castsShadow = true
dLight.type = SCNLightTypeDirectional
dLight.zNear = 50
dLight.zFar = 120
dLight.orthographicScale = 30
let dLightNode = SCNNode()
dLightNode.light = dLight
var dLightTransform = SCNMatrix4Identity
dLightTransform = SCNMatrix4Rotate(dLightTransform, -90 * Float(M_PI)/180, 1, 0, 0)
dLightTransform = SCNMatrix4Rotate(dLightTransform, 65 * Float(M_PI)/180, 0, 0, 1)
dLightTransform = SCNMatrix4Rotate(dLightTransform, -20 * Float(M_PI)/180, 0, 1, 0)
dLightTransform = SCNMatrix4Translate(dLightTransform, -20, 50, -10)
dLightNode.transform = dLightTransform
scene.rootNode.addChildNode(dLightNode)
Produces the following image.
The scene size is 60x60, so in this case setting the orthographic scale to 30 produces shadows for the objects close to the light. The directional light shadows appear different to the spot light due to the difference in projections (orthographic vs perspective) used when rendering the shadow map.
Ambient occlusion calculations will give you the best results, but is very expensive, particularly in a dynamically changing world, which it looks like this is.
There are several ways to cheat, and get the look of Ambient occlusion.
Here's one:
place transparent, gradient shadow textures on geometry "placards" used to place/present the shadows at the places required. This will involve doing checks of geometry around the new block before determining what placards to place, with which desired texture for the shadowing. But this can be made to look VERY good, at a very low cost in terms of polygons, draw calls and filtrate. It's probably the cheapest way to do this, and have it look good/great, and can only really be done (with a good look) in a world of blocks. A more organic world rules this technique out. Please excuse the pun.
Or, another, similar: Place additional textures onto/into objects that have the shadow, and blend this with the other textures/materials in the object. This will be a bit fiddly, and I'm not an expert on the powers of materials in Scene Kit, so can't say for sure this is possible and/or easy in it.
Or: Use a blend of textures with a vertex shader that's adding a shadow from the edges that touch or otherwise need/desire a shadow based on your ascertaining what and where you want shadows and to what extent. Will still need the placards trick on the floors/walls unless you add more vertices inside flat surfaces for the purpose of vertex shading for shadows.
Here's something I did for a friend's CD cover... shows the power of shadows. It's orthographic, not true 3D perspective, but the shadows give the impression of depths and create the illusions of space:
all answers above (or below) seem to be good ones (at the time of this writing) however,
what I use (just for setting up a simple scene) is one ambient light (lights everything in all directions) to make things visible.And then one omnidirectional light positioned somewhere in the middle of your scene, the omni light can be raised up (Y up I mean) to light the whole of your scene. The omni light gives the user a sense of shading and the ambient light makes it more like a sun light.
for example:
Imagine sitting in a living room (like I am right now) and the sun-light peers through the window to your right.
You can obviously see a shadow of an area that the couch is not getting sun light, however you can still see details of what is in the shadow.
Now! all the sudden your wold gets rid of ambient light BOOM! The shadow is now pitch black, you can't anymore see what is in the shadow.
Or say the ambient light came back again (what a relief), but all the sudden the omni light stopped working. (probably my fault :( ) Everything now is lighted the same, no shadow, no difference, but if you lay a paper on the table, and look at it from above, there is no shadow! So you think it is part of the table! In a world like this your rely on the contour of something in order to see it- you would have to look at the table from side view, to see the thickness of the paper.
Hope this helps (at least a little)
Note: ambient lighting give a similar effect to emissive material

Moving SCNLight with SCNAction

I have a spotlight, created with the code beneath, casting shadows on all of my nodes:
spotLight.type = SCNLightTypeSpot
spotLight.spotInnerAngle = 50.0
spotLight.spotOuterAngle = 150.0
spotLight.castsShadow = true
spotLight.shadowMode = SCNShadowMode.Deferred
spotlightNode.light = spotLight
spotlightNode.eulerAngles = SCNVector3(x: GLKMathDegreesToRadians(-90), y: 0, z: 0)
spotlightNode.position = levelData.coordinatesForGridPosition(column: 0, row: playerGridRow)
spotlightNode.position.y = 1.5
rootNode.addChildNode(spotlightNode)
The scene is moving along the z axis, and the camera has an infinite animation that makes it move:
let moveAction = SCNAction.moveByX(0.0, y: 0.0, z: CGFloat(-GameVariables.segmentSize / 2), duration: 2.0)
cameraContainerNode.runAction(SCNAction.repeatActionForever(moveAction))
As the camera moves though, the light doesn't, so after a while, the whole scene is dark. I want to move the light with the camera, however if I apply to the light node the same moving animation, all the shadows start to flicker. I tried to change the SCNShadowMode to Forward and the light type to Directional, but the flickering is still there. With directional, I actually loose most of my shadows. If I create a new light node later on, it will seem that I have two "suns", which of course is impossible. The final aim is simply to have an infinite light that shines parallel to the scene from the left, casting all the shadows to the right. Any ideas?
Build a node tree to hold both spotlight and camera.
Create, say, cameraRigNode as an SCNNode with no geometry. Create cameraContainerNode and spotlightNode the same way you are now. But make them children of cameraRigNode, not the scene's root node.
Apply moveAction to cameraRigNode. Both the camera and the light will now move together.

SKLIghtNode with Swift

I have a player in the center of the screen, which I'd like to be the "source" of the light, so every other sprite, such as flying enemies, are casting a shadow.
I have this code:
light.categoryBitMask = 1
light.falloff = 1
light.shadowColor = UIColor.blackColor()
light.position = CGPoint(x: size.width / 2, y: size.height / 2)
addChild(light)
I'm totally new to Swift, never had any experience with SpriteKit, so I figured out that I should add this to every Sprite:
enemyOne.shadowCastBitMask = 1
What I get is quite interesting. The screen flickers everytime an enemy sprite is being spawned. But other than that, and a really small light in the center of the screen, where my player is, I'm not getting any shadows.
To make my plan more clear, I'd like to make it look like this:
This is what it looks like, as requested:

Random movements / turbulences - SWIFT

I'm developing a game on Iphone and Ipad like a space invaders.
Balloons to destroy are falling from the top of the screen in a straight line.
Here my codes to add them :
func addBalloonv(){
var balloonv:SKSpriteNode = SKSpriteNode (imageNamed: "ballonvert.png")
balloonv.physicsBody = SKPhysicsBody (circleOfRadius: balloonv.size.width/2)
balloonv.physicsBody.dynamic = true
balloonv.physicsBody.categoryBitMask = balloonCategory | greenCategory
balloonv.physicsBody.contactTestBitMask = flechetteCategory
balloonv.physicsBody.collisionBitMask = balloonCategory
balloonv.physicsBody.mass = 1
balloonv.physicsBody.restitution = 1
balloonv.physicsBody.allowsRotation = true
let minX = balloonv.size.width/2
let maxX = self.frame.size.width - balloonv.size.width/2
let rangeX = maxX - minX
let position:CGFloat = CGFloat(arc4random()) % CGFloat(rangeX) + CGFloat(minX)
balloonv.position = CGPointMake(position, self.frame.size.height+balloonv.size.height)
self.addChild(balloonv)
I have one func by balloon color.
So for the moment they move in straight line and I'm looking for random movements (with turbulences like balloon in air) from the top and both sides.
How can I do that?
Thank you very much !!
This is exactly what the new Physics Fields feature in SpriteKit (as of iOS 8 / OS X Yosemite) is for. These let you apply different kinds of forces to all physics bodies in region, like gravity, drag, and turbulence. See the SKFieldNode class docs for details.
Fields are a kind of node, so to get what you're after, you'd add one noise (or turbulence) field to your scene, covering the area that the balloons fall through, and it'll perturb the path of each balloon that passes. The simplest way to do it goes something like this:
let field = SKFieldNode.noiseFieldWithSmoothness(0.5, animationSpeed: 0.1)
scene.addChild(field)
You'll want to tweak the smoothness, animation speed, and field.strength till you get just the level of noise you want. You might also look into whether you want just a noise field, which applies random forces in random directions, or a turbulence field, which does the same thing, but with forces that get stronger when bodies are moving faster.
The above code gets you a field whose region of effect is infinite. You might want to limit it to a specific area (for example, so it doesn't keep knocking your balloons around after they land). I did this to make a field that covers only the top 3/4 of a 300x200 scene:
field.region = SKRegion(size: CGSize(width: 300, height: 100))
field.position = CGPoint(x: 150, y: 150)

Resources