如何用SCNSkinner为SceneKit合并后的自定义几何体添加Blender绑定并驱动动画
问题解答
结论
可以通过SCNSkinner将Blender导出的骨骼绑定附加到合并后的model节点上,实现对几何体的骨骼操控。
具体实现步骤
- 第一步:导出并加载骨骼资源
从Blender中将骨骼rig单独导出为DAE/USDZ格式,导出时仅勾选骨骼层级,不导出网格资源。将导出的骨骼文件导入Xcode项目后,加载得到骨骼根节点:
注意替换代码中的文件名、骨骼根节点名称为你Blender导出时对应的实际名称。// 加载骨骼场景 guard let rigScene = SCNScene(named: "yourRigFile.dae"), let skeletonRoot = rigScene.rootNode.childNode(withName: "skeleton", recursively: true) else { fatalError("骨骼资源加载失败") } - 第二步:确认顶点权重数据正常
你在Blender中需要提前给head、upperBody、legs等各个部位的网格,针对这套rig完成顶点权重绘制,再导出单个部件导入SceneKit。调用flattenedClone()合并后,得到的model.geometry会自动整合所有子网格的boneWeights(骨骼权重)和boneIndices(骨骼索引)数据,无需额外处理。 - 第三步:收集骨骼数据构建
SCNSkinner
先递归收集所有骨骼节点,再创建SCNSkinner实例:// 递归收集所有骨骼节点 func collectBones(from node: SCNNode, into array: inout [SCNNode]) { if node.bone != nil { array.append(node) } node.childNodes.forEach { collectBones(from: $0, into: &array) } } var bones = [SCNNode]() collectBones(from: skeletonRoot, into: &bones) // 获取所有骨骼的逆绑定姿势矩阵 let inverseBindTransforms = bones.map { $0.bone!.inverseBindTransform } // 创建SCNSkinner guard let geometry = model.geometry, let weightSource = geometry.sources(for: .boneWeights).first, let indexSource = geometry.sources(for: .boneIndices).first, let skinner = SCNSkinner( baseGeometry: geometry, bones: bones, boneInverseBindTransforms: inverseBindTransforms as [NSValue], boneWeights: weightSource, boneIndices: indexSource ) else { fatalError("SCNSkinner创建失败") } - 第四步:绑定到model节点
将skinner附加到model,同时把骨骼根节点加入场景,不需要显示骨骼可设为隐藏:skinner.skeleton = skeletonRoot model.skinner = skinner model.addChildNode(skeletonRoot) // 隐藏骨骼线框 skeletonRoot.isHidden = true - 第五步:测试骨骼操控
修改任意骨骼的变换属性即可控制网格变形,示例:// 旋转第一根骨骼测试效果 bones.first?.eulerAngles.x = .pi / 4
注意事项
- 从Blender导出骨骼和各个部位网格时,必须保证两者的坐标系、原点位置、初始姿态完全一致,否则会出现网格错位问题
- 调用
flattenedClone()合并前,不要修改head、upperBody、legs等节点的初始位置、缩放、旋转参数,保持和Blender中初始姿态一致,避免权重匹配出错 - SceneKit默认每个顶点最多受4根骨骼影响,Blender导出权重时需要对应设置权重限制
内容的提问来源于stack exchange,提问作者schnabler
相关产品推荐
相关产品推荐

