【OSG學習筆記】Day 16: 骨骼動畫與蒙皮(osgAnimation)

在這里插入圖片描述

骨骼動畫基礎

骨骼動畫是 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的異步更新…