OSG动画库Animation解析(三)

骨骼动画驱动的一般方法,以及使用FBX SDK的一些实现 我终于要更新博客了,进公司之后每天学的做的很多,但真正有时间去总结的时候很少。之后的话尽量每周都要做一个知识的总结和积累吧,一方面自己真的是做过就忘,另一方面也能够将自己踩到的坑,遇到的问题与大家分享一下。入职三个月吧第一个月最主要的事情就是在骨骼动画的驱动,一开始是研究的Assimp这个库,也实现了相应的功能,但后来发现坑实在是太多,对之后的扩展也不好,最后还是开始啃超级不友好的FBX SDK。... 阅读详情

通过之前的两篇文章,基本上对osgAnimation整个动画的实现体系有了一个比较完整的介绍,本文主要介绍动画中其他的一些内容,主要包括权、优先级、以及动画变换中的顺序等内容。

1. 动画的权重(weight)

假设对一辆匀速运行中的汽车施加两股力量,单独作用时,其中一股力量令汽车以60公里每小时向东北方向移动,另一股力量以100公里每小时向西北方向运动,如果希望汽车的方向修正为正北方向运行,需要向西北方向施加一个权重小于1.0的权重,让它与东北方向的力量持平,这就是运动叠加和加权的意义。这个过程有点类似于向量的加法运算,遵循平行四边形规则,权重类似于对向量乘上一个标量。

1.1 权重的设置

在osgAnimation中,和权重相关的类应该是:
1. AnimationManagerBase及其派生类
2. Animation
3. Channel
4. Target
这其中Channel和Target没有设置权重的接口,但是在它们的内部需要使用权重这个参数参与运算,换言之它们权重是Animation设置给它们的。Animation可以通过设置函数void Animation::setWeight (float weight)设置动画的权重,动画管理类BasicAnimationManager使用void playAnimation (Animation* pAnimation, int priority = 0, float weight = 1.0);指定动画的权重。假设使用BasicAnimationManager来编写动画代码,那么权重设置的传递关系是:

WeightDirection

也就是说playAnimation中指定的权重会设置给animation,animation遍历它管理的所有Channels,并把这个权重设置给这些Channels【也就是说同一个Animation中所有频道的权重都是一样的】,这些Channels把权重设置给它管理的各自的Target对象,Target对象使用这个设置的权重进行运算,得到最终的结果。

1.2 单动画单频道

在之前编写打RotateCallback的示例中,将一个频道添加到了一个动画之中,如果设置权重看是否会对结果产生影响。代码如下:

int main(int argc, char **argv)
{
    osgViewer::Viewer viewer;
    osg::Group* grp = new osg::Group;

    osg::MatrixTransform *mt = new osg::MatrixTransform;

    osgAnimation::UpdateMatrixTransform *umt1 = new osgAnimation::UpdateMatrixTransform;
    umt1->setName("move1UpdatedateCallback");
    umt1->getStackedTransforms().push_back(new osgAnimation::StackedTranslateElement("move1"));

    osgAnimation::Vec3LinearChannel *channel1 = new osgAnimation::Vec3LinearChannel();
    channel1->setTargetName("move1UpdatedateCallback");
    channel1->setName("move1");
    channel1->getOrCreateSampler()->getOrCreateKeyframeContainer()->push_back(osgAnimation::Vec3Keyframe(0, osg::Vec3(0, 0, 0)));
    channel1->getOrCreateSampler()->getOrCreateKeyframeContainer()->push_back(osgAnimation::Vec3Keyframe(2, osg::Vec3(5, 0, 0)));

    osgAnimation::Animation *anim1 = new osgAnimation::Animation();
    anim1->addChannel(channel1);

    osgAnimation::BasicAnimationManager *bam = new osgAnimation::BasicAnimationManager();
    bam->registerAnimation(anim1);
    bam->playAnimation(anim1, 10, 0.5);

    osg::Node* cowNode = osgDB::readNodeFile("cow.osg");
    grp->addChild(mt);
    mt->addChild(cowNode);
    grp->addUpdateCallback(bam);

    mt->addUpdateCallback(umt1);

    viewer.setSceneData(grp);
    viewer.setUpViewInWindow(300, 300, 800, 600);
    return (viewer.run());
}

