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 (, ), .
, . , . . .
, â , â . , . RT- .
bind- (inverse bind pose matrix). . .
bind- (xyz-) , â .
:

, 3 . :

, 3 bind- , , .
, .
, bind- , , .
(matrix palette), bind- . bind- .
, . .
, , , .
, , . ( ) . , , , â ( ).
, . , .
, , glm.
. :
struct Bone {
uint8_t parentId;
glm::mat4 inverseBindPoseMatrix;
static constexpr uint8_t ROOT_BONE_PARENT_ID = 255;
};
: 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()
, . , , , . .
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();
}
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
, "" .
, . :
- . .
- . , . , , .
- . . , , ( , );
, , .
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.cpp
initialisation 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.