如何在SceneKit中从SCNGeometry提取面信息
从SCNNode几何体提取面信息的实现方法
SceneKit中,顶点数据存储在SCNGeometrySource中,而面(图元)数据全部存储在SCNGeometryElement实例中,每个SCNGeometry对象可以包含1个或多个SCNGeometryElement,分别对应不同的渲染分组、材质或者图元类型(三角形、线、点等),提取面信息的具体操作如下:
实现步骤
- 从目标SCNNode中获取关联的SCNGeometry对象
注意:如果节点的geometry为nil,或需要提取带变形、蒙皮、空间变换后的面信息,需要先将几何体烘焙到对应空间的静态网格,或取节点的presentation节点的几何体再进行后续解析。
- 遍历SCNGeometry的
elements数组,每个元素对应一组面数据 - 解析单个
SCNGeometryElement的核心属性,获取面的基础信息:primitiveType:面的类型,常用类型包括.triangles(三角形面)、.triangleStrip(三角带)、.polygon(多边形面)、.lines(线段)、.points(点)primitiveCount:当前element包含的面总数量data:存储顶点索引的二进制数据,每个面由N个顶点索引组成,N的取值由primitiveType决定,例如三角形面每个对应3个索引
- 将二进制索引数据解析为整数索引数组,关联提前提取的顶点坐标数组,即可得到完整的面信息
代码示例(Swift)
// 1. 从目标节点获取几何体 guard let geometry = yourSCNNode.geometry else { return } // 2. 提前提取顶点坐标数组备用 var vertexList: [SCNVector3] = [] if let vertexSource = geometry.sources(for: .vertex).first { let vertexCount = vertexSource.vectorCount vertexList = Array(repeating: SCNVector3Zero, count: vertexCount) vertexSource.getVertices(&vertexList, range: NSRange(location: 0, length: vertexCount)) } // 3. 遍历所有geometry element提取面信息 for (elementIndex, element) in geometry.elements.enumerated() { print("第\(elementIndex)组面,类型:\(element.primitiveType),总面数:\(element.primitiveCount)") let indexData = element.data let bytesPerIndex = element.bytesPerIndex var indexOffset = 0 // 遍历当前组的所有面 for faceIdx in 0..<element.primitiveCount { var faceIndices: [Int] = [] let indexCountPerFace: Int // 不同图元类型对应不同的索引数量 switch element.primitiveType { case .triangles: indexCountPerFace = 3 case .triangleStrip: indexCountPerFace = faceIdx == 0 ? 3 : 1 case .lines: indexCountPerFace = 2 case .points: indexCountPerFace = 1 case .polygons: // 多边形类型需要先读取第一个值作为当前面的顶点数 var polyVertexCount: UInt8 = 0 indexData.copyBytes(to: &polyVertexCount, from: indexOffset..<indexOffset+1) indexCountPerFace = Int(polyVertexCount) indexOffset += 1 @unknown default: indexCountPerFace = 0 } // 读取当前面的所有顶点索引 for i in 0..<indexCountPerFace { let index: Int switch bytesPerIndex { case 1: var value: UInt8 = 0 indexData.copyBytes(to: &value, from: indexOffset + i*bytesPerIndex ..< indexOffset + (i+1)*bytesPerIndex) index = Int(value) case 2: var value: UInt16 = 0 indexData.copyBytes(to: &value, from: indexOffset + i*bytesPerIndex ..< indexOffset + (i+1)*bytesPerIndex) index = Int(value) case 4: var value: UInt32 = 0 indexData.copyBytes(to: &value, from: indexOffset + i*bytesPerIndex ..< indexOffset + (i+1)*bytesPerIndex) index = Int(value) default: index = -1 } if index >= 0 && index < vertexList.count { faceIndices.append(index) } } indexOffset += indexCountPerFace * bytesPerIndex // 面信息提取完成:faceIndices为当前面对应的顶点索引,关联vertexList即可得到面的所有顶点坐标 let faceVertices = faceIndices.map { vertexList[$0] } print("第\(faceIdx)个面的顶点坐标:\(faceVertices)") } }
注意事项
- 如果需要提取应用了节点变换后的世界空间面坐标,拿到顶点坐标后调用
yourSCNNode.convertPosition(vertex, to: nil)转换即可。 - 带骨骼动画、形变动画的动态几何体,需要先获取节点当前帧的presentation节点的几何体再提取,才能拿到实时变形后的面信息。
- 从外部导入的多边形格式(如OBJ)几何体,如果
primitiveType为.polygons,每个面的第一个索引是当前面的顶点数,之后才是对应顶点索引,解析时需要注意偏移处理。
内容的提问来源于stack exchange,提问作者Mahnoor Khan
相关产品推荐
相关产品推荐