使用playAnimation设置了当前的动画权重是0.5, 优先级是10(默认参数weight=0.5, priority=0),通过1.1中对权重的设置,可以最终得到
Channel的权重代码部分:

        virtual void update(double time, float weight, int priority)
        {
            // skip if weight == 0
            if (weight < 1e-4)
                return;
            typename SamplerType::UsingType value;
            _sampler->getValueAt(time, value);
            _target->update(weight, value, priority);
        }

如果设置了权重小于(0.00001,也就是近似等于0的情况),那么动画是不会起作用的。设置其他值,代码会进入到Target使用权重计算的代码中:

        void update(float weight, const T& val, int priority)
        {
            if (_weight || _priorityWeight)
            {
                if (_lastPriority != priority)
                {
                    _weight += _priorityWeight * (1.0 - _weight);
                    _priorityWeight = 0;
                    _lastPriority = priority;
                }

                _priorityWeight += weight;
                float t = (1.0 - _weight) * weight / _priorityWeight;
                lerp(t, _target, val);
            }
            else
            {
                _priorityWeight = weight;
                _lastPriority = priority;
                _target = val;
            }
        }

先看看Target类保存的和优先级以及权重相关的变量

    class  Target : public osg::Referenced
    {
    public:
        Target(): _weight(0), _priorityWeight(0), _lastPriority(0) {}
        virtual ~Target() {}
        void reset() { _weight = 0; _priorityWeight = 0; }
        int getCount() const { return referenceCount(); }
        float getWeight() const { return _weight; }
    protected:
        float _weight;
        float _priorityWeight;
        int _lastPriority;
    };

这三个变量_weight, _priorityWeight, _lastPriority的含义是:
_weight: 当前的已经计算的权重,如果把整个动画管理器管理的动画按照优先级进行排序,那么当优先级进行切换时,_weight会记录之前所有优先级动画所消耗的权重(如果把整个权重看作是1)
_priorityWeight:指的是遍历某一个优先级时,它下面所有频道的权重之和。当优先级切换时,这个值为重设为0,并重新开始计算。(这也是为什么名字是priorityWeight,因为它只记录某一priority下面的权重之和)
_lastPriority是上一次的优先级(通过记录上一次优先级,可以知道什么时候优先级发生了切换)
这三个变量默认情况下都是0,通过reset函数的调用可以将_weight和_priorityWeight设置为0.
当单动画单频道的时候,BasicAnimationManager的更新代码如下:


void BasicAnimationManager::update (double time)
{
    ...
    for (TargetSet::iterator it = _targets.begin(); it != _targets.end(); ++it)
        (*it).get()->reset();
    for( AnimationLayers::reverse_iterator iterAnim = _animationsPlaying.rbegin(); iterAnim != _animationsPlaying.rend(); ++iterAnim )
    {
        // update all animation
        int priority = iterAnim->first;
        AnimationList& list = iterAnim->second;
        for (unsigned int i = 0; i < list.size(); i++)
        {
            list[i]->update(time, priority)
        }
    }
    ...
}

整个遍历的循环其实只遍历了一次,因为只存在一个优先级为0的动画,并且动画中仅有一个Channel,这段循环只执行一次,注意在执行update之前,执行了target的reset操作,将target中的_weight和_priorityWeight设置为0,导致Target在执行update时,仅仅返回Channel中插值好的值,

            else
            {
                _priorityWeight = weight;
                _lastPriority = priority;
                _target = val;
            }

并没有权重的参与计算。
也就是说单动画单频道的情况下:
1. 设置的权重并没有什么作用,但是不要设置为0(确切的说是不要小于1e-4)
2. 设置的优先级并没有什么作用
3. Target仅仅帮助传递数据,传递Channel中Sampler计算的数据给UpdateMatrixTransform

1.2 单动画多频道

既然动画Animation是用来管理众多Channel,尝试在Animation中添加多个Channel:

int main(int argc, char **argv)
{
    osgViewer::Viewer viewer;
    osg::Group* grp = new osg::Group;

    osg::MatrixTransform *mt = new osg::MatrixTransform;

    osgAnimation::UpdateMatrixTransform *umt1 = new osgAnimation::UpdateMatrixTransform;
    umt1->setName("move1UpdatedateCallback");
    umt1->getStackedTransforms().push_back(new osgAnimation::StackedTranslateElement("move1"));

    //Channel 1
    osgAnimation::Vec3LinearChannel *channel1 = new osgAnimation::Vec3LinearChannel();
    channel1->setTargetName("move1UpdatedateCallback");
    channel1->setName("move1");
    channel1->getOrCreateSampler()->getOrCreateKeyframeContainer()->push_back(osgAnimation::Vec3Keyframe(0, osg::Vec3(0, 0, 0)));
    channel1->getOrCreateSampler()->getOrCreateKeyframeContainer()->push_back(osgAnimation::Vec3Keyframe(2, osg::Vec3(5, 0, 0)));

    //Channel 2
    osgAnimation::Vec3LinearChannel *channel2 = new osgAnimation::Vec3LinearChannel();
    channel2->setTargetName("move1UpdatedateCallback");
    channel2->setName("move1");
    channel2->getOrCreateSampler()->getOrCreateKeyframeContainer()->push_back(osgAnimation::Vec3Keyframe(0, osg::Vec3(0, 0, 0)));
    channel2->getOrCreateSampler()->getOrCreateKeyframeContainer()->push_back(osgAnimation::Vec3Keyframe(2, osg::Vec3(0, 0, 5)));

    osgAnimation::Animation *anim1 = new osgAnimation::Animation();
    anim1->addChannel(channel1);
    anim1->addChannel(channel2);

    osgAnimation::BasicAnimationManager *bam = new osgAnimation::BasicAnimationManager();
    bam->registerAnimation(anim1);
    bam->playAnimation(anim1);

    osg::Node* cowNode = osgDB::readNodeFile("cow.osg");
    grp->addChild(mt);
    mt->addChild(cowNode);
    grp->addUpdateCallback(bam);

    mt->addUpdateCallback(umt1);

    viewer.setSceneData(grp);
    viewer.setUpViewInWindow(300, 300, 800, 800);
    return (viewer.run());
}

