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

如何在RealityKit中「展平」ModelEntity

如何在RealityKit中「展平」ModelEntity

我太懂你从SceneKit转RealityKit时的这种纠结了——SceneKit里要么用现成的flattenedClone()一键搞定,要么手动撸SCNGeometry也能把变换烘焙进去,但RealityKit这边确实没直接提供这个API,得自己动手捣鼓两步。不过别担心,有两种靠谱的方法能实现你要的效果:把ModelEntity的变换「烤」进MeshResource里,之后就能放心用Transform.identity了。


方法一:手动处理顶点数据(纯RealityKit方案)

这种方法更灵活,能保留RealityKit的原生属性,适合不想依赖SceneKit的场景。核心思路就是把每个顶点位置、法线都用目标变换矩阵处理一遍,再重新生成MeshResource。

具体步骤:

  1. 先拿到你要烘焙的变换矩阵——如果是局部变换就用modelEntity.transform.matrix,要世界空间的变换就用modelEntity.transformMatrix(relativeTo: nil)。
  2. 从原MeshResource里提取顶点、法线、UV这些属性,逐个应用变换(注意法线得用逆转置矩阵,不然缩放后光照会崩)。
  3. 用处理后的顶点数据重新生成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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 13:08:00