如何在代码中创建SceneKit SCNSkinner对象?


89

我有一个使用SceneKit for iOS 8的Swift应用程序。我从.dae文件加载了一个场景,该文件包含由骨架控制的网格。在运行时,我需要修改纹理坐标。不能使用变换-我需要为网格中的每个顶点计算一个不同的全新UV。

我知道几何体在SceneKit中是不可变的,并且我已经读到建议的方法是手动制作副本。我正在尝试这样做,但是在尝试重新创建SCNSkinnerin代码时,我总是以崩溃告终。飞机坠毁是EXC_BAD_ACCESS内部的C3DSourceAccessorGetMutableValuePtrAtIndex。不幸的是,目前还没有源代码,因此我不确定为什么它会崩溃。我将其范围缩小到SCNSkinner连接到网格节点的对象。如果不设置该值,则不会崩溃,并且一切正常。

编辑:这是崩溃的更完整的调用堆栈:

C3DSourceAccessorGetMutableValuePtrAtIndex
C3DSkinPrepareMeshForGPUIfNeeded
C3DSkinnerMakeCurrentMesh
C3DSkinnerUpdateCurrentMesh
__CFSetApplyFunction_block_invoke
CFBasicHashApply
CFSetApplyFunction
C3DAppleEngineRenderScene
...

我没有找到有关如何SCNSkinner手动创建对象的任何文档或示例代码。由于我只是基于以前工作的网格来创建它,所以它应该不太困难。我正在SCNSkinner根据Swift文档创建,并将所有正确的内容传递给init。但是,SCNSkinner我不确定如何设置中的骨骼属性。我将其设置为SCNSkinner要复制的网格物体原件上的骨架,我认为该骨架可以工作……但事实并非如此。设置骨架属性时,它似乎没有分配。分配后立即检查它仍然为零。作为测试,我尝试将原始网格物体的骨架属性设置为其他属性,并且在分配后也保持不变。

任何人都可以对正在发生的事情有所了解吗?或者如何正确地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的文档中,我无法弄清楚问题出在哪里。我是做错了一些事情,遗漏了一些关键步骤,或者当从.dae文件加载时,SceneKit使用了一些私有API来正确设置SCNSkinner。有趣的是,SceneKit从.dae加载后的骨骼属性是一个没有子节点的节点,但骨骼动画显然起作用。
jcr 2014年

不幸的是,不能覆盖.dae文件。当Xcode将.dae文件复制到设备时,将发生优化和压缩步骤。无法在应用内的设备上保存新的.dae文件,因为iOS上的SceneKit不会加载尚未通过Xcode脚本运行的.dae文件。
jcr 2014年

你得到这份工作了吗?
μολὼν.λαβέ

不,我最终编写了自己的引擎,此后就再也没有使用过SceneKit。
jcr

令人印象深刻。您是否使用过opengl或metal?
μολὼν.λαβέ

Answers:


1

这三种方法可以帮助您找到解决方案:

  1. SCNNode *hero = [SCNScene sceneNamed:@"Hero"].rootNode;
    SCNNode *hat = [SCNScene sceneNamed:@"FancyFedora"].rootNode;
    hat.skinner.skeleton = hero.skinner.skeleton;
  2. [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);
    }
  3. 也请参见此链接


0

我不特别知道是什么导致您的代码崩溃,但是这是一种生成网格,骨骼和蒙皮网格的方法-全部来自代码。Swift4和iOS 12。

在示例中,存在表示两个圆柱体的串联的网格,其中一个圆柱体以45度角分叉,如下所示:

 \
 |

圆柱体只是挤压的三角形,即,radialSegmentCount = 3.(请注意,有12个顶点,而不是9个顶点,因为两个圆柱体并不是真正相连的。三角形的排序如下:

      v5
      ^
v3 /__|__\ v1
   |  |  |
   |  v4 |
v2 |/___\| v0

有3个骨骼,分别对应于圆柱体的头和脚,其中中间骨骼对应于下圆柱体的头,同时对应于上圆柱体的脚。因此,例如,顶点v0v2以及v4对应bone0; v1v3v5对应bone1,等等。这就解释了为什么boneIndices(见下文)具有它所具有的价值。

骨骼的静止位置对应于几何中圆柱体的静止位置(bone2以45度角从bone1就像圆柱体几何形状一样,与圆柱体)。

以此为上下文,以下代码创建了为几何体蒙皮所需的一切:

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

注意:在大多数情况下,您应该使用UInt16not UInt8

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.