
骨骼动画基础
骨骼动画是 3D 计算机图形中常用的技术,它通过以下两个主要组件实现角色动画。
- 骨骼系统 (Skeleton):由层级结构的骨头组成,类似于人体骨骼
- 蒙皮 (Mesh Skinning):将模型网格顶点绑定到骨骼上,使骨骼移动时带动网格变形
在 OSG 中,这些功能主要由osgAnimation
库提供支持。
当然可以!以下是 Day 16:骨骼动画与蒙皮(osgAnimation) 中的"核心知识点"部分,以标准 Markdown (.md
) 格式呈现:
核心知识点
类名 | 作用 |
---|---|
osgAnimation::AnimationManagerBase |
动画管理器接口,定义了动画更新的基本行为 |
osgAnimation::BasicAnimationManager |
基础动画管理器实现类,用于管理和播放多个动画 |
osgAnimation::RigGeometry |
蒙皮网格数据结构,支持骨骼影响顶点的变形计算 |
osgAnimation::AnimationPathCallback |
动画路径回调,可用于控制骨骼或模型沿特定路径运动 |
osgDB::readNodeFile() |
加载模型文件(支持 OSGT 等格式),自动解析动画和骨骼信息 |
OSGT 格式
格式 | 骨骼支持 | 动画保留 | OSG 兼容性 |
---|---|---|---|
.osgt |
✅ 完整 | ✅ 完整 | ⭐⭐⭐⭐⭐(原生支持,推荐使用) |
.dae |
✅ 完整 | ✅ 完整 | ⭐⭐⭐⭐(需 Collada 插件) |
.fbx |
❌ 不支持 | ❌ 不支持 | 不可直接读取 |
.osgt
:OSG 的二进制序列化格式,支持完整的骨骼与动画数据,加载速度快,兼容性最好。.dae
(Collada) :开放的 XML 格式,广泛用于三维模型交换,支持骨骼和动画,但需要 OSG 的osgdb_collada
插件。.fbx
:Autodesk 的私有格式,功能强大,但 OpenSceneGraph 默认不支持 FBX ,需要借助第三方插件(如OpenSceneGraph-FBX
)才能加载。
实战
实战1
通过代码自动生成动画效果。
animation.cpp
cpp
#include <osgViewer/Viewer>
#include <osg/Group>
#include <osg/Geode>
#include <osg/ShapeDrawable>
#include <osg/MatrixTransform>
#include <osgAnimation/BasicAnimationManager>
#include <osgAnimation/UpdateMatrixTransform>
#include <osgAnimation/Animation>
#include <osgAnimation/Channel>
#include <osgGA/TrackballManipulator>
#include <osg/Material>
#include <osgDB/Registry>
#include <osg/Notify>
#include <iostream>
// 创建骨骼节点
osg::MatrixTransform* createBone(const std::string& name, float length, const osg::Vec4& color) {
osg::ref_ptr<osg::MatrixTransform> bone = new osg::MatrixTransform;
bone->setName(name);
// 创建骨骼可视化(圆柱体)
osg::ref_ptr<osg::Cylinder> cylinder = new osg::Cylinder(osg::Vec3(0, 0, length/2), 0.1f, length);
osg::ref_ptr<osg::ShapeDrawable> drawable = new osg::ShapeDrawable(cylinder);
// 设置颜色
osg::ref_ptr<osg::Material> material = new osg::Material;
material->setDiffuse(osg::Material::FRONT, osg::Vec4(color));
drawable->getOrCreateStateSet()->setAttributeAndModes(material.get());
// 添加关节球
osg::ref_ptr<osg::Sphere> jointSphere = new osg::Sphere(osg::Vec3(0, 0, 0), 0.15f);
osg::ref_ptr<osg::ShapeDrawable> jointDrawable = new osg::ShapeDrawable(jointSphere);
jointDrawable->setColor(osg::Vec4(1.0f, 1.0f, 0.0f, 1.0f));
osg::ref_ptr<osg::Geode> geode = new osg::Geode;
geode->addDrawable(drawable.get());
geode->addDrawable(jointDrawable.get());
bone->addChild(geode.get());
return bone.release();
}
// 在main函数中添加临时测试动画
class SimpleRotationCallback : public osg::NodeCallback {
public:
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) {
static double start = osg::Timer::instance()->time_s();
double t = osg::Timer::instance()->time_s() - start;
float angle = sin(t) * 1.0f; // 摆动幅度
osg::MatrixTransform* mt = dynamic_cast<osg::MatrixTransform*>(node);
if (mt) {
mt->setMatrix(osg::Matrix::rotate(angle, osg::Vec3(0,1,0)));
}
traverse(node, nv);
}
};
// 创建动画
osgAnimation::Animation* createArmAnimation() {
osg::ref_ptr<osgAnimation::Animation> animation = new osgAnimation::Animation;
animation->setName("ArmAnimation");
// 创建肩关节动画通道
osg::ref_ptr<osgAnimation::QuatSphericalLinearChannel> shoulderChannel =
new osgAnimation::QuatSphericalLinearChannel;
shoulderChannel->setName("rotation");
shoulderChannel->setTargetName("Shoulder");
osgAnimation::QuatKeyframeContainer* shoulderKeyframes = shoulderChannel->getOrCreateSampler()->getOrCreateKeyframeContainer();
shoulderKeyframes->push_back(osgAnimation::QuatKeyframe(0.0, osg::Quat(0, osg::Vec3(0,1,0))));
shoulderKeyframes->push_back(osgAnimation::QuatKeyframe(2.0, osg::Quat(osg::PI/4, osg::Vec3(0,1,0))));
shoulderKeyframes->push_back(osgAnimation::QuatKeyframe(4.0, osg::Quat(-osg::PI/4, osg::Vec3(0,1,0))));
shoulderKeyframes->push_back(osgAnimation::QuatKeyframe(6.0, osg::Quat(0, osg::Vec3(0,1,0))));
// 创建肘关节动画通道
osg::ref_ptr<osgAnimation::QuatSphericalLinearChannel> elbowChannel =
new osgAnimation::QuatSphericalLinearChannel;
elbowChannel->setName("rotation");
elbowChannel->setTargetName("Elbow");
osgAnimation::QuatKeyframeContainer* elbowKeyframes = elbowChannel->getOrCreateSampler()->getOrCreateKeyframeContainer();
elbowKeyframes->push_back(osgAnimation::QuatKeyframe(0.0, osg::Quat(0, osg::Vec3(0,1,0))));
elbowKeyframes->push_back(osgAnimation::QuatKeyframe(1.0, osg::Quat(osg::PI/2, osg::Vec3(0,1,0))));
elbowKeyframes->push_back(osgAnimation::QuatKeyframe(2.0, osg::Quat(0, osg::Vec3(0,1,0))));
elbowKeyframes->push_back(osgAnimation::QuatKeyframe(3.0, osg::Quat(osg::PI/2, osg::Vec3(0,1,0))));
elbowKeyframes->push_back(osgAnimation::QuatKeyframe(4.0, osg::Quat(0, osg::Vec3(0,1,0))));
elbowKeyframes->push_back(osgAnimation::QuatKeyframe(6.0, osg::Quat(0, osg::Vec3(0,1,0))));
// 创建腕关节动画通道
osg::ref_ptr<osgAnimation::QuatSphericalLinearChannel> wristChannel =
new osgAnimation::QuatSphericalLinearChannel;
wristChannel->setName("rotation");
wristChannel->setTargetName("Wrist");
osgAnimation::QuatKeyframeContainer* wristKeyframes = wristChannel->getOrCreateSampler()->getOrCreateKeyframeContainer();
wristKeyframes->push_back(osgAnimation::QuatKeyframe(0.0, osg::Quat(0, osg::Vec3(1,0,0))));
wristKeyframes->push_back(osgAnimation::QuatKeyframe(1.5, osg::Quat(osg::PI/3, osg::Vec3(1,0,0))));
wristKeyframes->push_back(osgAnimation::QuatKeyframe(3.0, osg::Quat(-osg::PI/3, osg::Vec3(1,0,0))));
wristKeyframes->push_back(osgAnimation::QuatKeyframe(4.5, osg::Quat(osg::PI/3, osg::Vec3(1,0,0))));
wristKeyframes->push_back(osgAnimation::QuatKeyframe(6.0, osg::Quat(0, osg::Vec3(1,0,0))));
animation->addChannel(shoulderChannel.get());
animation->addChannel(elbowChannel.get());
animation->addChannel(wristChannel.get());
animation->setPlayMode(osgAnimation::Animation::LOOP);
animation->setDuration(6.0);
return animation.release();
}
// 创建手臂模型
osg::Group* createArm() {
osg::ref_ptr<osg::Group> root = new osg::Group;
// 创建骨骼层级结构
osg::ref_ptr<osg::MatrixTransform> shoulder = createBone("Shoulder", 2.0f, osg::Vec4(1.0f, 0.0f, 0.0f, 1.0f));
osg::ref_ptr<osg::MatrixTransform> elbow = createBone("Elbow", 1.5f, osg::Vec4(0.0f, 1.0f, 0.0f, 1.0f));
osg::ref_ptr<osg::MatrixTransform> wrist = createBone("Wrist", 1.0f, osg::Vec4(0.0f, 0.0f, 1.0f, 1.0f));
// 设置骨骼初始位置
shoulder->setMatrix(osg::Matrix::translate(0, 0, 0));
elbow->setMatrix(osg::Matrix::translate(0, 0, 2.0f));
wrist->setMatrix(osg::Matrix::translate(0, 0, 1.5f));
// 构建层级关系
shoulder->addChild(elbow);
elbow->addChild(wrist);
// 添加动画更新回调
shoulder->setUpdateCallback(new SimpleRotationCallback);
elbow->setUpdateCallback(new osgAnimation::UpdateMatrixTransform("Elbow"));
wrist->setUpdateCallback(new osgAnimation::UpdateMatrixTransform("Wrist"));
// 创建手爪模型
osg::ref_ptr<osg::Geode> handGeode = new osg::Geode;
osg::ref_ptr<osg::Box> leftFinger = new osg::Box(osg::Vec3(-0.2f, 0, 0.5f), 0.1f, 0.1f, 1.0f);
osg::ref_ptr<osg::Box> rightFinger = new osg::Box(osg::Vec3(0.2f, 0, 0.5f), 0.1f, 0.1f, 1.0f);
osg::ref_ptr<osg::ShapeDrawable> leftDrawable = new osg::ShapeDrawable(leftFinger);
osg::ref_ptr<osg::ShapeDrawable> rightDrawable = new osg::ShapeDrawable(rightFinger);
leftDrawable->setColor(osg::Vec4(0.8f, 0.8f, 0.8f, 1.0f));
rightDrawable->setColor(osg::Vec4(0.8f, 0.8f, 0.8f, 1.0f));
handGeode->addDrawable(leftDrawable);
handGeode->addDrawable(rightDrawable);
wrist->addChild(handGeode);
root->addChild(shoulder);
// 创建底座
osg::ref_ptr<osg::Geode> baseGeode = new osg::Geode;
osg::ref_ptr<osg::Cylinder> baseCylinder = new osg::Cylinder(osg::Vec3(0,0,-0.5f), 1.0f, 0.5f);
osg::ref_ptr<osg::ShapeDrawable> baseDrawable = new osg::ShapeDrawable(baseCylinder);
baseDrawable->setColor(osg::Vec4(0.5f, 0.5f, 0.5f, 1.0f));
baseGeode->addDrawable(baseDrawable);
root->addChild(baseGeode);
return root.release();
}
int main() {
// 设置调试输出级别
osg::setNotifyLevel(osg::NOTICE);
// 创建Viewer
osgViewer::Viewer viewer;
// 创建主场景组
osg::ref_ptr<osg::Group> root = new osg::Group;
// 添加手臂模型
root->addChild(createArm());
// 创建动画管理器
osg::ref_ptr<osgAnimation::BasicAnimationManager> animManager = new osgAnimation::BasicAnimationManager;
// 将动画管理器作为场景图的更新回调
root->setUpdateCallback(animManager);
// 添加动画
osgAnimation::Animation* anim = createArmAnimation();
animManager->registerAnimation(anim);
animManager->playAnimation(anim);
// 输出调试信息
std::cout << "动画管理器状态: " << (animManager.valid() ? "有效" : "无效") << std::endl;
std::cout << "已注册动画数量: " << animManager->getAnimationList().size() << std::endl;
if (animManager->getAnimationList().size() > 0) {
std::cout << "正在播放动画: " << animManager->getAnimationList()[0]->getName() << std::endl;
}
viewer.setSceneData(root);
viewer.setCameraManipulator(new osgGA::TrackballManipulator);
// 设置初始视角
viewer.getCameraManipulator()->setHomePosition(
osg::Vec3(0, -10, 5), // 眼睛位置
osg::Vec3(0, 0, 2), // 中心位置
osg::Vec3(0, 0, 1) // 上方向
);
viewer.home(); // 应用初始视角设置
return viewer.run();
}
运行效果

