Arkit -Drop a Shadow of 3D Object on the Plane Surface

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:

  1. Render your scene and keep the image in memory
  2. Change all the materials in your object for pure black, no specular
  3. Change the plane and the sky to a fully white material, lights to white
  4. Render the scene to another image
  5. On the second image, apply the CIColorInvertand CIMaskToAlpha Core Image filters
  6. 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):

Sample Image

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



Leave a reply



Submit