[英]SceneKit: how to identify and access faces from 3D model?
在 Blender 中,您可以查看和訪問 3D 模型的每個面,如下所示: https : //poly.google.com/view/6mRHqTCZHxw
在 SceneKit 中是否可以做同樣的事情,即訪問模型的每個面?
這個問題很相似,暗示這是不可能的,但不能確認 SceneKit 是否允許您以編程方式訪問模型的所有面。 (它側重於識別被觸摸的臉。)
兩個問題:
1) 你能以編程方式訪問每張臉嗎?
2)您能否過濾並僅訪問可見的人臉(即,忽略“內部”或被其他人臉遮擋的人臉)?
@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 []
}
}
}
結果數組是一個面數組,每個面都包含一個頂點索引列表。
關於如何從SCNGeometrySource
提取頂點的SCNGeometrySource
可以在https://stackoverflow.com/a/66748865/3605958
找到,並且可以更新以獲取顏色。
您將需要這個擴展來實現上面使用的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)])
}
}
}
對於#2,我不相信有辦法。
你可以,但是SceneKit沒有內置方便的方法讓你這樣做,所以你必須自己構建它。
請注意, faceIndex 返回渲染的三角形而不是四邊形/多邊形,因此您必須轉換它(如果所有四邊形非常可行)。
我正在開發一個基於 SceneKit 的應用程序,它基本上是 ipad 專業人士的迷你攪拌機。 它使用帶有頂點、邊和面對象的半邊數據結構。 這允許訪問這些元素,但實際上它允許訪問映射到模型的半邊數據結構,它構成了替換渲染的幾何圖形的基礎。
話雖如此,面僅僅是頂點和索引的集合,它們存儲在 SCNGeometrySources 中。 如果添加要添加面的原因以及要對其頂點執行的操作,則可能更容易提供更好的答案。
編輯:根據您的評論“例如,如果他們點擊臉部,臉部應該變成藍色。”
正如我上面提到的,面僅僅是頂點和索引的集合,面本身沒有顏色,只有頂點可以有顏色。 SCNNode 有一個 SCNGeometry,其中有多個SCNGeometrySources ,這些SCNGeometrySources保存有關頂點的信息以及它們如何用於渲染面。 所以你想要做的是從 faceIndex 到 SCNGeometrySource 中對應的頂點索引。 然后,您需要將后者讀入向量數組,根據需要更新它們,然后根據您自己的向量數組創建 SCNGeometrySource。
正如我提到的,faceIndex 僅提供渲染內容的索引,不一定是您提供給它的內容(SCNGeometrySource),因此這需要將模型映射到數據結構。
如果您的模型由所有三角形組成並具有與共享相反的唯一頂點,不交錯頂點數據,則 faceIndex 0 將對應於頂點 0、1 和 2,而 faceIndex 1 將對應於頂點 3、4 和 5在 SCNGeometrySource 中。 在四邊形和其他多邊形以及交錯頂點數據的情況下,它變得更加復雜。
簡而言之,SceneKit 中無法直接訪問人臉實體,但可以通過編程方式修改 SCNGeometrySources(具有頂點位置、顏色、法線 uv 坐標)。
編輯 2:基於進一步的評論:primitiveType 告訴 Scenekit 模型是如何構建的,它實際上並沒有轉換它。 所以它仍然需要已經對模型進行三角剖分。 但是,如果所有三角形,並且如果模型使用唯一的頂點(反對與相鄰面共享頂點,模型 io 提供了一個函數來將頂點從共享中拆分為唯一的,如果需要)並且如果 SCNGeometrySource 中的所有頂點實際上都被渲染(其中如果模型構造正確,通常就是這種情況),那么是的。 可以對多邊形執行相同的操作,請參閱https://developer.apple.com/documentation/scenekit/scngeometryprimitivetype/scngeometryprimitivetypepolygon
多邊形 5, 3, 4, 3 僅當它們都是三角形時才對應於面索引 0, 1, 2, 3,而它們顯然不是。 但是,根據每個多邊形的頂點數,您可以確定將為多邊形渲染多少個三角形。 基於此,可以得到相應頂點的索引。
例如,第一個多邊形對應於面索引 0、1 和 2(需要 3 個三角形來創建具有 5 個頂點的多邊形),第二個多邊形是面索引 3,第三個多邊形是 faceIndex 4 和 5。
在實踐中,這意味着循環遍歷元素中的多邊形並添加到 faceCounter var(每個超過 2 的頂點增加 1),直到達到與 faceIndex 相同的值。 盡管在我自己的數據結構上,我實際上自己也做了同樣的基本轉換並且工作得很好。
EDIT3:在實際步驟中:
將 SCNGeometryElement 轉換為整數數組。
將具有顏色語義的 SCNGeometrySource 轉換為向量數組。 可能沒有帶有顏色語義的 SCNGeometrySource,在這種情況下,您必須創建它。
如果使用多邊形基元,則循環遍歷您從 SCNGeometryElement 創建的數組的第一部分(最多為基元數,在本例中為多邊形),並保留一個計數器,為每個大於 2 的頂點添加 1。所以如果多邊形有 3 個頂點,則計數器加 1,如果多邊形有 4 個頂點,則增加 2。每次增加計數器,因此對於每個多邊形,檢查是否已達到 faceIndex。 一旦到達包含被點擊的面的多邊形,您就可以使用上圖所示的映射從 SCNGeometryElement 的第二部分獲取相應的頂點索引。 如果您添加第二個變量並在循環遍歷它們時使用每個多邊形的頂點數增加它,您已經知道存儲在元素中的頂點索引的索引。
如果所有多邊形都是四邊形,則轉換更容易,faceindex 0 和 1 對應於多邊形 0,face index 2 和 3 對應於多邊形 1。
從 SCNGeometryElement 獲得頂點索引后,您可以修改從 SCNGeometrySource 創建的數組中這些索引處的頂點。 然后重新創建並更新 SCNGeometry 的 SCNGeometrySource。
最后但並非最不重要的一點是,除非您使用自定義着色器,否則您通過 SCNGeometrySource 提供的頂點顏色只會在指定的材質將白色作為漫反射時正確顯示(因此您可能必須將基礎紋理也設為白色)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.