SceneKit Node Lighting - swift

I have a scenekit node that I want to add a glow effect to.
let newScene = SCNScene(named: "art.scnassets/cubeOne.scn")!
let newNode = (newScene.rootNode.childNode(withName: "cubeOneNode", recursively: false))!
newNode.geometry?.firstMaterial?.selfIllumination = UIColor.red
newNode.geometry?.firstMaterial?.ambientOcclusion = UIColor.red
sceneView.scene.rootNode.addChildNode(newNode)

You could achieve this by using a so called CIFilter and attach it to you node - but I personally don't recommend this, because it is incredibly performance intense and uses like three times more memory.
I recommend configuring this on your camera. It will also cost you more GPU performance and some memory, but not as much as a CIFilter does. Give it a try:
camera.wantsHDR = true
camera.bloomThreshold = 0.8
camera.bloomIntensity = 2
camera.bloomBlurRadius = 16.0
camera.wantsExposureAdaptation = false
and then use the emission.intensity property of the node material. Set it between 2.0 to 5.0, depending on how much you want the effect to be visible. I usually use the .physicallyBased lighting option for my on scene stuff. (I never tried to use it with selfIllumination.)
PS: Using the red color does not add a very intense effect. Really intense is cyan or green. Using the red color you might screw up the emission.intensity value to 10.0 or higher.

Related

SceneKit & Swift: How to use SCN object as floor?

I have created a mountain landscape in Blender and imported it into my Xcode project.
https://github.com/QBeukelman/Mars_Curiosity.git
I would like to drive the SCNVehicle on the landscape as if it were the floor of the scene (landscapeMountains.scn).
The vehicle falls through the landscape!
I have tried the following to solve the issue, but without success:
Different combinations of static, kinetic and dynamic physics bodies.
Using different collision margins such as 0.01
Using category and collision masks (see image)
Does anyone know how to use a scn object as a floor using SceneKit and Xcode?
category & collision bit masks
Replace your entire "// add mountain" code block (line 185 to 191 in GameViewController.swift) with this:
func floorPhysBody(type: SCNPhysicsBodyType = .static, shape: SCNGeometry, scale: SCNVector3 = SCNVector3(1.0,1.0,1.0)) -> SCNPhysicsBody {
// Create Physics Body and set Physics Properties
let body = SCNPhysicsBody(type: type, shape: SCNPhysicsShape(geometry: shape, options: [SCNPhysicsShape.Option.type: SCNPhysicsShape.ShapeType.concavePolyhedron, SCNPhysicsShape.Option.scale: scale]))
// Physics Config
body.isAffectedByGravity = false
return body
}
// configure Physics Floor
let mountain = scene!.rootNode.childNode(withName: "mountain", recursively: true)!
mountain.physicsBody = floorPhysBody(type: .static, shape: mountain.geometry!)
In addition: Your Mountain Geometry is very large (file is over 50MB in size for some geometry). I recommend you to reduce this, to avoid memory issues when your game grows. Also: try to avoid texture sizes bigger than 1024x1024. Use textures sizes from a power of 2 if possible - like 128px, 256px, 512px, 1024px squared.
Have fun with your project.

SKPhysicBodies appear to be slightly off-place

