在这里插入图片描述

骨骼动画基础

骨骼动画是 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

#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);// 创建ViewerosgViewer::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

#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版本有关系,会有一些报错。耐心解决。_

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/82954.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/82954.shtml
英文地址,请注明出处:http://en.pswp.cn/web/82954.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

jdk同时安装多个版本并自由切换

一、安装不同版本的JDK 二、配置环境变量&#xff08;多版本JDK&#xff09; 1. 新建版本专用环境变量&#xff08;用于切换&#xff09; 操作位置&#xff1a;系统变量 > 新建 变量名&#xff1a;JAVA_HOME_1.8 变量值&#xff1a;JDK 8安装路径变量名&#xff1a;JAVA1…

java中装饰模式

目录 一 装饰模式案例说明 1.1 说明 1.2 代码 1.2.1 定义数据服务接口 1.2.2 定义基础数据库服务实现 1.2.3 日志装饰器 1.2.4 缓存装饰器 1.2.5 主程序调用 1.3 装饰模式的特点 一 装饰模式案例说明 1.1 说明 本案例是&#xff1a;数据查询增加缓存&#xff0c;使用…

【论文阅读】YOLOv8在单目下视多车目标检测中的应用

Application of YOLOv8 in monocular downward multiple Car Target detection​​​​​ 原文真离谱&#xff0c;文章都不全还发上来 引言 自动驾驶技术是21世纪最重要的技术发展之一&#xff0c;有望彻底改变交通安全和效率。任何自动驾驶系统的核心都依赖于通过精确物体检…

在uni-app中如何从Options API迁移到Composition API?

uni-app 从 Options API 迁移到 Composition API 的详细指南 一、迁移前的准备 升级环境&#xff1a; 确保 HBuilderX 版本 ≥ 3.2.0项目 uni-app 版本 ≥ 3.0.0 了解 Composition API 基础&#xff1a; 响应式系统&#xff1a;ref、reactive生命周期钩子&#xff1a;onMount…

408第一季 - 数据结构 - 图

图的概念 完全图 无向图的完全图可以这么想&#xff1a;如果有4个点&#xff0c;每个点都会连向3个点&#xff0c;每个点也都会有来回的边&#xff0c;所以除以2 有向图就不用除以2 连通分量 不多解释 极大连通子图的意思就是让你把所有连起来的都圈出来 强连通图和强连通…

31.2linux中Regmap的API驱动icm20608实验(编程)_csdn

regmap 框架就讲解就是上一个文章&#xff0c;接下来学习编写的 icm20608 驱动改为 regmap 框架。 icm20608 驱动我们在之前的文章就已经编写了&#xff01; 因为之前已经对icm20608的设备树进行了修改&#xff0c;所以大家可以看到之前的文章&#xff01;当然这里我们还是带领…

Vue速查手册

Vue速查手册 CSS deep用法 使用父class进行限定&#xff0c;控制影响范围&#xff1a; <template><el-input class"my-input" /> </template><style scoped> /* Vue 3 推荐写法 */ .my-input :deep(.el-input__inner) {background-color…

振动力学:无阻尼多自由度系统(受迫振动)

本文从频域分析和时域分析揭示系统的运动特性&#xff0c;并给出系统在一般形式激励下的响应。主要讨论如下问题&#xff1a;频域分析、频响函数矩阵、反共振、振型叠加法等。 根据文章1中的式(1.7)&#xff0c;可知无阻尼受迫振动的初值问题为&#xff1a; M u ( t ) K u …

真实案例分享,Augment Code和Cursor那个比较好用?

你有没有遇到过这种情况&#xff1f;明明知道自己想要什么&#xff0c;写出来的提示词却让AI完全理解错了。 让AI翻译一篇文章&#xff0c;结果生成的中文不伦不类&#xff0c;机器僵硬&#xff0c;词汇不同&#xff0c;鸡同鸭讲。中国人看不懂&#xff0c;美国人表示耸肩。就…

zotero及其插件安装

zotero官网&#xff1a;Zotero | Your personal research assistant zotero中文社区&#xff1a;快速开始 | Zotero 中文社区 插件下载镜像地址&#xff1a;Zotero 插件商店 | Zotero 中文社区 翻译&#xff1a;Translate for Zotero 接入腾讯翻译API&#xff1a;总览 - 控制…

【SSM】SpringMVC学习笔记8:拦截器