运行修改程序,发现模型在两个频道的共同作用下(Channel1向右移动,Channel2向上移动)向屏幕右上方向运动。
由于同一个动画的权重是一样的,也就是说该动画下所有的频道的权重也都是一样的。UpdateMatrixTransform只包含一个StackedTranslateElement,这个StackedTranslateElement被两个频道都连接,因此产生的Target也是二者共用的。从Target使用权重计算的代码,可以知道:
1. Target的_weight和_priorityWeight在每一次遍历的过程中会首先被设置为0, 如果所有的频道的优先级都没有变化,那么Target中的_weight永远将是0,下面这段代码将永远不会被执行(1.2中单动画由于所有的动画频道下的优先级是一样的,因此下面这段代码不会被执行)

                if (_lastPriority != priority)
                {
                    // change in priority
                    // add to weight with the same previous priority cumulated weight
                    _weight += _priorityWeight * (1.0 - _weight);
                    _priorityWeight = 0;
                    _lastPriority = priority;
                }
  1. 遍历的第一个频道肯定会执行else中的语句,也就是(第一次循环必定会进入else分支)
            else
            {
                _priorityWeight = weight;
                _lastPriority = priority;
                _target = val;
  1. 对于本例中的情况,Target的update代码会被执行两次,由于_weight = 0,实际代码如下:
                _priorityWeight += weight;
                float t =  weight / _priorityWeight;
                lerp(t, _target, val);

lerp是一个线性的差值,lerp的这一行是
_target = _target * (1-t) + t * val;
由于一个Animation中所有的weight都是相等的,也就是这个t=0.5,假设一个Animation中有多个Channel,那么t依次等于 1/2, 1/3, 1/4, … 知道 1/Channel个数。

如果权重相同,那么一段动画的最终取值应该是:
最终值 = 1/N * Channel1取值 + 1/N Channel2取值 + … + 1/N ChannelN取值。
使用这段代码可以得到这个结论。

这段代码的计算推导过程简单说一下:

假设一个动画Animation中有n个Channel,由于这些Channel的权重和优先级都是一样的,假设它们的权值都是w,并且每个Channel由它的Sampler采样器计算出来的结果是v, 最终的计算的结果(由target存储)是value,那么代码的计算过程如下:

第一次迭代: value = v1 (_priorityWeight = w)

第二次迭代: 由于_priorityWeight = _priorityWeight + w, 也就是_priorityWeight = 2w,那么有: value = ( 1 - w / 2 * w )v1 + v2 w = 1/2*(v1+v2)

第三次迭代: 同样 value = (1 - w / 3*w)(0.5(v1+v2))+ 1/3 cv3 = 1/3*(v1+v2+v3)
……

第n次迭代: value = [1−(1/n)]∗(1/n−1)∗(v1+v2+...+vn−1)+(1/n)∗vn = 1/n * (v1+v2+…+v_n)

也就是说当一个动画中有多个频道时,有以下结论:
1. 所有频道的权重和优先级都只能由Animation设置,并且权重都一样,优先级也一样。
2. 权重不能设置为0(具体来说是不能小于1e-4),否则update函数直接退出。除此之外,权重设置成任何其他数都是一样的效果
3. 最后得到的结果是所有频道的平均值

2. 动画的优先级(Priority)

osgAnimation的动画管理器BasicAnimationManager中存储着动画的数组,它是用一个map来进行存储的,这个map的键值是动画的优先级,默认情况下添加进去的Animation的优先级是0,在使用void playAnimation (Animation* pAnimation, int priority = 0, float weight = 1.0);进行播放时设置优先级。按照之前的讨论,优先级只有在存在多个动画的时候才有意义。

2.1 多动画单频道

多动画单频道指的是管理器BasicAnimationManager中添加很多个Animation动画,每一个动画中只包含一个Channel,修改代码:

int main(int argc, char **argv)
{
    osgViewer::Viewer viewer;
    osg::Group* grp = new osg::Group;

    osg::MatrixTransform *mt = new osg::MatrixTransform;

    osgAnimation::UpdateMatrixTransform *umt1 = new osgAnimation::UpdateMatrixTransform;
    umt1->setName("move1UpdatedateCallback");
    umt1->getStackedTransforms().push_back(new osgAnimation::StackedTranslateElement("move1"));


    //第一个动画
    osgAnimation::Vec3LinearChannel *channel1 = new osgAnimation::Vec3LinearChannel();
    channel1->setTargetName("move1UpdatedateCallback");
    channel1->setName("move1");
    channel1->getOrCreateSampler()->getOrCreateKeyframeContainer()->push_back(osgAnimation::Vec3Keyframe(0, osg::Vec3(0, 0, 0)));
    channel1->getOrCreateSampler()->getOrCreateKeyframeContainer()->push_back(osgAnimation::Vec3Keyframe(2, osg::Vec3(5, 0, 0)));
    osgAnimation::Animation *anim1 = new osgAnimation::Animation();
    anim1->addChannel(channel1);

    //第二个动画
    osgAnimation::Vec3LinearChannel *channel2 = new osgAnimation::Vec3LinearChannel();
    channel2->setTargetName("move1UpdatedateCallback");
    channel2->setName("move1");
    channel2->getOrCreateSampler()->getOrCreateKeyframeContainer()->push_back(osgAnimation::Vec3Keyframe(0, osg::Vec3(0, 0, 0)));
    channel2->getOrCreateSampler()->getOrCreateKeyframeContainer()->push_back(osgAnimation::Vec3Keyframe(2, osg::Vec3(0, 0, 5)));
    osgAnimation::Animation *anim2 = new osgAnimation::Animation();
    anim2->addChannel(channel2);

    //第三个动画
    //第二个动画
    osgAnimation::Vec3LinearChannel *channel3 = new osgAnimation::Vec3LinearChannel();
    channel3->setTargetName("move1UpdatedateCallback");
    channel3->setName("move1");
    channel3->getOrCreateSampler()->getOrCreateKeyframeContainer()->push_back(osgAnimation::Vec3Keyframe(0, osg::Vec3(0, 0, 0)));
    channel3->getOrCreateSampler()->getOrCreateKeyframeContainer()->push_back(osgAnimation::Vec3Keyframe(2, osg::Vec3(0, 5, 0)));
    osgAnimation::Animation *anim3 = new osgAnimation::Animation();
    anim2->addChannel(channel3);

    osgAnimation::BasicAnimationManager *bam = new osgAnimation::BasicAnimationManager();
    bam->registerAnimation(anim1);
    bam->registerAnimation(anim2);
    bam->registerAnimation(anim3);
    bam->playAnimation(anim1, 1, 1);
    bam->playAnimation(anim2, 1, 1);
    bam->playAnimation(anim3, 1, 1);

    osg::Node* cowNode = osgDB::readNodeFile("cow.osg");
    grp->addChild(mt);
    mt->addChild(cowNode);
    grp->addUpdateCallback(bam);

    mt->addUpdateCallback(umt1);

    viewer.setSceneData(grp);
    viewer.setUpViewInWindow(300, 300, 800, 600);
    return (viewer.run());
}

有以下几种情况:
1. 当所有Animation的优先级一样,权重也一样的时候,和单动画多频道的结果是一样的(所有的动画平均起作用)
2. 当Animation的优先级一样,但是权重不一样的时候,按照权重起作用,也就是最终的结果是 : value = w1 * v1 + w2 * v2 (这里的权重w1、w2,都是它们设置的权除以它们所有的权的和),也就是说这时候的权可以随便设置,不要求所有的权重之和是1
3. 当Animation的优先级不一样,但是权重一样的时候
4. 当Animation的优先级不一样,权重也不一样的时候
第3和第4两种情况其实是一样的处理过程,算法基本上是这样的:


优先级高的动画值先计算,在先计算的时候有优先权,它的权重起的作用更大,假设有n的动画,一次按优先级从高到底排列是 A1, A2,A3,A4… An,假设A1的动画的权重是W1,A2是W2… An是Wn,那么A1优先级最高,它的权重分的是整个权重是已1为参考的,如果A1的权重是1,那么其他A2到An的动画都不起作用。如果A1的权重不是1,而是一个小于1的值W1,那么剩下的A2到An只能去分享(1-W1),它们的权重都是以(1-W1)来计算的,也就是分到的权重就会更小。

这个过程可以用下面一个场景来类比:假设很多人花钱买了一个西瓜分,现在大家安排怎么分这个西瓜,西瓜怎么分以家庭为单位,家庭里面每个人分到的西瓜大小都一样。每个家庭可以分一个百分数的大小,但是这个百分数是以剩余部分来计算的。也就是说第一个分西瓜的家庭,他分的百分数是以整个西瓜大小来计算的(假设他分50%),那么直接就切走一半的西瓜。剩下的人分的部分的百分数只能以剩下的半个西瓜来计算,假设第二家分的的也是50%,那么事实上它只能分走1/4个西瓜,那么以此类推,越是后分的家庭,分到的西瓜越小(虽然后面分的家庭可能的百分数也可能很大,但是给他分的基数小了(剩余的西瓜大小)),也就是说优先级越高的家庭得到的实际西瓜多,也就是贡献越大。


上面这个类比已经很贴切了。这里面每一个家庭实际上对应一个Animation,每个家庭成员对应一个Channel。我们现在讨论的是多动画单频道,也就是相当于每一个家庭只有一个成员,多动画多频道就相当于每个家庭有多个成员而已,但是实际上不影响最后计算的结果。从上面的分析可以看出:

  1. 优先级越高的动画对最后的Target得到的最终插值结果的贡献越大。
  2. 动画的计算如果存在不同优先级和权重的时候,设置的权重并不是简单的线性关系
  3. 如果我们把优先级最高的权设置为大于1,那么会有一个很奇怪的效果,其他动画的权重会在计算中出现负数,导致动画的计算结果是反的。也就是说插值的结果是我们预期插值结果的相反数,最好避免这样做,在设置权重的时候都设置在[0,1]之间。

2.2 多动画多频道

这种情况和多动画单频道类似,只是每个动画多出来很多的Channel,但是这些Channel叠加起来的权重是和整个动画的权值一样。假设动画的权值是 W,那么所有它包含的Channel(N个)叠加的结果权值也是W。

w=1/N∗w+1/N∗w+...+1/N∗w(共有N项,每项的权重一样)

整个过程在2.1中已经论述清楚了。

至此,整个osgAnimation中的动画过程都解析完整了。接下来的文章会继续讨论osgAnimation中渐进动画和骨骼动画等内容。

osgAnimation库解析 osgAnimation库解析 osgAnimation库为osg中的核心库,本文为一片总结性文章,并在此记录,主要参考网上Frank专栏的三篇关于动画的文章。本文将解释关键帧、插值器、采样器、频道、动画的概念,同时解析动画的驱动类,osgAnimation::AnimationUpdate,在总结动画的基本逻辑的基础上回答如下基本问题: 1)Target对象 如何实现的插值计算 2)Target对象 如何在动画驱动类 和 动画 之间实现值的传递 目录 1 osg动画的基本原理介... 阅读详情