Edit: I have been able to solve this problem by using PhysicsEditor to make a polygonal physicsbody instead of using SKPhysicsBody(... alphaThreshold: ... )
--
For some reason I'm having trouble with what I'm assuming is SKPhysicBodies being slightly off-place. While using showPhysics my stationary obstacle nodes appear to have their physicbodies in the correct position, however I am able to trigger collisions without actually touching the obstacle. If you look at the image below it shows where I have found the physicsbodies to be off centre, despite showPhysics telling me otherwise. (Note, the player node travels in the middle of these obstacle nodes).
I also thought it would be worth noting that while the player is travelling, its physicbody appears to travel slightly ahead but I figured this is probably normal.
I also use SKPhysicsBody(... alphaThreshold: ... ) to create the physicbodies from .png images.
Cheers.
Edit: Here's how I create the obstacle physicbodies. Once they're added into the worldNode they are left alone until they need to be removed. Apart from that I don't change them in any way.
let obstacleNode = SKSpriteNode(imageNamed: ... )
obstacleNode.position = CGPoint(x: ..., y: ...)
obstacleNode.name = "obstacle"
obstacleNode.physicsBody = SKPhysicsBody(texture: obstacleNode.texture!, alphaThreshold: 0.1, size: CGSize(width: obstacleNode.texture!.size().width, height: obstacleNode.texture!.size().height))
obstacleNode.physicsBody?.affectedByGravity = false
obstacleNode.physicsBody?.isDynamic = false
obstacleNode.physicsBody!.categoryBitMask = CC.wall.rawValue
obstacleNode.physicsBody!.collisionBitMask = CC.player.rawValue
obstacleNode.physicsBody!.contactTestBitMask = CC.player.rawValue
worldNode.addChild(obstacleNode)
The player node is treated the same way, here is how the player moves.
playerNode.physicsBody?.velocity = CGVector(dx: dx, dy: dy)
I'm assuming you aren't showing the exact images that you used to create your SKSpriteNode and SKPhysicsBody instances. Since you are using a texture to define the shape of your SKPhysicsBody you are likely running up against this:
SKPhysicsBody documentation
If you do not want to create your own shapes, you can use SpriteKit to create a shape for you based on the sprite’s texture.
This is easy and convenient but it can sometimes give unexpected results depending on the textures you are using for your sprite. Perhaps try making an explicit mask or using a simple shape to represent your physics body. There are very good examples and guidelines in that documentation.
I would also follow this pattern when you set the properties on your objects:
// safely unwrap and handle failure if it fails
guard let texture = obstacleNode.texture else { return }
// create the physics body
let physicsBody = SKPhysicsBody(texture: texture,
alphaThreshold: 0.1,
size: CGSize(width: texture.size().width,
height: texture.size().height))
// safely set its properties without the need to unwrap an Optional
physicsBody.affectedByGravity = false
// set the rest of the properties
// set the physics body property on the node
obstacleNode.physicsBody = physicsBody
By setting the properties on a concrete instance of SKPhysicsBody and fully unwrapping and testing Optionals you minimize the chances for a run-time crash that may be difficult to debug.

How to draw concave shape using Stencil test on Metal

