简体   繁体   English

SceneKit:如何从 3D 模型中识别和访问人脸?

[英]SceneKit: how to identify and access faces from 3D model?

In Blender, you can see and access each face of a 3D model like this one: https://poly.google.com/view/6mRHqTCZHxw在 Blender 中,您可以查看和访问 3D 模型的每个面,如下所示: https : //poly.google.com/view/6mRHqTCZHxw

在此处输入图片说明

Is it possible in SceneKit to do the same, that is access each face of the model?在 SceneKit 中是否可以做同样的事情,即访问模型的每个面?

This question is similar and implies it is impossible, but does not confirm if SceneKit lets you programmatically access all faces of a model. 这个问题很相似,暗示这是不可能的,但不能确认 SceneKit 是否允许您以编程方式访问模型的所有面。 (It focuses on identifying the face touched.) (它侧重于识别被触摸的脸。)

Two questions:两个问题:

1) Can you programmatically access each face? 1) 你能以编程方式访问每张脸吗?

2) Can you filter and only access faces that are visible (ie, ignore faces that "inside" or occluded by other faces)? 2)您能否过滤并仅访问可见的人脸(即,忽略“内部”或被其他人脸遮挡的人脸)?

An implementation of @Xartec's answer for your first question #1, based also on the Apple documentation, in Swift 5.3: @Xartec 在 Swift 5.3 中对第一个问题 #1 的回答的实现,同样基于 Apple 文档:

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 []
        }
    }
}

The resulting arrays is an array of faces, each faces containing a list of vertex index.结果数组是一个面数组,每个面都包含一个顶点索引列表。
An answering for how to extract the vertices from SCNGeometrySource can be found there https://stackoverflow.com/a/66748865/3605958 , and can be updated to get colors instead.关于如何从SCNGeometrySource提取顶点的SCNGeometrySource可以在https://stackoverflow.com/a/66748865/3605958找到,并且可以更新以获取颜色。

You will need this extension that implements the chunked(into:) method used above:您将需要这个扩展来实现上面使用的chunked(into:)方法:


extension Collection {
    func chunked(into size: Index.Stride) -> [[Element]] where Index: Strideable {
        precondition(size > 0, "Chunk size should be atleast 1")
        return stride(from: self.startIndex, to: self.endIndex, by: size).map {
            Array(self[$0..<Swift.min($0.advanced(by: size), self.endIndex)])
        }
    }
}

For #2, I don't believe there's a way.对于#2,我不相信有办法。

You can, but there is no convenient way built into SceneKit that lets you do it so you would have to built that yourself.可以,但是SceneKit没有内置方便的方法让你这样做,所以你必须自己构建它。

  1. Yes, if you define what a face is and map that to the vertices in the model.是的,如果您定义面是什么并将其映射到模型中的顶点。 For example, you could read the SCNGeometry's SCNGeometrySources into your own arrays of face objects, in the same order.例如,您可以按照相同的顺序将 SCNGeometry 的 SCNGeometrySources 读入您自己的面部对象数组。 Using the faceIndex you can than get the index to your array of faces.使用 faceIndex,您可以获得面部数组的索引。 To update them, you would have to construct a SCNGeometry based on SCNGeometrySources programmatically, based on your own data from the faces array.要更新它们,您必须根据您自己的面数组中的数据,以编程方式基于 SCNGeometrySources 构建 SCNGeometry。

Note, the faceIndex returns the triangle rendered and not the quad/polygon so you have to convert it (very doable if all quads).请注意, faceIndex 返回渲染的三角形而不是四边形/多边形,因此您必须转换它(如果所有四边形非常可行)。

I'm working on a SceneKit based app that is basically a mini Blender for ipad pros.我正在开发一个基于 SceneKit 的应用程序,它基本上是 ipad 专业人士的迷你搅拌机。 It uses a halfedge datastructure with objects for vertices and edges and faces.它使用带有顶点、边和面对象的半边数据结构。 This allows access to those elements but in reality it allows access to the half edge data structure mapped to the model, which forms the basis for the geometry that replaces the one rendered.这允许访问这些元素,但实际上它允许访问映射到模型的半边数据结构,它构成了替换渲染的几何图形的基础。

  1. Not directly.不直接。 If you have the geometry mapped to a data model it is of course possible to calculate it before rendering but unfortunately Scenekit doesn't provide a convenient way to know which faces weren't rendered.如果您将几何图形映射到数据模型,当然可以在渲染之前计算它,但不幸的是,Scenekit 没有提供一种方便的方法来了解哪些面没有被渲染。

