이 페이지에서

Qt Quick 3D - 간단한 스키닝 예제

Qt Quick 3D 에서 간단한 스키닝 애니메이션을 렌더링하는 방법을 보여줍니다.

골격의 변형을 보여주는 두 개의 애니메이션 도형

일반적으로 대부분의 스킨 애니메이션은 모델링 툴을 통해 디자인되며, Quick3D는 Balsam 임포터를 통해 glTF 형식도 지원합니다. Qt Design Studio이를 통해 glTF 형식도 지원합니다. 이 예제는 Quick3D에서 스킨 애니메이션에 각 속성이 어떻게 사용되는지 보여줍니다.

참고: 이 예제의모든 데이터는 gfTF-Tutorial Skins에서 가져온 것입니다.

스키닝 지오메트리를 만듭니다.

사용자 정의 지오메트리 데이터를 사용하기 위해, 위치, 조인트, 가중치 및 인덱스를 가진 지오메트리를 정의합니다.

Q_OBJECT
QML_NAMED_ELEMENT(SkinGeometry)
Q_PROPERTY(QList<QVector3D> positions READ positions WRITE setPositions NOTIFY positionsChanged)
Q_PROPERTY(QList<qint32> joints READ joints WRITE setJoints NOTIFY jointsChanged)
Q_PROPERTY(QList<float> weights READ weights WRITE setWeights NOTIFY weightsChanged)
Q_PROPERTY(QList<quint32> indexes READ indexes WRITE setIndexes NOTIFY indexesChanged)

각 위치는 정점 위치이며, 각 정점에는 4개의 조인트 인덱스와 해당 가중치가 있습니다.

QML에서 스키닝 데이터 설정하기

위치 데이터 및 인덱스

10개의 정점을 가진 8개의 삼각형을 그릴 것입니다. 아래 표는 QML 코드와 정점의 시각화 결과를 보여줍니다.

QML 코드시각화
positions: [
    Qt.vector3d(0.0, 0.0, 0.0), // vertex 0
    Qt.vector3d(1.0, 0.0, 0.0), // vertex 1
    Qt.vector3d(0.0, 0.5, 0.0), // vertex 2
    Qt.vector3d(1.0, 0.5, 0.0), // vertex 3
    Qt.vector3d(0.0, 1.0, 0.0), // vertex 4
    Qt.vector3d(1.0, 1.0, 0.0), // vertex 5
    Qt.vector3d(0.0, 1.5, 0.0), // vertex 6
    Qt.vector3d(1.0, 1.5, 0.0), // vertex 7
    Qt.vector3d(0.0, 2.0, 0.0), // vertex 8
    Qt.vector3d(1.0, 2.0, 0.0)  // vertex 9
]
indexes: [
    0, 1, 3, // triangle 0
    0, 3, 2, // triangle 1
    2, 3, 5, // triangle 2
    2, 5, 4, // triangle 3
    4, 5, 7, // triangle 4
    4, 7, 6, // triangle 5
    6, 7, 9, // triangle 6
    6, 9, 8  // triangle 7
]

정점 위치와 기하 구조

관절 및 가중치 데이터

각 꼭짓점은 스키닝 과정에서 자신에게 영향을 미쳐야 할 조인트의 인덱스를 지정해야 합니다. 각 꼭짓점에 대해 이러한 인덱스를 4차원 벡터로 저장합니다(Qt는 한 꼭짓점에 영향을 미칠 수 있는 조인트의 수를 4개로 제한합니다). 이 지오메트리에는 두 개의 조인트 노드(0과 1)만 있지만, 4차원 벡터를 사용하기 때문에 나머지 두 조인트 인덱스와 해당 가중치를 0으로 설정합니다.

joints: [
    0, 1, 0, 0, // vertex 0
    0, 1, 0, 0, // vertex 1
    0, 1, 0, 0, // vertex 2
    0, 1, 0, 0, // vertex 3
    0, 1, 0, 0, // vertex 4
    0, 1, 0, 0, // vertex 5
    0, 1, 0, 0, // vertex 6
    0, 1, 0, 0, // vertex 7
    0, 1, 0, 0, // vertex 8
    0, 1, 0, 0  // vertex 9
]

해당 가중치 값은 다음과 같습니다.

weights: [
    1.00, 0.00, 0.0, 0.0, // vertex 0
    1.00, 0.00, 0.0, 0.0, // vertex 1
    0.75, 0.25, 0.0, 0.0, // vertex 2
    0.75, 0.25, 0.0, 0.0, // vertex 3
    0.50, 0.50, 0.0, 0.0, // vertex 4
    0.50, 0.50, 0.0, 0.0, // vertex 5
    0.25, 0.75, 0.0, 0.0, // vertex 6
    0.25, 0.75, 0.0, 0.0, // vertex 7
    0.00, 1.00, 0.0, 0.0, // vertex 8
    0.00, 1.00, 0.0, 0.0  // vertex 9
]
골격 및 관절 계층 구조