相关推荐

一文了解英伟达(NVIDIA)所有显卡芯片

英伟达(NVIDIA)的产品线庞杂繁复,从面向游戏玩家的GeForce RTX系列,到数据中心的H100、B200,再到Jetson、DRIVE等边缘智能平台,一般人很难以厘清其全貌。这篇文章将用通俗而专业的语言,一文打通英伟达显卡芯片体系,让你彻底读懂这个GPU帝国是如何运转的。

bjdx_001的博客 6360

OSG动画库Animation解析(一)

本文主要介绍osgAnimation中关键帧、插值器、采样器、频道和动画等概念,以及它们的使用方式和原理

Frank的专栏 8968

【主力资金密码】副图指标升级版使用技术图文案例分解,通达信软件炒股指标公式代码

(1)主力资金线在低位时,对应的副图指标显示柱线是绿色,这个阶段主力资金参与小,行情处于下跌趋势或弱势震荡。当然,如果结合主图指标【翻倍密码系统】操作,主图的两个买卖点以及K线均线变色跟踪功能,更多的技术支撑,多空更加清晰,共振做多或多头更加确定和安全。如上图标记,中档虚线是参考安全操作的一个区间划分,粉色阶段做多或持股为主,为操作增加一道安全保护。如上图,副图指标【主力资金密码】更新叠加操作区间背景,绿色阶段持币或做空,粉色阶段做多或持股。如上图,副图指标,标记的两个区间,绿色区间时做空或持币。

