Add shadow to a 3D Object in ARKIT
// 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
casting shadow on invisible plane in Scenekit / ARkit
Try it like so:
The plane for the shadows:
func shadowPlane() -> SCNNode {
let objectShape = SCNPlane(width: 30.0, height: 30.0)
objectShape.heightSegmentCount = 1
objectShape.widthSegmentCount = 1
let objectNode = SCNNode(geometry: objectShape)
objectNode.renderingOrder = -10 // for Shadow Material Standard
objectNode.position = yourPosition
objectNode.geometry?.firstMaterial = shadowMaterialStandard()
objectNode.castsShadow = false // Important
objectNode.eulerAngles = SCNVector3(CGFloat.pi/2, 0.0, 0.0)
objectNode.name = "floor"
return objectNode
}
For the Material:
func shadowMaterialStandard() -> SCNMaterial {
let material = SCNMaterial()
material.colorBufferWriteMask = SCNColorMask(rawValue: 0) // important
material.diffuse.contents = UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
material.lightingModel = .physicallyBased // OK
material.isDoubleSided = true
return material
}
For the Lighting:
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 // 3.25 // suggestion by StackOverflow
light.shadowCascadeCount = 3 // suggestion by StackOverflow
light.shadowCascadeSplittingFactor = 0.09 // suggestion by StackOverflow
light.shadowBias = 0.1 // what's this?
light.shadowSampleCount = 8 // Quality of the Shadow - impacts performance when to high
light.categoryBitMask = -1 // Shine on Everything
return light
}
func ambientLight() -> SCNLight {
let light = SCNLight()
light.type = .ambient
light.color = UIColor.white
light.intensity = 250
light.categoryBitMask = -1 // Shine on Everything
return light
}
SceneKit: Is it possible to cast an shadow on an Transparent Object?
I do not have an answer to your question, however I have a workaround:
- Render your scene and keep the image in memory
- Change all the materials in your object for pure black, no specular
- Change the plane and the sky to a fully white material, lights to white
- Render the scene to another image
- On the second image, apply the
CIColorInvert
andCIMaskToAlpha
Core Image filters - Using Core Image apply the Alpha Mask to the first render.
You'll get an image with a correct Alpha channel, and transparent shadows. You will need to tweak the materials and lights to get the results you want.
The shadow may become lighter on the edges, and the only way around that is rendering it as yet another image, and filling it with black after the Mask to Alpha step.
Add shadow to SCNPlane
UPDATED.
In order to cast and receive a shadows from 3D lights, you have to use a 3D primitives (like cube, sphere, cylinder, etc), or 3D models in such formats as .usdz
, .dae
or .obj
.
Shadow on a visible plane.
Use the following code to test how light generates shadows for 3D geometry:
let scene = SCNScene()
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = .spot
lightNode.light!.castsShadow = true
lightNode.light!.shadowMode = .deferred
lightNode.rotation = SCNVector4(x: -1, y: 0, z: 0, w: CGFloat.pi/2)
lightNode.position = SCNVector3(x: 0, y: 20, z: 0)
scene.rootNode.addChildNode(lightNode)
let sphereNode = SCNNode()
sphereNode.geometry = SCNSphere(radius: 2)
sphereNode.position = SCNVector3(x: 0, y: -2, z: 0)
scene.rootNode.addChildNode(sphereNode)
let planeNode = SCNNode()
planeNode.geometry = SCNPlane(width: 15, height: 15)
planeNode.position = SCNVector3(x: 0, y: -5, z: 0)
planeNode.rotation = SCNVector4(x: -1, y: 0, z: 0, w: CGFloat.pi/2)
planeNode.geometry?.materials.first?.diffuse.contents = UIColor.red
scene.rootNode.addChildNode(planeNode)
Shadow on invisible plane.
In case you need an invisible plane that receives a shadow, use the following code:
planeNode.geometry?.materials.first?.colorBufferWriteMask = []
planeNode.geometry?.materials.first?.writesToDepthBuffer = true
planeNode.geometry?.materials.first?.lightingModel = .constant
Fake shadow.
And if you want to use a fake shadow written as a .png
(png file format can hold 4 channels) texture on geometry (with a premultiplied alpha channel – RGB x A), use the following approach:
planeNode.geometry?.materials.first?.diffuse.contents = UIImage(named: "shadow.png")
lightNode.light!.castsShadow = false
Here's my answer on fake shadows.
And here's your premultiplied shadow in .png
format (just drag-and-drop it on your desktop):
You can change its size and transparency and, of course, you can blur it.
Is it possible to project light on a transparent SCNFloor in ARKit?
Yes, you can do that by casting a white (or bleeded color) shadow instead of a black one. For that you have to use deferred shadows type that are rendered in a post-processing pass.
Consider, you must use two separate lights in your scene – first one for white shadows casting, and a second one for lighting objects that cast these white shadows. You're able to include and exclude objects from lighting scheme implementing categoryBitMask property.
// SETTING A SHADOW CATCHER
let plane = SCNPlane(width: 10, height: 10)
plane.firstMaterial?.isDoubleSided = true
plane.firstMaterial?.lightingModel = .shadowOnly
let planeNode = SCNNode(geometry: plane)
planeNode.eulerAngles.x = -.pi / 2
scene.rootNode.addChildNode(planeNode)
// SETTING A LIGHT
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light?.type = .directional
lightNode.light?.intensity = 50
lightNode.light?.color = UIColor.black
lightNode.eulerAngles.x = -.pi / 2
scene.rootNode.addChildNode(lightNode)
// DEFERRED SHADOWS
lightNode.light?.castsShadow = true
lightNode.light?.shadowMode = .deferred // important
lightNode.light?.forcesBackFaceCasters = true // important
lightNode.light?.shadowRadius = 10
lightNode.light?.shadowColor = UIColor.white
Related Topics
Add Skreferencenode/Skscene to Another Skscene in Spritekit
How Are Int and String Accepted as Anyhashable
How to Open a Screen Directly in Xcuitest
Map and Flatmap Difference in Optional Unwrapping in Swift 1.2
Redeclaring Members in an Extension Hides the Original Member *Sometimes*. Why
Generatesdecimalnumbers for Numberformatter Does Not Work
How to Load My Own Reality Composer Scene into Realitykit
How to Handle Hash Collisions for Dictionaries in Swift
How to Set Exit Code Value for a Command Line Utility in Swift
Swift - Reorder Uitableview Cells
Skaction Completion Handlers; Usage in Swift
Difference Between Text("") and Text(Verbatim: "") Initializers in Swiftui
Use Multiple Codingkeys for a Single Property
What Is the Use of the Validate() Method in Alamofire.Request