ARKit/SceneKit Shadow Issue - ios

I'm working on an ARKit app for iPhone and iPad and I'm having some troubles getting the shadows to work in the live view from the camera. My scene (ShadowTest.scn) looks like the following:
I have unchecked Red, Green, Blue, and Alpha for the Write to Color property on the material for the plane as well as set the visibility to Cull back.
For the directional light, Enable Shadows is selected and the mode is set to Deferred.
In the scene file itself, everything looks good. The shadow is visible and the plane is not.
My issue is when I go to add it to my scene view. This is how I'm adding it to my scene:
func addTestBox(at position: SCNVector3, rotation: SCNVector4) {
guard let scene = SCNScene(named: "Assets.scnassets/ShadowTest.scn") else {
return
}
let node = SCNNode()
scene.rootNode.childNodes.forEach(node.addChildNode(_:))
node.position = position
node.rotation = rotation
updateQueue.async { [weak self] in
self?.sceneView.scene.rootNode.addChildNode(node)
}
}
When the nodes are added to my scene, the shadow is not visible at all.
The lighting appears to be working because it is lit as it should be.
Am I missing some very obvious setting on the light/plane/cube/scene view that is needed to make the shadows show up?
Thank you in advanced for all the help.

You should be able to do this in code. Try this for the light:
func directionalLight() -> SCNLight {
let light = SCNLight()
light.type = .directional
light.castsShadow = true
light.color = UIColor.white
light.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.75)
light.shadowMode = .deferred
light.shadowRadius = 2.0
light.shadowCascadeCount = 3
light.shadowCascadeSplittingFactor = 0.09
light.shadowBias = 0.1
light.shadowSampleCount = 8 // (the smaller the value, the better the performance)
light.categoryBitMask = -1 // Shine on Everything
return light
}
and this for the Material:
func shadowMaterialStandard() -> SCNMaterial {
let material = SCNMaterial()
material.diffuse.contents = UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
material.lightingModel = .physicallyBased
material.colorBufferWriteMask = SCNColorMask(rawValue: 0)
material.isLitPerPixel = false
material.isDoubleSided = true
return material
}
and this for the SCNPlane (the Shadow Plane):
func setupShadowPlane() {
let objectShape = SCNPlane(width: 50.0, height: 50.0)
objectShape.heightSegmentCount = 1
objectShape.widthSegmentCount = 1
let objectNode = SCNNode(geometry: objectShape)
objectNode.renderingOrder = -10 // for Shadow Material Standard
objectNode.position = initialStartPosition
objectNode.geometry?.firstMaterial = shadowMaterialStandard()
objectNode.castsShadow = false // Important
objectNode.eulerAngles = SCNVector3(-90.degreesToRadians, 0.0, 0.0)
objectNode.name = "floor"
objectNode.physicsBody = Physics.floorPhysicsBody(shape: objectShape)
sceneView.scene.rootNode.addChildNode(objectNode)
}

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

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)

How do I find the SCNMatrix4 transformation that moved an SCNNode from one point on a sphere to another?

