Cast shadow on all nodes SCNLight - ios

I am trying to cast a short dark shadow from the left of the scene. My light settings are the following:
func setupLights() {
// Create shadow
let spotLight = SCNLight()
spotLight.type = SCNLightTypeSpot
spotLight.spotInnerAngle = 30.0
spotLight.spotOuterAngle = 80.0
spotLight.castsShadow = true
let spotLightNode = SCNNode()
spotLightNode.light = spotLight
spotLightNode.position = SCNVector3(1.5, 1.5, 1.5)
rootNode.addChildNode(spotLightNode)
// Create ambient light
let ambientLight = SCNLight()
ambientLight.type = SCNLightTypeAmbient
ambientLight.color = UIColor.whiteColor()
let ambientLightNode = SCNNode()
ambientLightNode.name = "AmbientLight"
ambientLightNode.light = ambientLight
ambientLightNode.castsShadow = true
rootNode.addChildNode(ambientLightNode)
// Create an omni-directional light
let omniLight = SCNLight()
omniLight.type = SCNLightTypeOmni
omniLight.color = UIColor.whiteColor()
let omniLightNode = SCNNode()
omniLightNode.name = "OmniLight"
omniLightNode.light = omniLight
omniLightNode.position = SCNVector3(x: -10.0, y: 20, z: 10.0)
omniLightNode.castsShadow = true
rootNode.addChildNode(omniLightNode)
}
With this code I have a bright scene with some very light and long shadow not coming from the left. I tried to alter the position which currently is SCNVector3(1.5, 1.5, 1.5), however whichever other position I put, the shadow just disappears. Any ideas?

If you want directional light on all parts of the scene, use SCNLightTypeSpot for your light's type. Or maybe SCNLightTypeDirectional.
Per documentation for SCNLight.castsShadow:
Geometries illuminated by the light cast shadows only if the value of this property is YES and the type property of the light is SCNLightTypeSpot. The default value is NO.
However, #mnuages states in SceneKit shadows on wall that directional lights can cast shadows.

Related

Swift - SceneKit - How to properly orient Camera and Light Node with SCNCylinder (that's a 3D rendering of coordinate path)

I'm trying to recreate a feature like this:
Where I take coordinates and elevation points of a hiking path and create a SceneKit rendering and animation. The orange ball animates along the white path while the entire object slowly rotates on the X - axis 360 degrees and loops.
I've gotten the path to plot accurately as a SCNCylinder. However I cannot figure out how to correctly orient the camera and lighting to recreate the view as shown in the first image. I've unfortunately sunk way too much time into trying and have gotten no where! I'm quite familiar with Swift overall but no experience whatsoever with SceneKit and am trying to learn. I'm quite confused with properly positioning the camera, light and X number of cylinders that make up the path.
Here's what I've got so far:
func sceneKit () {
let sceneView = SCNView(frame: CGRect(x: 0, y: 0, width: self.canvasView.frame.width, height: self.canvasView.frame.height))
self.canvasView.addSubview(sceneView)
let scene = SCNScene()
sceneView.scene = scene
let camera = SCNCamera()
let cameraNode = SCNNode()
cameraNode.camera = camera
let ambientLight = SCNLight()
ambientLight.type = SCNLight.LightType.ambient
ambientLight.color = UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 1.0)
cameraNode.light = ambientLight
let light = SCNLight()
light.type = SCNLight.LightType.spot
light.spotInnerAngle = 30.0
light.spotOuterAngle = 80.0
light.castsShadow = true
let lightNode = SCNNode()
lightNode.light = light
var pathNodeLst = self.get3DPath(scene: scene)
var lastPost = SCNVector3()
for pathNode in pathNodeLst {
scene.rootNode.addChildNode(pathNode)
lastPost = pathNode.position
}
cameraNode.position = lastPost
lightNode.position = lastPost
scene.rootNode.addChildNode(lightNode)
scene.rootNode.addChildNode(cameraNode)
sceneView.allowsCameraControl = true
sceneView.backgroundColor = UIColor.blue
}
But this not really even close to what I want and have totally hit a dead end in better ideas.
The blue background is just for testing and the path in the blue image is correct - it's a different hike than the black example.
Basically I don't know how to get the point of view to face above and on an angle like the black image. How to achieve the cascading white shadow below the path is also a complete mystery.
Any help would be desperately appreciated - thanks!
Here's the best I was able to get (by omitting the camera and light nodes completely and then manually moving the camera with iOS touch gestures)

Issue with adding shadow to a SCNPlane with clear background

