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

ARKit中ModelEntity点击变色及切换恢复原材质的问题

ARKit选中ModelEntity后恢复原始材质的问题解决

问题原因

你的代码中保存原始材质时,直接赋值了entity.model!.materials,而Material是引用类型,后续修改实体的材质(比如改成蓝色)时,保存到modelEntitiesMaterials里的“原始材质”会被同步修改。导致再次选中该实体时,先恢复的已经是被修改过的蓝色材质,再设置蓝色就看不到变化了。

解决方案

1. 保存材质的副本而非引用

需要为每个实体的原始材质创建独立副本,确保后续修改实体材质不会影响保存的原始数据。

2. 优化选中状态管理

直接跟踪当前选中的实体,避免每次遍历所有实体,提升性能的同时简化逻辑。

修改后的代码

替换状态跟踪变量

enum EntityState {
    case unselected
    case selected
    case correctName
    case wrongName
}

private var entitiesState: [String: EntityState] = [:]
private var modelEntities: [ModelEntity] = []
private var modelEntitiesMaterials: [String: [Material]] = [:]
// 新增:直接跟踪当前选中的实体,替代遍历查找
private var currentSelectedEntity: ModelEntity?

修改handleTap中的材质保存逻辑

@objc private func handleTap(sender: UITapGestureRecognizer) {
    let tapLocation = sender.location(in: arView)
    let result = arView.hitTest(tapLocation)
    
    guard let hitTest = result.first, hitTest.entity.name != "Ground Plane" else {
        selectEntity(withSelectedEntity: nil)
        return
    }
    
    guard let entity = hitTest.entity as? ModelEntity else { return }
    
    let keyExists = modelEntitiesMaterials[entity.name] != nil
    if !keyExists {
        // 保存材质的副本,避免引用导致原始材质被修改
        let originalMaterials = entity.model!.materials.map { material in
            if let simpleMat = material as? SimpleMaterial {
                // 手动复制SimpleMaterial的所有属性,创建新实例
                return SimpleMaterial(
                    color: simpleMat.color,
                    roughness: simpleMat.roughness,
                    isMetallic: simpleMat.isMetallic,
                    specularColor: simpleMat.specularColor,
                    clearcoat: simpleMat.clearcoat,
                    clearcoatRoughness: simpleMat.clearcoatRoughness,
                    normalTexture: simpleMat.normalTexture,
                    occlusionTexture: simpleMat.occlusionTexture,
                    emissiveColor: simpleMat.emissiveColor,
                    emissiveTexture: simpleMat.emissiveTexture,
                    transparency: simpleMat.transparency,
                    texture: simpleMat.texture
                )
            } else {
                // 其他材质类型使用NSObject的copy方法
                return material.copy() as! Material
            }
        }
        modelEntitiesMaterials[entity.name] = originalMaterials
    }
    
    selectEntity(withSelectedEntity: entity)
}

重写selectEntity函数

private func selectEntity(withSelectedEntity: ModelEntity?) {
    modelInformationView.isHidden = false
    
    // 先取消当前选中的实体,恢复其原始材质
    if let currentSelected = currentSelectedEntity {
        entitiesState[currentSelected.name] = .unselected
        if let originalMaterials = modelEntitiesMaterials[currentSelected.name] {
            currentSelected.model!.materials = originalMaterials
        }
        currentSelectedEntity = nil
    }
    
    guard let selectedEntity = withSelectedEntity else {
        modelInformationView.isHidden = true // 未选中任何实体时隐藏信息视图
        return
    }
    
    // 如果点击的是已选中的实体,直接取消(可选逻辑,根据需求调整)
    if selectedEntity == currentSelectedEntity {
        return
    }
    
    // 设置新的选中实体
    entitiesState[selectedEntity.name] = .selected
    currentSelectedEntity = selectedEntity
    // 应用选中状态的蓝色材质
    let selectedMaterial = SimpleMaterial(color: .link, isMetallic: false)
    selectedEntity.model?.materials[0] = selectedMaterial
}

可选:确保实体名称唯一

在placeObject中,为每个子实体设置唯一名称,避免modelEntitiesMaterials的键冲突:

if let geomChildrens = geomChildrens {
    for children in geomChildrens.children {
        let childModelEntity = children as! ModelEntity
        childModelEntity.collision = CollisionComponent(shapes: [ShapeResource.generateConvex(from: childModelEntity.model!.mesh)])
        // 设置唯一名称,避免键冲突
        childModelEntity.name = UUID().uuidString
        entitiesState[childModelEntity.name] = .unselected
        modelEntities.append(childModelEntity)
    }
}

说明

  • 材质副本:通过手动复制SimpleMaterial属性或使用copy()方法,确保保存的原始材质与实体当前材质完全独立,不会被后续修改影响。
  • 状态跟踪:直接维护currentSelectedEntity变量,避免每次遍历所有实体查找选中状态,代码更简洁,性能更优。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 12:25:13