2009年08月29日

スカイテクスチャ、ランプテクスチャ、ヘイローテクスチャ

テクスチャといえばマテリアルに設定するマテリアルテクスチャをまず思い浮かべると思いますが、Blenderにはそれ以外にもテクスチャが使用される場所がいくつかあります。

前回の記事では、テクスチャタイプがTEX_IMAGEのときに処理をしている場所にTEX_VECTEXでも同じ処理が行われるように修正を行いました。(blender/source/blender/render/intern/source/texture.c)
その修正を行った場所はdo_material_tex()の中以外に、do_halo_tex()、do_sky_tex()、do_lamp_tex()など6ヶ所ありました。

スカイテクスチャ、ランプテクスチャ、ヘイローテクスチャについては、単純にイメージテクスチャと同じ座標データが得られるようにしただけではvectexの処理が有効にはならないようで、前回の更新後の状態ではvectexを使用すると解像度が255ピクセルの画像が表示されるだけで、レンダリング画像で要求される最適な解像度のテクスチャは得られていませんでした。
ちなみにvectexはOSAがオフになっている場合など、テクスチャの解像度計算ができない状況ではタイル画像のサイズ(縦横255ピクセル)でテクスチャを表示します。
pic090829_04.jpg pic090829_05.jpg

今回は、スカイテクスチャ、ランプテクスチャ、ヘイローテクスチャでvectexが使用できるようにソースコードの修正をしています。
pic090829_01.jpg pic090829_02.jpg pic090829_03.jpg

今回から、QtSVGを使用したバージョンのダウンロードファイルをGraphicAll.orgに置かせてもらうようにしました。

実行ファイルはこちらになります。(実行ファイルへの直接リンクではなくて、ダウンロードページへのリンクとなります)
Linux 64bit

Linux 32bit

Windows 32bit

ソースファイルのパッチ (Blender2.49.2_Vectex_QtSVG_patch.zip)

AGGバージョンについても更新しています。
AGGバージョンの方は、これまでと同じようにSeesaaのサーバに同じURLで置いてあります。
AGGバージョンのソースファイルのパッチ (Blender_modified_patch090826.zip)

前回からの修正点は、上に書いたようにマテリアルテクスチャ以外のテクスチャへの対応を行ったことです。
バグフィックスについてはとくに行っていません...と思っていましたが、テクスチャプレビュー画面にあるデフォルトボタンを押したときにvectexパネルのresolution数値ボタンの値が「1.0」に初期化されなかったのを修正していました。


今回からは、更新時のリンクの書き換えの手間を省略するため、ソースファイルのパッチについても同じURLを使い続けることにしました。
その代わりに、前回の更新からの変更のみを出力したDiffファイルを毎回用意するようにしたいと思います。
おそらくこの方がソースコードの変更内容を確認するためには便利かと思います。
前回からの修正内容 (changes090829.zip)

ちなみに、この修正内容のファイルは開発用UbuntuのPCにローカルに置いているSubversionリポジトリで「svn diff」コマンドを使用して出力しています。
ソースファイル全体のパッチについては日本語が入ると紛らわしかったりする関係で、Linuxのdiffコマンドを使っています。
私がSubversionを使い始めたのはこのプロジェクトを開始した後ということもあって、まだ十分に使いこなせているとは言い難いのですが、このツールのおかげでQtバージョンとAGGバージョンを平行して更新し続けることができたりして色々と助かっています。


ここから修正内容の解説をしたいと思いますが、C言語のソースコードを読み慣れている方の場合、svn diffコマンドの出力を見るだけで十分というか、その方がわかりやすいかもしれません。

スカイテクスチャ、ランプテクスチャ、ヘイローテクスチャの処理は、

blender/source/blender/render/intern/source/texture.c
にある、do_sky_tex()、do_lamp_tex()、do_halo_tex()の中と、

blender/source/blender/render/intern/source/pixelshading.c
の中にあるいくつかの関数の中に書かれています。

このうち、vectexに関わる部分は解像度の計算に使用するdxt、dytの値を得るというものです。
まず、スカイテクスチャから見てみます。

dxt、dytの内容については、do_sky_tex()の中の始めの方で、
            /* dxt dyt just from 1 value */
            if(dxyview) {
                dxt[0]= dxt[1]= dxt[2]= dxyview[0];
                dyt[0]= dyt[1]= dyt[2]= dxyview[1];
            }
            else {
                dxt[0]= dxt[1]= dxt[2]= 0.0;
                dyt[0]= dyt[1]= dyt[2]= 0.0;
            }
というように記述されているだけです。
この関数が呼ばれる前に、別の関数のなかでdxyview[]という配列にあらかじめ計算済みの値が渡されています。

しかし、dxt[0]、dxt[1]、dxt[2]をすべて同じ値にしてしまうのは、ちょっと手抜きなような気がします。
これでは、vectexの中の解像度の計算で正しい値を計算できません。

ということで、この部分を次の様に修正しています。
            /* dxt dyt just from 1 value */
            if (mtex->tex->type==TEX_VECTEX) {
                if(dxyview) {
                    dxt[0]= dxyview[0];
                    dyt[1]= dxyview[1];
                    if(R.wrld.skytype & WO_SKYPAPER) {
                        dxt[0]= dxyview[0] * 2.0;
                        dyt[1]= dxyview[1] * 2.0;
                    }
                    dxt[1] = dxt[2] = dyt[0] = dyt[2] = 0.0;
                }
                else {
                    dxt[0]= dxt[1]= dxt[2]= 0.0;
                    dyt[0]= dyt[1]= dyt[2]= 0.0;
                }
            }

            else {
                if(dxyview) {
                    dxt[0]= dxt[1]= dxt[2]= dxyview[0];
                    dyt[0]= dyt[1]= dyt[2]= dxyview[1];
                }
                else {
                    dxt[0]= dxt[1]= dxt[2]= 0.0;
                    dyt[0]= dyt[1]= dyt[2]= 0.0;
                }
            }
スカイタイプにPaperが選択されているとき、引数に格納されてくる値はそれ以外のときと違った値が渡されますが、実際に数値を確認してみたところ、本来渡されるはずの値の半分になっていたので2倍にしています。

スカイテクスチャの設定を行うパネルには「View」「Global」「AngMap」「Sphere」「Tube」「Object」の6つの座標系が選べるようになっています。
上の変更だけで対応できるのは、このうちの「View」「Global」だけで、後の4つの座標系が選ばれた場合にはそれぞれに対応した計算処理を自力で作成する必要があるようです。

そのためには、そもそもdxt、dytという値が何なのかを完全に理解していないと無理だと思うのですが、幸いなことに前回の修正のときにとても参考になるコードを見つけることができました。

前回、マテリアルテクスチャの座標系で「Refl」を選択したときにvectexの処理中にBlenderが異常終了してしまう問題に対応していますが、その際に
blender/source/blender/render/intern/source/shadeoutput.c (line:809-)
void calc_R_ref(ShadeInput *shi)
という関数の中でdxt、dytの値を計算しているコードがあることを知りました。

そのコードはこのような内容です。
void calc_R_ref(ShadeInput *shi)
{
    float i;

    /* shi->vn dot shi->view */
    i= -2*(shi->vn[0]*shi->view[0]+shi->vn[1]*shi->view[1]+shi->vn[2]*shi->view[2]);

    shi->ref[0]= (shi->view[0]+i*shi->vn[0]);
    shi->ref[1]= (shi->view[1]+i*shi->vn[1]);
    shi->ref[2]= (shi->view[2]+i*shi->vn[2]);
    if(shi->osatex) {
        if(shi->vlr->flag & R_SMOOTH) {
            i= -2*( (shi->vn[0]+shi->dxno[0])*(shi->view[0]+shi->dxview) +
                (shi->vn[1]+shi->dxno[1])*shi->view[1]+ (shi->vn[2]+shi->dxno[2])*shi->view[2] );

            shi->dxref[0]= shi->ref[0]- ( shi->view[0]+shi->dxview+i*(shi->vn[0]+shi->dxno[0]));
            shi->dxref[1]= shi->ref[1]- (shi->view[1]+ i*(shi->vn[1]+shi->dxno[1]));
            shi->dxref[2]= shi->ref[2]- (shi->view[2]+ i*(shi->vn[2]+shi->dxno[2]));

            i= -2*( (shi->vn[0]+shi->dyno[0])*shi->view[0]+
                (shi->vn[1]+shi->dyno[1])*(shi->view[1]+shi->dyview)+ (shi->vn[2]+shi->dyno[2])*shi->view[2] );

            shi->dyref[0]= shi->ref[0]- (shi->view[0]+ i*(shi->vn[0]+shi->dyno[0]));
            shi->dyref[1]= shi->ref[1]- (shi->view[1]+shi->dyview+i*(shi->vn[1]+shi->dyno[1]));
            shi->dyref[2]= shi->ref[2]- (shi->view[2]+ i*(shi->vn[2]+shi->dyno[2]));
        }
        else {

            i= -2*( shi->vn[0]*(shi->view[0]+shi->dxview) +
                shi->vn[1]*shi->view[1]+ shi->vn[2]*shi->view[2] );

            shi->dxref[0]= shi->ref[0]- (shi->view[0]+shi->dxview+i*shi->vn[0]);
            shi->dxref[1]= shi->ref[1]- (shi->view[1]+ i*shi->vn[1]);
            shi->dxref[2]= shi->ref[2]- (shi->view[2]+ i*shi->vn[2]);

            i= -2*( shi->vn[0]*shi->view[0]+
                shi->vn[1]*(shi->view[1]+shi->dyview)+ shi->vn[2]*shi->view[2] );

            shi->dyref[0]= shi->ref[0]- (shi->view[0]+ i*shi->vn[0]);
            shi->dyref[1]= shi->ref[1]- (shi->view[1]+shi->dyview+i*shi->vn[1]);
            shi->dyref[2]= shi->ref[2]- (shi->view[2]+ i*shi->vn[2]);
        }
    }
}
始めの部分でshi->ref[]には、vectex()関数に渡されるテクスチャの座標値(texvecに相当する値)をビュー座標から計算した値が入力されています。
そして、その後の部分では基本的には始めの部分のテクスチャ座標の計算とかなり似ていますが、shi->dxrefにはビュー座標のX成分のみを増加した時のテクスチャ座標値を計算し、その値を始めに計算したshi->ref[]から引いた値を格納しています。
shi->dyrefには、ビュー座標のY成分のみを増加して同じことを行っています。
shi->dxrefがdxt、shi->dyrefがdytに対応しています。