That all said, a face is merely a collection of vertices and indices, which are stored in the SCNGeometrySources.话虽如此,面仅仅是顶点和索引的集合,它们存储在 SCNGeometrySources 中。 It may be easier to provide a better answer if you add why you want to add the faces and what you want to do with its vertices.如果添加要添加面的原因以及要对其顶点执行的操作,则可能更容易提供更好的答案。

EDIT: based on your comment "if they tap on face, for instance, the face should turn blue."编辑:根据您的评论“例如,如果他们点击脸部,脸部应该变成蓝色。”

As I mentioned above, a face is merely a collection of vertices and indices, a face itself does not have a color, it is the vertices that can have a color.正如我上面提到的,面仅仅是顶点和索引的集合,面本身没有颜色,只有顶点可以有颜色。 A SCNNode has a SCNGeometry that has several SCNGeometrySources that hold the information about the vertices and how they are used to render faces. SCNNode 有一个 SCNGeometry,其中有多个SCNGeometrySources ,这些SCNGeometrySources保存有关顶点的信息以及它们如何用于渲染面。 So what you want to do is go from faceIndex to corresponding vertex indices in the SCNGeometrySource.所以你想要做的是从 faceIndex 到 SCNGeometrySource 中对应的顶点索引。 You then need to read the latter into an array of vectors, update them as desired, and then create a SCNGeometrySource based on your own array of vectors.然后,您需要将后者读入向量数组,根据需要更新它们,然后根据您自己的向量数组创建 SCNGeometrySource。

As I mentioned the faceIndex merely provides an index of what was rendered an not necessarily what you fed it (the SCNGeometrySource) so this requires mapping the model to a data structure.正如我提到的,faceIndex 仅提供渲染内容的索引,不一定是您提供给它的内容(SCNGeometrySource),因此这需要将模型映射到数据结构。

If your model would consists of all triangles and has unique verts opposed to shared, does not interleave the vertex data, then faceIndex 0 would correspond to vertex 0, 1, and 2, and faceIndex 1 would correspond to vertex 3, 4, and 5 in the SCNGeometrySource.如果您的模型由所有三角形组成并具有与共享相反的唯一顶点,不交错顶点数据,则 faceIndex 0 将对应于顶点 0、1 和 2,而 faceIndex 1 将对应于顶点 3、4 和 5在 SCNGeometrySource 中。 In case of quads and other polygons and interleaved vertex data it becomes significantly more complicated.在四边形和其他多边形以及交错顶点数据的情况下,它变得更加复杂。

In short, there is no direct access to face entities in SceneKit but it is possible to modify the SCNGeometrySources (with vertex positions, colors, normals uv coords) programmatically.简而言之,SceneKit 中无法直接访问人脸实体,但可以通过编程方式修改 SCNGeometrySources(具有顶点位置、颜色、法线 uv 坐标)。

EDIT 2: based on further comments: The primitiveType tells Scenekit how the model is constructed, it does not actually convert it.编辑 2:基于进一步的评论:primitiveType 告诉 Scenekit 模型是如何构建的,它实际上并没有转换它。 So it would still require the model to be triangulated already.所以它仍然需要已经对模型进行三角剖分。 But then if all triangles, AND if the model uses unique vertices (opposed to sharing verts with adjacent faces, model io provides a function to split vertices to unique from shared if necessary) AND if all the verts in the SCNGeometrySource are actually rendered (which is usually the case if the model is properly constructed), then yes.但是,如果所有三角形,并且如果模型使用唯一的顶点(反对与相邻面共享顶点,模型 io 提供了一个函数来将顶点从共享中拆分为唯一的,如果需要)并且如果 SCNGeometrySource 中的所有顶点实际上都被渲染(其中如果模型构造正确,通常就是这种情况),那么是的。 It is possible to do the same with polygons, see https://developer.apple.com/documentation/scenekit/scngeometryprimitivetype/scngeometryprimitivetypepolygon可以对多边形执行相同的操作,请参阅https://developer.apple.com/documentation/scenekit/scngeometryprimitivetype/scngeometryprimitivetypepolygon

在此处输入图片说明

