Realisierung der Skelettanimation für dreidimensionale Modelle

Hallo! Derzeit gibt es eine große Anzahl von Artikeln über Habré, die sich mit Computergrafik und der Implementierung verschiedener Effekte befassen, aber es gibt nicht genügend Texte zur Implementierung von Skelettanimationen (insbesondere von Grund auf neu). Ich werde versuchen, diese Lücke mit Hilfe dieses Textes mit einer Beschreibung der Technologie und einem Beispiel für eine einfache Implementierung in C ++ und OpenGL 4.5 (SDL2) zu schließen.



Einführung


. , , ( ).


, , . .


, .


, . , , () , . .


, , :


-  
  - 
      -  
      -  
  -  
  -  

, , - , .


, , .



.


, ( ). , , , . .


, , . "", "", . .


( ) 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++. - .


-

. sources/main.cpp OpenGL, , . sources/Animation.


, .


, , , , .


All Articles