如何在Reality Kit中为Entity设置动态生成的UIImage漫反射纹理
问题描述
我有一个书本的.usdz模型,支持用户自定义底色、书脊文字和封面图片,在SceneKit中可以正常动态修改纹理,但在RealityKit中无法实现。
动态生成纹理的逻辑是基于模型UV映射定位元素,生成的UIImage在SceneKit中直接赋值给材质的diffuse即可生效,但在RealityKit中尝试将生成的纹理保存到文档目录后加载时,报错**"Could not find texture or image named 'myBook.png' in supplied bundle"**。推测是因为动态生成的纹理不在编译包内,但无法提前加入包中,请问RealityKit中能否实现这个需求?
解决方案
完全可以在RealityKit中实现动态纹理替换,核心问题是你用了错误的纹理加载方式——TextureResource.load(named:)是从App的主bundle中加载资源,而你的动态纹理存在文档目录,需要用基于文件URL的加载方法,甚至可以直接从内存中的UIImage创建纹理资源,无需保存到本地。
步骤1:直接从内存UIImage创建TextureResource(推荐)
不需要把生成的UIImage保存到文档目录,直接用TextureResource.generate(from:)方法从内存中的图片生成纹理资源,省去IO操作:
// 假设已经通过generateTexture方法得到了目标UIImage let dynamicTexture = viewModel.generateTexture(color: .blue, userImage: userSelectedImage, text: "自定义书名") // 直接从UIImage生成TextureResource guard let textureResource = try? TextureResource.generate(from: dynamicTexture, options: .init(semantic: .color)) else { print("生成纹理资源失败") return }
步骤2:正确替换ModelEntity的材质
加载.usdz模型后,需要准确找到对应的ModelEntity,并替换其材质。注意不要直接替换所有材质(除非你的模型所有部分都用同一张纹理),最好根据材质名称定位:
func placeBookInFrontOfCamera() { do { let bookEntity = try Entity.load(named: "my_book.usdz") bookEntity.scale = SIMD3<Float>(1, 1, 1) // 1. 获取动态生成的纹理 let dynamicTexture = viewModel.generateTexture(color: .brown, userImage: nil, text: "我的自定义书籍") guard let textureResource = try? TextureResource.generate(from: dynamicTexture, options: .init(semantic: .color)) else { print("纹理生成失败") return } // 2. 创建RealityKit材质 var customMaterial = SimpleMaterial() customMaterial.color = .init(tint: .white, texture: .init(textureResource)) // 可选:设置其他材质属性,比如粗糙度、金属度 customMaterial.roughness = .init(floatLiteral: 0.5) customMaterial.metalness = .init(floatLiteral: 0.1) // 3. 遍历模型中的ModelEntity,替换对应材质 bookEntity.enumerateChildEntities { entity, _ in guard let modelEntity = entity as? ModelEntity, var modelComponent = modelEntity.model else { return } // 如果你知道目标材质的名称,可以精准替换,比如替换名为"BookCover"的材质 for (index, material) in modelComponent.materials.enumerated() { if let materialName = material.name, materialName == "BookCover" { modelComponent.materials[index] = customMaterial } // 如果所有材质都需要替换,直接赋值:modelComponent.materials[index] = customMaterial } modelEntity.model = modelComponent } // 4. 放置模型到相机前方 guard let currentFrame = session.currentFrame else { print("AR帧不可用") return } var translation = matrix_identity_float4x4 translation.columns.3.z = -0.35 let transform = simd_mul(currentFrame.camera.transform, translation) let anchor = AnchorEntity(world: transform) anchor.addChild(bookEntity) // 添加灯光 let light = DirectionalLight() light.light.intensity = 3000 light.look(at: .zero, from: SIMD3(0,1,1), relativeTo: nil) let lightAnchor = AnchorEntity() lightAnchor.addChild(light) scene.addAnchor(lightAnchor) scene.addAnchor(anchor) } catch { print("错误:\(error.localizedDescription)") } }
步骤3:如果必须从文档目录加载纹理
如果因为某些原因需要从文档目录加载,使用TextureResource.load(contentsOf:)方法,传入文件的URL:
let documentsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! let textureURL = documentsDir.appendingPathComponent("myBook-albedo.png") guard FileManager.default.fileExists(atPath: textureURL.path) else { print("纹理文件不存在") return } let textureResource = try TextureResource.load(contentsOf: textureURL)
关键错误修正
你之前代码中的核心错误是:
// 错误:这个方法是从bundle加载,不是文档目录 let textureResource = try TextureResource.load(named: "monthbook_5x7-albedo.png")
替换为基于URL或内存UIImage的加载方式即可解决找不到纹理的问题。
内容的提问来源于stack exchange,提问作者Benjamin Walbrecht