I am trying to add a shadow on SCNPlane, everything works fine but I cannot make SCNPlane transparent to show only the shadow not with the white background. here is the code:
let flourPlane = SCNPlane()
let groundPlane = SCNNode()
let clearMaterial = SCNMaterial()
clearMaterial.lightingModel = .constant
//clearMaterial.colorBufferWriteMask = []
clearMaterial.writesToDepthBuffer = true
clearMaterial.transparencyMode = .default
flourPlane.materials = [clearMaterial]
groundPlane.scale = SCNVector3(200, 200, 200)
groundPlane.geometry = flourPlane
groundPlane.castsShadow = false
groundPlane.eulerAngles = SCNVector3Make(-Float.pi/2, 0, 0)
groundPlane.position = SCNVector3(x: 0.0, y: shadowY, z: 0.0)
node.addChildNode(groundPlane)
// Create a ambient light
let ambientLight = SCNNode()
ambientLight.light = SCNLight()
ambientLight.light?.shadowMode = .deferred
ambientLight.light?.color = UIColor.white
ambientLight.light?.type = SCNLight.LightType.ambient
ambientLight.position = SCNVector3(x: 0,y: 5,z: 0)
// Create a directional light node with shadow
let myNode = SCNNode()
myNode.light = SCNLight()
myNode.light?.type = .directional
myNode.light?.castsShadow = true
myNode.light?.automaticallyAdjustsShadowProjection = true
myNode.light?.shadowSampleCount = 80
myNode.light?.shadowBias = 1
myNode.light?.orthographicScale = 1
myNode.light?.shadowMode = .deferred
myNode.light?.shadowMapSize = CGSize(width: 2048, height: 2048)
myNode.light?.shadowColor = UIColor.black.withAlphaComponent(0.5)
myNode.light?.shadowRadius = 10.0
myNode.eulerAngles = SCNVector3Make(-Float.pi/2, 0, 0)
node.addChildNode(ambientLight)
node.addChildNode(myNode)
When I add clearMaterial.colorBufferWriteMask = [] shadow disappears! how can create a transparent material to show only the shadow.
The white area is SCNPlane and the red is the background.
You can set materials "color" to .clear like below:
extension SCNMaterial {
convenience init(color: UIColor) {
self.init()
diffuse.contents = color
}
convenience init(image: UIImage) {
self.init()
diffuse.contents = image
}
}
let clearColor = SCNMaterial(color: .clear)
flourPlane.materials = [clearColor]
I have found a trick in another SO answer.
Adjust the floor plane's blendMode to alter how its pixels are combined with the underlying pixels.
let clearMaterial = SCNMaterial()
// alter how pixels affect underlying pixels
clearMaterial.blendMode = .multiply
// use the simplest shading that shows a shadow (so not .constant!)
clearMaterial.lightingModel = .lambert
// unobstructed parts are render pure white and thus have no effect
clearMaterial.diffuse.contents = UIColor.white
floorPlane.firstMaterial = clearMaterial
You can see the effect in this image. The main plane is invisible, yet the shadow is visible. The cube, its shadow and the ball grid have the same XZ position. Notice how the "shadow" affects underlying geometries and the scene background.

Directional Light is affected by Camera's movement

I want to achieve the shadows effect like in IKEA Place, where the shadow is stick with the virtual object.
Here is my implementation. I added the direction light and shadow plane to the virtual object. When I moved the virtual object in the scene, shadow is not positioned right under the bottom of the virtual object.
Here is my code:
func setupShadows() {
let flourPlane = SCNFloor()
let groundPlane = SCNNode()
let groundMaterial = SCNMaterial()
groundMaterial.lightingModel = .constant
groundMaterial.writesToDepthBuffer = true
groundMaterial.readsFromDepthBuffer = true
groundMaterial.colorBufferWriteMask = []
flourPlane.materials = [groundMaterial]
groundPlane.geometry = flourPlane
addChildNode(groundPlane)
// Create a ambient light
let ambientLight = SCNNode()
ambientLight.light = SCNLight()
ambientLight.light?.shadowMode = .deferred
ambientLight.light?.color = UIColor.white
ambientLight.light?.type = .ambient
addChildNode(ambientLight)
// Create a directional light node with shadow
let directionalLightNode = SCNNode()
directionalLightNode.light = SCNLight()
directionalLightNode.light?.type = .directional
directionalLightNode.light?.color = UIColor.white
directionalLightNode.light?.castsShadow = true
directionalLightNode.light?.automaticallyAdjustsShadowProjection = true
directionalLightNode.light?.shadowMode = .deferred
directionalLightNode.light?.categoryBitMask = -1
directionalLightNode.light?.shadowColor = UIColor.black.withAlphaComponent(0.4)
directionalLightNode.rotation = SCNVector4(x: 1, y: 0, z: 0, w: .pi * 1.5)
addChildNode(directionalLightNode)
}

I want a shadow on the floor for all my objects in SWIFT / Scenekit