これは、レンダリングされる画像のピクセルの位置とそこで使用されるテクスチャの画像のピクセルの位置との対応で考えるなら、レンダリング画像の1ピクセル横の位置に対応するテクスチャ画像の座標のX成分がdxt[0]、Y成分がdxt[1]となり、レンダリング画像の1ピクセル上か下の位置に対応するテクスチャ画像のX成分がdyt[0]、Y成分がdyt[1]となります。

ということで、do_sky_tex()関数の中でも同じように処理を行おうとしたのですが、なぜか計算値が正しくならないようです。
色々と調べた結果、この関数に引数として渡されるビュー座標loの値が、この関数に渡される前に変更されているのが問題の原因だと分かりました。

loの値はdo_sky_tex()関数が呼ばれる直前に、shadeSkyView()関数の中で行列演算されています。
テクスチャ座標のX成分、Y成分をそれぞれ増加させる処理は、その行列演算を行う前にする必要があるようです。

そのため、ちょっと危険な感じもするのですが、その計算処理を元の関数shadeSkyView()から削除してdo_sky_tex()の中に移動しました。
shdeSkyView()関数の該当箇所のすぐ次の行でdo_sky_tex()関数が呼ばれているので処理的に問題は起こらないはずですが、できれば勝手にこのような変更はしたくないところです。
(修正を行った後で気づいたのですが、テクスチャ座標に加える増分の方に同じ行列演算を行ってしまうことでも問題が解決できそうな気がします。do_lamp_tex()関数の中ではそうなっています。あとで修正し直すかもしれません。)

blender/source/blender/render/intern/source/pixelshading.c (line:514-)
void shadeSkyView(float *colf, float *rco, float *view, float *dxyview, short thread)
{
...
    if(R.wrld.skytype & WO_SKYTEX) {
        VECCOPY(lo, view);
                       
        /*if(R.wrld.skytype & WO_SKYREAL) {
            MTC_Mat3MulVecfl(R.imat, lo);
           
            SWAP(float, lo[1],  lo[2]);
           
        }*/
        do_sky_tex(rco, lo, dxyview, hor, zen, &blend, skyflag, thread);
    }
...
}
blender/source/blender/render/intern/source/texture.c (line:3052)
void do_sky_tex(float *rco, float *lo, float *dxyview, float *hor, float *zen, float *blend, int skyflag, short thread)
{
...
    float tempvec2[3],lo1[3], lo2[3];
   
    if (R.r.scemode & R_NO_TEX) return;
    /* todo: add flag to test if there's a tex */
    texres.nor= NULL;
   
    lo1[0] = lo[0];
    lo1[1] = lo[1];
    lo1[2] = lo[2];
    if(R.wrld.skytype & WO_SKYREAL) {
        MTC_Mat3MulVecfl(R.imat, lo);
       
        SWAP(float, lo[1],  lo[2]);
       
    }

...
}
このようにlo1という配列を作って行列演算の前の値を保存しておき、それを使ってビュー座標をX、Y方向に増加させた値でテクスチャ座標を計算し、ソースコードに元々ある計算で得られる値からその値を引いて、dxt、dytの値を計算します。

