OPEN-PROGRAMMING
_ 2002.5.4 Frame-Rate-Independent Interpolation

■Introduction

「Simple and Flexible Keyframe Interpolation」で述べたアプローチでは、フレーム間隔が一定であるという前提条件の元に成立っており、
高速/低速にモーションを再生したり、可変のフレームレートに対応するといったことはできません。
しかしこのようなフレームレート非依存の補間への要求は少なからずあります。
ここでは前回のアプローチをベースにフレームレートに依存しない補間アルゴリズムを考案します。

■The Principle

まず前回のアプローチを用いた3次関数による補間の流れについて考えます。

初期値a,一階微分b,二階微分c,三階微分dと置いた時の、時間軸上における値の変化は以下のようになります。
時間[t] 0 1 2 3 4 5 6
初期値 a a+b a+2b+c a+3b+3c+d a+4b+6c+4d a+5b+10c+10d a+6b+15c+20d
一階微分 b b+c b+2c+d b+3c+3d b+4c+6d b+5c+10d b+6c+15d
二階微分 c c+d c+2d c+3d c+4d c+5d c+6d
三階微分 d d d d d d d

従って時間軸上における各係数の変化は以下のようになります。
時間[t] 0 1 2 3 4 5 6
一階微分(b) 0 1 2 3 4 5 6
二階微分(c) 0 0 1 3 6 10 15
三階微分(d) 0 0 0 1 4 10 20

上記から時間tにおける各係数の変化を表す関数を導出します。
fb(t) = t
fc(t-1) = t^2 / 2 + t / 2
fd(t-2) = t^3 / 6 + t^2 / 2 + t / 3

よって任意時間tにおける値を表す関数は以下のようになります。
f(t) = a + b * fb(t) + c * fc(t-1) + d * fd(t-2)

■Implementation

導出した式を用いて、任意時間進める関数を実装すると以下のようになります。
このルーチンでは3次関数までの補間について、値が正確であることが保障されます。
4次以上の多次関数による補間については誤差が発生しますが無視できる程度です。
    //-----------------------------------------------------------
    //デルタ時間進める
    //-----------------------------------------------------------
    public boolean nextFrame(float delta)
    {
        mCount-=delta;
        if (mCount <= 0){
            if (mDataPt < mData.length){        
                float r = -mCount;//余り

                //次のキーフレーム情報を取得する                
                mCount  = mData[mDataPt++];
                mDegree = (int)mData[mDataPt++];

                for (int i=0;i<mWork.length;i++)
                    mWork[i] = 0;
                for (int i=0;i<mDegree+1;i++)
                    mWork[i] = mData[mDataPt++];
                
                nextFrame(r);
            }else{
                //最後のキーフレームに到達した
                mCount  = 0;
                mDegree = 0;                
                return false;
            }
        }else{
            //積分
            float d1 = delta - 1;
            float a1 = d1*d1*(1.0f/2.0f) + d1*(1.0f/2.0f);
            float d2 = delta - 2;
            float a2 = d2*d2*d2*(1.0f/6.0f) + d2*d2*(1.0f/2.0f) + d2*(1.0f/3.0f);

            for (int i=0;i<mDegree;i++)
                mWork[i] += mWork[i+1] * delta + mWork[i+2] * a1 + mWork[i+3] * a2;
        }
        
        return true;
    }

■Conclusion

前回のアプローチに比べて大幅に計算量が増えてしまいましたが、任意時間の変化にも同じデータを適用できるのは大きいと思います。
通常は単位時間のステップを使って、必要な時のみΔ時間のステップを使うのがベストでしょう。
あとはベクトル単位で並列処理したり3次までに限定してループを展開したりすれば、かなり高速になると思われます。


■匿名意見/感想フォーム


_ 2002.4.30 Simple and Flexible Keyframe Interpolation

■Introduction

ここではキーフレームアニメーションのためのシンプルで柔軟なキーフレーム間の補間アプローチを提案します。
このアプローチでは線形補間、スプライン補間、球面線形補間等の複数のアルゴリズムによる補間を単一のデータストリームに混在させることができ、
シンプルな共通ルーチンによってフレーム毎に補間された値を得ることができます。
またクリティカルな処理は、数回の分岐と数回の加減算のみであり補間関数の複雑さに殆ど依存しません。

■The Principle

2点a,bにおいて2点間をt区間で線形補間する時、(b-a) / tとして増分値を求め、順次加算によって値を求めていくアプローチを我々はよく使います。
これは一次関数を微分し、順次積分することによって値を求めていると考えることができます。
この考えを多次関数に適用します。
つまり有限回微分可能な関数は、有限回の加算によって表現することができるのです。
Hermiteスプラインや、Bezierなどのパラメトリック曲線は3次関数であり、3階微分と3段階の加算によって導出できます。