Polygon 5, 3, 4, 3 would correspond to face index 0, 1, 2, 3 only if they were all triangles which they are obviously not.多边形 5, 3, 4, 3 仅当它们都是三角形时才对应于面索引 0, 1, 2, 3,而它们显然不是。 Based on the number of vertices per polygon however you can determine how many triangles will be rendered for the polygon.但是,根据每个多边形的顶点数,您可以确定将为多边形渲染多少个三角形。 Based on that it is possible to get the index of the corresponding verts.基于此,可以得到相应顶点的索引。

For example, the first polygon corresponds to face index 0, 1 and 2 (takes 3 triangles to create that polygon with 5 verts), the second polygon is face index 3, the third polygon is faceIndex 4 and 5.例如,第一个多边形对应于面索引 0、1 和 2(需要 3 个三角形来创建具有 5 个顶点的多边形),第二个多边形是面索引 3,第三个多边形是 faceIndex 4 和 5。

In practice that means looping through the polygons in the element and adding to a faceCounter var (increment with 1 for each vert more than 2) till you reached the same value as faceIndex.在实践中,这意味着循环遍历元素中的多边形并添加到 faceCounter var(每个超过 2 的顶点增加 1),直到达到与 faceIndex 相同的值。 Though on my own datastructure, I actually do this same basic conversion myself and works quite well.尽管在我自己的数据结构上,我实际上自己也做了同样的基本转换并且工作得很好。

EDIT3: in practical steps: EDIT3:在实际步骤中:

Convert the SCNGeometryElement to an array of ints.将 SCNGeometryElement 转换为整数数组。

Convert the SCNGeometrySource with the color semantic to an array of vectors.将具有颜色语义的 SCNGeometrySource 转换为向量数组。 It is possible there is no SCNGeometrySource with the color semantic in which case you will have to create it.可能没有带有颜色语义的 SCNGeometrySource,在这种情况下,您必须创建它。

If the polygon primitive is used, loop through the first portion (up to the number of primitives, in this case polygons) of the array you created from the SCNGeometryElement and keep a counter to which you add 1 for every vert more than 2. So if the polygon has 3 verts, increment the counter with 1, if the polygon has 4 verts, increment with 2. Everytime you increment the counter, thus for every polygon, check if faceIndex has been reached.如果使用多边形基元,则循环遍历您从 SCNGeometryElement 创建的数组的第一部分(最多为基元数,在本例中为多边形),并保留一个计数器,为每个大于 2 的顶点添加 1。所以如果多边形有 3 个顶点,则计数器加 1,如果多边形有 4 个顶点,则增加 2。每次增加计数器,因此对于每个多边形,检查是否已达到 faceIndex。 Once you get to the polygon that contains the tapped face, you can get the corresponding vertex indices from the second part of the SCNGeometryElement using the mapping depicted in the image above.一旦到达包含被点击的面的多边形,您就可以使用上图所示的映射从 SCNGeometryElement 的第二部分获取相应的顶点索引。 If you add a second variable and increment that with the vertex count of each polygon while looping through them you already know the indices of the vertex indices stored in the element.如果您添加第二个变量并在循环遍历它们时使用每个多边形的顶点数增加它,您已经知道存储在元素中的顶点索引的索引。

If all the polygons are quads the conversion is easier and faceindex 0 and 1 correspond to polygon 0, face index 2 and 3 to polygon 1.如果所有多边形都是四边形,则转换更容易,faceindex 0 和 1 对应于多边形 0,face index 2 和 3 对应于多边形 1。

Once you got the vertex indices from the SCNGeometryElement, you can modify the vertices at those indices in the array you created from and for the SCNGeometrySource.从 SCNGeometryElement 获得顶点索引后,您可以修改从 SCNGeometrySource 创建的数组中这些索引处的顶点。 Then recreate and update the SCNGeometrySource of the SCNGeometry.然后重新创建并更新 SCNGeometry 的 SCNGeometrySource。

Last but not least, unless you use a custom shader, the vertex colors you provide through the SCNGeometrySource will only show up correctly if the material assigned has a white color as diffuse (so you may have to make the base texture white too).最后但并非最不重要的一点是,除非您使用自定义着色器,否则您通过 SCNGeometrySource 提供的顶点颜色只会在指定的材质将白色作为漫反射时正确显示(因此您可能必须将基础纹理也设为白色)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM