如何在代码中创建 SceneKit SCNSkinner 对象?
Posted
技术标签:
【中文标题】如何在代码中创建 SceneKit SCNSkinner 对象?【英文标题】:How to create a SceneKit SCNSkinner object in code? 【发布时间】:2015-01-29 17:37:24 【问题描述】:我有一个使用适用于 ios 8 的 SceneKit 的 Swift 应用程序。我从包含由骨架控制的网格的 .dae 文件加载场景。 在运行时,我需要修改纹理坐标。使用变换不是一种选择——我需要为网格中的每个顶点计算一个不同的、全新的 UV。
我知道几何在 SceneKit 中是不可变的,并且我读过建议的方法是手动制作副本。我正在尝试这样做,但是在尝试在代码中重新创建 SCNSkinner
时,我总是最终崩溃。崩溃是EXC_BAD_ACCESS
内的C3DSourceAccessorGetMutableValuePtrAtIndex
。不幸的是,没有源代码,所以我不确定它到底为什么会崩溃。我已将其缩小到附加到网格节点的SCNSkinner
对象。如果我不设置它,我不会崩溃并且一切似乎都在工作。
编辑:这是一个更完整的崩溃调用堆栈:
C3DSourceAccessorGetMutableValuePtrAtIndex
C3DSkinPrepareMeshForGPUIfNeeded
C3DSkinnerMakeCurrentMesh
C3DSkinnerUpdateCurrentMesh
__CFSetApplyFunction_block_invoke
CFBasicHashApply
CFSetApplyFunction
C3DAppleEngineRenderScene
...
我没有找到任何关于如何手动创建SCNSkinner
对象的文档或示例代码。由于我只是根据以前工作的网格创建它,所以应该不会太难。我正在根据 Swift 文档创建SCNSkinner
,将所有正确的东西传递给 init。但是,SCNSkinner
中有一个骨架属性,我不确定如何设置。我将它设置为我正在复制的网格的原始SCNSkinner
上的骨架,我认为
应该工作......但它没有。设置骨架属性时,它似乎没有分配。在赋值后立即检查它表明它仍然是 nil。作为测试,我尝试将原始网格的骨架属性设置为其他内容,并且在分配后它也保持不变。
任何人都可以了解正在发生的事情吗?或者如何手动正确创建和设置SCNSkinner
对象?
这是我用来手动克隆网格并将其替换为新网格的代码(我没有在这里修改任何源数据——我只是想确保我可以在这点):
// This is at the start of the app, just so you can see how the scene is set up.
// I add the .dae contents into its own node in the scene. This seems to be the
// standard way to put multiple .dae models into the same scene. This doesn't seem to
// have any impact on the problem I'm having -- I've tried without this indirection
// and the same problem exists.
let scene = SCNScene()
let modelNode = SCNNode()
modelNode.name = "ModelNode"
scene.rootNode.addChildNode(modelNode)
let modelScene = SCNScene(named: "model.dae")
if modelScene != nil
if let childNodes = modelScene?.rootNode.childNodes
for childNode in childNodes
modelNode.addChildNode(childNode as SCNNode)
// This happens later in the app after a tap from the user.
let modelNode = scnView.scene!.rootNode.childNodeWithName("ModelNode", recursively: true)
let modelMesh = modelNode?.childNodeWithName("MeshName", recursively: true)
let verts = modelMesh?.geometry!.geometrySourcesForSemantic(SCNGeometrySourceSemanticVertex)
let normals = modelMesh?.geometry!.geometrySourcesForSemantic(SCNGeometrySourceSemanticNormal)
let texcoords = modelMesh?.geometry!.geometrySourcesForSemantic(SCNGeometrySourceSemanticTexcoord)
let boneWeights = modelMesh?.geometry!.geometrySourcesForSemantic(SCNGeometrySourceSemanticBoneWeights)
let boneIndices = modelMesh?.geometry!.geometrySourcesForSemantic(SCNGeometrySourceSemanticBoneIndices)
let geometry = modelMesh?.geometry!.geometryElementAtIndex(0)
// Note: the vertex and normal data is shared.
let vertsData = NSData(data: verts![0].data)
let texcoordsData = NSData(data: texcoords![0].data)
let boneWeightsData = NSData(data: boneWeights![0].data)
let boneIndicesData = NSData(data: boneIndices![0].data)
let geometryData = NSData(data: geometry!.data!)
let newVerts = SCNGeometrySource(data: vertsData, semantic: SCNGeometrySourceSemanticVertex, vectorCount: verts![0].vectorCount, floatComponents: verts![0].floatComponents, componentsPerVector: verts![0].componentsPerVector, bytesPerComponent: verts![0].bytesPerComponent, dataOffset: verts![0].dataOffset, dataStride: verts![0].dataStride)
let newNormals = SCNGeometrySource(data: vertsData, semantic: SCNGeometrySourceSemanticNormal, vectorCount: normals![0].vectorCount, floatComponents: normals![0].floatComponents, componentsPerVector: normals![0].componentsPerVector, bytesPerComponent: normals![0].bytesPerComponent, dataOffset: normals![0].dataOffset, dataStride: normals![0].dataStride)
let newTexcoords = SCNGeometrySource(data: texcoordsData, semantic: SCNGeometrySourceSemanticTexcoord, vectorCount: texcoords![0].vectorCount, floatComponents: texcoords![0].floatComponents, componentsPerVector: texcoords![0].componentsPerVector, bytesPerComponent: texcoords![0].bytesPerComponent, dataOffset: texcoords![0].dataOffset, dataStride: texcoords![0].dataStride)
let newBoneWeights = SCNGeometrySource(data: boneWeightsData, semantic: SCNGeometrySourceSemanticBoneWeights, vectorCount: boneWeights![0].vectorCount, floatComponents: boneWeights![0].floatComponents, componentsPerVector: boneWeights![0].componentsPerVector, bytesPerComponent: boneWeights![0].bytesPerComponent, dataOffset: boneWeights![0].dataOffset, dataStride: boneWeights![0].dataStride)
let newBoneIndices = SCNGeometrySource(data: boneIndicesData, semantic: SCNGeometrySourceSemanticBoneIndices, vectorCount: boneIndices![0].vectorCount, floatComponents: boneIndices![0].floatComponents, componentsPerVector: boneIndices![0].componentsPerVector, bytesPerComponent: boneIndices![0].bytesPerComponent, dataOffset: boneIndices![0].dataOffset, dataStride: boneIndices![0].dataStride)
let newGeometry = SCNGeometryElement(data: geometryData, primitiveType: geometry!.primitiveType, primitiveCount: geometry!.primitiveCount, bytesPerIndex: geometry!.bytesPerIndex)
let newMeshGeometry = SCNGeometry(sources: [newVerts, newNormals, newTexcoords, newBoneWeights, newBoneIndices], elements: [newGeometry])
newMeshGeometry.firstMaterial = modelMesh?.geometry!.firstMaterial
let newModelMesh = SCNNode(geometry: newMeshGeometry)
let bones = modelMesh?.skinner?.bones
let boneInverseBindTransforms = modelMesh?.skinner?.boneInverseBindTransforms
let skeleton = modelMesh!.skinner!.skeleton!
let baseGeometryBindTransform = modelMesh!.skinner!.baseGeometryBindTransform
newModelMesh.skinner = SCNSkinner(baseGeometry: newMeshGeometry, bones: bones, boneInverseBindTransforms: boneInverseBindTransforms, boneWeights: newBoneWeights, boneIndices: newBoneIndices)
newModelMesh.skinner?.baseGeometryBindTransform = baseGeometryBindTransform
// Before this assignment, newModelMesh.skinner?.skeleton is nil.
newModelMesh.skinner?.skeleton = skeleton
// After, it is still nil... however, skeleton itself is completely valid.
modelMesh?.removeFromParentNode()
newModelMesh.name = "MeshName"
let meshParentNode = modelNode?.childNodeWithName("MeshParentNode", recursively: true)
meshParentNode?.addChildNode(newModelMesh)
【问题讨论】:
我目前的解决方法是简单地计算新的纹理坐标,覆盖 .dae 文件,刷新场景,然后重新加载 .dae 文件。这应该是完全没有必要的,但是从 Apple 的文档中,我无法弄清楚问题是什么。我要么做错了事,遗漏了一些关键步骤,要么 SceneKit 在从 .dae 文件加载时使用了一些私有 API 来正确设置 SCNSkinner。有趣的是,SceneKit 从 .dae 加载后的骨架属性是一个没有没有子节点的节点,但骨骼动画显然可以工作。 很遗憾,不能覆盖 .dae 文件。当 Xcode 将您的 .dae 文件复制到设备时,会发生一个优化和压缩步骤。在应用程序内的设备上保存新的 .dae 文件不起作用,因为 iOS 上的 SceneKit 不会加载尚未通过 Xcode 脚本运行的 .dae 文件。 你得到这份工作了吗? 不,我最终编写了自己的引擎,从那以后就没有使用 SceneKit。 令人印象深刻。你是用opengl还是metal? 【参考方案1】:这三种方法可以帮你找到解决办法:
SCNNode *hero = [SCNScene sceneNamed:@"Hero"].rootNode;
SCNNode *hat = [SCNScene sceneNamed:@"FancyFedora"].rootNode;
hat.skinner.skeleton = hero.skinner.skeleton;
[Export ("initWithFrame:")]
public UIView (System.Drawing.RectangleF frame) : base (NSObjectFlag.Empty)
// Invoke the init method now.
var initWithFrame = new Selector ("initWithFrame:").Handle;
if (IsDirectBinding)
Handle = ObjCRuntime.Messaging.IntPtr_objc_msgSend_RectangleF (this.Handle, initWithFrame, frame);
else
Handle = ObjCRuntime.Messaging.IntPtr_objc_msgSendSuper_RectangleF (this.SuperHandle, initWithFrame, frame);
参见this link。
【讨论】:
【参考方案2】:我不具体知道是什么导致您的代码崩溃,但这里有一种生成网格、骨骼和蒙皮的方法——所有这些都来自代码。 Swift4 和 iOS 12。
在示例中,网格表示两个圆柱体的串联,其中一个圆柱体以 45 度角分叉,如下所示:
\
|
圆柱体只是拉伸的三角形,即radialSegmentCount = 3.
(请注意,有 12 个顶点,而不是 9 个,因为两个圆柱体并没有真正连接。三角形的顺序如下:
v5
^
v3 /__|__\ v1
| | |
| v4 |
v2 |/___\| v0
有 3 块骨头,分别对应圆柱体的头和脚,中间的骨头对应底部圆柱体的头部,同时对应顶部圆柱体的底部。所以比如顶点v0
、v2
、v4
对应bone0
; v1
、v3
、v5
对应于bone1
,依此类推。这就解释了为什么boneIndices
(见下文)具有它的价值。
骨骼的静止位置对应于几何体中圆柱体的静止位置(bone2
与 bone1
成 45 度角,就像圆柱体几何体一样)。
以此为上下文,以下代码创建了为几何体蒙皮所需的一切:
let vertices = [float3(0.17841241, 0.0, 0.0), float3(0.17841241, 1.0, 0.0), float3(-0.089206174, 0.0, 0.1545097), float3(-0.089206174, 1.0, 0.1545097), float3(-0.089206256, 0.0, -0.15450965), float3(-0.089206256, 1.0, -0.15450965), float3(0.12615661, 1.1261566, 0.0), float3(-0.58094996, 1.8332633, 0.0), float3(-0.063078284, 0.9369217, 0.1545097), float3(-0.7701849, 1.6440284, 0.1545097), float3(-0.063078344, 0.93692166, -0.15450965), float3(-0.77018493, 1.6440284, -0.15450965)]
let indices: [UInt8] = [0, 1, 2, 3, 4, 5, 0, 1, 1, 6, 6, 7, 8, 9, 10, 11, 6, 7]
let geometrySource = SCNGeometrySource(vertices: vertices.map SCNVector3($0) )
let geometryElement = SCNGeometryElement(indices: indices, primitiveType: .triangleStrip)
let geometry = SCNGeometry(sources: [geometrySource], elements: [geometryElement])
let bone0 = SCNNode()
bone0.simdPosition = float3(0,0,0)
let bone1 = SCNNode()
bone1.simdPosition = float3(0,1,0)
let bone2 = SCNNode()
bone2.simdPosition = float3(0,1,0) + normalize(float3(-1,1,0))
let bones = [bone0, bone1, bone2]
let boneInverseBindTransforms: [NSValue]? = bones.map NSValue(scnMatrix4: SCNMatrix4Invert($0.transform))
var boneWeights: [Float] = vertices.map _ in 1.0
var boneIndices: [UInt8] = [
0, 1, 0, 1, 0, 1,
1, 2, 1, 2, 1, 2,
]
let boneWeightsData = Data(bytesNoCopy: &boneWeights, count: boneWeights.count * MemoryLayout<Float>.size, deallocator: .none)
let boneIndicesData = Data(bytesNoCopy: &boneIndices, count: boneWeights.count * MemoryLayout<UInt8>.size, deallocator: .none)
let boneWeightsGeometrySource = SCNGeometrySource(data: boneWeightsData, semantic: .boneWeights, vectorCount: boneWeights.count, usesFloatComponents: true, componentsPerVector: 1, bytesPerComponent: MemoryLayout<Float>.size, dataOffset: 0, dataStride: MemoryLayout<Float>.size)
let boneIndicesGeometrySource = SCNGeometrySource(data: boneIndicesData, semantic: .boneIndices, vectorCount: boneIndices.count, usesFloatComponents: false, componentsPerVector: 1, bytesPerComponent: MemoryLayout<UInt8>.size, dataOffset: 0, dataStride: MemoryLayout<UInt8>.size)
let skinner = SCNSkinner(baseGeometry: geometry, bones: bones, boneInverseBindTransforms: boneInverseBindTransforms, boneWeights: boneWeightsGeometrySource, boneIndices: boneIndicesGeometrySource)
let node = SCNNode(geometry: geometry)
node.skinner = skinner
注意:在大多数情况下,您应该使用UInt16
而不是UInt8
。
【讨论】:
如果您设置骨骼旋转值,则不会发生任何事情。以上是关于如何在代码中创建 SceneKit SCNSkinner 对象?的主要内容,如果未能解决你的问题,请参考以下文章