SceneKit:如何识别和访问3D模型中的面部?

时间:2018-01-24 18:07:46

标签: ios 3d polygon scenekit mesh

在Blender中,您可以查看和访问3D模型的每个面部,如下所示:https://poly.google.com/view/6mRHqTCZHxw

enter image description here

在SceneKit中是否可以这样做,即访问模型的每个面?

This question is similar并暗示它是不可能的,但不确认SceneKit是否允许您以编程方式访问模型的所有面。 (它侧重于识别被触摸的面部。)

两个问题:

1)您能以编程方式访问每张脸吗?

2)你可以过滤并只访问可见的面孔(即忽略"内部"或被其他面部遮挡的面孔)?

2 个答案:

答案 0 :(得分:1)

可以,但 SceneKit 中没有方便的方法让你这样做,所以你必须自己构建它。

  1. 是的,如果您定义一个面是什么,并将其映射到模型中的顶点。例如,您可以按照相同的顺序将SCNGeometry的SCNGeometrySources读取到您自己的面对象数组中。使用faceIndex可以获得面数组的索引。要更新它们,您必须基于您自己的faces数组中的数据,以编程方式构建基于SCNGeometrySources的SCNGeometry。
  2. 注意,faceIndex返回渲染的三角形而不是四边形/多边形,因此您必须转换它(如果所有四边形都非常可行)。

    我正在开发基于SceneKit的应用程序,它基本上是ipad专业版的迷你Blender。它使用halfedge数据结构,其中包含顶点,边和面的对象。这允许访问这些元素,但实际上它允许访问映射到模型的半边数据结构,这形成了替换渲染的几何的基础。

    1. 不直接。如果将几何体映射到数据模型,当然可以在渲染之前计算它,但遗憾的是,Scenekit不能提供一种方便的方法来了解哪些面没有渲染。
    2. 所有人都说,面只是顶点和索引的集合,它们存储在SCNGeometrySources中。如果您添加要添加面的原因以及要对其顶点执行的操作,可能更容易提供更好的答案。

      编辑:根据您的评论"例如,如果他们点击脸部,脸部应该变成蓝色。"

      正如我上面提到的,面部只是顶点和索引的集合,面部本身没有颜色,它是可以有颜色的顶点。 SCNNode具有一个SCNGeometry,它有几个SCNGeometrySources,用于保存有关顶点的信息以及它们如何用于渲染面。所以你想要做的是从faceIndex转到SCNGeometrySource中的相应顶点索引。然后,您需要将后者读入一个向量数组,根据需要更新它们,然后根据您自己的向量数组创建一个SCNGeometrySource。

      正如我所提到的,faceIndex只提供了渲染内容的索引,不一定是你提供的内容(SCNGeometrySource),因此这需要将模型映射到数据结构。

      如果你的模型由所有三角形组成并且具有与共享相对的唯一顶点,不交错顶点数据,则faceIndex 0将对应于顶点0,1和2,而faceIndex 1将对应于顶点3,4 ,和SCNGeometrySource中的5。对于四边形和其他多边形以及交错的顶点数据,它变得非常复杂。

      简而言之,在SceneKit中没有直接访问面部实体,但可以通过编程方式修改SCNGeometrySources(具有顶点位置,颜色,法线uv coords)。

      编辑2:基于进一步的评论: primitiveType告诉Scenekit如何构造模型,它实际上并不转换它。因此,它仍然需要对模型进行三角测量。但是如果所有三角形都是AND,并且如果模型使用唯一顶点(与共享顶点与相邻面相对,则模型io提供了一个函数,可以在必要时将顶点从共享中拆分为唯一)并且如果SCNGeometrySource中的所有顶点都实际渲染(如果模型是正确构造的,通常就是这种情况),然后是。可以对多边形执行相同操作,请参阅https://developer.apple.com/documentation/scenekit/scngeometryprimitivetype/scngeometryprimitivetypepolygon

      enter image description here

      多边形5,3,4,3只有在它们都是明显不是的三角形时才对应于面部索引0,1,2,3。根据每个多边形的顶点数量,您可以确定将为多边形渲染多少个三角形。基于此,可以获得相应顶点的索引。

      例如,第一个多边形对应于面部索引0,1和2(需要3个三角形来创建具有5个顶点的多边形),第二个多边形是面部索引3,第三个多边形是faceIndex 4和5。

      实际上,这意味着循环遍历元素中的多边形并添加到faceCounter var(每个顶点的增量为1,大于2),直到达到与faceIndex相同的值。虽然在我自己的数据结构中,我实际上自己做了同样的基本转换并且工作得很好。

      EDIT3:实际步骤:

      将SCNGeometryElement转换为整数数组。

      将具有颜色语义的SCNGeometrySource转换为矢量数组。有可能没有带颜色语义的SCNGeometrySource,在这种情况下你必须创建它。

      如果使用了多边形基元,则循环遍历从SCNGeometryElement创建的数组的第一部分(最多为基元数,在本例中为多边形),并保留一个计数器,为每个顶点添加1 2.因此,如果多边形有3个顶点,则将计数器增加1,如果多边形有4个顶点,则增加2.每次增加计数器,因此对于每个多边形,检查是否已到达faceIndex。一旦到达包含点击面的多边形,就可以使用上图中描绘的映射从SCNGeometryElement的第二部分获取相应的顶点索引。如果添加第二个变量并使用每个多边形的顶点计数递增,同时循环遍历它们,您已经知道元素中存储的顶点索引的索引。

      如果所有多边形都是四边形,则转换更容易,faceindex 0和1对应多边形0,面对索引2和3对应多边形1.

      从SCNGeometryElement获得顶点索引后,可以修改从中创建的数组和SCNGeometrySource中的那些索引处的顶点。然后重新创建并更新SCNGeometry的SCNGeometrySource。

      最后但并非最不重要的是,除非您使用自定义着色器,否则通过SCNGeometrySource提供的顶点颜色只有在指定的材质具有白色作为漫反射时才会正确显示(因此您可能必须使基本纹理变为白色) )。