まずは、AngMapの場合です。
            /* Grab the mapping settings for this texture */
            switch(mtex->texco) {
            case TEXCO_ANGMAP:
                /* only works with texture being "real" */
                fact= (1.0/M_PI)*acos(lo[2])/(sqrt(lo[0]*lo[0] + lo[1]*lo[1]));
                tempvec[0]= lo[0]*fact;
                tempvec[1]= lo[1]*fact;
                tempvec[2]= 0.0;
                co= tempvec;
                /*if(G.f & G_DEBUG) printf("  co[0]%f   co[1]%f\n", co[0], co[1]);*/   
               
                if ((mtex->tex->type==TEX_VECTEX) && (dxyview)) {
                    lo2[0] = lo1[0] + dxyview[0];
                    lo2[1] = lo1[1];
                    lo2[2] = lo1[2];
                    if(R.wrld.skytype & WO_SKYREAL) {
                        MTC_Mat3MulVecfl(R.imat, lo2);
                        SWAP(float, lo2[1],  lo2[2]);
                    }
                    fact= (1.0/M_PI)*acos(lo2[2])/(sqrt(lo2[0]*lo2[0] + lo2[1]*lo2[1]));
                    tempvec2[0]= lo2[0]*fact;
                    tempvec2[1]= lo2[1]*fact;
                    dxt[0] = tempvec[0] - tempvec2[0];
                    dxt[1] = tempvec[1] - tempvec2[1];
                   
                    lo2[0] = lo1[0];
                    lo2[1] = lo1[1] + dxyview[1];
                    lo2[2] = lo1[2];
                    if(R.wrld.skytype & WO_SKYREAL) {
                        MTC_Mat3MulVecfl(R.imat, lo2);
                        SWAP(float, lo2[1],  lo2[2]);
                    }
                    fact= (1.0/M_PI)*acos(lo2[2])/(sqrt(lo2[0]*lo2[0] + lo2[1]*lo2[1]));
                    tempvec2[0]= lo2[0]*fact;
                    tempvec2[1]= lo2[1]*fact;
                    dyt[0] = tempvec[0] - tempvec2[0];
                    dyt[1] = tempvec[1] - tempvec2[1];
                  
                    if (R.wrld.skytype & WO_SKYREAL) {
                    }
                    else {
                        fact = 10;
                        dxt[0]*=fact;
                        dxt[1]*=fact;
                        dyt[0]*=fact;
                        dyt[1]*=fact;
                    }   
                }
                break;

最後の部分は、スカイタイプの設定がRealになっていない場合、AngMapの計算そのものが正しく行われないらしく、vectexの解像度計算で必要以上に高い解像度でテクスチャが作成されるのを防ぐために適当にdxt、dytの値を補正しています。

ShpereとTubeはまとめて計算されています。
追加した処理は、AngMapとほとんど似たような感じです。
            case TEXCO_H_SPHEREMAP:
            case TEXCO_H_TUBEMAP:
                if(skyflag & WO_ZENUP) {
                    if(mtex->texco==TEXCO_H_TUBEMAP) tubemap(lo[0], lo[2], lo[1], tempvec, tempvec+1);
                    else spheremap(lo[0], lo[2], lo[1], tempvec, tempvec+1);
                    /* tube/spheremap maps for outside view, not inside */
                    tempvec[0]= 1.0-tempvec[0];
                    /* only top half */
                    tempvec[1]= 2.0*tempvec[1]-1.0;
                    tempvec[2]= 0.0;
                    /* and correction for do_2d_mapping */
                    tempvec[0]= 2.0*tempvec[0]-1.0;
                    tempvec[1]= 2.0*tempvec[1]-1.0;
                    co= tempvec;
                   
                    if ((mtex->tex->type==TEX_VECTEX) && (dxyview)) {
                        lo2[0] = lo1[0] + dxyview[0];
                        lo2[1] = lo1[1];
                        lo2[2] = lo1[2];
                        if(R.wrld.skytype & WO_SKYREAL) {
                            MTC_Mat3MulVecfl(R.imat, lo2);
                            SWAP(float, lo2[1],  lo2[2]);
                        }
                        if(mtex->texco==TEXCO_H_TUBEMAP) tubemap(lo2[0], lo2[2], lo2[1], &tempvec2[0], &tempvec2[1]);
                        else spheremap(lo2[0], lo2[2], lo2[1], &tempvec2[0], &tempvec2[1]);
                       
                        /* tube/spheremap maps for outside view, not inside */
                        tempvec2[0]= 1.0-tempvec2[0];
                        /* only top half */
                        tempvec2[1]= 2.0*tempvec2[1]-1.0;
                        tempvec2[2]= 0.0;
                        /* and correction for do_2d_mapping */
                        tempvec2[0]= 2.0*tempvec2[0]-1.0;
                        tempvec2[1]= 2.0*tempvec2[1]-1.0;
                       
                        dxt[0] = tempvec[0] - tempvec2[0];
                        dxt[1] = tempvec[1] - tempvec2[1];
                       
                        lo2[0] = lo1[0];
                        lo2[1] = lo1[1] + dxyview[1];
                        lo2[2] = lo1[2];
                        if(R.wrld.skytype & WO_SKYREAL) {
                            MTC_Mat3MulVecfl(R.imat, lo2);
                            SWAP(float, lo2[1],  lo2[2]);
                        }
                        if(mtex->texco==TEXCO_H_TUBEMAP) tubemap(lo2[0], lo2[2], lo2[1], &tempvec2[0], &tempvec2[1]);
                        else spheremap(lo2[0], lo2[2], lo2[1], &tempvec2[0], &tempvec2[1]);
                       
                        /* tube/spheremap maps for outside view, not inside */
                        tempvec2[0]= 1.0-tempvec2[0];
                        /* only top half */
                        tempvec2[1]= 2.0*tempvec2[1]-1.0;
                        tempvec2[2]= 0.0;
                        /* and correction for do_2d_mapping */
                        tempvec2[0]= 2.0*tempvec2[0]-1.0;
                        tempvec2[1]= 2.0*tempvec2[1]-1.0;
                       
                        dyt[0] = tempvec[0] - tempvec2[0];
                        dyt[1] = tempvec[1] - tempvec2[1];
                    }

                }
                else {
                    /* potentially dangerous... check with multitex! */
                    continue;
                }
                break;

次は、Objectの場合です。
こちらも、それほど違いはありません。
            case TEXCO_OBJECT:
                if(mtex->object) {
                    VECCOPY(tempvec, lo);
                    MTC_Mat4MulVecfl(mtex->object->imat, tempvec);
                    co= tempvec;
                   
                    if ((mtex->tex->type==TEX_VECTEX) && (dxyview)) {
                        lo2[0] = lo1[0] + dxyview[0];
                        lo2[1] = lo1[1];
                        lo2[2] = lo1[2];
                        if(R.wrld.skytype & WO_SKYREAL) {
                            MTC_Mat3MulVecfl(R.imat, lo2);
                            SWAP(float, lo2[1],  lo2[2]);
                        }
                        VECCOPY(tempvec2, lo2);
                        MTC_Mat4MulVecfl(mtex->object->imat, tempvec2);
                        dxt[0] = tempvec[0] - tempvec2[0];
                        dxt[1] = tempvec[1] - tempvec2[1];
                       
                        lo2[0] = lo1[0];
                        lo2[1] = lo1[1] + dxyview[1];
                        lo2[2] = lo1[2];
                        if(R.wrld.skytype & WO_SKYREAL) {
                            MTC_Mat3MulVecfl(R.imat, lo2);
                            SWAP(float, lo2[1],  lo2[2]);
                        }
                        VECCOPY(tempvec2, lo2);
                        MTC_Mat4MulVecfl(mtex->object->imat, tempvec2);
                        dyt[0] = tempvec[0] - tempvec2[0];
                        dyt[1] = tempvec[1] - tempvec2[1];
                    }

                }
                break;

次に、ヘイローテクスチャについてです。
こちらは、スカイテクスチャのように複数の座標系が選べるようにはなっていないので、修正内容はもっと少なくなっています。
void do_halo_tex(HaloRen *har, float xn, float yn, float *colf)
{
...
        if (mtex->tex->type==TEX_VECTEX) {
            if(mtex->projx) {
                dxt[0]= mtex->size[0]*dx;
                dyt[0]= 0.0;
            }
            else dxt[0]= dyt[0]= 0.0;
           
            if(mtex->projy) {
                dxt[1]= 0.0;
                dyt[1]= mtex->size[1]*dx;
            }
            else dxt[1]= dyt[1]= 0.0;
        }
        else {
            if(mtex->projx) {
                dxt[0]= mtex->size[0]*dx;
                dyt[0]= mtex->size[0]*dx;
            }
            else dxt[0]= dyt[0]= 0.0;
           
            if(mtex->projy) {
                dxt[1]= mtex->size[1]*dx;
                dyt[1]= mtex->size[1]*dx;
            }
            else dxt[1]= dyt[1]= 0.0;
        }
...
}

スカイテクスチャの処理の最初の部分で行ったのと似たようなことをしています。
元の処理でdxt[0]とdxt[1]、dyt[0]とdyt[1]が同じ値にされていたのを、dxt[1]、dyt[0]をそれぞれ0.0に変更することでvectexでの解像度計算が正しく行われるようにしています。

次にランプテクスチャです。
do_lamp_tex()関数では、スカイテクスチャやヘイローテクスチャとは違ってきちんとdxt、dytが計算されています。
しかし、なぜかvectexの計算で正しい解像度が計算されません。
調べてみると、dxt、dytの値を計算する部分のif文の条件式に(shi->osatex)という記述があり、この値がイメージテクスチャの場合と、そうでない場合とで違っているようです。
ということで、その値を設定している部分をvectexのときにも有効になるように変更することにします。

blender/source/blender/render/intern/source/convertblender.c (line:3326-)
static GroupObject *add_render_lamp(Render *re, Object *ob)
{
...
            if(G.rendering) {
                if(re->osa) {
                    if((la->mtex[c]->tex->type==TEX_IMAGE) || (la->mtex[c]->tex->type==TEX_VECTEX)) lar->mode |= LA_OSATEX;
                }
            }
...
}

この修正で、ランプテクスチャでもvectexが使用できるようになりました。
posted by mato at 00:46| Comment(0) | Blender Vectex | このブログの読者になる | 更新情報をチェックする

2009年08月19日

Map Input(マッピング座標系)への対応

Blenderでテクスチャを使用する際には、マテリアルの設定(F5)とテクスチャの設定(F6)の両方で設定が必要です。
マテリアルの設定では、Texture Panel、Map Input Panel、Map To Panelの3つのパネルがテクスチャに関係しています。

このうちMap Input Panelがマッピング座標系を指定するために使用されます。
オリジナルのvectexは、Blenderの初期状態で作成されるキューブにマッピング座標としてOrcoなどを指定してレンダリングすると、途中でフリーズするなどの問題があります。

今回は、この問題に(若干問題が残りますが)対応してみました。
pic090819_01.jpg

ということで、vectex内臓版ビルドを更新しました。
実行ファイルはこちらです。
QtSVGバージョン
Linux 32bit (Blender_Qt_vectex_Linux32090709.tar.gz)
Linux 64bit (Blender_Qt_vectex_Linux64090709.tar.gz)
Windows 32bit (Blender_Qt_vectex_win32090709.zip)

ソースファイルのパッチ (Blender_Qt_vectex_patch090817.zip)

追加した機能は、
Map Inputのほとんどのマッピング座標系(Glob、Object、Orco、Stick、その他)に対応
テクスチャ解像度の変更用数値ボタンを追加

バグフィックス
前回の更新でアルファ値を使用できるようにしていますが、その副作用という形でアルファ値を使用しない場合には背景色との境界部分がきれいに描画されなくなっていました。
この問題を修正していますが、今度はその代償としてPremulボタンを押していない状態でもPremulが有効な状態で描画される、という状況になっています。

AGGバージョンのソースファイルのパッチ (Blender_modified_patch090817.zip)
実行ファイルのURLは以前のものと同じなので省略します。

Resolution(解像度の変更)ボタンについては、ソースコードの解説の方でも少し触れていますがvectexの作成するタイル画像の解像度を調整するために付けました。
数値を下げると画像が荒くなり、数値を上げるとより詳細な画像を作成しますが、補完処理の関係でエッジがなめらかに表示されなくなるため画質は低下します。
値が1.0のときレンダリング画像とタイル画像の解像度が同じになるので、ほとんどの場合この値は1.0のままにしておくことで最適な画像が得られます。
pic090819_03.jpg


ここから、ソースコードの更新内容の解説となります。

vectex()関数は、Blenderのレンダリングの際に描画される各ピクセル毎に呼び出されます。
その処理の中では、テクスチャの画像の位置を保持するtexvecと、現在のテクスチャの座標と次に描画される座標との差をあらかじめ計算した値を保持するdxt、dytという変数が重要な役割を果たします。

texvec、dxt、dytはそれぞれ配列となっていて、texvec[0]はテクスチャのX座標、texvec[1]はテクスチャのY座標、texvec[2]はテクスチャのZ座標、dxt[0],dxt[1],dxt[2]は、それぞれテクスチャのX座標の差分値、dyt[0],dyt[1],dyt[2]はテクスチャのY座標の差分値が、vectex()関数を呼び出す前の段階であらかじめ計算された状態で関数に引数として渡されてきます。

今回のマッピング座標系の問題は、これらの変数に書き込まれている値がvectexの期待している値とは違っているために起こっていると考えられます。

とりあえず、texture.cでvectex()関数を呼び出す前の処理を調べてみると、テクスチャタイプがイメージテクスチャの場合だけマッピング座標を変換しているコードがあることが分かりました。
vectexはX,Y,Zの3次元のマッピング座標を使うプロシージャルテクスチャよりも、X,Yの2次元のマッピング座標を使うイメージテクスチャに近い動作をします。
そこで、まずはvectex()関数が受け取る座標系をイメージテクスチャが受け取るのと同じになるようにソースコードを修正しました。

これは、texture.cの中で「if(tex->type==TEX_IMAGE)」となっている場所を探して、「if(tex->type==TEX_IMAGE || tex->type==TEX_VECTEX)」のように置き換えるだけの処理です。
blender/source/blender/render/intern/source/texture.c
(line:2124-)
int multitex_thread(Tex *tex, float *texvec, float *dxt, float *dyt, int osatex, TexResult *texres, short thread, short which_output)
{
...
    if(tex->type==TEX_IMAGE || tex->type==TEX_VECTEX) {
...
}

(line:2341-)
void do_material_tex(ShadeInput *shi)
{
...
            if(tex->type==TEX_IMAGE || tex->type==TEX_VECTEX) {
...
                    if ((tex->type==TEX_IMAGE || tex->type==TEX_VECTEX) && (tex->imaflag & TEX_NORMALMAP)) {
...
}

(line:2907-)
void do_halo_tex(HaloRen *har, float xn, float yn, float *colf)
{
...
    if(mtex->tex->type==TEX_IMAGE || mtex->tex->type==TEX_VECTEX) do_2d_mapping(mtex, texvec, NULL, NULL, dxt, dyt);
...
}

(line:3035-)
void do_sky_tex(float *rco, float *lo, float *dxyview, float *hor, float *zen, float *blend, int skyflag, short thread)
{
...
            if(mtex->tex->type==TEX_IMAGE || mtex->tex->type==TEX_VECTEX) do_2d_mapping(mtex, texvec, NULL, NULL, dxt, dyt);
...
}

(line:3210-)
void do_lamp_tex(LampRen *la, float *lavec, ShadeInput *shi, float *colf, int effect)
{
...
            if(tex->type==TEX_IMAGE || tex->type==TEX_VECTEX) {
...
}

(line:3387-)
int externtex(MTex *mtex, float *vec, float *tin, float *tr, float *tg, float *tb, float *ta)
{
...
    if(tex->type==TEX_IMAGE || tex->type==TEX_VECTEX) {
...
}

以上の様に6ヶ所の修正が必要になりました。これを見るとvectexがテクスチャとして使われる場所は通常のマテリアルの設定以外にもヘイローテクスチャ、スカイテクスチャやランプテクスチャなど、いくつかあることに気づきます。

このようにしてイメージテクスチャと同じマッピング座標を受け取るようにした状態でvectexのテクスチャをレンダリングすると、レンダリング結果は画像の位置とサイズが元のものとは違ってしまいます。
pic090819_02.jpg

プラグインテクスチャや通常のプロシージャルテクスチャではX,Yともに-1.0から1.0までの範囲でテクスチャの座標値(texvec)を受けとります。
しかし、イメージテクスチャではX,Yとも0.0から1.0になっているようです。
これは、UV/Image Editorなどで操作しているときのUV座標と同じです。

レンダリング結果を元のように戻すには、vectexのソースコードを修正する必要があります。

(line:1068-)
void vtex_sample(VtexInstance *instance, float *texvec0, float *texvec1, int power, int interp,
            double *pr, double *pg, double *pb, double *pa, Tex *tex)
{
...
        /* Simply pick and return the nearest pixel */
        /*int x = (int)((*texvec0 + 1.0) * (resolution >> 1));
        int y = (int)((1.0 - *texvec1) * (resolution >> 1));*/
        int x = (int)(*texvec0 * resolution);

        int y = (int)((1.0 - *texvec1) * resolution);

...
        /* Pick the four nearest pixels and return a bilinear interpolation */
        /*double u = (*texvec0 + 1.0) * (resolution >> 1) - 0.5;
        double v = (1.0 - *texvec1) * (resolution >> 1) - 0.5;*/
        double u = *texvec0 * resolution - 0.5;

        double v = (1.0 - *texvec1) * resolution - 0.5;

...
}
コメントアウトした元のコードを見ると、resolutionを1だけ右にシフトすることで1/2に値を下げています。
この状態でレンダリングすると、位置とスケールが正しく修正されてはいますが、解像度が倍になっているためエッジの部分がなめらかに補間されなくなってしまいます。

これを修正するにはdxt,dytの値から解像度を計算している部分に変更を加えます。
今回この部分を修正してみて、もしこの解像度を自由に変更できるようにしたら便利かもしれないと思い、vectexパネルにResolution変更用の数値ボタンを追加してみました。
static int vectex(Tex *tex, float *texvec, float *dxt, float *dyt, int osatex, TexResult *texres)
{
...
    float ideal_res_scale;
...
        /* Compute the ideal image resolution and its upper bound power of two */
        if ((tex->vtex_resolution_scale > 0.0) && (tex->vtex_resolution_scale <= 4.0)) {
        }
        else {
            tex->vtex_resolution_scale = 1.0;
        }
        ideal_res_scale = tex->vtex_resolution_scale;
        ideal_res = (int)(ideal_res_scale / pixel_size);
       
        if (ideal_res <= 0) {
            MSG0("#");
            ideal_res = 1 << instance->tile_set_max_power;
            power     = instance->tile_set_max_power;
        }
        else {
            for (power = 1; ideal_res >> power; power++);

            /* Avoid precision errors that cause the creation of high resolution images */
            if (power > instance->tile_set_max_power)
            {
                /* The test below is faster than calculating the exact angle with
                   double angle = 180.0 * atan2(signed_area, dot_product) / M_PI; */
                double dot_product = dxt[0] * dyt[0] + dxt[1] * dyt[1];
                if (dot_product != 0.0 && fabs(signed_area / dot_product) < 0.1)
                {
                    /* Angle is less than 6 degrees, therefore the pixel area is very
                       thin: fallback to the max resolution used so far */
                    MSG0("*");
                    ideal_res = 1 << instance->tile_set_max_power;
                    power     = instance->tile_set_max_power;
                }
            }
        }
...
}

この部分のソースコードでは、Map InputでOrcoなどを指定したときに無限ループに陥ることがあったため、それを回避するための修正も加えてあります。
修正前のコードでは、
       
        /* Compute the ideal image resolution and its upper bound power of two */
        ideal_res = (int)(2.0 / pixel_size);
        for (power = 1; ideal_res >> power; power++);


このようにテクスチャの解像度を保持するideal_resが計算されていますが、この式ではpixel_sizeが0のときにideal_resが無限大になってしまうという問題があります。
pixel_sizeが0でなくてもマイナスの値であれば、次のfor文が無限ループとなってやはり問題が起こります。
今回は、これを回避するためにideal_resが0より小さい場合はpowerの計算をスキップするように修正しています。

ただ、これだけだとまだ不十分なようで、デフォルトキューブのような面の法線にスムース処理がされていない形状の場合、Glob、Stick、Winなどを使用すると、状況によってレンダリングが非常に遅くなることがあります。

デフォルトキューブにMap InputのReflを使った場合、この修正内容はまったく効果がなく、レンダリングの途中でblenderが異常終了してしまうようです。
調べてみるとvectex()関数に渡されているdxt、dytの値がideal_resを0以下にするような不正な値ではないものの、他のマッピング座標の1/1000程度の非常に小さい値であるために1ピクセルの計算毎に1つのタイル画像が作成されるような状況になっていることが分かりました。

このReflのマッピング座標の計算を行っているのは、
blender/source/blender/render/intern/source/shadeoutput.c (line:809-)
void calc_R_ref(ShadeInput *shi)

という関数です。
中身を見るとベクトルの演算が行われていて、視線ベクトルとオブジェクトの法線ベクトルを使ってテクスチャの座標を計算しています。
dxt、dytの計算の部分は、さらに

blender/source/blender/render/intern/source/shadeinput.c (line:577-)
void shade_input_calc_viewco(ShadeInput *shi, float x, float y, float z, float *view, float *dxyview, float *co, float *dxco, float *dyco)

という関数の中で計算された値を使用しているようです。
おそらくこの計算の中のどこかに問題があると思うのですが、この計算式の根拠となっているアルゴリズム等についてまったく分からない状況では何が変なのかさっぱり見当がつかないというのが実状です。
とりあえずデバッガ等で調べた範囲では、

                if(dxyview) {
                    if(fac!=0.0f) fac= 1.0f/fac;
                    dxyview[0]= -R.viewdx*fac;
                    dxyview[1]= -R.viewdy*fac;
                }

この計算式で元々小さい値をさらに小さくしているのが原因になっていると思えるので、この部分の処理を無効化することにしました。
ただし、この部分を無効化してもdxt、dytの値は理論的に本来こうなるはず...という値にはならないようです。
本来あるべき状態に修正できればいいのですが、現状ではどこをどう修正すればいいのかよくわかりません。
このような状況で安易に元の計算処理を変更してしまうと、この計算結果を使用している他の関数によくない影響が出る可能性があります。
そこで、もとの計算には一切変更は加えずに、vectex()関数にdxt、dytを渡す部分で値を補正することにしました。
(line:2341-)
void do_material_tex(ShadeInput *shi)
{
...
                if (tex->type==TEX_VECTEX) {
                    if (shi->vlr->flag & R_SMOOTH) {
                    }
                    else {
                        fac = (-R.viewdx / shi->dxview) * 10;
                       
                        dx[0] *= fac;
                        dx[1] *= fac;
                        dx[2] *= fac;
                        dy[0] *= fac;
                        dy[1] *= fac;
                        dy[2] *= fac;
                    }
                }

...
}
問題がありそうな計算式の中でdxviewの値に乗算されていたfacの値の逆数に、さらに10を掛けていますが、この値はかなり適当に決めたものであって、この部分の修正はあまり信頼できるものではありません。
何度かレンダリングのテストをした範囲で問題がなさそうな値を選んでいる、という程度のものです。
あまりいい修正ではありませんが、とりあえずレンダリングの途中でBlenderが異常終了することは回避できるようになりました。
後でReflの計算処理の内容をもう少し詳しく調べて、dxt、dytの値を正しく計算できるように修正し直すつもりです。

Resolutionボタンを追加した関係で、texture.c以外のファイルにも修正を加えています。
既存のファイルを開いたときにResolutionボタンの値が1.0に初期化されないで、ボタンに設定された最小値に設定されてしまうことがあります。
通常はBlenderのバージョンを元にしてResolutionボタンの値を初期化するかどうかを判別しますが、このvectex内臓ビルドのようなカスタムビルドではBlenderのバージョンアップとは無関係に更新を繰り返さなければならないため、公式のバージョンとは別になんらかの形でバージョン管理が必要になります。
これは本来はあまり望ましいものとは思えないのですが、Tex構造体の中にvectex用にバージョンを保持する変数を作成しました。
blender/source/blender/makesdna/DNA_texture_types.h (line:154-)
typedef struct Tex {
...
    float vtex_resolution_scale;
...
    short vtex_version;
...
}
blender/source/blender/src/buttons_shading.c (line:800-)
static void texture_panel_vectex(Tex *tex)
{
...
    uiDefButF(block, NUM, B_TEXPRV, "Resolution", 10, 85, 150, 18, &tex->vtex_resolution_scale, 0.1, 4.0, 10, 0, "Texture pixel resolution scale");
...
}
Resolutionボタンの追加の他に、若干ボタン配置の変更などもしています。
blender/source/blender/blenloader/intern/readfile.c (line:5052-)
static void do_versions(FileData *fd, Library *lib, Main *main)
{
...
    if (main->versionfile < 248) {
    ...
        for(tex= main->tex.first; tex; tex= tex->id.next) {
            ...
            tex->vtex_imaflag = 0;
            tex->vtex_resolution_scale = 1.0;

        }
    }
...
    if (main->versionfile <= 249 && main->subversionfile <= 2) {
        Tex *tex;
        for(tex= main->tex.first; tex; tex= tex->id.next) {
            if (tex->vtex_version < 1) {
                tex->vtex_resolution_scale = 1.0;
               
                tex->vtex_version = VTEX_VERSION_NUM;
            }
        }
    }

...
}
これ以外に、テクスチャのプレビューパネルにあるデフォルトボタンを押したときにもvtex_resolution_scaleの値を1.0に初期化する必要があるのですが、今回の更新に入れるのを忘れました。次の更新のときには修正するつもりです。

今回マッピング座標に関して行った修正は、大体こんな感じです。
今回はマテリアルテクスチャのみに限定して、ヘイローテクスチャ、スカイテクスチャなどについては詳しく調べていませんが、ちょっと見た感じではきちんとvectexの解像度計算がされていないようですので、後で対応するつもりです。

これ以外に今回の更新では、前回の修正で行ったアルファ値の使用のための変更でアルファ値を使わないときに問題が起こるようになっていたため、これを修正しています。

前回の修正ではQtSVGのレンダリングを行う関数内で、タイル画像のメモリ領域を確保した後、背景色で全画素を塗りつぶして初期化する際にアルファ値の値を0とするように修正していました。
この結果、アルファ値を使わないときに背景色との境界部分がスムースに描画されなくなり、修正前よりもエッジ部分の画像品質が落ちています。

AGGやlibrsvgなどではこのようなことは起こらないようですが、Qtのマニュアルを調べたり、フォーラムの質問などを調べたりしても、これといった解決方法は見つかりませんでした。

とりあえず、アルファ値を使わない場合には元のように処理することでなんとか修正しています。
(実際には、画像モードをQImage::Format_ARGB32から、QImage::Format_ARGB32_Premultipliedに変更するというようなことを行っています。以前の処理は、alpha値に1を設定した背景色で画像を矩形塗りつぶしするというものでしたが、結果として作成される画像は同じになります。)
この結果、Premulボタンを押さない状態でもPremulが有効になっている、というような状況になっています。
もし、Qtの描画処理でこの問題を回避できる方法が見つかったら、この部分の処理はもう一度修正し直すつもりです。

EXT unsigned char *vtex_backend_render_tile(void *backend_data, int resolution,
                                       int tile_size, int tile_row, int tile_col,
                                       double base_r, double base_g, double base_b,
                                       int border, Tex *tex)
{
...
    QImage image;
    if ((tex->imaflag & TEX_USEALPHA) || (tex->vtex_imaflag & IMA_DO_PREMUL)) {
         image = QImage(buf, width, height, QImage::Format_ARGB32);
     }

     else {
         image = QImage(buf, width, height, QImage::Format_ARGB32_Premultiplied);
     }


    QPainter painter(&image);
    painter.setRenderHint(QPainter::Antialiasing);
...
}


ここで、vtex_backend_render_tile()関数の引数にTex構造体を渡すようにしているのは、vectexパネルでPremulボタン、UseAlphaボタンが押されている場合とそうでない場合とで処理を分けるためです。
Premulボタンを押さない状態でもPremul機能が有効になっている現在の状況を考えると、Premulボタンを削除してしまって常にQImage::Format_ARGB32_Premultipliedのフォーマットを使用するようにしてしまっても良さそうに思えます。
実際、そのようにソースコードを修正して、vtex_sample()関数内でPremul計算を行っている部分のコードを削除してレンダリングしてみました。
すると若干ですが、Qtの計算するPremulとBlenderのPremul計算では結果が違っていて、Blenderの計算の方が少し品質が上回っているようでした。

それと、Premulボタンを押したときには常に背景色が黒になりますが、QtのPremul計算では背景色がどのような色であっても構わないという違いもあって、Premulボタンを削除してしまうとイメージテクスチャでの操作と違ってしまい、紛らわしいように思えました。

以上のような理由から、ちょっと無駄なように思えますがPremul/UseAlphaボタンを押さない状態のときにQtの描画にQImage::Format_ARGB32_Premultipliedを使用し、Premul/UseAlphaボタンを押さない状態のときにはQtの描画でQImage::Format_ARGB32を使用してvtex_sample()関数内でPremul計算を行う、という処理にしています。

このようにPremulボタン、UseAlphaボタンの状態に応じてタイル画像への描画処理を変更するようにしてしまうと、それらのボタンの状態変化を監視して、もし状態が変わったら作成ずみのタイル画像をすべて削除して、タイル画像をすべて作成しなおすという処理が必要になります。

以下の様にコードを修正しています。
まず、Premulボタン、UseAlphaボタンの状態を判別できるようにするために、VtexInstanceにimaflag、vtex_imaflagの値をコピーしておき、その値とTex構造体の中のimaflag、vtex_imaflagを比較できるようにします。
blender/source/blender/makesdna/DNA_texture_types.h
(line:140-)
typedef struct VtexInstance
{
...
    short imaflag;
    short vtex_imaflag;
    float pad;

..
}
(line:154-)
typedef struct Tex {
...
    short vtex_imaflag;
...
}
VtexInstanceの作成時に、その時点でのtex->imaflag、tex->vtex_imaflagの値をコピーしておきます。
blender/source/blender/render/intern/source/texture.c

(line:916-)
void vtex_create_instance(Tex *tex)
{
...
    instance->imaflag = tex->imaflag;
    instance->vtex_imaflag = tex->vtex_imaflag;

}
vtex_backend_render_tile()関数の引数にTex構造体を追加した関係で、この関数を呼び出すvtex_get_tile()関数とそれを呼び出すvtex_sample()関数の引数にもTex構造体を追加して、Tex構造体をリレーするように修正しています。
vectex.hのvtex_backend_render_tile()関数のプロトタイプ宣言も修正しています。
blender/source/blender/vectex/vectex.h (line:58)
EXT unsigned char *vtex_backend_render_tile(void *backend_data, int resolution,
                                       int tile_size, int tile_row, int tile_col,
                                       double base_r, double base_g, double base_b,  
                                       int border, Tex *tex);

(line:968-)
int vtex_get_tile(VtexInstance *instance, int x, int y, int power, Tex *tex)
{
...
        unsigned char *image = vtex_backend_render_tile(instance->backend_data, resolution,
                                                   tile_size, tile_row, tile_col,
                                                   instance->current_base_color[0] / 255.0,
                                                   instance->current_base_color[1] / 255.0,
                                                   instance->current_base_color[2] / 255.0,
                                                   VTEX_TILE_BORDER, tex);
...
}

(line:1068-)
void vtex_sample(VtexInstance *instance, float *texvec0, float *texvec1, int power, int interp,
            double *pr, double *pg, double *pb, double *pa, Tex *tex)
{
     int premul;

     if (tex->vtex_imaflag & IMA_DO_PREMUL) {
         premul = 1;
     }
     else {
         premul = 0;
     }

...
        int index = vtex_get_tile(instance, x, y, power, tex);  
...
        int index = vtex_get_tile(instance, x, y, power, tex);       
...    
}

vectex()関数内では、VtexInstanceにコピーしておいたimaflag、vtex_imaflagの値と、Tex構造体のimaflag、vtex_imaflagの値を比較して、もし状態が変わっていたらすべてのタイル画像を作成し直して、VtexInstanceのimaflag、vtex_imaflagの値を更新します。
また、vtex_sample()関数の呼び出しを行っている部分には、引数にTex構造体を追加しています。
static int vectex(Tex *tex, float *texvec, float *dxt, float *dyt, int osatex, TexResult *texres)
{
...
    /* If imaflag or vtex_imaflag has changed then empty the tile set */

    if ((instance->imaflag != tex->imaflag) || (instance->vtex_imaflag != tex->vtex_imaflag))
    {
        MSG0("- imaflag or vtex_imaflag changed; empty the tile set\n");
        vtex_destroy_tile_set(instance);
        vtex_create_tile_set(instance);
        instance->imaflag = tex->imaflag;
        instance->vtex_imaflag = tex->vtex_imaflag;
    }

...
    vtex_sample(instance, &texvec0, &texvec1, power, tex->vtex_interpol, &r, &g, &b, &a, tex);
...
                vtex_sample(instance, &texvec0, &texvec1, power, tex->vtex_interpol, &r1, &g1, &b1, &a1, tex);
...
                vtex_sample(instance, &texvec0, &texvec1, power, tex->vtex_interpol, &r1, &g1, &b1, &a1, tex);
...
        vtex_sample(instance, &texvec0, &texvec1, power - 1, tex->vtex_interpol, &r2, &g2, &b2, &a2, tex);
...
                vtex_sample(instance, &texvec0, &texvec1, power - 1, tex->vtex_interpol, &r1, &g1, &b1, &a1, tex);
...
                vtex_sample(instance, &texvec0, &texvec1, power - 1, tex->vtex_interpol, &r1, &g1, &b1, &a1, tex);
...
}
posted by mato at 01:56| Comment(0) | Blender Vectex | このブログの読者になる | 更新情報をチェックする

2009年08月04日

SVGファイルのPack機能、UseAlpha機能の追加

vectex内臓Blenderにいくつかの機能追加とバグフィックスを行いました。
実行ファイルはこちらです。
QtSVGバージョン
Linux 32bit (Blender_Qt_vectex_Linux32090709.tar.gz)
Linxu 64bit (Blender_Qt_vectex_Linux64090709.tar.gz)
Windows 32bit (Blender_Qt_vectex_win32090709.zip)

ソースファイルのパッチ (Blender_Qt_vectex_patch090804.zip)

追加した機能は、
・SVGファイルのパック機能の追加
・アルファ値の使用に関する機能(premul)の追加

バグフィックスの内容については、この記事の下の方を見てください。
実行ファイルのURLは以前のものと同じで、古いファイルは削除しています。
AGGバージョンも同時に更新していますが、アルファ値の使用に関してはAGGのSVG描画機能がもともとアルファ値に対応していないため、あまり意味がないかもしれません。

AGGバージョンのソースファイルのパッチ(Blender_modified_patch090804.zip)
実行ファイルのURLは、以前のものと同じなので省略します。
pic090804_01.jpg


ここから、更新内容についての解説となります。

○Pack機能
この機能は、テクスチャ用画像ファイルなどを.blendファイルの中に埋め込んで保存するものです。
その分.blendファイルのサイズが大きくなってしまいますが、別のコンピュータなどに移動して使う場合に画像ファイルが見つからなくなるのを防ぐことができます。

この機能を実装するためには、QtSVGなどのライブラリでSVGファイルを開く際に通常はファイル名を渡していますが、これを.blendファイルに保存されたデータのメモリ領域へのポインタを渡すように変更することになります。
こんなことが簡単にできるものなのかとちょっと不安に思いましたが、QtSVGにはまさにこのためにあるのではないかというように、QSvgRender::load(const QByteArray & contents)という機能が用意されていました。
あとは、QByteArrayというデータ領域を扱うクラスが、BlenderのPack機能の作成するデータを直接扱えるかどうか、という点が問題になります。
QByteArrayのコンストラクタには、QByteArray(const char *data, int size)というようにchar型のデータ領域へのポインタとそのサイズを渡すものがあります。
今回は、これを使うことで.blendファイルに保存されたメモリ領域からSVGファイルを読み込むことができました。

ソースコードの修正内容です。
まずは、ボタンを追加します。
今回はvectexパネルで全体のボタンの配置を変更しているため、機能を追加している部分以外もソースコードが修正されています。
パック機能のボタンには専用のアイコンが使われていて、ファイルがパックされているか、されていないかの状態に応じて表示されるアイコンも変更されるようになっています。

blender/source/blender/src/buttons_shading.c (line:798-)
static void texture_panel_vectex(Tex *tex)
{
...
    if (tex->vtex_packedfile) packdummy = 1;
    else packdummy = 0;
    but= uiDefIconButBitI(block, TOG, 1, B_REDR, ICON_PACKAGE, 286,100,24,20, &packdummy, 0, 0, 0, 0, "Toggles Packed status of this SVG file");
    uiButSetFunc(but, vectex_pack_cb, tex, NULL);

...
}

このコードには、パックボタンを押したときにvectex_pack_cb()という関数が実行されるようになっています。
この関数は元々存在していませんので、自分で用意する必要があります。
イメージテクスチャ用のimage_pack_cb()という関数を参考にして作成しました。

blender/source/blender/src/buttons_shading.c (line:250-)
static void vectex_pack_cb(void *tex_v, void *unused)
{
    Tex *tex= tex_v;

    if (tex->vtex_packedfile) {
        if (G.fileflags & G_AUTOPACK) {
            if (okee("Disable AutoPack ?")) {
                G.fileflags &= ~G_AUTOPACK;
            }
        }
       
        if ((G.fileflags & G_AUTOPACK) == 0) {
            unpackVectex(tex, PF_ASK);
            /*BIF_undo_push("Unpack image");*/
        }
    }
    else {
        tex->vtex_packedfile = newPackedFile(tex->vtex_file_name);
        /*BIF_undo_push("Pack image");*/
    }
}

すでにファイルをパック済みか、そうでないかによって処理を分けています。
パック済みであればパックを解除し、パックされていないなら新規パックファイルを作成します。
イメージテクスチャにはアンドゥ処理用のコードが書かれていましたが、今回はアンドゥ処理は省略してあります。

このコードの中のnewPackedFile()関数からの戻り値はPackFile構造体へのポインタです。
これをTex構造体に保持するために、tex->vtex_packedfileという新しいメンバをTex構造体に追加しています。

blender/source/blender/makesdna/DNA_texture_types.h (line:151-)
typedef struct Tex {
...
    struct PackedFile * vtex_packedfile;
...
}
Tex構造体に保持されるPackFile構造体の定義は、DNA_packedFile_types.hに書かれています。

blender/source/blender/makesdna/DNA_packedFile_types.h (line:36-)
typedef struct PackedFile {
    int size;
    int seek;
    int flags;
    int pad;
    void * data;
} PackedFile;
パックされたデータにアクセスするときには、この構造体のsize、dataメンバを使用します。
vectexでSVGファイルを開く処理をしているのは、vtex_create_backend()という関数です。

blender/source/blender/vectex/vectex_agg.cpp (line:53-)
EXT void *vtex_create_backend(Tex *tex)
{
    char *filename = tex->vtex_file_name;
...
    if (tex->vtex_packedfile) {

        QByteArray svgdata((char *)tex->vtex_packedfile->data, tex->vtex_packedfile->size);
        if (backend->svg_render.load(svgdata) != true)
        {
            fprintf (stderr, "Cannot open input file %s\n", filename);
            delete backend;
            return (NULL);
        };       
    }
    else {

...
}

Tex構造体のvtex_packedfileにデータが作成されているときは、QtのQByteArrayというクラスのインスタンスsvgdataを作成します。
QByteArrayのコンストラクタにはいくつかのバリエーションがありますが、char型のポインタとint型のサイズを引数として使用するものを使ってPackFile構造体に保存されたデータを読み込んでいます。

このようにTex構造体のvtex_packedfileというメンバにアクセスするために、vtex_create_backend()関数の引数も修正しました。元々は、
EXT void *vtex_create_backend(char *filename);
というようにファイル名を渡していましたが、Tex構造体のポインタを渡すように修正しファイル名もtex->vtex_file_nameというメンバから取得するように変更しました。
これに対応するvectex.hのプロトタイプ宣言、texture.cのvtex_create_instance()関数でvtex_create_backend()関数を呼び出す部分も修正しています。

blender/source/blender/render/intern/source/texture.c (line:916-)
void vtex_create_instance(Tex *tex)
{
...
    void *backend_data = vtex_create_backend(tex);
...
}
Tex構造体にPackFile構造体へのポインタをメンバとして追加したため、.blendファイルを保存するコードでPackFile構造体を保存する処理を追加する必要があります。

blender/source/blender/blenloader/intern/writefile.c (line:1323-)
static void write_textures(WriteData *wd, ListBase *idbase)
{
    Tex *tex;
    PackedFile * pf;
...
            if (tex->vtex_packedfile) {
                pf = tex->vtex_packedfile;
                writestruct(wd, DATA, "PackedFile", 1, pf);
                writedata(wd, DATA, pf->size, pf->data);
            }

...
}
同じように、.blendファイルを読み込むコードでもPackFile構造体を扱うための処理を追加しました。

blender/source/blender/blenloader/intern/readfile.c (line:2529-)
static void direct_link_texture(FileData *fd, Tex *tex)
{
...
    tex->vtex_packedfile = direct_link_packedfile(fd, tex->vtex_packedfile);
...
}
BlenderのFileメニューには「External Data」->「Pack into .blend file」 / 「Unpack into Files...」というように、ファイル全体の外部ファイルに対してすべてパックする、アンパックする機能があります。
pic090804_02.jpg

これを行っているソースコードはpackedFile.cの中にあります。
ファイルのパック機能はイメージファイル以外にもサウンド、フォントなどで利用でき、packedFile.c中にはそれらのすべてのファイルタイプに対して処理を行う関数が3つあります。
今回、その部分にvectex用のコードを追加しました。

blender/source/blender/blenkernel/intern/packedFile.c
line(123-)
int countPackedFiles()
{
...
    Tex *tex;

...
    tex = G.main->tex.first;
    while (tex) {
        if (tex->vtex_packedfile) {
            count++;
        }
        tex = tex->id.next;
    }

...
}

line(228-)
void packAll()
{
...
    Tex *tex;
...
    tex = G.main->tex.first;
    while (tex) {
        if(tex->vtex_file_name[0] != '\0'){
            if (tex->vtex_packedfile == NULL) {
                tex->vtex_packedfile = newPackedFile(tex->vtex_file_name);
            }
        }
        tex = tex->id.next;
    }

...
}

line(628-)
void unpackAll(int how)
{
...
    Tex *tex;
...
    tex = G.main->tex.first;
    while (tex) {
        if (tex->vtex_packedfile) {
            unpackVectex(tex, how);
        }
        tex = tex->id.next;
    }

...
}

最後のunpackAll()関数では、unpackVectex()という関数を使っています。
この関数は自分で用意する必要がありますので、他のファイルタイプの関数を参考にして新しく作成しています。
(line:603-)
int unpackVectex(struct Tex * tex, int how)
{
    char localname[FILE_MAXDIR + FILE_MAX], fi[FILE_MAX];
    char * newname;
    int ret_value = RET_ERROR;
   
    if (tex != NULL) {
        strcpy(localname, tex->vtex_file_name);
        BLI_splitdirstring(localname, fi);
        sprintf(localname, "//textures/%s", fi);
           
        newname = unpackFile(tex->vtex_file_name, localname, tex->vtex_packedfile, how);
        if (newname != NULL) {
            ret_value = RET_OK;
            freePackedFile(tex->vtex_packedfile);
            tex->vtex_packedfile = NULL;
            strcpy(tex->vtex_file_name, newname);
            MEM_freeN(newname);
            /*BKE_image_signal(ima, NULL, IMA_SIGNAL_RELOAD);*/
        }
    }
   
    return(ret_value);
}

これに対応して、関数のプロトタイプ宣言をBKE_packedFile.hに追加しています。

blender/source/blender/blenkernel/BKE_packedFile.h (line:58)
int unpackVectex(struct Tex * tex, int how);

○UseAlpha機能

vectexのSVG描画ライブラリをAGGからQtSVGに変更した時点でキャッシュされるタイルイメージの画像モードをRGB24からRGBA32に変更していました。
しかし、描画されているアルファ値のデータを参照するコードをまったく書いていなかったので、これまではリソースの無駄遣いにしかなっていませんでした。
もともとvectexの作者のmgmalheirosさんはアルファ値を使えるようにする予定だったらしく、base_colorの配列サイズなどはあらかじめ4バイト確保されていたりしています。
私自身は、SVGファイルには通常のビットマップ画像のように独立したアルファチャンネルを自由に操作する機能がないので、むしろグレースケール画像を別に用意してそちらに透明度を管理させる方がより柔軟に使えるように思うのですが、以上のような理由もあってアルファ値を使えるようにコードを修正しました。

基本的には、イメージテクスチャのコードにあるアルファ値関連の部分をそのままコピーアンドペーストするだけの改造です。
ちょっと苦労したのは、Qtの描画機能を使ってアルファ値まで含めて画面を初期化する方法がよくわからなかったことです。最終的には、Qtの機能を使わずにあらかじめ確保した画像領域にデータを書き込んだ状態でQtに渡すようにコードを修正することでなんとかなりました。

ソースコードの修正内容です。
ボタンを追加します。イメージテクスチャにあるボタン「UseAlpha」「CalcAlpha」「NegAlpha」「Premul」をそのままコピーしています。
ボタンで変更されるデータの実体については、UseAlpha、CalcAlpha、NegAlphaについてはTex構造体のメンバ変数imaflagにビットマスクの形で書き込むようになっています。
もう一つのPremulについては、もともとImage構造体のメンバ変数flagにビットマスクとして書き込まれていますが、vectexからはそのデータを使うことはできないので今回新たにTex->vtex_imaflagというメンバ変数を追加しました。

blender/source/blender/src/buttons_shading.c (line:798-)
static void texture_panel_vectex(Tex *tex)
{
...
    uiDefButBitS(block, TOG, TEX_USEALPHA, B_TEXPRV, "UseAlpha",    10, 45, 100, 20, &tex->imaflag, 0, 0, 0, 0, "Click to use Image's alpha channel");
    uiDefButBitS(block, TOG, TEX_CALCALPHA, B_TEXPRV, "CalcAlpha",    110, 45, 100, 20, &tex->imaflag, 0, 0, 0, 0, "Click to calculate an alpha channel based on Image RGB values");
    uiDefButBitS(block, TOG, TEX_NEGALPHA, B_TEXPRV, "NegAlpha",    210, 45, 100, 20, &tex->flag, 0, 0, 0, 0, "Click to invert the alpha values");

     /* fields */
     uiBlockBeginAlign(block);
     uiDefButBitS(block, TOG, IMA_DO_PREMUL, B_TEXPRV, "Premul",        10, 20, 65, 20, &tex->vtex_imaflag, 0, 0, 0, 0, "Toggles premultiplying alpha");
     uiBlockEndAlign(block);

}

blender/source/blender/makesdna/DNA_texture_types.h (line:151-)
typedef struct Tex {
...
    short vtex_imaflag;
...
}
blender/source/blender/vectex/vectex_agg.cpp (line:105-)
EXT unsigned char *vtex_backend_render_tile(void *backend_data, int resolution,
                                       int tile_size, int tile_row, int tile_col,
                                       double base_r, double base_g, double base_b,
                                       int border)
{
...
    /* Initialize buf with col */
    int r, g, b, a;
    r = (int)(255.0 * base_r);
    g = (int)(255.0 * base_g);
    b = (int)(255.0 * base_b);
    a = 0;
   
    for (int i = 0; i < (width * height * 4) - 4; i += 4)
    {
        buf[i] = b;
        buf[i + 1] = g;
        buf[i + 2] = r;
        buf[i + 3] = a;
    }

...
}

texture.cのvectex()関数の最後には、imagetexute.cのimagewrap()、imagewraposa()関数の中にあるアルファ値関連のコードをそっくりコピーしました。
そのコピーしたコードが動くようにするために、vtex_sample()関数の引数に &a、premulという2つの変数を追加しています。

blender/source/blender/render/intern/source/texture.c
(line:1201-)
static int vectex(Tex *tex, float *texvec, float *dxt, float *dyt, int osatex, TexResult *texres)
{
...
    int premul;
...
    if (tex->vtex_imaflag & IMA_DO_PREMUL) {
        premul = 1;
    }
    else {
        premul = 0;
    }

...
    double r, g, b, a;
    double r1, g1, b1, a1;
    float val1, val2, val3;
    float val11, val12, val13;
    float texvec0, texvec1;
    texvec0 = texvec[0];
    texvec1 = texvec[1];
    vtex_sample(instance, &texvec0, &texvec1, power, tex->vtex_interpol, &r, &g, &b, &a, premul);
...
                vtex_sample(instance, &texvec0, &texvec1, power, tex->vtex_interpol, &r1, &g1, &b1, &a1, premul);
...
                vtex_sample(instance, &texvec0, &texvec1, power, tex->vtex_interpol, &r1, &g1, &b1, &a1, premul);
...
        double r2, g2, b2, a2;
        texvec0 = texvec[0];
        texvec1 = texvec[1];
        vtex_sample(instance, &texvec0, &texvec1, power - 1, tex->vtex_interpol, &r2, &g2, &b2, &a2, premul);
...
                vtex_sample(instance, &texvec0, &texvec1, power - 1, tex->vtex_interpol, &r1, &g1, &b1, &a1, premul);
...
                vtex_sample(instance, &texvec0, &texvec1, power - 1, tex->vtex_interpol, &r1, &g1, &b1, &a1, premul);
...
        a = a * max_weight + a2 * min_weight;
...
    /*texres->tin = 1.0;*/
    texres->tr = r;
    texres->tg = g;
    texres->tb = b;
    texres->ta = a;

    /* Alpha channel */
    BRICONTRGB;

    if(tex->imaflag & TEX_USEALPHA) {
        if(tex->imaflag & TEX_CALCALPHA);
        else texres->talpha= 1;
    }

    if(texres->talpha) texres->tin= texres->ta;
    else if(tex->imaflag & TEX_CALCALPHA) {
        texres->ta= texres->tin= MAX3(texres->tr, texres->tg, texres->tb);
    }
    else texres->ta= texres->tin= 1.0;
   
    if(tex->flag & TEX_NEGALPHA) texres->ta= 1.0f-texres->ta;

    float fx;
    /* de-premul, this is being premulled in shade_input_do_shade() */
    if(texres->ta!=1.0f && texres->ta>FLT_EPSILON) {
        fx= 1.0f/texres->ta;
        texres->tr*= fx;
        texres->tg*= fx;
        texres->tb*= fx;
    }


    /*return (TEX_INT | TEX_RGB);*/
    return retval;
}

引数を追加したvtex_sample()関数の中では、premulボタンが押されている場合にRGBの各値にアルファ値を乗算するコードを追加しました。
イメージテクスチャのもともとのソースコードでは、画像ファイルを読み込んだ直後にメモリ上のRGB値をアルファ値を乗算したものにすべて変換しているようですが、vectexでタイル画像のメモリ領域の全RGBデータを変換する必要というのは特になさそうなので、vtex_sample()関数の中のタイル画像からRGBデータを読み出す部分に修正を行うようにしています。
この部分のコードは、source/blender/blenkernel/intern/image.cにあるconverttopremus()関数のコードを参考にしています。
line(1066-)
void vtex_sample(VtexInstance *instance, float *texvec0, float *texvec1, int power, int interp,
            double *pr, double *pg, double *pb, double *pa, int premul)
{
...
            /* Constant color tile */
            if (premul) {
                int val= tile[3];
                *pb = (double)(((tile[0]*val)>>8) / 255.0);
                *pg = (double)(((tile[1]*val)>>8) / 255.0);
                *pr = (double)(((tile[2]*val)>>8) / 255.0);
                *pa = (double)(tile[3] / 255.0);
            }
            else {

                *pb = (double)(tile[0] / 255.0);
                *pg = (double)(tile[1] / 255.0);
                *pr = (double)(tile[2] / 255.0);
                *pa = (double)(tile[3] / 255.0);
             }

...
            if (premul) {
                int val= tile[offset + 3];
                   *pb = (double)(((tile[offset]*val)>>8)     / 255.0);
                *pg = (double)(((tile[offset + 1]*val)>>8) / 255.0);
                *pr = (double)(((tile[offset + 2]*val)>>8) / 255.0);
                *pa = (double)(tile[offset + 3] / 255.0);
            }
            else {

                *pb = (double)(tile[offset]     / 255.0);
                *pg = (double)(tile[offset + 1] / 255.0);
                *pr = (double)(tile[offset + 2] / 255.0);
                *pa = (double)(tile[offset + 3] / 255.0);
            }

...
            if (premul) {
                int val= tile[3];
                *pb = (double)(((tile[0]*val)>>8) / 255.0);
                *pg = (double)(((tile[1]*val)>>8) / 255.0);
                *pr = (double)(((tile[2]*val)>>8) / 255.0);
                *pa = (double)(tile[3] / 255.0);
            }
            else {

                *pb = (double)(tile[0] / 255.0);
                *pg = (double)(tile[1] / 255.0);
                *pr = (double)(tile[2] / 255.0);
                *pa = (double)(tile[3] / 255.0);
             }

...
            if (premul) {
                double val= ((tile[tl + 3] * u_opposite + tile[tr + 3] * u_ratio) * v_opposite +
                       (tile[bl + 3] * u_opposite + tile[br + 3] * u_ratio) * v_ratio) / 255.0;
                *pb = ((tile[tl]     * u_opposite + tile[tr]     * u_ratio) * v_opposite +
                       (tile[bl]     * u_opposite + tile[br]     * u_ratio) * v_ratio) * val / 255.0;   
                *pg = ((tile[tl + 1] * u_opposite + tile[tr + 1] * u_ratio) * v_opposite +
                       (tile[bl + 1] * u_opposite + tile[br + 1] * u_ratio) * v_ratio) * val / 255.0;   
                *pr = ((tile[tl + 2] * u_opposite + tile[tr + 2] * u_ratio) * v_opposite +
                       (tile[bl + 2] * u_opposite + tile[br + 2] * u_ratio) * v_ratio) * val / 255.0;
                *pa = val;
            }

            else {

                *pb = ((tile[tl]     * u_opposite + tile[tr]     * u_ratio) * v_opposite +
                       (tile[bl]     * u_opposite + tile[br]     * u_ratio) * v_ratio) / 255.0;   
                *pg = ((tile[tl + 1] * u_opposite + tile[tr + 1] * u_ratio) * v_opposite +
                       (tile[bl + 1] * u_opposite + tile[br + 1] * u_ratio) * v_ratio) / 255.0;   
                *pr = ((tile[tl + 2] * u_opposite + tile[tr + 2] * u_ratio) * v_opposite +
                       (tile[bl + 2] * u_opposite + tile[br + 2] * u_ratio) * v_ratio) / 255.0;
                *pa = ((tile[tl + 3] * u_opposite + tile[tr + 3] * u_ratio) * v_opposite +
                       (tile[bl + 3] * u_opposite + tile[br + 3] * u_ratio) * v_ratio) / 255.0;
            }

        }
    }
}


○バグフィックス

今回、これらの機能追加の他に2つほどバグフィックスを行っています。
一つは、QtSVGのレンダリングを行う部分でタイル画像にSVG画像のどの部分を描画するかを指定するための変換行列の計算ミスの修正です。
タイル画像は256+1ピクセルというように補間計算用に1ピクセル大きめに作成しています。
QtSVGの変換行列の指定方法はAGGのものとは全く違ったものであったため、変換行列の処理をするコードを自力で書き直しているのですが、そのときに1ピクセル大きくタイル画像が作成されていることを考慮していませんでした。

blender/source/blender/vectex/vectex_agg.cpp
EXT unsigned char *vtex_backend_render_tile(void *backend_data, int resolution,
                                       int tile_size, int tile_row, int tile_col,
                                       double base_r, double base_g, double base_b,
                                       int border)
{
...
    QTransform transform;
    transform.reset();
    if (resolution > width) {
        transform.translate(- tile_size * tile_col, - tile_size * tile_row);
        transform.scale(resolution / tile_size, resolution / tile_size);
    }
    transform.scale((double)tile_size / width, (double)tile_size / height);
    painter.setTransform(transform);
...
}

もう一つは、リンク、アペンド機能を使ったときに、ライブラリ側でvectexのテクスチャを相対パスとして使用していた場合、相対パスから絶対パスへの変換が正しく行われていなかったというものです。
リンクのときの処理は、vectex_agg.cppのvtex_create_backend()関数の中で行っています。
EXT void *vtex_create_backend(Tex *tex)
{
...
        BLI_strncpy(str, filename, sizeof(str));
        if(str[0] != '.') {
            if(tex->id.lib)
                BLI_convertstringcode(str, tex->id.lib->filename);
            else

                BLI_convertstringcode(str, G.sce);
        }
...
}
アペンドのときは、library.cの中のall_local()という関数の中で修正をしています。詳しいことはよく分からないのですが、イメージテクスチャの処理をデバッガで追ってみるとこの部分でパスの処理をしていることがわかりました。
イメージテクスチャの処理を参考にして、texture_fix_relative_path()という関数を新しく作りました。

blender/source/blender/blenkernel/intern/library.c
(line:1036-)
void all_local(Library *lib, int untagged_only)
{
...
                        /* relative file patch */
                        if(GS(id->name)==ID_IM)
                            image_fix_relative_path((Image *)id);
                       
                        if(GS(id->name)==ID_TE)
                            texture_fix_relative_path((Tex *)id);

...
}
(line:990-)
static void texture_fix_relative_path(Tex *tex)
{
    if(tex->id.lib==NULL) return;
    if(strncmp(tex->vtex_file_name, "//", 2)==0) {
        BLI_convertstringcode(tex->vtex_file_name, tex->id.lib->filename);
        BLI_makestringcode(G.sce, tex->vtex_file_name);
    }
}

※この記事を書いている途中、いきなりマウス操作できなくなって、あわてて不完全な状態のままで記事を公開してしまいました。
単純にコードレスマウスの電池が切れただけだったのですが、もし途中までしか書かれていない記事をご覧になられた方がいましたら、ご迷惑をおかけしてすみませんでした。
posted by mato at 23:46| Comment(0) | Blender Vectex | このブログの読者になる | 更新情報をチェックする
×

この広告は1年以上新しい記事の投稿がないブログに表示されております。