While showing a .USDZ model using a QLPreviewController
I get a grounding shadow out of the box. However, when I load the same model in the ARView
there is no such shadow. Why is it hidden and is it possible to show it ? Or do I have to create a custom shadow ?
The answer is quite obvious: since ARQuickLook
doesn't allow you customize anything in your scene (it's a ready-to-use solution), then if you want to implement everything that is activated in ARQuickLook
by default (be it collision shapes, gestures, grounding shadows, playing animations, etc) in ARView
or in RealityView
you'll have to implement this from scratch.
RealityKit 4.0 (iOS 18.0+) allows you generate grounding shadows
from the perspective of another entity that receives the first entity's shadow. In the previous version of RealityKit you would have to use a directional or spot lights for this.
import SwiftUI
import RealityKit
struct ContentView : View {
var body: some View {
ARViewContainer().ignoresSafeArea()
}
}
struct ARViewContainer : UIViewRepresentable {
let arView = ARView(frame: .zero)
let anchor = AnchorEntity()
init() {
arView.environment.lighting.intensityExponent = 1.001
arView.environment.background = .color(.white)
}
func makeUIView(context: Context) -> ARView {
// Biplane's Entity
let entity = try! Entity.load(named: "biplane")
entity.position.y = 0.05
anchor.addChild(entity)
// Biplane's ModelEntity
let model = entity.findEntity(named: "toy_biplane_bind") as! ModelEntity
model.components[GroundingShadowComponent.self] =
.init(castsShadow: true,
receivesShadow: false)
// Shadow plane
let mesh = MeshResource.generatePlane(width: 0.5,
depth: 0.5,
cornerRadius: 0.25)
let material = SimpleMaterial()
let shadowPlane = ModelEntity(mesh: mesh, materials: [material])
shadowPlane.components[GroundingShadowComponent.self] =
.init(castsShadow: false,
receivesShadow: true)
anchor.addChild(shadowPlane)
arView.scene.anchors.append(anchor)
return arView
}
func updateUIView(_ view: ARView, context: Context) { }
}
Here's some kind of tracked "VR mode" (camera feed is obscured by ARView's white BG).
For AR mode, you could use DirectionalLight
object with a non-opaque material catching a shadow, but for some reason OcclusionMaterial
causes the surface to be "overexposed". I hope that this bug will be fixed in future versions. It's quite possible that there's some parameter that can remove the "overexposed surface" effect of the plane, but I have not found it.
struct ARViewContainer : UIViewRepresentable {
let arView = ARView(frame: .zero)
let anchor = AnchorEntity()
func makeUIView(context: Context) -> ARView {
// Biplane's Entity
let entity = try! Entity.load(named: "biplane")
entity.position.y = 0.05
anchor.addChild(entity)
// Light
let sun = DirectionalLight()
sun.shadow = .init()
sun.light.intensity = 4000
sun.light.color = .white
sun.orientation = .init(angle: -.pi/2, axis: [1,0,0])
anchor.addChild(sun)
// Shadow plane
let mesh = MeshResource.generatePlane(width: 0.5,
depth: 0.5,
cornerRadius: 0.25)
let material = OcclusionMaterial(receivesDynamicLighting: true) // !!!
let shadowPlane = ModelEntity(mesh: mesh, materials: [material])
anchor.addChild(shadowPlane)
arView.scene.anchors.append(anchor)
return arView
}
func updateUIView(_ view: ARView, context: Context) { }
}
import SwiftUI
import RealityKit
struct ContentView : View {
var body: some View {
ARViewContainer().ignoresSafeArea()
}
}
struct ARViewContainer : UIViewRepresentable {
let arView = ARView(frame: .zero)
let anchor = AnchorEntity()
init() {
arView.cameraMode = .nonAR
arView.environment.background = .color(UIColor(white: 0.975, alpha: 1))
}
func makeUIView(context: Context) -> ARView {
// Biplane's Entity
let entity = try! Entity.load(named: "biplane")
entity.position.y = 0.05
anchor.addChild(entity)
print(entity)
// Perspective Camera
let camera = PerspectiveCamera()
camera.position = [-0.13, 0.2, 0.7]
camera.orientation = .init(angle: -.pi/16, axis: [1,1,0])
anchor.addChild(camera)
// Biplane's ModelEntity
let model = entity.findEntity(named: "toy_biplane_bind") as! ModelEntity
model.components[GroundingShadowComponent.self] =
.init(castsShadow: true,
receivesShadow: false)
// Shadow plane
let mesh = MeshResource.generatePlane(width: 0.5,
depth: 0.5,
cornerRadius: 0.25)
let material = SimpleMaterial()
let shadowPlane = ModelEntity(mesh: mesh, materials: [material])
shadowPlane.components[OpacityComponent.self] = .init(opacity: 0.9)
shadowPlane.components[GroundingShadowComponent.self] =
.init(castsShadow: false,
receivesShadow: true)
anchor.addChild(shadowPlane)
arView.scene.anchors.append(anchor)
return arView
}
func updateUIView(_ view: ARView, context: Context) { }
}