Thanks for being a part of WWDC25!

How did we do? We’d love to know your thoughts on this year’s conference. Take the survey here

A question about adding grounding shadow in visionPro

I want adding grounding shadow on my Entity in RealityView on visionPro. However it seems that the shadow can only appear on another Entity. So I using plane detection in ARKit and add a transparent plane on it to render shadow.

let planeEntity = ModelEntity(mesh: .generatePlane(width: anchor.geometry.extent.width, height: anchor.geometry.extent.height), materials: [material])
planeEntity.components.set(OpacityComponent(opacity: 0.0))

But sometimes there will be a border around my Entityon the plane.

I do not know why it will happen, and I want remove the border.

Answered by Vision Pro Engineer in 828743022

Thank you for the reply @YaShiho , I'm very happy to help you with your problem.



I can confirm that extra geometry is not necessary to render shadows on real geometry, however the effect is different on real geometry vs virtual geometry. Additionally, I believe the size of your objects is making it more difficult to see this effect on real world objects. To test this, try running this code in a brand new project inside the simulator (I've positioned the spheres above the table in the living room inside the simulator):


struct ImmersiveView: View {
    var body: some View {
        RealityView { content in
            let mat = SimpleMaterial(color: .yellow, isMetallic: false)
            @MainActor
            func createSphere(radius: Float, position: SIMD3<Float>) -> Entity {
                let sphere = Entity(components: [
                    ModelComponent(mesh: .generateSphere(radius: radius), materials: [mat]),
                    GroundingShadowComponent(castsShadow: true)
                ])
                sphere.setPosition(position, relativeTo: nil)
                return sphere
            }
            let sphereA = createSphere(radius: 0.1, position: [-0.2, 0.8, -1.7])
            let sphereB = createSphere(radius: 0.05, position: [-0, 0.75, -1.7])
            let sphereC = createSphere(radius: 0.01, position: [0.2, 0.71, -1.7])
            content.add(sphereA)
            content.add(sphereB)
            content.add(sphereC)
        }
    }
}



You will notice shadows beneath the first two spheres, but not the third, because it is very small. I think this may be related to what you are seeing, because the models in your screenshot are also very small.

If increasing the size of your objects is not viable, this certainly seems like unexpected behavior so please file a bug report via Feedback Assistant and include as much detail about your use case as possible. If you do submit a report, please share your report number here. Thank you!

Hello @YaShiho, thank you for your question!



Grounding shadows don't require virtual geometry in order to render. This means the planes you are creating shouldn't be necessary in the first place. To test this, try adding a GroundingShadowComponent to the spheres in the default immersive scene in Xcode's template project. You should see the spheres cast shadows onto your floor. Grounding shadows are handled by RealityKit, and all entities with a ModelComponent as well as flat real world objects will receive shadows.

In your photo, your real-world surface appears planar, but can you verify it is flat? Real world objects with non-planar shapes may have trouble rendering grounding shadows. Another possibility: do you see any difference if you set receivesShadow to true for the GroundingShadowComponent on your entities?

Additionally, would it be possible to share the code you are using to create the planes underneath your objects? As I said, these shouldn't be necessary, but the outline around the planes is strange, and I am wondering if something about your material or configuration is causing this.

Let me know if you have any more questions!

I am not create the planes underneath each object. Instead, I just create planes based on real world plane.

Here is the plane detection and creation code:

private let planeTracking = PlaneDetectionProvider(alignments: [.horizontal])

@MainActor
    func processPlaneAnchor(_ anchorUpdate: AnchorUpdate<PlaneAnchor>) {
        let anchor = anchorUpdate.anchor
        
        if anchor.classification == .window { return }
        
        switch anchorUpdate.event {
        case .added:
            print("PlaneAnchor added")
            
            if let entity = planeMap[anchor.id] {
                let planeEntity = entity.findEntity(named: "plane") as! ModelEntity
                planeEntity.model!.mesh = MeshResource.generatePlane(width: anchor.geometry.extent.width, height: anchor.geometry.extent.height)
                planeEntity.transform = Transform(matrix: anchor.geometry.extent.anchorFromExtentTransform)
            } else {
                let entity = Entity()

                // Add a new entity to represent this plane.
                var material = PhysicallyBasedMaterial()
                material.baseColor.tint = UIColor.white.withAlphaComponent(0.0)
                material.roughness = PhysicallyBasedMaterial.Roughness(floatLiteral: 1.0)
                material.metallic = PhysicallyBasedMaterial.Metallic(floatLiteral: 0.0)
                material.blending = .transparent(opacity: 0.0)
                
                let planeEntity = ModelEntity(mesh: .generatePlane(width: anchor.geometry.extent.width, height: anchor.geometry.extent.height), materials: [material])
                planeEntity.components.set(OpacityComponent(opacity: 0.0))
                planeEntity.name = "plane"
                planeEntity.transform = Transform(matrix: anchor.geometry.extent.anchorFromExtentTransform)
                entity.addChild(planeEntity)

                planeMap[anchor.id] = entity
                rootEntity.addChild(entity)
            }
            planeMap[anchor.id]?.transform = Transform(matrix: anchor.originFromAnchorTransform)
        case .updated:
            print("PlaneAnchor updated")
        case .removed:
            print("PlaneAnchor removed")
            planeMap[anchor.id]?.removeFromParent()
            planeMap.removeValue(forKey: anchor.id)
        }
    }

And there is some strange event:

  1. The outlines just sometimes appear, not always, even for the same object.
  2. The outlines appears underneach the objects like its shadow, maybe it is not the boarder of the plane.

And I think maybe the outlines have something to do with the entity. Here is the code how I rendering shadow:

extension Entity {

    func enumerateHierarchy(_ body: (Entity, UnsafeMutablePointer<Bool>) -> Void) {
        var stop = false

        func enumerate(_ body: (Entity, UnsafeMutablePointer<Bool>) -> Void) {
            guard !stop else {
                return
            }

            body(self, &stop)
            
            for child in children {
                guard !stop else {
                    break
                }
                child.enumerateHierarchy(body)
            }
        }
        
        enumerate(body)
    }
}


entity.enumerateHierarchy { child, stop in               
child.components.set(GroundingShadowComponent(castsShadow: true)) 
}

And it seems that Grounding shadows do require virtual geometry in order to render. When I remove the plane, the grounding shadow disappears.

Accepted Answer

Thank you for the reply @YaShiho , I'm very happy to help you with your problem.



I can confirm that extra geometry is not necessary to render shadows on real geometry, however the effect is different on real geometry vs virtual geometry. Additionally, I believe the size of your objects is making it more difficult to see this effect on real world objects. To test this, try running this code in a brand new project inside the simulator (I've positioned the spheres above the table in the living room inside the simulator):


struct ImmersiveView: View {
    var body: some View {
        RealityView { content in
            let mat = SimpleMaterial(color: .yellow, isMetallic: false)
            @MainActor
            func createSphere(radius: Float, position: SIMD3<Float>) -> Entity {
                let sphere = Entity(components: [
                    ModelComponent(mesh: .generateSphere(radius: radius), materials: [mat]),
                    GroundingShadowComponent(castsShadow: true)
                ])
                sphere.setPosition(position, relativeTo: nil)
                return sphere
            }
            let sphereA = createSphere(radius: 0.1, position: [-0.2, 0.8, -1.7])
            let sphereB = createSphere(radius: 0.05, position: [-0, 0.75, -1.7])
            let sphereC = createSphere(radius: 0.01, position: [0.2, 0.71, -1.7])
            content.add(sphereA)
            content.add(sphereB)
            content.add(sphereC)
        }
    }
}



You will notice shadows beneath the first two spheres, but not the third, because it is very small. I think this may be related to what you are seeing, because the models in your screenshot are also very small.

If increasing the size of your objects is not viable, this certainly seems like unexpected behavior so please file a bug report via Feedback Assistant and include as much detail about your use case as possible. If you do submit a report, please share your report number here. Thank you!

Hello,

I've been struggling for a long time trying to get Grounding shadows to work with occlusion material but I haven't found any solution.

What I need is to add occlusion material to reconstructed mesh so that virtual content is occluded by real world.

I also need to enable physics on the mesh (I'm working on a game where characters interact with the real world).

The grounding shadows work fine when I just add the collision and physics body to the reconstructed mesh. However when I do, there is no occlusion and it breaks the illusion. When I add a model to the mesh entities with occlusion material, then the occlusion works fine however the grounding shadows stop working.

Thanks for your time.

Gil

A question about adding grounding shadow in visionPro
 
 
Q