This is the first time I'm trying to use Stencil Test but I have seen some examples using OpenGL and a few on Metal but focused on the Depth test instead. I understand the theory behind the Stencil test but I don't know how to set it up on Metal.
I want to draw irregular shapes. For the sake of simplicity lets consider the following 2D polygon:
I want the stencil to pass where the number of overlapping triangles is odd, so that I can reach something like this, where the white area is the area to be ignored:
I'm doing the following steps in the exact order:
Setting the depthStencilPixelFormat:
mtkView.depthStencilPixelFormat = .stencil8
mtkView.clearStencil = .allZeros
Stencil attachment:
let textureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .stencil8, width: drawable.texture.width, height: drawable.texture.height, mipmapped: true)
textureDescriptor.textureType = .type2D
textureDescriptor.storageMode = .private
textureDescriptor.usage = [.renderTarget, .shaderRead, .shaderWrite]
mainPassStencilTexture = device.makeTexture(descriptor: textureDescriptor)
let stencilAttachment = MTLRenderPassStencilAttachmentDescriptor()
stencilAttachment.texture = mainPassStencilTexture
stencilAttachment.clearStencil = 0
stencilAttachment.loadAction = .clear
stencilAttachment.storeAction = .store
renderPassDescriptor.stencilAttachment = stencilAttachment
Stencil descriptor:
stencilDescriptor.depthCompareFunction = MTLCompareFunction.always
stencilDescriptor.isDepthWriteEnabled = true
stencilDescriptor.frontFaceStencil.stencilCompareFunction = MTLCompareFunction.equal
stencilDescriptor.frontFaceStencil.stencilFailureOperation = MTLStencilOperation.keep
stencilDescriptor.frontFaceStencil.depthFailureOperation = MTLStencilOperation.keep
stencilDescriptor.frontFaceStencil.depthStencilPassOperation = MTLStencilOperation.invert
stencilDescriptor.frontFaceStencil.readMask = 0x1
stencilDescriptor.frontFaceStencil.writeMask = 0x1
stencilDescriptor.backFaceStencil = nil
depthStencilState = device.makeDepthStencilState(descriptor: stencilDescriptor)
and lastly, Im setting the reference value and the stencil state in the main pass:
renderEncoder.setStencilReferenceValue(0x1)
renderEncoder.setDepthStencilState(self.depthStencilState)
Am I missing something because the result I got is just like there is no stencil at all. I can see some differences when changing the settings of the depth test but nothing happens when changing the settings of the stencil ...
Any clue?
Thank you in advance
You're clearing the stencil texture to 0. The reference value is 1. The comparison function is "equal". So, the comparison will fail (1 does not equal 0). The operation for when the stencil comparison fails is "keep", so the stencil texture remains 0. Nothing changes for subsequent fragments.
I would expect that you'd get no rendering, although depending on the order of your vertexes and the front-face winding mode, you may be looking at the back faces of your triangles, in which case the stencil test is effectively disabled. If you don't otherwise care about front vs. back, just set both stencil descriptors the same way.
I think you need to do two passes: first, a stencil-only render; second, the color render governed by the stencil buffer. For the stencil only, you would make the compare function .always. This will toggle (invert) the low bit for each triangle that's drawn over a given pixel, giving you an indication of even or odd count. Because neither the compare function nor the operation involve the reference value, it doesn't matter what it is.
For the second pass, you'd set the compare function to .equal and the reference value to 1. The operations should all be .keep. Also, make sure to set the stencil attachment load action to .load (not .clear).

How to Improve SKEffectNode Performance (Swift)?

so in my project I have an SKEffectNode that I use to provide a glow effect around some of my spriteNodes. I use a spriteNode (blurNode) to get the color of my obstacle and then give it to the effectNode. This works fine.
let blurNode = SKSpriteNode(imageNamed: "neonLine.png")
blurNode.color = obstacle.color
blurNode.colorBlendFactor = 1.0
blurNode.size = CGSize(width: obstacle.size.width + 30, height: obstacle.size.height + 30)
let effectNode = SKEffectNode()
effectNode.shouldRasterize = true
obstacle.addChild(effectNode)
effectNode.addChild(blurNode)
effectNode.filter = CIFilter(name: "CIGaussianBlur", withInputParameters: ["inputRadius":30])
effectNode.alpha = 1.0
My issue occurs here.
let colorFadegreen = SKAction.sequence([SKAction.colorize(with: UIColor(red: 0, green: 0.6471, blue: 0.3569, alpha: 1.0), colorBlendFactor: 1.0, duration: 3)])
obstacle.removeAllActions()
obstacle.run(colorFadegreen)
blurNode.removeAllActions()
blurNode.run(colorFadegreen)
What I want to do is have the "glow" that's around the obstacle change colors with the obstacle. That is exactly what happens; however, when I do so my frame rate drops down to 30fps.
So, my question is does anyone know how to improve the performance of this task? Or is there maybe another way I could go about doing this.
One of the ideas I thought of would be to manually blur the "neonLine.png" in photoshop and then add it to the blur node like so
let blurNode = SKSpriteNode(imageNamed: "bluredNeonLine.png"). The only thing is I can never get the blur right it always looks off.
Any help would be very much appreciated. Thanks!
EDIT:
Here are some photos of the glows in my project:
Here is the glow and lines changing color:
Three answers to the performance question with regards glows:
Use a pre-rendered glow, as per your mentioning in the question, done in Photoshop or similar bitmap editor, exported as bitmap with opacity and used as an SKSpriteNode texture, probably with additive blending for best results, and colour caste to taste.
Bake a texture of the SKEffectNode that's creating a desirable glow within SpriteKit by making it into a texture, and then loading it into an SKSpriteNode, as per this example: https://stackoverflow.com/a/40137270/2109038
Rasterise the results from your SKEffectNode and then hope your changes to colour casts don't cause re-rendering. This is shown in a wonderful extension, here: https://stackoverflow.com/a/40362874/2109038
In all cases, you're best off rendering a white glow that fades out as you like, and then applying colour blend changes to it, since SpriteKit has this built in, and it's reasonably performant in the few tests I've done. This is known as colorizing:
You can change and animate both the blend amount: https://developer.apple.com/reference/spritekit/skspritenode/1519780-colorblendfactor
and the color being blended with the texture: https://developer.apple.com/reference/spritekit/skspritenode/1519639-color