스키닝을 위해 Model 에 스켈레톤 속성을 추가합니다:

skeleton: qmlskeleton
Skeleton {
    id: qmlskeleton
    Joint {
        id: joint0
        index: 0
        skeletonRoot: qmlskeleton
        Joint {
            id: joint1
            index: 1
            skeletonRoot: qmlskeleton
            eulerRotation.z: 45
        }
    }
}

두 개의 ` Joint`는 ` Skeleton`로 연결됩니다. ` joint1 `를 z축을 중심으로 45도 회전시킬 것입니다. 아래 이미지는 조인트가 지오메트리에 어떻게 배치되어 있는지, 그리고 초기 스켈레톤의 방향이 어떻게 설정되어 있는지를 보여줍니다.

지오메트리 내의 조인트초기 스켈레톤

지오메트리 내의 2개 조인트

초기 골격

inverseBindPoses를 사용한 모델 배치

모델에 유효한 skeleton 가 설정되면, 스켈레톤의 초기 포즈를 정의해야 합니다. 이는 스켈레톤 애니메이션의 기준이 됩니다. 관절을 초기 위치에서 이동시키면, joints 및 weights 테이블에 따라 모델의 버텍스가 이동하게 됩니다. 각 노드의 기하학적 구조는 독특한 방식으로 지정됩니다. 즉, ` Model.inverseBindPoses `는 관절을 초기 위치로 변환하는 행렬의 역행렬로 설정됩니다. 중심으로 이동시키기 위해, 두 조인트 모두에 동일한 변환을 적용하면 됩니다. 즉, x축을 따라 -0.5, y축을 따라 -1.0만큼 평행 이동시키는 행렬을 설정하면 됩니다.

QML 코드초기 위치결과
inverseBindPoses: [
    Qt.matrix4x4(1, 0, 0, -0.5,
                 0, 1, 0, -1,
                 0, 0, 1, 0,
                 0, 0, 0, 1),
    Qt.matrix4x4(1, 0, 0, -0.5,
                 0, 1, 0, -1,
                 0, 0, 1, 0,
                 0, 0, 0, 1)
]

초기 위치

InversebindPoses에 의해 변환됨

조인트 노드를 사용한 애니메이션

스킨이 적용된 오브젝트를 준비했으므로, 이제 Joint의 속성, 특히 eulerRotation을 변경하여 애니메이션을 적용할 수 있습니다.

Timeline {
    id: timeline0
    startFrame: 0
    endFrame: 1000
    currentFrame: 0
    enabled: true
    animations: [
        TimelineAnimation {
            duration: 5000
            from: 0
            to: 1000
            running: true
        }
    ]

    KeyframeGroup {
        target: joint1
        property: "eulerRotation.z"

        Keyframe {
            frame: 0
            value: 0
        }
        Keyframe {
            frame: 250
            value: 90
        }
        Keyframe {
            frame: 750
            value: -90
        }
        Keyframe {
            frame: 1000
            value: 0
        }
    }
}

스킨 처리에 대한 보다 포괄적인 접근 방식

스켈레톤은 리소스이지만, 그 계층 구조와 위치는 모델의 변환에 사용됩니다.

Skeleton 노드 대신, 리소스 유형인 Skin 를 사용할 수 있습니다. Skin 유형은 씬 내의 공간 노드가 아니므로, 그 위치는 모델에 영향을 미치지 않습니다. 최소한의 작동 가능한 스킨 노드는 일반적으로 노드 목록, 조인트 및 선택 사항인 역 바인딩 행렬(inverseBindPoses)으로 구성됩니다.

Skin 항목을 사용하면 앞의 예제를 다음과 같이 작성할 수 있습니다:

skin: Skin {
    id: skin0
    joints: [
        joint0,
        joint1
    ]
    inverseBindPoses: [
        Qt.matrix4x4(1, 0, 0, -0.5,
                     0, 1, 0, -1,
                     0, 0, 1, 0,
                     0, 0, 0, 1),
        Qt.matrix4x4(1, 0, 0, -0.5,
                     0, 1, 0, -1,
                     0, 0, 1, 0,
                     0, 0, 0, 1)
    ]
}

코드 스니펫에서 볼 수 있듯이, ` Skin `에는 `joints`와 `inverseBindPoses`라는 두 개의 목록만 포함되어 있습니다. 이는 ` Skeleton ` 방식과 달리 별도의 계층 구조를 갖지 않고 기존 노드의 계층 구조만을 사용한다는 점에서 차이가 있습니다.

Node {
    id: joint0
    Node {
        id: joint1
        eulerRotation.z: 45
    }
}

예제 프로젝트 @ code.qt.io

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.