Add shadow to a 3D Object in ARKIT - ios

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

Related

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)
}

Scenekit Shadows fading away as camera moves away

I've set up realistic lighting in Scenekit, but as the camera moves away from the object the lighting is on, the lighting fades away and eventually gets completely removed.
// LIGHTING
let floorPlane = SCNFloor()
let groundPlane = SCNNode()
let groundMaterial = SCNMaterial()
groundMaterial.lightingModel = .constant
groundMaterial.writesToDepthBuffer = true
groundMaterial.colorBufferWriteMask = []
groundMaterial.isDoubleSided = true
floorPlane.materials = [groundMaterial]
groundPlane.geometry = floorPlane
//
charNode.addChildNode(groundPlane)
// Create a ambient light
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
directLight.light = SCNLight()
directLight.light?.type = SCNLight.LightType.directional
directLight.light?.color = UIColor.white
directLight.light?.castsShadow = true
directLight.light?.automaticallyAdjustsShadowProjection = true
directLight.light?.shadowSampleCount = 64
directLight.light?.shadowRadius = 16
directLight.light?.shadowMode = .deferred
directLight.light?.shadowMapSize = CGSize(width: 2048, height: 2048)
directLight.light?.shadowColor = UIColor.black.withAlphaComponent(0.75)
directLight.position = SCNVector3(x: 0,y: 5,z: 0)
directLight.eulerAngles = SCNVector3(-Float.pi / 2, 0, 0)
// Add the lights to the container
charNode.addChildNode(ambientLight)
charNode.addChildNode(directLight)

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)

Scene not rendering properly in SceneKit

I created a floor in a scene and loaded a collada file(.dae) into my scene and tried to create a plane below that model. But I have problems like below.
This problem generates only when custom camera is used. Scene renders properly when system camera is used i.e if no custom camera is added
My code is as below:
import UIKit import SceneKit
class TestVC: UIViewController, SCNSceneRendererDelegate {
var cameraNode: SCNNode!
var modelNode: SCNNode!
var scnView: SCNView!
var camNode: SCNNode!
var cameraPosition: SCNVector3!
override func viewDidLoad() {
super.viewDidLoad()
scnView = SCNView(frame: self.view.frame)
scnView.delegate = self
view.addSubview(scnView)
scnView.delegate = self
let scene = SCNScene()
scnView.scene = scene
//camera
let camera = SCNCamera()
cameraNode = SCNNode()
cameraNode.camera = camera
camera.zFar = 1000
cameraPosition = SCNVector3(0, 100, 150)
cameraNode.position = cameraPosition
scene.rootNode.addChildNode(cameraNode)
//floor
let floor = SCNFloor()
floor.reflectivity = 0
let floorNode = SCNNode(geometry: floor)
let firstmaterial = SCNMaterial()
firstmaterial.diffuse.contents = UIImage(named: "art.scnassets/grass.jpg")
firstmaterial.diffuse.wrapS = SCNWrapMode.Repeat
firstmaterial.diffuse.wrapT = SCNWrapMode.Repeat
floor.materials = [firstmaterial]
scene.rootNode.addChildNode(floorNode)
let light = SCNLight()
let lightNode = SCNNode()
lightNode.light = light
light.type = SCNLightTypeAmbient
light.color = UIColor.darkGrayColor()
scene.rootNode.addChildNode(lightNode)
let light1 = SCNLight()
let light1Node = SCNNode()
light1Node.light = light1
light1Node.position = SCNVector3(0, 200, -100)
light1.type = SCNLightTypeOmni
scene.rootNode.addChildNode(light1Node)
let modelScene = SCNScene(named: "Barcelona Chair.dae")
let solNode = modelScene?.rootNode
solNode?.eulerAngles = SCNVector3(GLKMathDegreesToRadians(-90), 0, 0)
scene.rootNode.addChildNode(solNode!)
scnView.allowsCameraControl = true
let plane = SCNPlane(width: 100, height: 100)
let planeNode = SCNNode(geometry: plane)
planeNode.pivot = SCNMatrix4MakeRotation(Float(M_PI_2), 1, 0, 0)//(CGFloat(M_PI_2), 1, 0, 0)
planeNode.position = SCNVector3(0, 1, 0)
plane.firstMaterial?.diffuse.contents = UIColor.redColor()
scene.rootNode.addChildNode(planeNode)
}
What you're probably seeing is known as "z-fighting". Basically, your floor and your plane are coexisting - they are coplanar - so the rendering of them is ambiguous. Try lifting the plane up a little by setting its position higher than the floor plane, and you should see this problem go away.
Don't know why the above problem exists but I got a different way of doing the thing. You can use SCNShape to draw an overlay over a floor. I drew a rectangular path using UIBezierPath and set the path property of the SCNShape class. :)

Resources