答案 1 :(得分:0)

@Xartec 对第一个问题的回答的实现,同样基于 Apple 文档,在 Swift 5.3 中:

extension SCNGeometryElement {
    var faces: [[Int]] {
        func arrayFromData<Integer: BinaryInteger>(_ type: Integer.Type, startIndex: Int = 0, size: Int) -> [Int] {
            assert(self.bytesPerIndex == MemoryLayout<Integer>.size)
            return [Integer](unsafeUninitializedCapacity: size) { arrayBuffer, capacity in
                self.data.copyBytes(to: arrayBuffer, from: startIndex..<startIndex + size * MemoryLayout<Integer>.size)
                capacity = size
            }
                .map { Int($0) }
        }

        func integersFromData(startIndex: Int = 0, size: Int = self.primitiveCount) -> [Int] {
            switch self.bytesPerIndex {
            case 1:
                return arrayFromData(UInt8.self, startIndex: startIndex, size: size)
            case 2:
                return arrayFromData(UInt16.self, startIndex: startIndex, size: size)
            case 4:
                return arrayFromData(UInt32.self, startIndex: startIndex, size: size)
            case 8:
                return arrayFromData(UInt64.self, startIndex: startIndex, size: size)
            default:
                return []
            }
        }

        func vertices(primitiveSize: Int) -> [[Int]] {
            integersFromData(size: self.primitiveCount * primitiveSize)
                .chunked(into: primitiveSize)
        }

        switch self.primitiveType {
        case .point:
            return vertices(primitiveSize: 1)
        case .line:
            return vertices(primitiveSize: 2)
        case .triangles:
            return vertices(primitiveSize: 3)
        case .triangleStrip:
            let vertices = integersFromData(size: self.primitiveCount + 2)
            return (0..<vertices.count - 2).map { index in
                Array(vertices[(index..<(index + 3))])
            }
        case .polygon:
            let polygonSizes = integersFromData()
            let allPolygonsVertices = integersFromData(startIndex: polygonSizes.count * self.bytesPerIndex, size: polygonSizes.reduce(into: 0, +=))
            var current = 0
            return polygonSizes.map { count in
                defer {
                    current += count
                }
                return Array(allPolygonsVertices[current..<current + count])
            }
        @unknown default:
            return []
        }
    }
}

结果数组是一个面数组,每个面包含一个顶点索引列表。
关于如何从 SCNGeometrySource 中提取顶点的答案可以在 https://stackoverflow.com/a/66748865/3605958 处找到,并且可以更新以获取颜色。

对于#2,我认为没有办法。