RĂ©alisation d'animation squelettique pour des modĂšles tridimensionnels

salut! À l'heure actuelle, il existe un grand nombre d'articles sur HabrĂ© consacrĂ©s Ă  l'infographie et Ă  la mise en Ɠuvre de divers effets, cependant, il y a pas mal de textes sur la mise en Ɠuvre de l'animation squelettique (en particulier Ă  partir de zĂ©ro). Je vais essayer de combler cette lacune Ă  l'aide de ce texte avec une description de la technologie et un exemple d'une implĂ©mentation simple en C ++ et OpenGL 4.5 (SDL2).



introduction


. , , ( ).


, , . .


, .


, . , , () , . .


, , :


-  
  - 
      -  
      -  
  -  
  -  

, , - , .


, , .



.


, ( ). , , , . .


, , . "", "", . .


( ) Autodesk 3ds Max. , , .



, . . . , 3D- , .


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


( — ).



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



( ) , . ( ) ( , , ).


, — , , , . . , .



, , , .


, , , FBX COLLADA. . ( ) assimp.



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


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


, "", . Joint (, ), .


, . , . . .


Pi→j, i— , j— . M, . RT- .


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


bind- (xyz-) v, — vâ€Č.


:



, 3 . :



, 3 bind- B3, , .


, P0→M.


, bind- , Pi→j, .


(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() , Pi→j. , , , . .


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=t−t0t1−t0.
  • . . , , ( , );

, , .


  //  ""         
  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++. - .


-

Le code complet peut ĂȘtre trouvĂ© ici . L' sources/main.cppinitialisation d'OpenGL, le chargement des ressources, la mise Ă  jour de l'Ă©tat de la scĂšne et le rendu sont effectuĂ©s. Une implĂ©mentation d'animation est en cours sources/Animation.


Je serai ravi de répondre à vos questions et de recevoir des critiques et des commentaires objectifs.


Si ce sujet est intĂ©ressant, dans les articles suivants, vous pouvez envisager la mise en Ɠuvre d'Ă©lĂ©ments tels que le mixage de clips, des transitions fluides entre eux, une machine Ă  Ă©tats pour gĂ©rer les transitions.


All Articles