Realisasi animasi kerangka untuk model tiga dimensi

Halo! Saat ini, ada sejumlah besar artikel tentang Habré yang ditujukan untuk grafis komputer dan implementasi berbagai efek, namun, ada beberapa teks tentang implementasi animasi kerangka (terutama dari awal). Saya akan mencoba mengisi celah ini dengan bantuan teks ini dengan deskripsi teknologi dan contoh implementasi sederhana di C ++ dan OpenGL 4.5 (SDL2).



pengantar


. , , ( ).


, , . .


, .


, . , , () , . .


, , :


-  
  - 
      -  
      -  
  -  
  -  

, , - , .


, , .



.


, ( ). , , , . .


, , . "", "", . .


( ) Autodesk 3ds Max. , , .



, . . . , 3D- , .


( ). ( ). , ( 0.0 1.0), . , . .


( — ).



, ( , ). T- ( T) bind- .



( ) , . ( ) ( , , ).


, — , , , . . , .



, , , .


, , , FBX COLLADA. . ( ) assimp.



, , : , bind- ( ), , , . .


, . SRT- (scale, rotation, translation — , , /). , , , RT- ( R-). .


, "", . Joint (, ), .


, . , . . .


Pij, i— , j— . M, . RT- .


bind- (inverse bind pose matrix). Bi. .


bind- (xyz-) v, — v.


:



, 3 . :



, 3 bind- B3, , .


, P0M.


, bind- , Pij, .


(matrix palette), bind- . bind- .


, . .


, , , .



, , . t( ) . , , , — ( ).



, . , .


, , glm.


. :


struct Bone {
  uint8_t parentId; // ID   (      ROOT_BONE_PARENT_ID,  ,     ,  254 )
  glm::mat4 inverseBindPoseMatrix; //  bind-pose          

  static constexpr uint8_t ROOT_BONE_PARENT_ID = 255; // ID  
};

: get/set , , , , . .


( , ):


struct Skeleton {
  std::vector<Bone> bones; //   
};

. , . , , .


//     
struct BoneAnimationPositionFrame {
  float time; //  
  glm::vec3 position; //    
};

//     
struct BoneAnimationOrientationFrame {
  float time; //  
  glm::quat orientation; //    
};

— , float.


//     
struct BoneAnimationChannel {
  std::vector<BoneAnimationPositionFrame> positionFrames;
  std::vector<BoneAnimationOrientationFrame> orientationFrames;
};

, . . .


//        ()
struct BonePose {
  [[nodiscard]] glm::mat4 getBoneMatrix() const
  {
    return glm::translate(glm::identity<glm::mat4>(), position) * glm::mat4_cast(orientation);
  }

  glm::vec3 position = glm::vec3(0.0f);
  glm::quat orientation = glm::identity<glm::quat>();
};

getBoneMatrix() , Pij. , , , . .


RT- , . , , :


inline BonePose operator*(const BonePose& a, const BonePose& b)
{
  BonePose result;
  result.orientation = a.orientation * b.orientation;
  result.position = a.position + glm::vec3(a.orientation * glm::vec4(b.position, 1.0f));

  return result;
}

RT- , , - .


:


struct AnimationMatrixPalette {
  std::vector<glm::mat4> bonesTransforms;
};

, , (, ). . , , . .


//      
class AnimationPose {
 public:
  //     
  [[nodiscard]] const AnimationMatrixPalette& getMatrixPalette() const {
    //      ,         
    m_matrixPalette.bonesTransforms[0] = bonesLocalPoses[0].getBoneMatrix();

    auto bonesCount = static_cast<uint8_t>(m_matrixPalette.bonesTransforms.size());

    //   ,           
    for (uint8_t boneIndex = 1; boneIndex < bonesCount; boneIndex++) {
      m_matrixPalette.bonesTransforms[boneIndex] =
        m_matrixPalette.bonesTransforms[m_skeleton.bones[boneIndex].parentId] *
          bonesLocalPoses[boneIndex].getBoneMatrix();
    }

    //          bind-
    for (uint8_t boneIndex = 0; boneIndex < bonesCount; boneIndex++) {
      m_matrixPalette.bonesTransforms[boneIndex] *= m_skeleton.bones[boneIndex].inverseBindPoseMatrix;
    }

    return m_matrixPalette;
  }

 public:
  std::vector<BonePose> bonesLocalPoses;

 private:
  Skeleton m_skeleton;
  mutable AnimationMatrixPalette m_matrixPalette;
};

. .


, (, , , ). .


class AnimationClip {
 // ...
 private:
  Skeleton m_skeleton // ,     ;
  std::vector<BoneAnimationChannel> m_bonesAnimationChannels //       ;

  mutable AnimationPose m_currentPose; //   

  float m_currentTime = 0.0f; //   ()
  float m_duration = 0.0f; //   ()
  float m_rate = 0.0f; //   (  )

, , :


  void increaseCurrentTime(float delta) {
    m_currentTime += delta * m_rate;

    if (m_currentTime > m_duration) {
      int overflowParts = static_cast<int>(m_currentTime / m_duration);
      m_currentTime -= m_duration * static_cast<float>(overflowParts);
    }
  }

— . ( — ).


, , . - , , .


. , AnimationPose . .


  [[nodiscard]] const AnimationPose& getCurrentPose() const {
    m_currentPose.bonesLocalPoses[0] = getBoneLocalPose(0, m_currentTime);

    auto bonesCount = static_cast<uint8_t>(m_skeleton.bones.size());

    for (uint8_t boneIndex = 1; boneIndex < bonesCount; boneIndex++) {
      m_currentPose.bonesLocalPoses[boneIndex] = getBoneLocalPose(boneIndex, m_currentTime);
    }

    return m_currentPose;
  }

, getBoneLocalPose(), .


