天天看点

关键帧系统的实现(Hermite位置插值+Squad四元数空间的朝向插值)

    谈谈工作中的低层运动控制方法吧。动画师设计动画时一般先设计好物体的运动轨迹,然后指定物体沿该轨迹的运动。物体的运动轨迹为样条曲线,由用户交互给出。     假设物体的运动轨迹为一空间参数曲线Q(u),我们必须对Q(u)等间隔采样,以求得物体在每一帧的位置。但是,当对参数u等间隔采样时,并不能得到样条曲线上的等间隔采样,因为等间距的参数不一定对应等间距的弧长。由于弧长函数s=A(u)是u的严格增函数,因此s和u是一对一的关系。A(u)是一个积分方程,它没有解析解。我们无法直接将A -1(s)解析表达出来,通常只能采用数值求解的方法。此方法在《Advanced Animation and Rendering Techniques》中有详细说明,此书作者Watt还根据他们的实践提出了一种基于向前差分的近似计算方法,这在算法中均以实现。而我们的轨迹用什么样条的类型来表示呢?计算机辅助几何设计中常用的样条函数有Bezier、B样条、β样条,各有优缺点吧。 B样条是动画中较常用的曲线之一,其二阶连续性保证了运动的光滑性,局部性保证了可对动画进行局部调整,因此非常适合于作轨迹曲线,但并不太适合关键帧插值。在Hermite函数基础上Kochanek将两个附加参数引入到约束方程中,得到的样条函数非常适合关键帧插值系统。     具体的Hermite样条函数很多有讲解,只列出入切矢量和出切矢量的一般公式:

关键帧系统的实现(Hermite位置插值+Squad四元数空间的朝向插值)
关键帧系统的实现(Hermite位置插值+Squad四元数空间的朝向插值)

张量参数 t用来控制曲线在插值点处的尖锐性;偏移量参数b用来调整源弦和目标弦的相对权值;连续性参数c来控制左右切向的大小。利用这三个参数非常适合于指定曲线的形状调整。     关于位置插值曲线的设计: class Spline  { public:     Spline();     virtual ~Spline();   public:     void AddKeyPoint(CPoint3D* const pt);     void RemovePoint(CPoint3D* const pt);       //return the point of the arc length being s(0<= s <=1)     virtual CPoint3D ArcLengthPoint(double s) = 0;     //The method based on forward difference approximately     virtual CPoint3D ArcIntervalLengthPoint(double s) = 0;       //Build the segments according to KeyPoints, whose spline type may be Hermite,     //Bspline, Bezier inherited from Spline class     virtual void    BuildSegment() = 0;     //Update these KeyPoint values when data are changed     virtual void    UpdateSplineValues() = 0;   protected:   public:     static vector<CPoint3D*> m_splineKeyPoint;   private:     Spline(const Spline& s);     Spline& operator =(const Spline& s);   };   class HermiteSpline : public Spline  { public:     HermiteSpline();     virtual ~HermiteSpline();   public:     void    SetLengths();       //build a table of u against accumulated arclength     virtual CPoint3D ArcLengthPoint(double s);       void    SetIntervalLengths();        //a method based on forward differences     virtual CPoint3D ArcIntervalLengthPoint(double s);       virtual void    BuildSegment();       virtual void    UpdateSplineValues();   private:         class HermiteSegment     {     public:         virtual ~HermiteSegment() {};           HermiteSegment(const CPoint3D* const p0, const CPoint3D* const p1, const CPoint3D* const p2, const CPoint3D* const p3);       private:         HermiteSegment();         HermiteSegment(const HermiteSegment& segment);         HermiteSegment& operator =(const HermiteSegment& segment);       public:         //tension   [-1, 1] default value is 0.0,可控制切矢量Ti的大小         float   tension() const { return m_tension; }         //bias [-1, 1], default value is 0.0,可控制曲线在Pi处的切向方向         float   bias() const { return m_bias; }         //continuity [-1, 1], default value is 0.0, 可控制左右切向的大小         float   continuity() const { return m_continuity; }         //curve resolution, default is 10         int     resolution() const { return m_resolution; }           void    SetTension(float t) { m_tension = t; }         void    SetBias(float b) { m_bias = b; }         void    SetContinuity(float c) { m_continuity = c; }         void    SetResolution(int res) { m_resolution = res; }       public:         float       ArcLength(float ustart, float uend);         CPoint3D    GetPointOnSegment(float u);       private:         void    SetCoef();         float   ArcIntegrand(float u);       private:         CPoint3D    m_keypoint[4];         float       m_coef[4][3];           float   m_bias;         float   m_tension;         float   m_continuity;         int     m_resolution;           bool    m_coefIsValid;     };             vector<double> m_seglength;                //for SetLengths()     vector<double> m_intervallength;           //for SetIntervalLengths()     double          m_totallength;     int             m_deltaU;   public:     vector<HermiteSegment*> m_curveseg;       bool    m_segmentsAreValid;     bool    m_lengthTableAreValid;   };     Spline为一抽象类,方便其他插值曲线扩展,例如B样条等。定义了一个static vector<CPoint3D*> m_splineKeyPoint;为插值曲线类所共有。       关键帧插值问题实际上可分为位置插值和朝向插值两个子问题。对物体朝向的表示我们使用四元数(quaternion)表示。Quaternion的基本概念及应用《Advanced Animation and Rendering Techniques》也有涉及。最重要的概念是单位四元数空间的球面线性插值函数Slerp。从q1到q2的球面线性插值函数如下: CQuaternion CQuaternion::slerp(const CQuaternion& q1, const CQuaternion& q2, const double t, bool allowFlip)         如何用四元数构造物体朝向的插值呢?Shoemake用球面线性插值取代普通的线性插值,将这种几何构造方法推广到四维超球面上,从而来构造四维超球面上的三次样条。 CQuaternion CQuaternion::squad(const CQuaternion& a, const CQuaternion& tgA, const CQuaternion& tgB, const CQuaternion& b, double t)      Squad是使用的较普遍的四元数插值得的构造方式。1995年,Kim等人提出了一种构造单位四元数曲线的解析方法。它能把R 3中的曲线推广到四元数空间,构造出与原始曲线具有类似微分性质的单位四元数曲线。    我在具体实现中使用的是squad方法插值曲线,效果图如下,图1定义了4个关键帧,图2定义了6个关键帧。

关键帧系统的实现(Hermite位置插值+Squad四元数空间的朝向插值)
关键帧系统的实现(Hermite位置插值+Squad四元数空间的朝向插值)

基于文献[2]中的具有高阶导数的单位四元数曲线的一般构造方法以后再实现吧,把四元数函数的导数和角速度搞清楚也不是很难的^_^ See also: 1、《Advanced Animation and Rendering Techniques》 2、Kim M J, Kim M S, Shin S Y. A general construction scheme for unit quaternion curves with simple high order derivatives. Computer Graphics, 1995, 29(3):369-376

继续阅读