I'm working on an ARKit project in swift.
In SWIFT I add objects to the scene. What I want to do with those objects is show a shadow beneath them to make them more realistic. I tried some things and those all didn't work. I'll explain that here.
Please understand that I'm still new to swift. I'm creating objects programmatically in SWIFT. I thought I could do it by creating a invisible plane underneath all the objects and place a light above the scene. I learned that I can cast a shadow on an invisible plane in the Scene editor from Xcode by unchecking the "write to color" values red, green, blue and alpha. I placed a spot light above it and it worked. Now I want to do this programatically.
In swift I created the light's and the plane as shown below. I don't use a spot anymore because the scene is so large. That's why I create a really large plane. I added those to light's ambient and directional. Ambient so it doesn't look black on the side and directional so the shadow is shown. This doesn't. The objects look weirdly lit and there are no shadows.
let worldGroundPlaneGeometry = SCNPlane(width: 1000, height: 1000)
worldGroundPlaneGeometry.firstMaterial?.colorBufferWriteMask = SCNColorMask(rawValue: 0)
let worldGroundPlane = SCNNode()
worldGroundPlane.geometry = worldGroundPlaneGeometry
worldGroundPlane.position = worldPosition
worldGroundPlane.castsShadow = true
worldGroundPlane.eulerAngles = SCNVector3(Float.pi / 2, 0, 0)
self.addChildNode(worldGroundPlane)
// Create a ambient light
let ambientLight = SCNNode()
ambientLight.light = SCNLight()
ambientLight.light?.color = UIColor.white
ambientLight.light?.type = SCNLight.LightType.ambient
ambientLight.position = SCNVector3(x: 0,y: 5,z: 0)
// Create a directional light node with shadow
let directionalNode = SCNNode()
directionalNode.light = SCNLight()
directionalNode.light?.type = SCNLight.LightType.directional
directionalNode.light?.color = UIColor.white
directionalNode.light?.castsShadow = true
directionalNode.light?.automaticallyAdjustsShadowProjection = true
directionalNode.light?.shadowSampleCount = 64
directionalNode.light?.shadowMode = .deferred
directionalNode.light?.shadowMapSize = CGSize(width: 2048, height: 2048)
directionalNode.light?.shadowColor = UIColor.black.withAlphaComponent(0.75)
directionalNode.position = SCNVector3(x: 0,y: 5,z: 0)
// Add the lights to the container
self.addChildNode(ambientLight)
self.addChildNode(directionalNode)
It needs to look like this in the whole scene:
P.S. The object and shadow look the same if I render it on my phone inside the app.
I Hope you can help me find a solution to achieve the goal described above.
I hope you can help me! Thanks 😊
EDIT:
The solution provided below is not the solution. Now my scene looks like this with still no shadow:
the code:
let worldGroundPlaneGeometry = SCNPlane(width: 1000, height: 1000)
let worldGroundPlane = SCNNode()
worldGroundPlane.geometry?.firstMaterial?.lightingModel = .constant
worldGroundPlane.geometry?.firstMaterial?.writesToDepthBuffer = true
worldGroundPlane.geometry?.firstMaterial?.colorBufferWriteMask = []
worldGroundPlane.geometry = worldGroundPlaneGeometry
worldGroundPlane.position = worldPosition
worldGroundPlane.castsShadow = true
worldGroundPlane.eulerAngles = SCNVector3(Float.pi / 2, 0, 0)
self.addChildNode(worldGroundPlane)
// Create a ambient light
let ambientLight = SCNNode()
ambientLight.light = SCNLight()
ambientLight.light?.shadowMode = .deferred
ambientLight.light?.color = UIColor.white
ambientLight.light?.type = SCNLight.LightType.ambient
ambientLight.position = SCNVector3(x: 0,y: 5,z: 0)
// Create a directional light node with shadow
let directionalNode = SCNNode()
directionalNode.light = SCNLight()
directionalNode.light?.type = SCNLight.LightType.directional
directionalNode.light?.color = UIColor.white
directionalNode.light?.castsShadow = true
directionalNode.light?.automaticallyAdjustsShadowProjection = true
directionalNode.light?.shadowSampleCount = 64
directionalNode.light?.shadowMode = .deferred
directionalNode.light?.shadowMapSize = CGSize(width: 2048, height: 2048)
directionalNode.light?.shadowColor = UIColor.black.withAlphaComponent(0.75)
directionalNode.position = SCNVector3(x: 0,y: 5,z: 0)
// Add the lights to the container
self.addChildNode(ambientLight)
self.addChildNode(directionalNode)
I was playing around with your lighting setup and found the ambient light gave it a very washed out look... & the shadow seemed too unnaturally dark.
Anwway I tweaked your light setup configuration to the following. I got rid of the ambient light all together, and I added a constraint on the object node (timber box) is this case. I also controlled the lighting intensity with just one directional light.
let directionalNode = SCNNode()
let constraint = SCNLookAtConstraint(target:node)
directionalNode.light = SCNLight()
directionalNode.light?.type = .directional
directionalNode.light?.color = UIColor.white
directionalNode.light?.castsShadow = true
directionalNode.light?.intensity = 2000
directionalNode.light?.shadowRadius = 16
directionalNode.light?.shadowMode = .deferred
directionalNode.eulerAngles = SCNVector3(Float.pi/2,0,0)
directionalNode.light?.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.3)
directionalNode.position = SCNVector3((node?.position.x)! + 10,(node?.position.y)! + 30,(node?.position.z)!+30)
directionalNode.constraints = [constraint]
// Add the lights to the container
self.sceneView.scene.rootNode.addChildNode(directionalNode)
The problem was that I wasn't actually using the material. Working Code:
let worldGroundPlaneGeometry = SCNFloor()
let worldGroundPlane = SCNNode()
let worldGroundMaterial = SCNMaterial()
worldGroundMaterial.lightingModel = .constant
worldGroundMaterial.writesToDepthBuffer = true
worldGroundMaterial.colorBufferWriteMask = []
worldGroundMaterial.isDoubleSided = true
worldGroundPlaneGeometry.materials = [worldGroundMaterial]
worldGroundPlane.geometry = worldGroundPlaneGeometry
self.addChildNode(worldGroundPlane)
// Create a ambient light
let ambientLight = SCNNode()
ambientLight.light = SCNLight()
ambientLight.light?.shadowMode = .deferred
ambientLight.light?.color = UIColor.white
ambientLight.light?.type = SCNLight.LightType.ambient
ambientLight.position = SCNVector3(x: 0,y: 5,z: 0)
// Create a directional light node with shadow
let directionalNode = SCNNode()
directionalNode.light = SCNLight()
directionalNode.light?.type = SCNLight.LightType.directional
directionalNode.light?.color = UIColor.white
directionalNode.light?.castsShadow = true
directionalNode.light?.automaticallyAdjustsShadowProjection = true
directionalNode.light?.shadowSampleCount = 64
directionalNode.light?.shadowRadius = 16
directionalNode.light?.shadowMode = .deferred
directionalNode.light?.shadowMapSize = CGSize(width: 2048, height: 2048)
directionalNode.light?.shadowColor = UIColor.black.withAlphaComponent(0.75)
directionalNode.position = SCNVector3(x: 0,y: 5,z: 0)
directionalNode.eulerAngles = SCNVector3(-Float.pi / 2, 0, 0)
// Add the lights to the container
self.addChildNode(ambientLight)
self.addChildNode(directionalNode)

