Home > Back-end >  SCNFloor goes unvisible randomly on SceneKit
SCNFloor goes unvisible randomly on SceneKit

Time:07-14

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

CodePudding user response:

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.

  • Related