Prevent SKPhysicsBody From Pushing SKSpriteNode Upwards

I currently have two SKSpriteNodes on top of each other like so (The white part is one and the brown round circle part is the other one):
The white part is positioned 1/3 the way down on top of the round brown sprite node. In the picture, the brown round part has a SKPhysicsBody applied to it already as seen by the light blue outline around it. When I add a SKPhysicsBody around the top ovalish white part it pushes it up and not in the position I wanted it.
How can I have a SKPhysics body coving both bodies of sprites but not have the physics bodies push on one another which makes the white part move upwards? I would like the white part to stay in the position it was in the first image.
Thanks for anyone help!
Here's the code I used for the SKPhysicsBody's:
// create, position, scale & add the round body
roundBody = SKSpriteNode( imageNamed: "roundBody" )
roundBody.position = CGPoint( x: 207, y: 70 )
roundBody.zPosition = 1
roundBody.xScale = 0.3
roundBody.yScale = 0.3
// add sprite node to view
self.addChild( roundBody )
// create, position, scale & add the head
theHead!.position = CGPoint( x: 207, y: roundBody.frame.maxY / 1.15 )
theHead!.zPosition = 2
theHead!.xScale = 0.3
theHead!.yScale = 0.3
// setting up a SKPhysicsBody for the round body
roundBody.physicsBody = SKPhysicsBody( circleOfRadius: roundBody.size.width / 4 )
roundBody.physicsBody!.dynamic = true
roundBody.physicsBody!.affectedByGravity = true
roundBody.physicsBody!.allowsRotation = false
roundBody.physicsBody!.pinned = false
// setting up a SKPhysicsBody for the head
theHead!.physicsBody = SKPhysicsBody(circleOfRadius: theHead!.size.width / 2 )
theHead!.physicsBody!.dynamic = true
theHead!.physicsBody!.affectedByGravity = false
theHead!.physicsBody!.allowsRotation = false
theHead!.physicsBody!.pinned = false
I was able to figure out that if you use SKPhysicsJointPin it does the exact thing I needed! (Which was to basically pin a sprite head on it's body and share a physics body)
let joinTogether = SKPhysicsJointPin.jointWithBodyA(
roundBody.physicsBody!,
bodyB:theHead!.physicsBody!,
anchor: GPointMake(CGRectGetMidX(roundBody.frame),
CGRectGetMinY(theHead!.frame)))
scene!.physicsWorld.addJoint(joint)
Hope this helps someone in the future!
If you never want it to move, set it's .dynamic property to false. Then other objects may or may not bounce/collide with it (depending upon their collisionBitMask) but it won't move in response to those collisions.
Your own answer is correct and a better solution but just to explain further.
The reason of the bodies colliding is that by default a physics body's collision bit mask is set to all categories which means it will collide with everything. In your code you are not calling
roundBody.physicsBody?.collisionBitMask = ...
which is why its using the default values.
To change that you could give your body and head a different collisionBitMask.
Im sure you will deal with this sooner or later when you handle collisions
Also as a tip it's a better idea to not force unwrap the physics bodies unless you have too, even though you know they exist. So you should replace your ! with ? whenever possible.