如何实现VisionOS Portal实体突出效果?仿《Encounter Dinosaurs》体验
实现《Encounter Dinosaurs》式的Portal实体突出效果
核心原理分析
WWDC演讲中的标准Portal实现依赖PortalComponent的裁剪平面,会强制裁剪超出Portal边界的内容。而《Encounter Dinosaurs》的效果是将场景拆分为受限背景元素(如岩石,保留裁剪)和可突破前景元素(如恐龙,取消裁剪),通过动态调整渲染和遮挡逻辑,实现实体从Portal突出,同时保证相机在Portal前后都有正确表现。
分步实现方案
1. 拆分场景层级
将虚拟场景分为两个独立部分:
- 背景世界:包含需要被Portal边界裁剪的元素(如岩石、环境),使用标准
PortalMaterial+PortalComponent实现基础Portal效果。 - 前景实体:需要突破Portal的元素(如恐龙),不加入背景世界,直接添加到主场景,通过空间变换与虚拟世界对齐。
2. 配置基础Portal与背景世界
先搭建标准的Portal框架,确保背景元素正常显示且被裁剪:
// 创建背景世界(承载需裁剪的元素) let backgroundWorld = Entity() backgroundWorld.components.set(WorldComponent()) // 添加岩石、环境等实体到backgroundWorld content.add(backgroundWorld) // 创建Portal实体 let portalEntity = Entity() portalEntity.components.set(PortalComponent(world: backgroundWorld)) // 创建Portal平面网格 let portalMesh = MeshResource.generatePlane(width: 2.0, height: 3.0) let portalMaterial = PortalMaterial() portalEntity.components.set(MeshComponent(mesh: portalMesh, materials: [portalMaterial])) // 定位Portal到真实空间的目标位置 portalEntity.transform = Transform(translation: [0, 1.5, -2]) content.add(portalEntity)
3. 配置可突出的前景实体
将前景实体直接加入主场景,对齐到虚拟世界的空间位置,同时调整渲染属性避免被裁剪:
// 加载恐龙实体 let dinosaurEntity = try! Entity.load(named: "Dinosaur") // 将恐龙的空间位置与背景世界对齐 dinosaurEntity.transform = Transform(matrix: backgroundWorld.transform.matrix) // 移除默认的裁剪限制(若有) dinosaurEntity.components.remove(PortalComponent.self) content.add(dinosaurEntity)
4. 动态调整遮挡逻辑
监听相机位置变化,根据相机相对于Portal平面的位置,动态切换前景实体的遮挡状态,确保相机在Portal前后都有正确表现:
// 订阅场景更新事件,实时检测相机位置 arView.scene.subscribe(to: SceneEvents.Update.self) { [weak self] _ in guard let self = self, let portalTransform = self.portalEntity?.transform else { return } let cameraPosition = self.arView.cameraTransform.translation let portalPosition = portalTransform.translation // 获取Portal平面的法向量(假设Portal朝向相机,即Z轴负方向) let portalNormal = portalTransform.matrix.columns.2.xyz // 计算相机到Portal平面的距离:正数表示相机在真实空间侧,负数表示在虚拟空间侧 let distanceToPortal = simd_dot(cameraPosition - portalPosition, portalNormal) if distanceToPortal > 0 { // 相机在真实空间侧:让恐龙被Portal遮挡后方部分,同时突出的部分正常显示 dinosaurEntity.components.set(OcclusionComponent(occlusionPriority: .high)) // 调整材质的深度属性,确保恐龙在Portal前方的部分优先渲染 for (index, material) in dinosaurEntity.model?.materials.enumerated() ?? [] { if var standardMat = material as? StandardMaterial { standardMat.depthTestEnabled = true standardMat.depthWriteEnabled = true standardMat.zOffset = -0.01 // 避免与Portal平面的深度冲突 dinosaurEntity.model?.materials[index] = standardMat } } } else { // 相机在虚拟空间侧:移除遮挡,让恐龙完全可见 dinosaurEntity.components.remove(OcclusionComponent.self) } }
5. 细节优化
- 视觉过渡:给Portal添加半透明边框实体,增强恐龙突出部分的自然感。
- 交互适配:为前景实体添加合适的碰撞体,确保在真实空间中也能响应手势交互。
- 性能优化:避免在每帧更新中重复修改材质,可缓存材质实例,仅在相机位置切换时更新。
内容的提问来源于stack exchange,提问作者Reality-Dev
相关产品推荐
相关产品推荐