weixin_69760830的博客 2555

osgAnimation(三):动画种类介绍及实现流程

osg动画种类 osg中的动画主要的有五种分别是轨迹动画,序列动画,骨骼动画,变形动画和粒子动画。甚至自己也可以通过重写frame()函数或者说利用各种回调函数来实现动画效果。 轨迹动画 轨迹动画主要是指的osg::AnimationPathUpdateCallBack 这个回调类,其本质是一个随时间变化的4*4矩阵序列,用以控制刚体在轨迹上以给定姿态运动。 回调类的构造函数参数是轨迹osg::AnimationPath,轨迹由若干个控制点进行控制,一个控制点就是一个变换矩阵(控制点有很多种构造函数

h1063135843的博客 4520

告别Selenium!纯Python+Requests搞定微信视频号登录Cookie(附完整代码与避坑指南)

本文详细介绍了如何利用纯Python和Requests库实现微信视频号的自动化登录,替代传统的Selenium方案。通过解析HTTP请求交互流程,提供完整的代码实现和避坑指南,显著提升爬虫效率和隐蔽性,适用于轻量化爬虫开发。

weixin_30642561的博客 312

AgentScope Java 2.0 项目实战:从零构建企业级自主Coding Agent

本文基于 AgentScope Java 框架,完整拆解两个生产级 AI Agent 项目的从零开发全过程。其一是企业级自主编码机器人(agentscope-codingagent),支持 Issue→PR 自动实现、PR Code Review、Docker 沙箱隔离执行与多通道接入;其二是个人 AI 助手管理平台(agentscope-paw),采用 Spring Boot + React 全栈架构,支持多 Agent、技能市场与多 IM 通道。

program哲学 839

OSG动画库Animation解析(二)

本文继续(一)中的内容,讨论了osgAnimation中的Target和UpdateMatrixTransform以及osgAnimation中对动画的管理

Frank的专栏 2961

8、音频运行时系统的架构设计

本文深入探讨了音频运行时系统的分层设计及其优势,包括逻辑层和物理层的功能划分。通过分层设计,开发者可以更轻松地管理和优化音频资源,确保其在不同平台上的高效运行。文章还介绍了平台适配与优化、错误处理与调试以及未来发展方向等内容,为开发者提供了宝贵的参考和指导。

juice的博客 462

osgAnimation之动画基础篇

简介 osgAnimation是osg库中提供场景动画效果的一个类库,它为我们提供了许多与场景动画相关的类,比如关键帧、插值、采样、频道、骨骼动画、材质变化等。本课就对osgAnimation库中的基础类进行一些解析。以下都是我个人学习过程中的一些记录和体会,方便以后自己复习之用。 开始 Keyframe 对应文件 osgAnimation/keyframe

Frank的专栏 6190

stable-diffusion-webui的逐步部署教程

本文主要介绍了stable-diffusion-webui的逐步部署教程,希望能对使用Stable Diffusion的同学们有所帮助。 文章目录 1. 前言 2. 逐步部署教程 2.1 创建Python环境 2.2 安装依赖库 2.3 运行代码

weixin_43178406的博客 6万+

FakeNewsGPT4:通过知识增强的大规模视觉语言模型推进多模态假新闻检测

当前,多模态假新闻的大量涌现导致了显著的分布差异,这一现状亟需我们开发具备广泛适用性的检测器。然而,现有假新闻检测器因特定领域内的训练方式,难以有效获取开放世界中的事实信息。这种训练的封闭性限制了传统检测器的能力,特别是在面对跨域假新闻时,其表现往往不尽如人意。具体而言,开放世界中的假新闻在两个方面具有显著的分布差异:一方面,其操纵内容多种多样,新闻的任一部分都可能受到创意策略的操控;另一方面,真实背景复杂多变,不同地区的新闻背景和主题焦点存在显著差异。

qq_52053775的博客 822

osgAnimation之动画管理器

简介 在上一节osgAnimation之作用对象一文中的UpdateMatrixTransform示例里面,我们自己通过添加UpdateCallback来实现动画,事实上osgAnimation已经为我们提供了基本的动画处理类BasicAnimationManager,这个类继承自NodeCallback,负责多个动画对象的管理,现在我们看看它究竟是如何实现的。 动画管理器

Frank的专栏 2482

有手就行的CNVD证书获取

知己知彼百战不殆。要想成功挖掘CNVD漏洞并获得证书,第一步就是深入了解CNVD的漏洞发放规则和评分标准。CNVD(国家信息安全漏洞共享平台)对漏洞的评级主要基于漏洞的危害程度、影响范围以及利用难度。此外,CNVD倾向于收录那些影响国内重要信息系统或民生领域的漏洞,因此在目标选择上可以优先考虑政府、教育、医疗、金融等行业的系统。同时,提交漏洞时需要提供详细的漏洞描述、复现步骤和修复建议,确保报告内容清晰完整,以提高审核通过率。

iSee857的博客 2444

OSG新版GLSL语法全解析

OSG(OpenSceneGraph)中使用的GLSL着色器语法随着OpenGL版本更新而改变,但OSG本身语法保持不变。本文系统总结了GLSL 3.30+的新语法特性与OSG集成方式,包括:1)GLSL在OSG中的使用结构;2)新版语法对比(顶点输入、输出、uniform等);3)OSG与GLSL的内置变量映射;4)顶点/片元着色器模板;5)OSG Uniform类型对应关系;6)开发技巧(版本一致、输入绑定、调试等);7)现代OSG Shader开发框架。重点指出新版GLSL废弃了固定管线变量,需使用自

