習(xí)教程(十四))
第 14 章 變換與動(dòng)畫本章定位靜態(tài)場(chǎng)景有了動(dòng)態(tài)才有生命力。本章講MatrixTransform變換與 VSG 的現(xiàn)代動(dòng)畫系統(tǒng)——Animation 各類Sampler??不是OSG 的AnimationPath。14.1 本章目標(biāo)用MatrixTransform組織層級(jí)變換第 6 章已講本章聚焦動(dòng)畫化理解vsg::Animation與vsg::TransformSampler的配合讓一個(gè)節(jié)點(diǎn)沿關(guān)鍵幀移動(dòng)/旋轉(zhuǎn)知道相機(jī)/骨骼/形變動(dòng)畫分別對(duì)應(yīng)哪個(gè) Sampler并了解AnimationManager如何驅(qū)動(dòng)更新。14.2 前置準(zhǔn)備第 6 章MatrixTransform與第 13 章加載模型已讀。14.3 ?? 沒有AnimationPathOSG 的osg::AnimationPath在 VSG 中不存在。VSG 的動(dòng)畫由一套「采樣器Sampler」驅(qū)動(dòng)vsg::Animation一條動(dòng)畫 一組AnimationSampler帶播放模式ONCE / REPEAT / FORWARD_AND_BACK與速度AnimationSampler基類把「時(shí)間」映射成「對(duì)某個(gè)場(chǎng)景圖對(duì)象的修改」具體子類TransformSampler變換、CameraSampler相機(jī)、JointSampler骨骼、MorphSampler形變。14.4Animation的結(jié)構(gòu)auto animation vsg::Animation::create(); animation-name spin; animation-mode vsg::Animation::REPEAT; // 循環(huán)播放 animation-speed 1.0; // animation-samplers 是 std::vectorref_ptrAnimationSamplerAnimation::update(simulationTime)會(huì)按speed推進(jìn)time再讓每個(gè)sampler-update(time)去修改目標(biāo)對(duì)象。14.5TransformSampler關(guān)鍵幀變換TransformSampler持有一個(gè)TransformKeyframes位置/旋轉(zhuǎn)/縮放的關(guān)鍵幀序列以及object——被動(dòng)畫修改的目標(biāo)通常是MatrixTransform// 1) 關(guān)鍵幀 auto keyframes vsg::TransformKeyframes::create(); keyframes-add(0.0, vsg::dvec3(0.0, 0.0, 0.0), vsg::dquat()); // t0原位 keyframes-add(2.0, vsg::dvec3(2.0, 0.0, 0.0), vsg::dquat()); // t2平移到 x2 keyframes-add(4.0, vsg::dvec3(2.0, 1.0, 0.0), vsg::dquat(0.0, 0.0, 0.707, 0.707)); // t4上移轉(zhuǎn)90° // 2) 采樣器綁定目標(biāo) MatrixTransform auto target vsg::MatrixTransform::create(); auto sampler vsg::TransformSampler::create(); sampler-keyframes keyframes; sampler-object target; // 每幀 apply(target) 更新其 matrix // 3) 裝進(jìn) Animation auto animation vsg::Animation::create(); animation-mode vsg::Animation::REPEAT; animation-samplers.push_back(sampler);TransformSampler::transform()結(jié)果是translate(position) * rotate(rotation) * scale(scale)——與第 6 章的變換鏈一致。14.6 讓動(dòng)畫跑起來(lái)AnimationManagerViewer內(nèi)置一個(gè)animationManagerref_ptrAnimationManager。把動(dòng)畫play進(jìn)去它會(huì)在viewer-update()中自動(dòng)推進(jìn)viewer-animationManager-play(animation); // 注冊(cè)并自動(dòng)隨幀更新注意viewer-animationManager是Viewer的成員默認(rèn)已創(chuàng)建無(wú)需自行new。在viewer-update()被調(diào)用時(shí)它會(huì)用當(dāng)前FrameStamp調(diào)用animationManager-run(...)逐幀更新所有已播放動(dòng)畫。其他 Sampler 同理CameraSampler→object設(shè)為Camera/LookAt做相機(jī)飛行JointSampler→object設(shè)為Joint做骨骼動(dòng)畫glTF 角色MorphSampler→ 控制形變權(quán)重。14.7 完整示例讓模型繞圈平移auto target vsg::MatrixTransform::create(); target-addChild(model); // model 來(lái)自第 13 章 auto keyframes vsg::TransformKeyframes::create(); for (int i 0; i 4; i) { double t i; // 每秒一幀關(guān)鍵幀 double a t * 0.5; // 角度 keyframes-add(t, vsg::dvec3(std::cos(a)*2.0, 0.0, std::sin(a)*2.0), vsg::dquat()); } auto sampler vsg::TransformSampler::create(); sampler-keyframes keyframes; sampler-object target; auto animation vsg::Animation::create(); animation-mode vsg::Animation::REPEAT; animation-samplers.push_back(sampler); viewer-animationManager-play(animation); // 幀循環(huán)里 viewer-update() 會(huì)自動(dòng)推進(jìn)下面是一個(gè)完整的、可直接運(yùn)行的 C 示例包含必要的頭文件、main 函數(shù)、模型加載使用簡(jiǎn)單立方體、動(dòng)畫創(chuàng)建和 Viewer 幀循環(huán)#include vsg/all.h #include iostream int main(int argc, char** argv) { // 1. 創(chuàng)建 Viewer 和窗口 auto options vsg::Options::create(); options-paths vsg::getEnvPaths(VSG_FILE_PATH); auto windowTraits vsg::WindowTraits::create(); windowTraits-gt;width 800; windowTraits-gt;height 600; windowTraits-gt;title VSG 動(dòng)畫示例 - 繞圈平移; auto viewer vsg::Viewer::create(); auto window vsg::Window::create(windowTraits); if (!window) { std::cout lt;lt; 無(wú)法創(chuàng)建窗口 lt;lt; std::endl; return 1; } viewer-gt;addWindow(window); // 2. 創(chuàng)建相機(jī)和視圖 auto lookAt vsg::LookAt::create( vsg::dvec3(0.0, 5.0, 10.0), // 相機(jī)位置 vsg::dvec3(0.0, 0.0, 0.0), // 觀察目標(biāo) vsg::dvec3(0.0, 1.0, 0.0) // 上方向 ); auto perspective vsg::Perspective::create( 30.0, // 垂直視場(chǎng)角 static_castlt;doublegt;(windowTraits-gt;width) / windowTraits-gt;height, 0.1, // 近平面 100.0 // 遠(yuǎn)平面 ); auto camera vsg::Camera::create(perspective, lookAt, vsg::ViewportState::create(window-gt;extent2D())); auto view vsg::View::create(camera); // 3. 創(chuàng)建場(chǎng)景圖一個(gè)簡(jiǎn)單的彩色立方體 // 創(chuàng)建立方體頂點(diǎn)數(shù)據(jù) auto vertices vsg::vec3Array::create({ {-0.5f, -0.5f, -0.5f}, { 0.5f, -0.5f, -0.5f}, { 0.5f, 0.5f, -0.5f}, {-0.5f, 0.5f, -0.5f}, {-0.5f, -0.5f, 0.5f}, { 0.5f, -0.5f, 0.5f}, { 0.5f, 0.5f, 0.5f}, {-0.5f, 0.5f, 0.5f} }); auto colors vsg::vec4Array::create({ {1.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 1.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 1.0f, 1.0f}, {1.0f, 1.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 1.0f, 1.0f}, {0.0f, 1.0f, 1.0f, 1.0f}, {0.5f, 0.5f, 0.5f, 1.0f}, {1.0f, 1.0f, 1.0f, 1.0f} }); auto indices vsg::ushortArray::create({ 0,1,2, 2,3,0, // 前 4,5,6, 6,7,4, // 后 0,4,7, 7,3,0, // 左 1,5,6, 6,2,1, // 右 3,2,6, 6,7,3, // 上 0,1,5, 5,4,0 // 下 }); auto geometry vsg::Geometry::create(); geometry-gt;arrays vsg::DataList{vertices, colors}; geometry-gt;indices indices; geometry-gt;commands.push_back(vsg::DrawIndexed::create(36, 1, 0, 0, 0)); // 4. 創(chuàng)建變換節(jié)點(diǎn)動(dòng)畫目標(biāo) auto target vsg::MatrixTransform::create(); target-gt;matrix vsg::translate(0.0, 0.0, 0.0); // 初始位置 target-gt;addChild(geometry); // 將立方體添加到變換節(jié)點(diǎn) // 5. 創(chuàng)建動(dòng)畫關(guān)鍵幀繞 Y 軸做圓周運(yùn)動(dòng) auto keyframes vsg::TransformKeyframes::create(); // 創(chuàng)建 8 個(gè)關(guān)鍵幀形成完整的圓周運(yùn)動(dòng) for (int i 0; i lt; 8; i) { double time i * 0.5; // 每 0.5 秒一個(gè)關(guān)鍵幀總時(shí)長(zhǎng) 4 秒 double angle time * (vsg::PI / 2.0); // 每秒轉(zhuǎn) 90 度 // 計(jì)算圓周位置半徑 3.0在 XZ 平面繞圈 double x std::cos(angle) * 3.0; double z std::sin(angle) * 3.0; // 添加關(guān)鍵幀位置變化旋轉(zhuǎn)保持默認(rèn)無(wú)旋轉(zhuǎn) keyframes-gt;add(time, vsg::dvec3(x, 0.0, z), vsg::dquat()); std::cout lt;lt; 關(guān)鍵幀 lt;lt; i lt;lt; : t lt;lt; time lt;lt; , 位置( lt;lt; x lt;lt; , 0.0, lt;lt; z lt;lt; ) lt;lt; std::endl; } // 6. 創(chuàng)建動(dòng)畫采樣器 auto sampler vsg::TransformSampler::create(); sampler-gt;keyframes keyframes; sampler-gt;object target; // 綁定到變換節(jié)點(diǎn) // 7. 創(chuàng)建動(dòng)畫 auto animation vsg::Animation::create(); animation-gt;name circle_motion; animation-gt;mode vsg::Animation::REPEAT; // 循環(huán)播放 animation-gt;speed 1.0; // 正常速度 animation-gt;samplers.push_back(sampler); // 8. 設(shè)置場(chǎng)景圖 auto scenegraph vsg::Group::create(); scenegraph-gt;addChild(target); auto root vsg::Group::create(); root-gt;addChild(view); view-gt;addChild(scenegraph); // 9. 編譯場(chǎng)景圖 auto commandGraph vsg::createCommandGraphForView(window, camera, root); viewer-gt;assignRecordAndSubmitTaskAndPresentation({commandGraph}); // 10. 啟動(dòng)動(dòng)畫 viewer-gt;animationManager-gt;play(animation); std::cout lt;lt; 動(dòng)畫已啟動(dòng): lt;lt; animation-gt;name lt;lt; std::endl; // 11. 主循環(huán) viewer-gt;compile(); while (viewer-gt;advanceToNextFrame()) { // 處理窗口事件 viewer-gt;handleEvents(); // 更新動(dòng)畫animationManager 會(huì)自動(dòng)更新 viewer-gt;update(); // 渲染幀 viewer-gt;recordAndSubmit(); viewer-gt;present(); } return 0; }關(guān)鍵步驟說明頭文件與初始化包含vsg/all.h獲取所有 VSG 功能創(chuàng)建 Viewer 和窗口。相機(jī)設(shè)置創(chuàng)建 LookAt 和 Perspective 相機(jī)確保能清楚看到動(dòng)畫效果。模型創(chuàng)建使用頂點(diǎn)數(shù)組和索引創(chuàng)建彩色立方體作為演示模型。變換節(jié)點(diǎn)創(chuàng)建MatrixTransform作為動(dòng)畫目標(biāo)立方體作為其子節(jié)點(diǎn)。關(guān)鍵幀定義創(chuàng)建 8 個(gè)關(guān)鍵幀在 XZ 平面上形成半徑為 3.0 的圓周運(yùn)動(dòng)。采樣器綁定創(chuàng)建TransformSampler將關(guān)鍵幀綁定到變換節(jié)點(diǎn)。動(dòng)畫配置創(chuàng)建Animation對(duì)象設(shè)置為循環(huán)播放模式。場(chǎng)景圖構(gòu)建將變換節(jié)點(diǎn)添加到場(chǎng)景圖中設(shè)置視圖和相機(jī)。動(dòng)畫啟動(dòng)通過viewer-animationManager-play(animation)注冊(cè)動(dòng)畫。主循環(huán)標(biāo)準(zhǔn) VSG 幀循環(huán)viewer-update()會(huì)自動(dòng)驅(qū)動(dòng)動(dòng)畫更新。運(yùn)行效果立方體會(huì)在 XZ 平面上以半徑 3.0 做圓周運(yùn)動(dòng)每 4 秒完成一圈然后循環(huán)重復(fù)??刂婆_(tái)會(huì)輸出每個(gè)關(guān)鍵幀的位置信息。14.7.1 動(dòng)畫速度與暫??刂圃趯?shí)際應(yīng)用中我們經(jīng)常需要?jiǎng)討B(tài)控制動(dòng)畫的播放速度或者暫停/恢復(fù)動(dòng)畫。VSG 的Animation和AnimationManager提供了相應(yīng)的接口來(lái)實(shí)現(xiàn)這些功能。1. 調(diào)整動(dòng)畫播放速度Animation對(duì)象的speed屬性控制動(dòng)畫播放速度默認(rèn)值為 1.0正常速度。通過修改這個(gè)值可以實(shí)現(xiàn)慢速如 0.5x或快速如 2.0x播放// 獲取動(dòng)畫對(duì)象假設(shè) animation 是之前創(chuàng)建的動(dòng)畫 auto animation ...; // 從之前代碼獲取 // 設(shè)置動(dòng)畫速度為 0.5 倍慢速播放 animation-speed 0.5; // 設(shè)置動(dòng)畫速度為 2.0 倍快速播放 animation-speed 2.0; // 設(shè)置動(dòng)畫速度為 0.0完全停止但不會(huì)暫停只是不推進(jìn)時(shí)間 animation-speed 0.0; // 恢復(fù)正常速度 animation-speed 1.0;speed值可以在動(dòng)畫播放過程中隨時(shí)修改下一幀就會(huì)生效。例如可以在鍵盤事件回調(diào)中動(dòng)態(tài)調(diào)整// 在鍵盤事件處理中調(diào)整動(dòng)畫速度 viewer-addEventHandler(vsg::CloseHandler::create(viewer)); viewer-addEventHandler(vsg::Trackball::create(camera)); // 添加自定義鍵盤事件處理器 viewer-addEventHandler(vsg::KeyPressHandler::create( [animation](const vsg::KeyPressEvent keyPress) { if (keyPress.keyBase 1) { animation-speed 0.5; // 按 1 鍵0.5 倍速 std::cout 動(dòng)畫速度設(shè)置為 0.5x std::endl; } else if (keyPress.keyBase 2) { animation-speed 1.0; // 按 2 鍵正常速度 std::cout 動(dòng)畫速度設(shè)置為 1.0x std::endl; } else if (keyPress.keyBase 3) { animation-speed 2.0; // 按 3 鍵2.0 倍速 std::cout 動(dòng)畫速度設(shè)置為 2.0x std::endl; } return false; } ));2. 暫停與恢復(fù)動(dòng)畫雖然將speed設(shè)為 0.0 可以停止動(dòng)畫推進(jìn)但更好的方式是使用AnimationManager的暫停/恢復(fù)功能// 暫停動(dòng)畫 viewer-animationManager-pause(animation); // 恢復(fù)動(dòng)畫 viewer-animationManager-resume(animation); // 檢查動(dòng)畫是否正在播放 bool isPlaying viewer-animationManager-isPlaying(animation); // 停止動(dòng)畫從 AnimationManager 中移除 viewer-animationManager-stop(animation);下面是一個(gè)完整的示例演示如何在主循環(huán)中結(jié)合鍵盤事件控制動(dòng)畫的速度和暫停/恢復(fù)// 在之前完整示例的主循環(huán)前添加鍵盤事件處理器 viewer-addEventHandler(vsg::CloseHandler::create(viewer)); auto trackball vsg::Trackball::create(camera); viewer-addEventHandler(trackball); // 添加動(dòng)畫控制鍵盤處理器 viewer-addEventHandler(vsg::KeyPressHandler::create( [animation, viewer](const vsg::KeyPressEvent keyPress) { if (keyPress.keyBase 1) { // 慢速播放 animation-speed 0.5; std::cout 動(dòng)畫速度: 0.5x std::endl; } else if (keyPress.keyBase 2) { // 正常速度 animation-speed 1.0; std::cout 動(dòng)畫速度: 1.0x std::endl; } else if (keyPress.keyBase 3) { // 快速播放 animation-speed 2.0; std::cout 動(dòng)畫速度: 2.0x std::endl; } else if (keyPress.keyBase P || keyPress.keyBase p) { // 暫停/恢復(fù)動(dòng)畫 if (viewer-animationManager-isPlaying(animation)) { viewer-animationManager-pause(animation); std::cout 動(dòng)畫已暫停 std::endl; } else { viewer-animationManager-resume(animation); std::cout 動(dòng)畫已恢復(fù) std::endl; } } else if (keyPress.keyBase S || keyPress.keyBase s) { // 停止動(dòng)畫 viewer-animationManager-stop(animation); std::cout 動(dòng)畫已停止 std::endl; } else if (keyPress.keyBase R || keyPress.keyBase r) { // 重新播放動(dòng)畫 viewer-animationManager-play(animation); std::cout 動(dòng)畫重新播放 std::endl; } return false; } )); // 主循環(huán) while (viewer-advanceToNextFrame()) { viewer-handleEvents(); viewer-update(); viewer-recordAndSubmit(); viewer-present(); }3. 控制說明速度控制animation-speed是浮點(diǎn)數(shù)1.0 表示正常速度0.5 表示半速2.0 表示兩倍速。值為 0.0 時(shí)動(dòng)畫時(shí)間不推進(jìn)但動(dòng)畫仍處于播放狀態(tài)。暫停與恢復(fù)pause()會(huì)暫停動(dòng)畫的時(shí)間推進(jìn)但保持動(dòng)畫在AnimationManager中的注冊(cè)狀態(tài)resume()從暫停點(diǎn)繼續(xù)播放。停止與重新播放stop()將動(dòng)畫從AnimationManager中移除需要重新調(diào)用play()才能再次播放。狀態(tài)查詢isPlaying()返回動(dòng)畫是否正在播放包括暫停狀態(tài)。通過這些控制接口可以在運(yùn)行時(shí)靈活調(diào)整動(dòng)畫的播放行為實(shí)現(xiàn)交互式的動(dòng)畫控制效果。14.8 常見問題現(xiàn)象原因解決動(dòng)畫不動(dòng)沒play進(jìn)viewer-animationManager或沒調(diào)用viewer-update()確認(rèn)viewer-animationManager-play(...)且?guī)h(huán)含update()找不到vsg::AnimationPathOSG 概念VSG 無(wú)此類型改用AnimationTransformSampler本章目標(biāo)沒變化sampler-object沒指向?qū)嶋H節(jié)點(diǎn)把object設(shè)為場(chǎng)景圖中真實(shí)存在的MatrixTransform跳變/不連續(xù)關(guān)鍵幀時(shí)間不單調(diào)或模式為 ONCE用REPEAT或FORWARD_AND_BACK保證時(shí)間遞增14.9 小結(jié)VSG 動(dòng)畫 Animation含若干SamplerAnimationManagerTransformSampler用TransformKeyframes位置/旋轉(zhuǎn)/縮放驅(qū)動(dòng)object通常是MatrixTransform用viewer-animationManager-play(animation)注冊(cè)隨viewer-update()自動(dòng)推進(jìn)相機(jī)/骨骼/形變動(dòng)畫分別對(duì)應(yīng)CameraSampler/JointSampler/MorphSampler。14.10 延伸閱讀與下一章預(yù)告第 15 章《紋理與材質(zhì)》給動(dòng)畫模型貼上會(huì)動(dòng)的紋理第 11 章《相機(jī)與視圖》用CameraSampler做相機(jī)漫游第 24 章《模型查看器》軌道相機(jī) 加載 動(dòng)畫整合。