I get AR object from an API call and store then in the file manager storage, during this process, I merged all the nodes in the one node, and also add an SCNFloor below the object:
let mergedScene = SCNScene()
let nodeContainer = SCNNode()
let floor = SCNNode()
let scnFloor = SCNFloor()
scnFloor.length = 2
scnFloor.width = 2
floor.geometry = scnFloor
floor.geometry?.firstMaterial!.colorBufferWriteMask = []
floor.geometry?.firstMaterial!.readsFromDepthBuffer = true
floor.geometry?.firstMaterial!.writesToDepthBuffer = true
floor.geometry?.firstMaterial!.lightingModel = .constant
nodeContainer.addChildNode(floor)
for node in scene.rootNode.childNodes {
nodeContainer.addChildNode(node)
}
mergedScene.rootNode.addChildNode(nodeContainer)
if mergedScene.write(to: url, options: nil, delegate: nil, progressHandler: { float, error, pointer in
if let error = error {
completion(.error(error))
}
})
and here is the default lighting that I added in viewDidLoad
let light = SCNLight()
light.type = .directional
light.shadowColor = UIColor(white: 0, alpha: 0.6)
light.color = UIColor.white
light.castsShadow = true
light.automaticallyAdjustsShadowProjection = true
light.shadowMode = .deferred
light.shadowRadius = 10
light.shadowSampleCount = 3
let sunLightNode = SCNNode()
sunLightNode.position = SCNVector3(x: 1_000, y: 1_000, z: 0)
sunLightNode.rotation = SCNVector4(x: 1, y: 0, z: 0, w: .pi * 1.5)
sunLightNode.light = light
light.orthographicScale = 5
sceneView.scene.rootNode.addChildNode(sunLightNode)
And the configuration:
sceneView.preferredFramesPerSecond = 30
sceneView.automaticallyUpdatesLighting = true
sceneView.autoenablesDefaultLighting = true
let configuration = ARWorldTrackingConfiguration()
configuration.isLightEstimationEnabled = false
configuration.planeDetection = .horizontal
configuration.providesAudioData = false
configuration.isAutoFocusEnabled = true
sceneView.session.run(configuration)
The problem is, maybe 4 out of 10 times, when you place the object in the scene, there is no shadow, or even when there is the shadows, maybe when you move camera away from the object and come back to the object, you will see the shadow is gone, it's not happened all the time but it happens quite a lot. But on the other times, when you place the object you can see the shadow it won't be gone by moving the camera!
And when I render a snapshot from sceneView
when there is no shadow on the screen, you can see the shadow on the rendered snapshot, so the shadow is there, but only not visible on the screen! Could anyone help me to solve this issue? Thank you
First configure your SceneView Setup:
Make sure to configure this:
sceneView.antialiasingMode = .none
sceneView.isJitteringEnabled = false
sceneView.autoenablesDefaultLighting = false // we configure our own lights
sceneView.automaticallyUpdatesLighting = false // we configure our own lights
For the Floor:
func shadowPlane(position: SCNVector3) -> SCNNode {
let objectShape = SCNPlane(width: 100, height: 100)
objectShape.heightSegmentCount = 1
objectShape.widthSegmentCount = 1
let objectNode = SCNNode(geometry: objectShape)
objectNode.name = "floor"
objectNode.renderingOrder = -10 // must be for Shadow Material Standard
objectNode.position = position
objectNode.geometry?.firstMaterial = Material.shadowMaterialStandard()
objectNode.castsShadow = false // Important
objectNode.eulerAngles = SCNVector3(-90.degreesToRadians, 0.0, 0.0)
objectNode.physicsBody = Physics.floorPhysicsBody(shape: objectShape)
return objectNode
}
For the Material:
func shadowMaterialStandard() -> SCNMaterial {
let material = SCNMaterial()
material.colorBufferWriteMask = SCNColorMask(rawValue: 0)
material.diffuse.contents = UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
material.lightingModel = .physicallyBased
material.isLitPerPixel = false // play around with it (can affect performance)
return material
}
For the Directional Light: (Add a SCNNode and configure its light property using this function. Do it for the directional and the ambient light)
func directionalLight() -> SCNLight {
let light = SCNLight()
light.type = .directional
light.castsShadow = true
light.color = UIColor.white
light.shadowMode = .deferred
light.shadowSampleCount = 16 // 8
light.shadowRadius = 1 // 1
light.automaticallyAdjustsShadowProjection = true // seems to be required, if not, the shadow seems incomplete
light.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.75)
// light.shadowMapSize = CGSize(width: 4096, height: 4096) // CGSize(width: 8192, height: 8192) // CGSize(width: 4096, height: 4096) // increases Memory usage to 700 MB (be careful with this)
light.categoryBitMask = -1 // shine on everything
return light
}
and do something like this on the Node containing the directional Light:
lightNode.eulerAngles = SCNVector3(-45.degreesToRadians, 0.0, 0.0)
you need this extension:
extension FloatingPoint {
var degreesToRadians: Self { return self * .pi / 180 }
var radiansToDegrees: Self { return self * 180 / .pi }
}
For the Ambient Light:
func ambientLight() -> SCNLight {
let light = SCNLight()
light.type = .ambient
light.intensity = 150
light.color = UIColor.white
light.categoryBitMask = -1 // shine on everything
return light
}
Note: try to place the Light Setup first, then the Floor and then the Geometry on the Floor, or whatever.
and I highly recommend you to NOT configure this setting you already made:
sceneView.preferredFramesPerSecond = 30
I never had any success with it. It can also cause some unexpected frame stuttering.