I am trying to reorient a cone so that it sits with its base flat on the surface of a sphere as I move it around the sphere.
The sphere is radius 4 and centred at the origin.
I tried to deduce the quaternion for the transformation, but it seems easier to use the pivot property of the cone itself.
I can get it to work in simple places on the axes by rotating it through known angles but I can’t figure out how to deduce the transformation from the location vector of the cone. I think I need to take the Vector representing the default orientation of the cone and the location vector of the point on the surface of the sphere and work out the SCNMatrix4 that transforms the default orientation into the new one I need. Then I can use it to transform the pivot of the SCNNode. Part of the problem is that I don’t know what the default orientation Vector is.
This is what I have so far (it doesn't work):
let distanceFromCentre = Float(4.0)
let newConeNode = SCNNode(geometry: SCNCone(topRadius: 0, bottomRadius: 0.5, height: 1.0))
let radialLocationVector = SCNVector3Make(0.0, 1.0, 1.0)
let radialLocationVectorMagnitude = pow((pow(radialLocationVector.x,2) + pow(radialLocationVector.y,2) + pow(radialLocationVector.z,2)),0.5)
let radialLocationUnitVector = SCNVector3Make(radialLocationVector.x/radialLocationVectorMagnitude, radialLocationVector.y/radialLocationVectorMagnitude, radialLocationVector.z/radialLocationVectorMagnitude)
let surfaceLocationVector = SCNVector3Make(radialLocationUnitVector.x*distanceFromCentre, radialLocationUnitVector.y*distanceFromCentre, radialLocationUnitVector.z*distanceFromCentre)
newConeNode.position = surfaceLocationVector
let nodePivotRotationMatrix = SCNMatrix4MakeRotation(0,radialLocationUnitVector.x, radialLocationUnitVector.y, radialLocationUnitVector.z) //This doesn't work
let nodePivotTranslationMatrix = SCNMatrix4MakeTranslation(0, -0.5, 0)
newConeNode.pivot = SCNMatrix4Mult(nodePivotRotationMatrix, nodePivotTranslationMatrix)
newConeNode.name = "MyCone"
newConeNode.geometry?.firstMaterial?.diffuse.contents = UIColor(red: 0.2, green: 0, blue: 0.8, alpha: 1.0)
scnView.scene?.rootNode.addChildNode(newConeNode)
Any help gratefully received
I got this to work by using the cross product to find the axis of rotation and the dot product to find the angle of rotation. You need to convert from SCNVector to GLKVector to use these functions. The code is below:
let distanceFromCentre = globeNode!.geometry!.boundingSphere.radius
let newConeNode = SCNNode(geometry: SCNCone(topRadius: 0, bottomRadius: 0.5, height: 1.0))
let northPoleLocationVector = SCNVector3Make(0.0, distanceFromCentre, 0)
newConeNode.position = northPoleLocationVector
let newLocationDirectionVector = SCNVector3Make(1.0, 2.0, 3.0)
let magnitudeLocation = Float(pow((pow(newLocationDirectionVector.x,2) + pow(newLocationDirectionVector.y,2) + pow(newLocationDirectionVector.z,2)),0.5))
let newLocationUnitVector = SCNVector3Make(newLocationDirectionVector.x/magnitudeLocation, newLocationDirectionVector.y/magnitudeLocation, newLocationDirectionVector.z/magnitudeLocation)
let newLocationOnSurface = SCNVector3Make(newLocationUnitVector.x*distanceFromCentre, newLocationUnitVector.y*distanceFromCentre, newLocationUnitVector.z*distanceFromCentre)
let locationTranslationVector = SCNVector3Make(northPoleLocationVector.x-newLocationOnSurface.x, northPoleLocationVector.y-newLocationOnSurface.y, northPoleLocationVector.z-newLocationOnSurface.z) //Not sure why this works - the vector from A to B should be b-a not a-b
let surfaceTranslationMatrix = SCNMatrix4MakeTranslation(locationTranslationVector.x, locationTranslationVector.y, locationTranslationVector.z)
let northPoleLocationVectorGLK = GLKVector3Make(northPoleLocationVector.x, northPoleLocationVector.y, northPoleLocationVector.z)
let magnitudeSLV = GLKVector3Length(northPoleLocationVectorGLK)
let newLocationOnSurfaceVectorGLK = GLKVector3Make(newLocationOnSurface.x, newLocationOnSurface.y, newLocationOnSurface.z)
let magnitudeNLV = GLKVector3Length(newLocationOnSurfaceVectorGLK)
let normalToTransformGLK = GLKVector3CrossProduct(northPoleLocationVectorGLK, newLocationOnSurfaceVectorGLK)
let angleOfRotation = acos(GLKVector3DotProduct(northPoleLocationVectorGLK, newLocationOnSurfaceVectorGLK)/(magnitudeNLV*magnitudeSLV))
let coneRotationMatrix = SCNMatrix4MakeRotation(-angleOfRotation, normalToTransformGLK.x, normalToTransformGLK.y, normalToTransformGLK.z)
let combinedTransformMatrix = SCNMatrix4Mult(surfaceTranslationMatrix, coneRotationMatrix)
newConeNode.pivot = combinedTransformMatrix
newConeNode.name = "MyCone"
newConeNode.geometry?.firstMaterial?.diffuse.contents = UIColor(red: 0.2, green: 0, blue: 0.8, alpha: 1.0)
scnView.scene?.rootNode.addChildNode(newConeNode)

Cast shadow on all nodes SCNLight

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.

Resources