จะสร้างวัตถุ SceneKit SCNSkinner ในโค้ดได้อย่างไร?


88

ฉันมีแอป Swift ที่ใช้ SceneKit สำหรับ iOS 8 ฉันโหลดฉากจากไฟล์. แดที่มีเมชที่ควบคุมโดยโครงกระดูก ในรันไทม์ฉันต้องแก้ไขพิกัดพื้นผิว การใช้การแปลงไม่ใช่ตัวเลือก - ฉันต้องคำนวณ UV ใหม่ที่แตกต่างกันโดยสิ้นเชิงสำหรับจุดยอดแต่ละจุดในตาข่าย

ฉันรู้ว่ารูปทรงเรขาคณิตไม่เปลี่ยนรูปใน SceneKit และฉันได้อ่านว่าแนวทางที่แนะนำคือการทำสำเนาด้วยตนเอง ฉันพยายามทำเช่นนั้น แต่ฉันมักจะขัดข้องเมื่อพยายามสร้างSCNSkinnerโค้ดใหม่อีกครั้ง ความผิดพลาดเป็นภายใน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 ฉันไม่สามารถเข้าใจได้ว่าปัญหาคืออะไร ฉันกำลังทำบางอย่างไม่ถูกต้องปล่อยให้เป็นขั้นตอนสำคัญบางอย่างหรือ SceneKit ใช้ API ส่วนตัวบางอย่างเพื่อตั้งค่า SCNSkinner อย่างถูกต้องเมื่อโหลดจากไฟล์. dae เป็นเรื่องที่น่าสนใจที่คุณสมบัติโครงกระดูกหลังจากโหลด SceneKit จาก. dae เป็นโหนดที่ไม่มีลูกแต่ภาพเคลื่อนไหวโครงกระดูกทำงานได้อย่างชัดเจน
jcr

การเขียนทับไฟล์. dae ไม่ใช่ตัวเลือก แต่น่าเสียดาย มีขั้นตอนการเพิ่มประสิทธิภาพและการบีบอัดที่เกิดขึ้นเมื่อ Xcode คัดลอกไฟล์. dae ของคุณไปยังอุปกรณ์ การบันทึกไฟล์. dae ใหม่บนอุปกรณ์ภายในแอพไม่ทำงานเนื่องจาก SceneKit บน iOS จะไม่โหลดไฟล์. dae ที่ไม่ได้เรียกใช้ผ่านสคริปต์ของ Xcode
jcr

คุณได้รับงานนี้หรือไม่?
μολὼν.λαβέ

ไม่ฉันลงเอยด้วยการเขียนเอนจิ้นของตัวเองและไม่ได้ใช้ SceneKit ตั้งแต่นั้นมา
jcr

น่าประทับใจ. คุณไปกับ opengl หรือ metal?
μολὼν.λαβέ

คำตอบ:


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 ชิ้นที่ตรงกับหัวและเท้าของกระบอกสูบโดยที่กระดูกตรงกลางจะตรงกับส่วนหัวของกระบอกสูบด้านล่างและพร้อมกันกับส่วนเท้าของกระบอกสูบด้านบน ดังนั้นสำหรับตัวอย่างเช่นจุดv0, v2และv4สอดคล้องกับbone0; v1, v3, v5ตรงตามลักษณะที่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

หมายเหตุ: ในกรณีส่วนใหญ่คุณควรจะใช้ไม่ได้UInt16UInt8

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.