加载osgt文件
animationOsgt.cpp
cpp
#include <osgDB/ReadFile>
#include <osgViewer/Viewer>
#include <osgGA/TrackballManipulator>
#include <osgAnimation/BasicAnimationManager>
int main() {
// 1. 加载模型
osg::ref_ptr<osg::Node> model = osgDB::readNodeFile("../dumptruck.osgt");
if (!model) return 1;
// 2. 获取动画管理器
osgAnimation::BasicAnimationManager* animManager =
dynamic_cast<osgAnimation::BasicAnimationManager*>(model->getUpdateCallback());
if (animManager && !animManager->getAnimationList().empty()) {
// 3. 正确播放动画(两种解决方案):
if (animManager) {
const osgAnimation::AnimationList& animList = animManager->getAnimationList();
if (!animList.empty()) {
// 打印所有动画信息
for (const auto& anim : animList) {
std::cout << "Found animation: " << anim->getName()
<< " (" << anim->getDuration() << "s)\n";
}
// 播放第一个动画
animManager->playAnimation(animList[0]);
} else {
std::cerr << "Warning: No animations found in the model" << std::endl;
}
} else {
std::cerr << "Error: No AnimationManager found" << std::endl;
}
}
// 4. 设置查看器
osgViewer::Viewer viewer;
viewer.setSceneData(model);
viewer.setCameraManipulator(new osgGA::TrackballManipulator());
return viewer.run();
}
运行效果

本章可能会和自己的osg版本有关系,会有一些报错。耐心解决。_
