рдирдорд╕реНрдХрд╛рд░! рдлрд┐рд▓рд╣рд╛рд▓, рдХрдВрдкреНрдпреВрдЯрд░ рдЧреНрд░рд╛рдлрд┐рдХреНрд╕ рдФрд░ рд╡рд┐рднрд┐рдиреНрди рдкреНрд░рднрд╛рд╡реЛрдВ рдХреЗ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдХреЗ рд▓рд┐рдП рд╕рдорд░реНрдкрд┐рдд Habr├й рдкрд░ рдмрдбрд╝реА рд╕рдВрдЦреНрдпрд╛ рдореЗрдВ рд▓реЗрдЦ рд╣реИрдВ, рд╣рд╛рд▓рд╛рдВрдХрд┐, рдХрдВрдХрд╛рд▓ рдПрдиреАрдореЗрд╢рди (рд╡рд┐рд╢реЗрд╖ рд░реВрдк рд╕реЗ рдЦрд░реЛрдВрдЪ рд╕реЗ) рдХреЗ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдкрд░ рдХрд╛рдлреА рдХреБрдЫ рдкрд╛рда рд╣реИрдВред рдореИрдВ рдЗрд╕ рд╡рд┐рд╡рд░рдг рдХреЛ рддрдХрдиреАрдХ рдХреЗ рд╡рд░реНрдгрди рдФрд░ C ++ рдФрд░ OpenGL 4.5 (SDL2) рдореЗрдВ рдПрдХ рд╕рд╛рдзрд╛рд░рдг рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдХреЗ рдЙрджрд╛рд╣рд░рдг рдХреЗ рд╕рд╛рде рдЗрд╕ рдЕрдВрддрд░ рдХреЛ рднрд░рдиреЗ рдХреА рдХреЛрд╢рд┐рд╢ рдХрд░реВрдВрдЧрд╛ред

рдкрд░рд┐рдЪрдп
. , , ( ).
, , . .
, .
, . , , () , . .
, , :
-
-
-
-
-
-
, , - , .
, , .
.
, ( ). , , , . .
, , . "", "", . .
( ) 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++. - .
рдкреВрд░рд╛ рдХреЛрдб рдпрд╣рд╛рдВ рдкрд╛рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ ред sources/main.cpp
рдУрдкрдирдЬреАрдПрд▓ рдХрд╛ рдЖрд░рдВрднреАрдХрд░рдг, рд╕рдВрд╕рд╛рдзрдиреЛрдВ рдХреЛ рд▓реЛрдб рдХрд░рдирд╛, рджреГрд╢реНрдп рдХреА рд╕реНрдерд┐рддрд┐ рдХреЛ рдЕрджреНрдпрддрди рдХрд░рдирд╛ рдФрд░ рд░реЗрдВрдбрд░ рдХрд░рдирд╛ рд╣реИред рдПрдХ рдПрдиреАрдореЗрд╢рди рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдореЗрдВ рд╣реИ sources/Animation
ред
рдореБрдЭреЗ рд╕рд╡рд╛рд▓реЛрдВ рдХреЗ рдЬрд╡рд╛рдм рджреЗрдиреЗ рдореЗрдВ рдЦреБрд╢реА рд╣реЛрдЧреА, рд╕рд╛рде рд╣реА рдЙрджреНрджреЗрд╢реНрдпрдкрд░рдХ рдЖрд▓реЛрдЪрдирд╛ рдФрд░ рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдВ рднреА рдорд┐рд▓реЗрдВрдЧреАред
рдпрджрд┐ рдпрд╣ рд╡рд┐рд╖рдп рджрд┐рд▓рдЪрд╕реНрдк рд╣реИ, рддреЛ рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рд▓реЗрдЦреЛрдВ рдореЗрдВ рдЖрдк рдРрд╕реЗ рддрддреНрд╡реЛрдВ рдХреЛ рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдкрд░ рд╡рд┐рдЪрд╛рд░ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ рдЬреИрд╕реЗ рдХрд┐ рдорд┐рдХреНрд╕рд┐рдВрдЧ рдХреНрд▓рд┐рдк, рдЙрдирдХреЗ рдмреАрдЪ рдЪрд┐рдХрдиреА рдмрджрд▓рд╛рд╡, рд╕рдВрдХреНрд░рдордг рдХреЗ рдкреНрд░рдмрдВрдзрди рдХреЗ рд▓рд┐рдП рдПрдХ рд░рд╛рдЬреНрдп рдорд╢реАрдиред