코드에서 SceneKit SCNSkinner 객체를 만드는 방법은 무엇입니까?


89

iOS 8 용 SceneKit을 사용하는 Swift 앱이 있습니다. 골격으로 제어되는 메시가 포함 된 .dae 파일에서 장면을로드합니다. 런타임에 텍스처 좌표를 수정해야합니다. 변형을 사용하는 것은 옵션이 아닙니다. 메시의 각 정점에 대해 완전히 새로운 UV를 계산해야합니다.

저는 SceneKit에서 지오메트리를 변경할 수 없다는 것을 알고 있으며 제안 된 접근 방식은 수동으로 복사하는 것임을 읽었습니다. 나는 그렇게하려고 노력하고 있지만 SCNSkinner코드 를 다시 만들려고 할 때 항상 충돌이 발생 합니다. 충돌은 EXC_BAD_ACCESS내부 C3DSourceAccessorGetMutableValuePtrAtIndex입니다. 불행히도 이것에 대한 소스 코드가 없기 때문에 정확히 왜 충돌하는지 모르겠습니다. SCNSkinner메쉬 노드에 연결된 개체 로 좁혔습니다 . 설정하지 않으면 충돌이 발생하지 않고 작동하는 것처럼 보입니다.

편집 : 다음은 충돌의 더 완전한 호출 스택입니다.

C3DSourceAccessorGetMutableValuePtrAtIndex
C3DSkinPrepareMeshForGPUIfNeeded
C3DSkinnerMakeCurrentMesh
C3DSkinnerUpdateCurrentMesh
__CFSetApplyFunction_block_invoke
CFBasicHashApply
CFSetApplyFunction
C3DAppleEngineRenderScene
...

SCNSkinner수동으로 개체 를 만드는 방법에 대한 문서 나 예제 코드를 찾지 못했습니다 . 이전에 작업 한 메시를 기반으로 생성하고 있기 때문에 그렇게 어렵지 않습니다. 내가 만드는거야 SCNSkinner초기화에 올바른 일을 모두 통과, 스위프트 문서에 따라. 그러나 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이 일부 개인 API를 사용하여 .dae 파일에서로드 할 때 SCNSkinner를 올바르게 설정합니다. .dae 만에서 SceneKit로드 후 골격 속성이있는 노드입니다 그것은 흥미 없는 아이들이 아직 골격 애니메이션이 명확하게 작동합니다.
jcr

불행히도 .dae 파일을 덮어 쓰는 것은 옵션이 아닙니다. Xcode가 .dae 파일을 장치에 복사 할 때 발생하는 최적화 및 압축 단계가 있습니다. iOS의 SceneKit은 Xcode의 스크립트를 통해 실행되지 않은 .dae 파일을로드하지 않기 때문에 앱 내의 장치에 새 .dae 파일을 저장하면 작동하지 않습니다.
jcr 2014

이 작품을 얻었습니까?
μολὼν.λαβέ dec.

아니, 나는 내 자신의 엔진을 작성하고 그 이후로 SceneKit을 사용하지 않았습니다.
jcr

감동적인. OpenGL 또는 Metal을 사용 했습니까?
μολὼν.λαβέ dec.

답변:


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.두 개의 실린더가 실제로 결합되지 않았기 때문에 9 개가 아닌 12 개의 꼭지점이 있다는 점에 유의하십시오. 삼각형은 다음과 같이 정렬됩니다.

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

원통의 머리와 발에 해당하는 3 개의 뼈가 있습니다. 가운데 뼈는 아래쪽 원통의 머리와 동시에 위쪽 원통의 발에 해당합니다. 그래서 예를 들어, 정점 v0, v2v4대응 bone0; v1,, v3v5해당 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.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.