如何在RealityKit中「展平」ModelEntity
如何在RealityKit中「展平」ModelEntity
我太懂你从SceneKit转RealityKit时的这种纠结了——SceneKit里要么用现成的flattenedClone()一键搞定,要么手动撸SCNGeometry也能把变换烘焙进去,但RealityKit这边确实没直接提供这个API,得自己动手捣鼓两步。不过别担心,有两种靠谱的方法能实现你要的效果:把ModelEntity的变换「烤」进MeshResource里,之后就能放心用Transform.identity了。
方法一:手动处理顶点数据(纯RealityKit方案)
这种方法更灵活,能保留RealityKit的原生属性,适合不想依赖SceneKit的场景。核心思路就是把每个顶点位置、法线都用目标变换矩阵处理一遍,再重新生成MeshResource。
具体步骤:
- 先拿到你要烘焙的变换矩阵——如果是局部变换就用
modelEntity.transform.matrix,要世界空间的变换就用modelEntity.transformMatrix(relativeTo: nil)。 - 从原MeshResource里提取顶点、法线、UV这些属性,逐个应用变换(注意法线得用逆转置矩阵,不然缩放后光照会崩)。
- 用处理后的顶点数据重新生成MeshDescriptor,再转成MeshResource赋值给ModelEntity,最后把transform设为identity。
举个代码例子:
func flattenModelEntity(_ modelEntity: ModelEntity) { guard let originalMesh = modelEntity.mesh else { return } // 1. 获取要烘焙的变换矩阵(这里用局部变换,按需换世界矩阵) let transformMatrix = modelEntity.transform.matrix // 2. 处理顶点位置 guard let positionAttribute = originalMesh.attribute(.position), let positionBuffer = positionAttribute.buffer.contents().bindMemory(to: SIMD3<Float>.self, capacity: originalMesh.vertexCount) else { return } var transformedPositions = [SIMD3<Float>]() for i in 0..<originalMesh.vertexCount { let originalPos = positionBuffer[i] let homogeneousPos = SIMD4<Float>(originalPos.x, originalPos.y, originalPos.z, 1.0) let transformedHomogeneous = transformMatrix * homogeneousPos transformedPositions.append(SIMD3<Float>(transformedHomogeneous.x, transformedHomogeneous.y, transformedHomogeneous.z)) } // 3. 处理法线(必须用逆转置矩阵,避免缩放影响法线方向) var transformedNormals = [SIMD3<Float>]() if let normalAttribute = originalMesh.attribute(.normal), let normalBuffer = normalAttribute.buffer.contents().bindMemory(to: SIMD3<Float>.self, capacity: originalMesh.vertexCount) { let normalMatrix = transformMatrix.inverse.transpose for i in 0..<originalMesh.vertexCount { let originalNormal = normalBuffer[i] let homogeneousNormal = SIMD4<Float>(originalNormal.x, originalNormal.y, originalNormal.z, 0.0) let transformedHomogeneous = normalMatrix * homogeneousNormal transformedNormals.append(normalize(SIMD3<Float>(transformedHomogeneous.x, transformedHomogeneous.y, transformedHomogeneous.z))) } } // 4. 复制原Mesh的其他属性(比如UV、颜色,这里以UV为例) var textureCoords = [SIMD2<Float>]() if let uvAttribute = originalMesh.attribute(.textureCoordinate(0)), let uvBuffer = uvAttribute.buffer.contents().bindMemory(to: SIMD2<Float>.self, capacity: originalMesh.vertexCount) { for i in 0..<originalMesh.vertexCount { textureCoords.append(uvBuffer[i]) } } // 5. 构建新的MeshDescriptor var meshDescriptor = MeshDescriptor() meshDescriptor.positions = MeshBuffers.Positions(transformedPositions) if !transformedNormals.isEmpty { meshDescriptor.normals = MeshBuffers.Normals(transformedNormals) } if !textureCoords.isEmpty { meshDescriptor.textureCoordinates = [MeshBuffers.TextureCoordinates(textureCoords)] } meshDescriptor.primitives = originalMesh.primitives // 6. 生成新Mesh并替换原ModelEntity的属性 guard let flattenedMesh = try? MeshResource.generate(from: [meshDescriptor]) else { return } modelEntity.mesh = flattenedMesh modelEntity.transform = Transform.identity }
方法二:借SceneKit的东风(快捷方案)
如果你不想手动撸顶点,直接复用SceneKit的flattenedClone()是最省事儿的——把RealityKit的ModelEntity转成SCNNode,展平后再转回来就行。缺点是转来转去可能会丢失一些RealityKit独有的属性(比如自定义物理材质),需要后续重新配置。
代码示例:
func flattenViaSceneKit(_ modelEntity: ModelEntity) throws -> ModelEntity { // 转成SCNNode let scnNode = modelEntity.toSCNNode() // 用SceneKit的展平API let flattenedScnNode = scnNode.flattenedClone() // 转回RealityKit的ModelEntity let flattenedEntity = try ModelEntity(scnNode: flattenedScnNode) // 强制设为identity变换 flattenedEntity.transform = Transform.identity return flattenedEntity }
我自己在转项目的时候,简单场景就直接用SceneKit的快捷方法,复杂场景需要保留RealityKit属性就手动处理顶点,两种都挺好用的。
内容来源于stack exchange
相关产品推荐
相关产品推荐