警警的博客 872

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

【OSG学习笔记】Day 56: 动画路径导入

本文介绍了如何在OSG中实现动画路径文件导入功能。核心是通过osg::AnimationPath类的read()方法从.path文件加载轨迹数据,配合AnimationPathCallback驱动模型运动。文章详细讲解了类继承关系、实现流程和典型应用场景,包括无人机轨迹回放、数字孪生同步等。该方法可实现轨迹数据与程序解耦,支持离线编辑和多模型复用,是三维仿真系统的重要功能。文中提供了完整可编译的代码示例,展示了路径文件读取、节点绑定和动画驱动的完整流程,为OSG工程化应用提供了实用解决方案。

u012739527的博客 460

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

osg解析系列-[osg::osgAnimation]-路径运动动画/变形动画/骨骼动画/纹理动画 4

【代码】M23.11.W4[osg::osgAnimation]-路径运动动画/变形动画/骨骼动画/纹理动画 4。

qq_42531872的博客 541

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

osg示例解析之osganimationmorph(1)

本课主要演示了osgAnimation中的变形动画,变形动画通常也称为逐顶点动画(Per-vertex Animation),是一种三维动画的表现形式。它对计算机的运算资源的消耗很大,通常以每帧计算和应用4000~9000个顶点的位置变化为宜,否则系统难以承受其他场景对象的更新和渲染工作。变形动画在osgAnimation中通过MorphGeometry以及UpdateMorph来实现,具体的实现

