DirectX 자체엔진 개발

[DirectX 11] Skinning Animation

종제로 2022. 4. 16. 23:06
Skinning Animation

 

개발 일자 : 2021년 5월 6일

업데이트 일자 : 2021년 12월 12일, FBX 포맷 지원하도록 업데이트 되었음

 

 

스키닝 애니메이션 과정은 다음과 같습니다.

1. ase 파일로부터 Bone 데이터 파싱

2. Animation 데이터 파싱

3. Animation Update하기

4. BoneTM 구하기

5. VS에서 vertex마다 BoneIndex, BoneWeight에 따라 BoneTM 곱하기

 

1. ase 파일로부터 Bone 데이터 파싱

본의 개수, 본의 이름, vertex의 bone index, bone weight를 파싱하고, 데이터를 저장해줍니다.

 

2. Bone List에 Bone Mesh의 Animation Data를 파싱해서 넣습니다.

ase를 기준으로 Bone 데이터 다음에 나오는 Mesh들은 모두 Bone입니다. 키프레임과 Pos값을 모두 파싱해 저장해줍니다.

 

3. Animation Update하기

/// 프레임 사이사이를 보간한다
for (UINT i = 0; i < m_MeshInfoVec.size(); i++)
{
    // Pos 보간
    if (m_MeshInfoVec[i]->PosSampleVec.size() > 0)
    {
        m_MeshInfoVec[i]->NowPosSample.x = MathHelper::Lerp<float>(m_MeshInfoVec[i]->StartPosSample.x, m_MeshInfoVec[i]->NextPosSample.x, m_Per);
        m_MeshInfoVec[i]->NowPosSample.y = MathHelper::Lerp<float>(m_MeshInfoVec[i]->StartPosSample.y, m_MeshInfoVec[i]->NextPosSample.y, m_Per);
        m_MeshInfoVec[i]->NowPosSample.z = MathHelper::Lerp<float>(m_MeshInfoVec[i]->StartPosSample.z, m_MeshInfoVec[i]->NextPosSample.z, m_Per);

        m_MeshInfoVec[i]->TranslationTM = XMMatrixTranslation(m_MeshInfoVec[i]->NowPosSample.x, m_MeshInfoVec[i]->NowPosSample.y, m_MeshInfoVec[i]->NowPosSample.z);
    }
    else
    {
        // 부모가 있을 경우
        if (m_MeshInfoVec[i]->Parent != nullptr)
        {
            Vector4 localTMPos = m_MeshInfoVec[i]->LocalTM.r[3];
            m_MeshInfoVec[i]->TranslationTM = XMMatrixTranslation(localTMPos.x, localTMPos.y, localTMPos.z);
        }
        // 부모가 없을 경우
        else
        {
            m_MeshInfoVec[i]->TranslationTM = XMMatrixTranslation(m_MeshInfoVec[i]->Pos.x, m_MeshInfoVec[i]->Pos.y, m_MeshInfoVec[i]->Pos.z);
        }
    }

    // ...

    m_MeshInfoVec[i]->WorldTM = m_MeshInfoVec[i]->RotationTM * m_MeshInfoVec[i]->TranslationTM;
}

/// 부모가 적은 순서부터 (상위 계층부터) 차례대로 World TM을 곱해준다.
for (UINT j = 1; j <= m_MaxParentCount; j++)
{
    for (UINT i = 0; i < m_MeshInfoVec.size(); i++)
    {
        if (m_MeshInfoVec[i]->ParentCount == j)
        {
            m_MeshInfoVec[i]->WorldTM = m_MeshInfoVec[i]->WorldTM * m_MeshInfoVec[i]->Parent->WorldTM;
        }
    }
}

 

4. Skinned Mesh로 부터 vertex가 얼마나 변환되어있는지를 구할 수 있는 BoneTM을 구해줍니다.

/// VS로 보낼 최종 Bone TM 구하기
for (UINT i = 0; i < m_BoneVec.size(); i++)
{
    Matrix boneWorldTM = m_BoneVec[i]->WorldTM;
    Matrix boneNodeTM = m_BoneVec[i]->NodeTM;

    Matrix skinWorldTM = m_SkinnedMesh->NodeTM;
    Matrix skinWorldInverseTM = XMMatrixInverse(nullptr, skinWorldTM);

    // Bone Offset TM은 해당 Bone의 최초위치를 나타내는 TM이다.
    // Bone Offset TM = Bone Node * Skin World의 역행렬
    // Skinned Mesh 기준의 Bone의 LocalTM
    Matrix boneOffsetTM = boneNodeTM * skinWorldInverseTM;
    Matrix boneOffsetInverseTM = XMMatrixInverse(nullptr, boneOffsetTM);

    // Bone Offset TM의 역행렬 * Bone TM
    Matrix finalBoneTM = boneOffsetInverseTM * boneWorldTM;

    XMFLOAT4X4 boneWorldTM4X4;
    XMStoreFloat4x4(&boneWorldTM4X4, finalBoneTM);
    m_BoneTransformVec.push_back(boneWorldTM4X4);
}

 

5. VS에서 vertex마다 BoneIndex, BoneWeight에 따라 BoneTM 곱하기

VertexOut SkinnedVS(VertexIn vin)
{
	VertexOut vout;

	// 버텍스 Weight 배열을 초기화한다. 아니면 SV_POSITION 값에 대한 이상한 경고를 볼 것이다.
	float weights[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
	weights[0] = vin.Weights.x;
	weights[1] = vin.Weights.y;
	weights[2] = vin.Weights.z;
	weights[3] = 1.0f - weights[0] - weights[1] - weights[2];

	float3 posL = float3(0.0f, 0.0f, 0.0f);
	float3 normalL = float3(0.0f, 0.0f, 0.0f);

	// Bone Index, Bone Weight 값에 따라 적절하게 BoneTM 곱해주기
	for (int i = 0; i < 4; ++i)
	{
		// nonuniform 스케일링이 없다고 가정하므로, 노말값을 위한 역행렬의 전치행렬이 필요없다.
		posL += weights[i] * mul(float4(vin.PosL, 1.0f), gBoneTransforms[vin.BoneIndices[i]]).xyz;
		normalL += weights[i] * mul(vin.NormalL, (float3x3)gBoneTransforms[vin.BoneIndices[i]]);
	}

	// Local -> World
	vout.PosW = mul(float4(posL, 1.0f), gWorld).xyz;
	vout.NormalW = mul(normalL, (float3x3)gWorldInvTranspose);

	// Local -> Projection
	vout.PosH = mul(float4(posL, 1.0f), gWorldViewProj);

	// 텍스쳐
	vout.Tex = mul(float4(vin.Tex, 0.0f, 1.0f), gTexTransform).xy;

	return vout;
}