You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

visionOS中Portal作为AnchorEntity子节点无法显示的问题排查

问题描述

我遵循WWDC2023中关于RealityKit的session制作了一个连接独立世界的Portal,并将其设为识别垂直墙面的AnchorEntity的子节点。但Portal实体无法显示,而Sun实体可以正常显示。我尝试将Portal直接放置在空间中时,它能完美显示。

以下是我的代码:

struct ImmersiveView: View {
    var addToAnchor = true
    var body: some View {
        RealityView { content in
            let world = makeWorld()
            let portal = makePortal(world)
            content.add(world)
            let anchor = AnchorEntity(.plane(.vertical, classification: .wall,
                                             minimumBounds: [1, 1]))
            content.add(anchor)
            content.subscribe(to: SceneEvents.AnchoredStateChanged.self, on: anchor, componentType: nil) { event in
                print("AnchoredStateChanged")
            }
            // the portal not shows
            anchor.addChild(portal)
            let sun = try! await Entity(named: "Sun", in: realityKitContentBundle)
            // the sun shows
            anchor.addChild(sun)
        }
    }
    
    @MainActor
    func makeWorld() -> Entity {
        let world = Entity()
        world.components[WorldComponent.self] = .init()
        let earth = try! Entity.load(named: "Earth", in: realityKitContentBundle)
        let sun = try! Entity.load(named: "Sun", in: realityKitContentBundle)
        earth.position = .init(x: -0.5, y: 1.5, z: -2)
        sun.position = .init(x: 0.5, y: 1.5, z: -2)
        world.addChild(earth)
        world.addChild(sun)
        
        let resource = try! TextureResource.load(named: "Starfield")
        var material = UnlitMaterial()
        material.color = .init(texture: .init(resource))
        // Attach the material to a large sphere.
        let background = Entity()
        background.components.set(ModelComponent(
            mesh: .generateSphere(radius: 1000),
            materials: [material]
        ))
        // Ensure the texture image points inward at the viewer.
        background.scale *= .init(x: -1, y: 1, z: 1)
        //world.addChild(background)
        return world
    }
    
    @MainActor
    func makePortal(_ world: Entity) -> Entity {
        let portal = Entity()
        portal.components[ModelComponent.self] = .init(mesh: .generatePlane(width: 1, 
                                                                            height: 1, 
                                                                            cornerRadius: 0.5),
                                                       materials: [PortalMaterial()])
        portal.components[PortalComponent.self] = .init(target: world)
        return portal
    }
}
问题分析与解决方案

核心问题

  1. Anchor锚定状态未稳定时添加Portal:AnchorEntity(.plane)需要时间完成空间墙面识别,锚定成功前它的空间变换矩阵是临时状态。Portal依赖正确的空间变换实现特殊渲染逻辑,此时添加会导致渲染失败;而普通模型(如Sun)不受此限制。
  2. 异步加载代码顺序混乱:try! await Entity(named: "Sun")是异步操作,会导致Sun的添加时机晚于Portal,此时Anchor可能已经完成锚定,所以Sun能正常显示,而Portal因早加在未锚定的Anchor上无法渲染。

修复代码

步骤1:修改RealityView为异步闭包,等待Anchor锚定后添加Portal

struct ImmersiveView: View {
    var addToAnchor = true
    var body: some View {
        // 将闭包改为async形式
        RealityView { content async in
            let world = makeWorld()
            let portal = makePortal(world)
            content.add(world)
            
            let anchor = AnchorEntity(.plane(.vertical, classification: .wall,
                                             minimumBounds: [1, 1]))
            content.add(anchor)
            
            // 监听锚定状态,成功后再添加Portal
            content.subscribe(to: SceneEvents.AnchoredStateChanged.self, on: anchor) { event in
                if event.state == .anchored {
                    anchor.addChild(portal)
                }
            }
            
            // 异步加载Sun,此时Anchor可能已锚定
            let sun = try! await Entity(named: "Sun", in: realityKitContentBundle)
            anchor.addChild(sun)
        }
    }
    
    // 原makeWorld和makePortal方法保持不变
    @MainActor
    func makeWorld() -> Entity {
        let world = Entity()
        world.components[WorldComponent.self] = .init()
        let earth = try! Entity.load(named: "Earth", in: realityKitContentBundle)
        let sun = try! Entity.load(named: "Sun", in: realityKitContentBundle)
        earth.position = .init(x: -0.5, y: 1.5, z: -2)
        sun.position = .init(x: 0.5, y: 1.5, z: -2)
        world.addChild(earth)
        world.addChild(sun)
        
        let resource = try! TextureResource.load(named: "Starfield")
        var material = UnlitMaterial()
        material.color = .init(texture: .init(resource))
        let background = Entity()
        background.components.set(ModelComponent(
            mesh: .generateSphere(radius: 1000),
            materials: [material]
        ))
        background.scale *= .init(x: -1, y: 1, z: 1)
        return world
    }
    
    @MainActor
    func makePortal(_ world: Entity) -> Entity {
        let portal = Entity()
        portal.components[ModelComponent.self] = .init(mesh: .generatePlane(width: 1, 
                                                                            height: 1, 
                                                                            cornerRadius: 0.5),
                                                       materials: [PortalMaterial()])
        portal.components[PortalComponent.self] = .init(target: world)
        return portal
    }
}

额外优化建议

  • 替换try!为do-catch处理资源加载失败,避免崩溃:
    do {
        let sun = try await Entity(named: "Sun", in: realityKitContentBundle)
        anchor.addChild(sun)
    } catch {
        print("加载Sun失败:\(error)")
    }
    
  • 可给Portal添加临时占位模型,在锚定完成前显示,提升用户体验。

内容的提问来源于stack exchange,提问作者tommy0607

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 04:18:26