linuxheik的专栏 433

基于FBX SDK的FBX模型解析与加载

1. 简介 FBX是Autodesk的一个用于跨平台的免费三维数 据交换的格式(最早不是由Autodesk开发,但后来被其收购),目前被众多的标准建模软件所支持,在游戏开发领域也常用来作为各种建模工具的标准导出 格式。Autodesk提供了基于C++(还有Python)的SDK来实现对FBX格式的各种读写、修改以及转换等操作,之所以如此是因为FBX的格式 不是公开的,这也是FBX的诟病之一。与FBX相对的则是格式开源的Collada,它的应用也很广泛。总体来说这两种格式还是各有优劣不相上下,关于两 种格式

翻肚鱼儿的博客 5336

dsh-purge 完整安装教程:4 步装好 DeepSeek Harness 破甲插件

dsh-purge 是一个装在官方 DeepSeek Harness 上的红队评测破甲插件:它给宿主的运行时策略打补丁,让提示词注入按模型切换,不同模型可以配不同的提示词,默认提示词面向国模「小码酱」。装好后,宿主侧栏会多出「清洗」和「演练台」两页,补丁的应用、还原、卸载都能在那两页里完成。 整篇教程只干一件事:带你把 dsh-purge 装进你正在用的那台宿主,并确认它真的生效。 怎么算「装

gitblog_00299的博客 226

每日一练(二) animationPath

1、创建棋盘纹理:       棋盘纹理,就是在一个二维空间创建一个网格,在这个网格的四角为一个区域,进行颜色填充。采用QUADS方式绘制改棋盘。 osg::Node* OSGAnimateImpl::CreateBase(osg::Vec3 center,float radius) { // 1. 创建棋盘纹理 // 网格数 int numTileX = 10;

kasteluo的专栏 876

记录一下

void RadarAdapter::updateTime(osg::Node *node, const QVariantMap &amp;data, double time){ QVariantMap scanDat = data["scan"].toMap(); double start = scanDat["start"].toDouble(); double period...

jiaoyaziyang的专栏 357

glTF格式介绍(1)——概要

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.youkuaiyun.com/qq_31709249/article/details/86477710 glTF格式介绍总目录:https://blog.youkuaiyun.com/qq_3...

linziping的博客 1845

OSG节点更新与事件回调

OSG节点更新与事件回调

danshiming的博客 2215

第36.2节 动画-骨骼动画的控制

目录本节功能具体实现全部代码如下 本节功能 本节实现从一个有骨骼动画的fbx文件中读取,并实现点击n是播放下一个动画,点击s是播放当前动画,点击p是暂停播放当前动画。 本节的内容在网盘中,目录为/osgChina站长文集/文件中的附件/, 有附件的会根据节的编号存放在该目录: 请使用浏览器打开,平时遇到问题或加群也可以加我微信:13324598743: 链接:https://pan.baidu.com/s/13gwJLwo_LbRnN3Bl2NXXXw 提取码:xrf5 具体实现 其实一个骨骼动画在osg

一切皆可视 2346

osg--动画

回调 osg::Node setUpdateCallback() 参数为osg::NodeCallback operator()重载 addUpdateCallback() setEventCallback() 参数为osg::NodeCallback operator()重载 setCullCallback() 参数为osg::NodeCallback opera...

蓝蓝的天 642
上一篇: OSG动画库Animation解析(二)
下一篇: Skyline Globe中3dml图层的遍历
csxiaoshui
博客等级 码龄16年 873粉丝 158原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值