Add shadow to a 3D Object in ARKIT

Can anyone please suggest me on how to add a shadow to a 3D object programmatically. Please see the below image. I want the same way to add a shadow under the chair programmatically.
// To Add Shadow on 3D Model Just Copy Paste this code and it will appear a shadow of 3D Model on Ground
let flourPlane = SCNFloor()
let groundPlane = SCNNode()
let groundMaterial = SCNMaterial()
groundMaterial.lightingModel = .constant
groundMaterial.writesToDepthBuffer = true
groundMaterial.colorBufferWriteMask = []
groundMaterial.isDoubleSided = true
flourPlane.materials = [groundMaterial]
groundPlane.geometry = flourPlane
//
mainNode.addChildNode(groundPlane)
// Create a ambient light
let ambientLight = SCNNode()
ambientLight.light = SCNLight()
ambientLight.light?.shadowMode = .deferred
ambientLight.light?.color = UIColor.white
ambientLight.light?.type = SCNLight.LightType.ambient
ambientLight.position = SCNVector3(x: 0,y: 5,z: 0)
// Create a directional light node with shadow
let myNode = SCNNode()
myNode.light = SCNLight()
myNode.light?.type = SCNLight.LightType.directional
myNode.light?.color = UIColor.white
myNode.light?.castsShadow = true
myNode.light?.automaticallyAdjustsShadowProjection = true
myNode.light?.shadowSampleCount = 64
myNode.light?.shadowRadius = 16
myNode.light?.shadowMode = .deferred
myNode.light?.shadowMapSize = CGSize(width: 2048, height: 2048)
myNode.light?.shadowColor = UIColor.black.withAlphaComponent(0.75)
myNode.position = SCNVector3(x: 0,y: 5,z: 0)
myNode.eulerAngles = SCNVector3(-Float.pi / 2, 0, 0)
// Add the lights to the container
mainNode.addChildNode(ambientLight)
mainNode.addChildNode(myNode)
// End

Resources