また球面線形補間(Slerp)については
f(t) = (sin(θ×(1-t)) + sin(θ×t)) / sin(θ)
と定義され、無限微分可能な関数となります。 従って有限回の微分/積分で表現することはできません。
しかし一定区間内における緩やかな変化は6階微分程度で十分に近似することができます。

それでは任意関数を微分することを考えます。
ここでは数値解析的なアプローチによって任意関数を微分します。
一階微分はf(t+Δt)-f(t)
二階微分は(f(t+2Δt)-f(t+Δt)) - (f(t+Δt)-f(t))
となり、これを一般化し実装すると以下のようなコードになります。
    //任意関数を微分する
    private int difference(double[] dst,double frame,double delta,int degree)
    {
        double work[] = new double[degree+1];
        
        for (int i=0;i<degree+1;i++)
            work[i] = function(frame + i * delta);
        
        for (int i=0;i<degree+1;i++){
            
            dst[i] = work[0];
            
            for (int j=0;j<degree;j++)
                work[j] = work[j+1] - work[j];
        }
        
        return degree+1;
    }
functionは時間tにおける値を返す任意関数であり、degreeは微分する回数、frameは現在の時間、deltaは時間の増加量です。
まず一定時間における値をサンプリングし、そして段階的に差分を取ることで微分を行います。
その結果dst[0]に初期値、dst[1]に一階微分、dst[2]に二階微分した値がそれぞれ入ります。
これを各キーフレームについて適用し、下のようにストリームデータとして出力します。
■次のキーフレームまでのフレーム数
■関数の次数(定数なら0、線形補間なら1、Hermiteなら3 etc.)
■初期値
■一階微分
■二階微分
:
:
■次のキーフレームまでのフレーム数
:
:
:

■Implementation


Applet Source

以下は補間を行うクラスの全ソースコードです。
reset();で初期化し、nextFrame();で値を補間し、getValue()で補間した値を取得します。

//-----------------------------------------------------------
//パラメータストリーム
//-----------------------------------------------------------
class MtParamStream
{
    //コンストラクタ
    public MtParamStream(float[] dat)
    {
        mData    = dat;
        reset();
    }

    //最初のフレーム位置に設定する
    public void reset()
    {
        mDegree = 0;
        mCount  = 0;
        mDataPt = 0;
        nextFrame();
    }
    
    //次のフレームにおける値を補間し計算する
    public boolean nextFrame()
    {                
        if ((--mCount) <= 0){
            if (mDataPt < mData.length){                
                //次のキーフレーム情報を取得する
                mCount  = (int)mData[mDataPt++];
                mDegree = (int)mData[mDataPt++];
                
                for (int i=0;i<mDegree+1;i++)
                    mWork[i] = mData[mDataPt++];
            }else{
                //最後のキーフレームに到達した
                mCount  = 0;
                mDegree = 0;                
                return false;
            }
        }else{
            //積分
            for (int i=0;i<mDegree;i++)
                mWork[i] += mWork[i+1];
        }
        return true;
    }

    //現在の値を取得する
    public float getValue()
    {
        return mWork[0];
    }

    //-----------------------------------------------------------
    private float[] mData;                    //データストリーム
    private int     mDataPt;                //現在のシーク位置
    private float[] mWork = new float[16];    //作業用バッファ
    private int     mCount;                    //次のフレームまでのカウンタ
    private int     mDegree;                //関数の次数
};

■Conclusion

Advantage
・簡単に複数の補間アルゴリズムを混在することができ、処理量は補間関数の複雑さにほとんど影響されない
・アルゴリズムが非常にシンプルかつ高速であり、マイクロコードによる実装にも有利である。
Drawback
・フレームが連続的に増加しない場合は、オーバーヘッドが大きくなる。
・キーフレーム間の間隔が非常に大きい場合は、誤差の蓄積によって破綻する場合がある。



■Rendering Techniques

・Rendering Outdoor Light Scattering (2002.3.28)
屋外光の散乱をレンダリングする
・Advanced CLUT Bump Mapping Techniques (2002.3.14)
CLUTバンプマッピング技術の応用
・Post-Process Glare Filter (2002.2.25)
ポストプロセスのグレアフィルタ
・Java Software Rendering Engine (2002.2.24)
Javaソフトウェアレンダリングエンジン
・CLUT BumpMapping (2002.2.12)
CLUTバンプマッピング
・FFX Encounter Effects (2002.1.28)
FinalFantasyXの遭遇エフェクト
・Fast Blurring (2002.1.23)
高速なぼかし

■Optimization Techniques

・Parallel Dithering Algorithm(2001.1.9)
並列ディザアルゴリズム
・Parallel Alpha Blending with MMX Instraction(2000.10.6)
MMXによる4ピクセル並列αブレンド