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

ARKit中SCNFloor不跟随AR对象、阴影显示异常问题咨询

问题根因

两个异常的核心原因集中在SCNFloor的使用逻辑和ARAnchor代理的实现缺失上:

  • 超出0.5米无阴影、地板不跟随:你仅在renderer(_:didAdd:for:)代理中初始化了一次地板的位置和尺寸,没有处理AR平面的更新回调。ARKit首次检测到平面时,对平面范围的估算非常小(通常仅0.3-0.5米见方),后续随着环境扫描深入会持续扩展平面边界,你代码里的地板始终停留在初始的小尺寸状态,超出范围后没有平面接收阴影,自然不会显示。另外SCNFloor本身是默认无限延伸的水平面,你手动绑定平面中心位置、限制固定宽高的写法,反而把它变成了位置固定的有限平面。
  • 第二个对象无阴影:你为每个检测到的平面都单独创建了独立的SCNFloor实例,每个实例的尺寸仅对应当前平面首次被检测到的极小范围,新对象如果落在其他平面未被初始地板覆盖的区域,就没有平面接收阴影,只有移动到第一个地板的覆盖范围内才会显示阴影。
解决方案

优先推荐实现成本最低、性能最好的全局单阴影地板方案,完全满足仅展示阴影的需求(你用的阴影材质本身是透明的,不会渲染出可见的地面):

  • 删除原有在renderer(_:didAdd:for:)中调用addFloorSCN的逻辑,移除相关的多地板创建代码。
  • 在viewDidLoad的灯光配置代码后,直接添加一次全局SCNFloor作为阴影接收面,不需要设置宽高和位置偏移:
// 全局阴影接收地板,仅需初始化一次
let shadowFloor = SCNFloor()
shadowFloor.reflectivity = 0
shadowFloor.materials = [shadowMaterialStandard()]
let shadowFloorNode = SCNNode(geometry: shadowFloor)
sceneView.scene.rootNode.addChildNode(shadowFloorNode)
  • 补全定向光的阴影参数,扩大阴影渲染范围,避免远距离物体阴影被裁剪:
// 在原有灯光配置代码中补充以下参数
light.shadowSampleCount = 8 // 平衡阴影清晰度与性能,可按需调整
light.maximumShadowDistance = 30 // 阴影最大渲染距离,单位为米,覆盖日常AR使用场景
light.shadowCascadeCount = 3 // 开启级联阴影,提升远距离阴影的渲染质量

如果你需要支持多高度平面的阴影渲染(比如同时在桌面、地面放置对象),则使用动态更新平面地板方案,实现逻辑如下:

  1. 在控制器属性中添加字典,存储每个平面对应的地板节点引用:
var floorNodeMap: [UUID: (node: SCNNode, floor: SCNFloor)] = [:]
  1. 保留原有addFloorSCN方法,在创建地板后将引用存入字典:
private func addFloorSCN(node: SCNNode, planeAnchor: ARPlaneAnchor) {
    let anchorX = planeAnchor.center.x
    let anchorZ = planeAnchor.center.z
    
    let floor = SCNFloor()
    let floorNode = SCNNode(geometry: floor)
    floorNode.position = SCNVector3(anchorX, 0, anchorZ)

    floor.length = CGFloat(planeAnchor.extent.z)
    floor.width = CGFloat(planeAnchor.extent.x)
    floor.reflectivity = 0
    floor.materials = [shadowMaterialStandard()]
    node.addChildNode(floorNode)
    // 存入字典供后续更新使用
    floorNodeMap[planeAnchor.identifier] = (floorNode, floor)
}
  1. 实现平面更新代理,每次ARKit更新平面估算范围时,同步更新对应地板的位置和尺寸:
func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
    guard let planeAnchor = anchor as? ARPlaneAnchor,
          let floorInfo = floorNodeMap[planeAnchor.identifier] else {
        return
    }
    // 同步更新地板位置与尺寸
    floorInfo.node.simdPosition = SIMD3<Float>(planeAnchor.center.x, 0, planeAnchor.center.z)
    floorInfo.floor.length = CGFloat(planeAnchor.extent.z)
    floorInfo.floor.width = CGFloat(planeAnchor.extent.x)
}
  1. 实现平面移除代理,在平面被ARKit剔除时清理对应地板节点,避免内存泄漏:
func renderer(_ renderer: SCNSceneRenderer, didRemove node: SCNNode, for anchor: ARAnchor) {
    guard let planeAnchor = anchor as? ARPlaneAnchor else { return }
    floorNodeMap.removeValue(forKey: planeAnchor.identifier)
}
  1. 同样需要补全前面提到的定向光阴影参数,保证阴影渲染范围足够。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 09:21:35