这篇学习笔记是Spring系列笔记的第8篇&#xff0c;该笔记是笔者在学习黑马程序员SSM框架教程课程期间的笔记&#xff0c;供自己和他人参考。 Spring学习笔记目录 笔记1&#xff1a;【SSM】Spring基础&#xff1a; IoC配置学习笔记-CSDN博客 对应黑马课程P1~P20的内容。 笔记2…

从认识AI开始-----变分自编码器:从AE到VAE

前言 之前的文章里&#xff0c;我已经介绍了传统的AE能够将高维输入压缩成低维表示&#xff0c;并重建出来&#xff0c;但是它的隐空间结构并没有概率意义&#xff0c;这就导致了传统的AE无法自行生成新的数据&#xff08;比如新图像&#xff09;。因此&#xff0c;我们希望&a…

智慧赋能:移动充电桩的能源供给革命与便捷服务升级

在城市化进程加速与新能源汽车普及的双重推动下&#xff0c;移动充电桩正成为能源供给领域的一场革命。传统固定充电设施受限于布局与效率&#xff0c;难以满足用户即时、灵活的充电需求&#xff0c;而移动充电桩通过技术创新与服务升级&#xff0c;打破了时空壁垒&#xff0c;…

发版前后的调试对照实践:用 WebDebugX 与多工具构建上线验证闭环

每次产品发版都是一次“高压时刻”。版本升级带来的不仅是新功能上线&#xff0c;更常伴随隐藏 bug、兼容性差异与环境同步问题。 为了降低上线风险&#xff0c;我们逐步构建了一套以 WebDebugX 为核心、辅以 Charles、Postman、ADB、Sentry 的发版调试与验证流程&#xff0c;…

如何安装huaweicloud-sdk-core-3.1.142.jar到本地仓库?

如何安装huaweicloud-sdk-core-3.1.142.jar到本地仓库&#xff1f; package com.huaweicloud.sdk.core.auth does not exist 解决方案 # 下载huaweicloud-sdk-core-3.1.142.jar wget https://repo1.maven.org/maven2/com/huaweicloud/sdk/huaweicloud-sdk-core/3.1.142/huawe…

Python学习(7) ----- Python起源

&#x1f40d;《Python 的诞生》&#xff1a;一段圣诞假期的奇妙冒险 &#x1f4cd;时间&#xff1a;1989 年圣诞节 在荷兰阿姆斯特丹的一个寒冷冬夜&#xff0c;灯光昏黄、窗外飘着雪。一个程序员 Guido van Rossum 正窝在家里度假——没有会议、没有项目、没有 bug&#xf…

DiMTAIC 2024 数字医学技术及应用创新大赛-甲状腺B超静态及动态影像算法赛-参赛项目

参赛成绩 项目介绍 去年参加完这个比赛之后&#xff0c;整理了项目文件和代码&#xff0c;虽然比赛没有获奖&#xff0c;但是参赛过程中自己也很有收获&#xff0c;自己一个人搭建了完整的pipeline并基于此提交了多次提高成绩&#xff0c;现在把这个项目梳理成博客&#xff0c…

绘制饼图详细过程

QtCharts绘制饼图 说明&#xff1a;qcustomplot模块没有绘制饼图的接口和模块&#xff0c;所以用Qt官方自带的QtCharts进行绘制。绘制出来还挺美观。 1 模块导入 QT chartsQT_BEGIN_NAMESPACE以上这两行代码必须得加 2 总体代码 widget.h #ifndef WIDGET_H #defin…

本地windows主机安装seafile部署详解,及无公网IP内网映射外网访问方案

在Windows上部署Seafile服务器是一个相对直接的过程&#xff0c;但需要你具备一定的系统管理知识。Seafile是一个开源的文件共享和协作平台&#xff0c;类似于Dropbox或Google Drive。 以下是在Windows上部署Seafile服务器的步骤&#xff1a; 1. 准备环境 确保你的Windows系…

Vue学习之---nextTick

前言&#xff1a;目前来说&#xff0c;nextTick我们遇到的比较少&#xff0c;至少对我来说是这样的&#xff0c;但是有一些聪明的小朋友早早就注意到这个知识点了。nextTick 是前端开发&#xff08;尤其是 Vue 生态&#xff09;中的核心知识点&#xff0c;原理上跟Vue的异步更新…