  [[nodiscard]] BonePose getBoneLocalPose(uint8_t boneIndex, float time) const
  {
    const std::vector<BoneAnimationPositionFrame>& positionFrames =
      m_bonesAnimationChannels[boneIndex].positionFrames;

    //  ""       
    auto position = getMixedAdjacentFrames<glm::vec3, BoneAnimationPositionFrame>(positionFrames, time);

    const std::vector<BoneAnimationOrientationFrame>& orientationFrames =
      m_bonesAnimationChannels[boneIndex].orientationFrames;

    //  ""       
    auto orientation = getMixedAdjacentFrames<glm::quat, BoneAnimationOrientationFrame>(orientationFrames, time);

    return BonePose(position, orientation);
  }

, , getMixedAdjacentFrames, "" .


, . :


  • . .
  • . , . t, t0t1, k=tt0t1t0.
  • . . , , ( , );

, , .


  //  ""         
  template<class T, class S>
  [[nodiscard]] T getMixedAdjacentFrames(const std::vector<S>& frames, float time) const
  {
    S tempFrame;
    tempFrame.time = time;

    //    
    auto frameIt = std::upper_bound(frames.begin(), frames.end(),
      tempFrame, [](const S& a, const S& b) {
        return a.time < b.time;
      });

    if (frameIt == frames.end()) {
      //     ,   
      return (frames.size() > 0) ? getKeyframeValue<T, S>(*frames.rbegin()) : getIdentity<T>();
    }
    else {
      T next = getKeyframeValue<T, S>(*frameIt);

      //    ,     ,      
      T prev = (frameIt == frames.begin()) ? getIdentity<T>() : getKeyframeValue<T, S>(*std::prev(frameIt));

      //    
      float currentFrameTime = frameIt->time;
      float prevFrameTime = (frameIt == frames.begin()) ? 0 : std::prev(frameIt)->time;

      float framesTimeDelta = currentFrameTime - prevFrameTime;

      return getInterpolatedValue<T>(prev, next, (time - prevFrameTime) / framesTimeDelta);
    }
  }

(getInterpolatedValue()), (getKeyframeValue()) - (getIdentity).


template<>
glm::vec3 AnimationClip::getIdentity() const
{
  return glm::vec3(0.0f);
}

template<>
glm::quat AnimationClip::getIdentity() const
{
  return glm::identity<glm::quat>();
}

template<>
glm::vec3 AnimationClip::getKeyframeValue(const BoneAnimationPositionFrame& frame) const
{
  return frame.position;
}

template<>
glm::quat AnimationClip::getKeyframeValue(const BoneAnimationOrientationFrame& frame) const
{
  return frame.orientation;
}

template<>
glm::vec3 AnimationClip::getInterpolatedValue(const glm::vec3& first, const glm::vec3& second, float delta) const
{
  return glm::mix(first, second, delta);
}

template<>
glm::quat AnimationClip::getInterpolatedValue(const glm::quat& first, const glm::quat& second, float delta) const
{
  return glm::slerp(first, second, delta);
}

, (glm::mix()), — (glm::slerp())



, , , . : , .


, , :


struct VertexPos3Norm3UVSkinned {
  glm::vec3 pos = {0.0f, 0.0f, 0.0f};
  glm::vec3 norm = {0.0f, 0.0f, 0.0f};
  glm::vec2 uv = {0.0f, 0.0f};
  glm::u8vec4 bonesIds = {0, 0, 0, 0};
  glm::u8vec4 bonesWeights = {0, 0, 0, 0};
};

, , . 0 1, 0 255 . 4- .


: , .


:


#version 450 core

layout (location = 0) in vec3 attrPos;
layout (location = 1) in vec3 attrNorm;
layout (location = 2) in vec2 attrUV;
//    
layout (location = 4) in uvec4 attrBonesIds;
layout (location = 5) in uvec4 attrBonesWeights;

// ...

//         128 
struct AnimationPalette {
    mat4 palette[128];
};

uniform AnimationPalette animation;

// ...

void main() {
    vec4 position = vec4(attrPos, 1.0);

    //            
    vec4 newPosition = (float(attrBonesWeights[0]) / 255.0) * animation.palette[attrBonesIds[0]] * position + 
(float(attrBonesWeights[1]) / 255.0) * animation.palette[attrBonesIds[1]] * position +
                (float(attrBonesWeights[2]) / 255.0) * animation.palette[attrBonesIds[2]] * position +
                (float(attrBonesWeights[3]) / 255.0) * animation.palette[attrBonesIds[3]] * position;

    outVertexData.uv = attrUV;
    gl_Position = scene.cameraToProjection * scene.worldToCamera * transform.localToWorld * (vec4(newPosition.xyz, 1.0));
}

- :


static void updateScene(float delta)
{
  g_animationClip->increaseCurrentTime(delta);
}

:


static void renderScene()
{
  // ...

  const AnimationMatrixPalette& currentMatrixPalette = g_animationClip->getCurrentPose().getMatrixPalette();

  setShaderArrayParameter(g_vertexShader, "animation.palette[0]", currentMatrixPalette.bonesTransforms);

  // ...
}


, C++. - .


-

Kode lengkap dapat ditemukan di sini . The sources/main.cppinisialisasi OpenGL, memuat sumber daya, memperbarui keadaan adegan dan rendering dilakukan. Implementasi animasi ada di sources/Animation.


Saya akan dengan senang hati menjawab pertanyaan, serta menerima kritik dan komentar yang objektif.


Jika topik ini menarik, dalam artikel berikut ini Anda dapat mempertimbangkan penerapan elemen seperti mencampur klip, memperlancar transisi di antara mereka, mesin keadaan untuk mengelola transisi.


All Articles