2009年07月17日

バンプマッピングとファイルブラウザ機能の追加

vectex内臓版Blenderをいくつかの機能アップを行ったものに更新しました。
実行ファイルはこちらです。
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_patch090717.zip)です。

※追記(2009/07/19) 実行ファイルをバグ修正版と置き換えました。
修正したバグの内容は、vectexは全ピクセル同色のタイル画像がある場合そのタイルを破棄して代わりにその色情報だけを保持しますが、その判別部分に問題があり全ピクセルが完全に同色でないときにも完全同色タイルとして処理してしまうことがありました。

ソースファイルのパッチはこちら(Blender_Qt_vectex_patch090719.zip)です。

Aggを使ったバージョンも同じ内容で更新してあります。
Linux 32bit(Blender_modified_Linux32090611.tar.gz)
Linux 64bit(Blender_modified_Linux64090611.tar.gz)
Windows 32bit(Blender_modified_win32090611.zip)

こちらバージョンのパッチはこちら(Blender_agg_vectex_patch090717.zip)です。

実行ファイルの方は、前回アップロードしたファイルを新しいものと交換していますので、Qt、AGGのどちらともURLは以前のものと同じです。


ここから今回の更新内容の説明になります。
今回はBlenderに組み込んだvectexにバンプマッピングを使用できるように修正を行いました。
さらに、前回追加済みのファイルブラウザの機能に加え、SVGファイル名に相対パスを使用できるように修正を加えています。

○バンプマップ
ずいぶん前になりますが、プラグイン版のvectexを使ってkururuの服のテクスチャをSVGファイルに置き換えてレンダリングを試してみたことがありました。
そのときは、texture pluginのプログラムの内容についてまったく知識がなかったこともあり、バンプマップが機能しない理由について完全に見当違いなことを書いてしまいました。
実は、Blenderのtexture pluginでバンプマッピングを使えるようにするためには、自前で法線を計算してそれをBlenderのレンダラに返すようにプログラムを作る必要があります。
プラグイン版のvectexには、そのようなバンプマッピング用の法線計算を行う処理はどこにも書かれていません。

vectexでは一旦キャッシュ上にSVGをレンダリングしたビットマップ画像を作成するという処理を行っています。
作成された画像は通常のイメージテクスチャと何も変わらないものなので、バンプマッピングに対応するために必要な処理というのは基本的にはBlenderのイメージテクスチャのソースコードをそのままコピーしてくるだけで済むように思えます。

Blenderのイメージテクスチャの計算が行われている関数は、source/blender/render/intern/source/imagetexture.cというファイルにある
  1. imagewrap() (line:105-)
  2. imagewraposa() (line:629-)
この2つの関数となります。

imagewrap()はRenderパネルのOSAボタンがオフのときに使用され、imagewraposa()はOSAボタンがオンのときに使用されます。
pic090717_01.jpg

imagewrap()の中でバンプマッピングに関係する部分は、以下の部分です。(line:207-235)
int imagewrap(Tex *tex, Image *ima, ImBuf *ibuf, float *texvec, TexResult *texres)
{
...
    if(texres->nor) {

        if(tex->imaflag & TEX_NORMALMAP) {
            // qdn: normal from color
            texres->nor[0] = 2.f*(texres->tr - 0.5f);
            texres->nor[1] = 2.f*(0.5f - texres->tg);
            texres->nor[2] = 2.f*(texres->tb - 0.5f);
        }
        else {
            /* bump: take three samples */
            val1= texres->tr+texres->tg+texres->tb;

            if(x<ibuf->x-1) {
                float col[4];
                ibuf_get_color(col, ibuf, x+1, y);
                val2= (col[0]+col[1]+col[2]);
            }
            else val2= val1;

            if(y<ibuf->y-1) {
                float col[4];
                ibuf_get_color(col, ibuf, x, y+1);
                val3= (col[0]+col[1]+col[2]);
            }
            else val3= val1;

            /* do not mix up x and y here! */
            texres->nor[0]= (val1-val2);
            texres->nor[1]= (val1-val3);
        }
...
}

このうち、始めの方の数行はバンプマップではなくて通常ノーマルマップと呼ばれている機能のものです。
バンプマッピングについては、簡単に説明すると

現在描画中のピクセルの色のRGB値を普通に合計してval1とします。
横方向に1ピクセル先の色を取得して、そのRGB値を合計してval2とします。
縦方向に1ピクセル先の色を取得して、そのRGB値を合計してval3とします。
Blenderのレンダラに計算結果を渡すために引数で受け取っているtexresの中の法線データ用の場所に
 X成分:val1-val2
 Y成分:val1-val3
を格納する、というようなものになっています。

imagewraposa()の方は、ミップマップがオンのときとオフのときそれぞれについて必要な処理が違っていて、これに比べるとかなりややこしいものになっています。
ミップマップがオフのときは、先ほどのものとかなり近い内容です。(line:950-967)
int imagewraposa(Tex *tex, Image *ima, ImBuf *ibuf, float *texvec, float *dxt, float *dyt, TexResult *texres)
{
...
        if((tex->imaflag & TEX_INTERPOL)) {

            /* sample 1 pixel minimum */
            if (minx < 0.5f / ibuf->x) minx = 0.5f / ibuf->x;
            if (miny < 0.5f / ibuf->y) miny = 0.5f / ibuf->y;
        }

        if(texres->nor && (tex->imaflag & TEX_NORMALMAP)==0) {
           
            boxsample(ibuf, fx-minx, fy-miny, fx+minx, fy+miny, texres, imaprepeat, imapextend);
            val1= texres->tr+texres->tg+texres->tb;
            boxsample(ibuf, fx-minx+dxt[0], fy-miny+dxt[1], fx+minx+dxt[0], fy+miny+dxt[1], &texr, imaprepeat, imapextend);
            val2= texr.tr + texr.tg + texr.tb;
            boxsample(ibuf, fx-minx+dyt[0], fy-miny+dyt[1], fx+minx+dyt[0], fy+miny+dyt[1], &texr, imaprepeat, imapextend);
            val3= texr.tr + texr.tg + texr.tb;
            /* don't switch x or y! */
            texres->nor[0]= (val1-val2);
            texres->nor[1]= (val1-val3);
        }
...
}

違っているのは、RGB成分を普通に足すのではなくboxsample()という関数で計算していること、val2、val3の色を取得する位置を縦横1ピクセル先ではなくて
 val2: x方向にdxt[0]、y方向にdxt[1]足した場所
 val3: x方向にdyt[0]、y方向にdyt[1]足した場所
となっていることです。

詳しいことはわからないのですが、dxt、dytにはOSAでアンチエイリアスの計算をするためにデータが渡されています。
dxt[0]、dyt[0]には現在描画中の位置に対応するテクスチャのUV座標、dxt[1]、dyt[1]には次に描画する位置に対応するUV座標が入っているようです。
※追記(2009/07/21)  現在描画中の位置のUV座標はtexvecに渡されています。dxt、dytには、UV座標上での現在の描画位置から次の描画位置までの差分のベクトルが入っているのではないかと思います...が、まだはっきりわかっていません。
このデータを使ってvectexでは現在描画中のピクセルの解像度を計算しています。

imagerap()の中の処理の用に縦横1ピクセル先というように指定してval2、val3を計算してしまうと、レンダリング画像内でのテクスチャの向きが変化したときにバンプマップの凹凸が正しく計算されません。
pic090717_02.jpg pic090717_03.jpg

ミップマップがオンの場合、かなり長くなるのでソースコードは省略しますが、異なる解像度の画像を現在レンダリングしている位置に応じた重み付けをして重ねるという処理をします。
さらにbumpscapeという値を計算し、オブジェクトがカメラから離れてテクスチャの解像度が低くなるほどバンプマップの凹凸の深さが小さくなるようにしています。
pic090717_04.jpg pic090717_05.jpg

以上のような内容のソースコードをvectex()関数の中に追加しました。
vectexではBlenderのイメージテクスチャで「MipMap」や「interpol」を指定したときに使用されるミップマップ処理や補完処理とは全く別の独自の計算で同様の処理を行っていますので、追加したコードもそれに合わせていろいろと修正をしています。
この部分は、特に参考にできるコードとかもないので自分で考えて書いているため、もしかすると色々と勘違いしたりしてバグがあったりするかもしれません。

とりあえず、自分でテストした範囲ではなんとか動いているようです。
参考までに、2400x2400ピクセルのpng画像を使った場合とvectexを使った場合の画像を比較できるように並べておきます。
pic090717_06.jpg pic090717_07.jpg

pic090717_08.jpg pic090717_09.jpg

○ファイルブラウザ
vectexを使っているとSVGファイルの指定をするのにフルパスでキーボードから名前を入力しなければならず、かなり面倒に感じました。
できるだけ早くなんとかしたかったこともあって、前回の更新のときにファイルブラウザの機能はすでに搭載済みです。
前回はこの機能について書けなかったので、今回詳しく書きたいと思います。

まず、ファイルブラウザを表示させるためのボタンが必要です。
そのボタンは、
source/blender/src/buttons_shading.cの中のvectexパネルの設定を行っているtexture_panel_vectex()関数の中で追加します。(line:796)
static void texture_panel_vectex(Tex *tex)
{
...
    uiDefIconBut(block, BUT, B_LOADVECTEX, ICON_FILESEL,    160, 100, 20, 20, 0, 0, 0, 0, 0, "Select the directory/name for saving animations");
...
}

この記述自体は、Outputパネルのアニメーションファイルの保存場所を指定するボタンの記述を参考にしています。
pic090717_11.jpg
(ツールティップの文字列がそのままになっていました。次の更新のときに直したいと思います...)

この記述は、ボタンが押されたときに「B_LOADVECTEX」というメッセージが送信されるように、uiDefIconBut()関数の3つめの引数を指定してあります。

このB_LOADVECTEXというのは番号を割り当てられているマクロ記述の定数で、
source/blender/include/butspace.h
というファイルの中に記述を追加しています。(line:256)
...
#define B_BANDCOL        1319
#define B_LOADTEXIMA1    1320
#define B_TEXPRV        1321
#define B_LOADVECTEX    1322
...
B_LOADVECTEXというメッセージを受け取って処理をするのは、
source/blender/src/buttons_shading.c の中の do_texbuts() (line:279-)
という関数です。

この中にプラグイン選択ボタンの処理があるので、それを参考に次のようにコードを追加しています。
void do_texbuts(unsigned short event)
{
...
    case B_LOADVECTEX:
        sa= closest_bigger_area();
        areawinset(sa->win);
       
        if(tex->vtex_instance_data) strcpy(str, tex->vtex_file_name);
#ifdef _WIN32
        else {
            if (strcmp (U.textudir, "/") == 0)
                strcpy(str, G.sce);
            else
                strcpy(str, U.textudir);
        }
#else
        else strcpy(str, U.textudir);
#endif
       
        activate_fileselect_args(FILE_SPECIAL, "SELECT SVG FILE", str, load_vectex_file, tex, NULL);

break;

...

}

ここで、最初の部分はファイルブラウザを開くためのウィンドウ上の位置を適当に確保しているようです。
最後のactive_fileselect_args()という関数が実際にファイルブラウザを開く処理をしているようです。
この関数では、ファイルブラウザで選択したファイルに対してどのように処理をするべきかを指示するため、4つ目の引数でload_vectex_file()という関数の名前を指定しています。
このような関数は元々存在していなくて、自分で追加する必要があります。
間の部分は、active_fileselect_args()関数からファイル名を受け取るための文字列に、デフォルト値としてあらかじめ文字列を設定しておくためのものですが、わざわざ書かなくてもとくに問題ないようなものです。
ただし、この文字列がまったく初期化されていないと、意味不明な文字列がファイルブラウザに表示されてしまいます。

active_fileselect_args()に渡したload_vectex_file()という関数です。(line:238-)
static void load_vectex_file(char *str, void *tex_v, void *unused)    /* called from fileselect */
{
    Tex *tex= tex_v;
   
    if(tex->type!=TEX_VECTEX) return;
   
    if(tex->vtex_instance_data)     strcpy(tex->vtex_file_name, str);   
   
    allqueue(REDRAWBUTSSHADING, 0);
    BIF_preview_changed(ID_TE);
}
この関数に引数で渡されるtex_vはsource/blender/makesdna/DNA_texture_types.hに定義されているTex構造体ですので、その中のtypeでTEX_VECTEXがセットされているか、vtex_instance_dataの実体が存在するかを確認した上で、vtex_file_nameにファイルブラウザから渡されたファイル名をコピーしています。

Tex構造体の中のvtex_file_nameが設定された後の処理は、普通にvectexパネルで文字列をキーボード入力で指定したときと同じです。
source/rblender/render/intern/source/texture.cのvectex()関数の中で常に文字列が変わったかどうかをチェックしています。

○拡張子の色別強調表示
ファイルブラウザが開いたとき、.blendファイルやpythonスクリプト、画像ファイルなど特定の種類のファイルに対して小さな四角形がファイル名の横に表示され、その色でファイルの種類を判別しやすいようになっています。

デフォルトのBlenderではSVGファイルについてはこの強調表示がされるようになっていません。
これでは色々と不便なのでSVGファイルにも小さな四角形を表示するようにしました。
pic090717_12.jpg

ただし、jpegファイルやPNGファイルと同じ画像ファイルにしてしまうと、vectexパネルから間違ってjpegファイルを開こうとしてしまったり、逆にイメージテクスチャを選択するときにSVGファイルを開いてしまったりすることになります。

そこで、SVGファイルのために新しいファイルタイプを作ってしまいました。
source/blender/makesdna/DNA_space_type.h (line:491)
...
#define SOUNDFILE            256
#define TEXTFILE            512
#define MOVIEFILE_ICON        1024 /* movie file that preview can't load */
#define FOLDERFILE            2048 /* represents folders for filtering */
#define VECTORIMAGEFILE        4096
...
ファイルブラウザで表示されるファイル名から拡張子を調べる関数がありますので、そこにSVGファイルのための処理を追加します。
source/blender/src/filesel.c (line:334-)
void test_flags_file(SpaceFile *sfile)
{
...
                else if(BLI_testextensie(file->relname, ".svg")) {
                    file->flags |= VECTORIMAGEFILE;

                }   
            }
        }
    }   
}
そして、同じファイルの中のprint_line()という関数に、VECTORIMAGEFILEに対応した新しい色の四角形をファイルブラウザに表示させるための記述を追加します。

source/blender/src/filesel.c (line:731-)
static void print_line(SpaceFile *sfile, struct direntry *files, int x, int y)
{
...
    else if(files->flags & VECTORIMAGEFILE) {
        cpack(0x7744dd);
        glRects(x-14,  y,  x-8,  y+7);

    }
...
}
cpack()という関数は、リトルエンディアン、ビッグエンディアンの違いに関係なくRGB値を文字列で指定するためのもので、
cpack(0x7744dd)
という指定は、RGB(221,68,119)という値となり、青より赤に近い薄紫色という感じになります。
pic090717_13.jpg

○相対パスの指定
BlenderではLinuxなどで通常使用される「.」(ドット)を指定した相対パスとは別の「//」という文字列をパスの先頭に指定することで、相対パスを表す仕組みになっているようです。

ファイルブラウザで「relative paths」というボタンを押してファイルを指定すると、この形式で相対パスを指定した文字列が返ってきます。
SVGファイルを開く関数でこの形式に対応するための処理を追加しました。
source/blender/vectex/vectex.cpp (line:58-)
EXT void *vtex_create_backend(char *filename)
{
...
    BLI_strncpy(str, filename, sizeof(str));
    if(str[0] != '.')
        BLI_convertstringcode(str, G.sce);

...
}
BLI_convertstringcode()という関数に絶対パスの入った文字列を渡すと、相対パスに変換されて戻ってきます。
ただ、この関数にLinuxの通常の「.」で始まる相対パスを渡すと問題があるようなので、その場合は処理をスキップするようにしています。
posted by mato at 23:02| Comment(0) | Blender Vectex | このブログの読者になる | 更新情報をチェックする

2009年07月09日

vectexのバックエンドレンダラをQtSVGに変更する

SVG描画ライブラリをQtSVGに変更したvectex内臓版のBlederを作成しました。

実行ファイルはこちらです。
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_src090709.zip)です。

Windows版はPython2.5、Linux版はPython2.6を使用しています。
以前公開したものはBlender2.49をベースとしていましたが、その後バグフィックスされたBlender2.49aがリリースされたので、今回はそちらの2.49aをベースとしています。
ついでにvectexのパネルにSVGファイルを選択する際にファイルブラウザを使用するためのボタンを付けました。
pic090709_01.jpg pic090709_02.jpg

QtSVGを使用しているので、通常のSVGファイルならほとんどが読み込めるようになっているのではないかと思います。
描画の速度もAGGのものよりは若干遅くなるようですが、それほど気にならない範囲だと思います。

6/12の記事で公開したオリジナルのAGG、Expatを使用して作成したvectex内臓Blenderの方も、Blender2.49aをベースとしたものに更新しました。
ファイルサイズがかなり大きいので、古いものは破棄して新しいものに差し替えています。

ダウンロード可能なソースコードのファイルは、これまではフォルダ構成を維持して変更のあったファイルをまるごとコピーしていました。
ファイル数が増えてくるとこれを用意するのはかなり面倒なので、今回からはdiffで作成した差分ファイル(patchファイル)のみにさせていただきます。

Blender2.49aのソースコードのフォルダを「/home/mato/Desktop/blender」に置いているとすると、
cd /home/mato/Desktop
patch -p0 < changes.text
のようにコマンドを実行すると、修正版のソースコードを作成できると思います。
Blenderの公式のパッチとは若干適用の仕方(patchコマンドを実行する場所)が違っていますのでご注意ください。(というか、同じように適用できなくてすみません...)
pic090709_03.jpg

ということで、ここからソースコードの修正内容について説明していきたいと思います。

AGG、ExpatのときはLinux版は両方、Windows版はAGGのみを静的リンクライブラリとして使用していましたが、QtSVGについてはLinux、Windowsともに動的リンクのライブラリで使用しています。
このため、Windows版にはBlender実行ファイルと同じディレクトリにQtのDLLファイルを入れてあります。
Linux版の場合は、使用しているOSのパッケージ管理ソフト等でQtをインストールする必要があります。使用しているのはQtのうち、QtGUI、QtSVG、QtCOERの3つです。

○ライブラリの設定
Blenderにvectexを組み込む際に、まずライブラリを組み込みました。
以前組み込んだAGG、Expatは不要になり、また、動的リンクによってライブラリを使用するため、必要な作業はScons用の設定ファイルの修正だけとなります。

・config/linux2-config.py
...
#for vectex SVG texture
WITH_BF_VECTEX = True

BF_QT = '/usr'
BF_QT_INC = '${BF_QT}/share/qt4/mkspecs/linux-g++ ${BF_QT}/include/qt4/QtCore ${BF_QT}/include/qt4/QtGui ${BF_QT}//include/qt4/QtSvg ${BF_QT}/include/qt4'
BF_QT_LIB = 'QtSvg QtGui QtCore'
BF_QT_LIBPATH = '${BF_QT}/lib'

...

・config/win32-mingw-config.py
...
#for vectex SVG texture
WITH_BF_VECTEX = True

BF_QT = LIBDIR + '/qt'
BF_QT_INC = '${BF_QT}/mkspecs/win32-g++ ${BF_QT}/include/QtCore ${BF_QT}/include/QtGui ${BF_QT}/include/QtSvg ${BF_QT}/include\ActiveQt ${BF_QT}/include'
BF_QT_LIB = 'QtSvg4 QtGui4 QtCore4'
BF_QT_LIBPATH = '${BF_QT}/lib'

...

QtはWindowsとLinuxとでライブラリの名前や場所が微妙に違っているようです。
それに合わせて若干細部が変わっていますが、基本的にはほとんど書いている内容は同じです。

・tools/Blender.py
...
    if lenv['WITH_BF_VECTEX']:
        libincs += Split(lenv['BF_QT_LIBPATH'])

...
...
    if lenv['WITH_BF_VECTEX']:
        syslibs += Split(lenv['BF_QT_LIB'])

...
・tools/btools.py
...
            'WITH_BF_VECTEX',
            'BF_QT', 'BF_QT_INC', 'BF_QT_LIB', 'BF_QT_LIBPATH',

...
...
        (BoolVariable('WITH_BF_VECTEX', 'Use vectex if tue', True)),

        ('BF_QT', 'Qt base path', ''),
        ('BF_QT_INC', 'Qt include path', ''),
        ('BF_QT_LIB', 'Qt library', ''),
        ('BF_QT_LIBPATH', 'Qt library path', ''),

...
どちらのファイルもlinux2/win32-mingw-config.pyで設定したQtの設定内容を追加しています。
WITH_BF_VECTEXというオプションをオンにしているときにだけQtライブラリを使用するようにしていますが、実はまだオフにしたときにvectexの機能を無効にするようにC、C++のソースコードを修正していません。
ただ、このソースコードはvectexを使用する場合以外には意味がまったくないので、オフにしたときにvectexのソースがコンパイルされないようにする必要はあまりなさそうです。

・source/blender/vectex/SConscript
以前の記事でAGG、Expatを組み込んだときは、AGGとExpatの他にAGGのSVG描画を行うファイルの部分もexternディレクトリに置いて、通常のライブラリと同じ扱いにしてしまいましたが、その後考え直してsource/blenderの中にvectexディレクトリを作成してそこに置くことにしました。
今回はAGG、Expatは使用しないのでこのディレクトリに置くのはSConscript、vectex.h、agg_vectex.cppの3つのファイルだけになります。
#!/usr/bin/python
import sys
import os

Import('env')

sources = env.Glob('*.cpp')
incs = '. #/intern/guardedalloc ../blenlib/ ../blenkernel ../makesdna'
incs += ' ' + env['BF_QT_INC']

defs = '-DQT_NO_DEBUG -DQT_SVG_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_SHARED'
cflags = '-pipe -O2 -Wall -W -D_REENTRANT'

if env['OURPLATFORM']=='linux2':
    cflags='-pipe -O2 -Wall -W -D_REENTRANT -pthread'

env.BlenderLib ( libname='bf_vectex',
        sources=sources, includes=Split(incs),
        defines=Split(defs),
        libtype='core', priority=45, compileflags = Split(cflags))
中身は、通常のライブラリとしてexternディレクトリに置いていたときとそれほど違いませんが、Qtのコンパイル用にコンパイルオプションを指定してあります。
QMakeを使用して作成したMakefileを調べてみると、コンパイルオプションの内容はLinuxとWindowsとでかなり違っているようですが、Windowsのものは中身がかなり複雑になっていてよくわからなかったので、とりあえずLinuxとほとんど同じままで済ませてしまいました。
とりあえず普通に動いているようですが、もしかすると後で修正することになるかもしれません。

○vectexのSVG描画ライブラリに関連するソースファイル

・source/blender/vectex/vectex.h
Qtを使用することによる修正ではありませんが、デバッグ用メッセージの表示に関連する部分をBlenderの仕様に合わせるために少し修正を加えました。
...
#include "BKE_global.h"
...
...
#define MSG0(F)         if(G.f & G_DEBUG) printf(F);
#define MSG1(F,A)       if(G.f & G_DEBUG) printf(F, (A));
#define MSG2(F,A,B)     if(G.f & G_DEBUG) printf(F, (A), (B));
#define MSG3(F,A,B,C)   if(G.f & G_DEBUG) printf(F, (A), (B), (C));
#define MSG4(F,A,B,C,D) if(G.f & G_DEBUG) printf(F, (A), (B), (C), (D));
...
Blenderの実行時にコマンドラインの引数としてデバッグオプション「-d」を指定することができます。
このソースコードでは、グローバル変数を調べることでこのオプションが有効かどうかを判定して、有効ならprintf()関数を実行するようなマクロの記述を行っています。

・source/blender/vectex/vectex_agg.cpp
この部分が今回のハイライトともいうべき部分です。
このファイルにはvectexのソースコードのうち、外部のSVG描画ライブラリを使用するコードがすべてまとめられています。
といっても、中身は一つの構造体と3つの関数の記述があるだけです。
...
#include <QtGui>
#include <QtSvg>
...
#include "BKE_global.h"
...
QtSVGを使用するためにヘッダファイルをインクルードします。
ここでは、2つのライブラリ用のヘッダファイルだけが必要になります。
その他に、vectex.hで修正したデバッグ用のコードを使用するためグローバル変数のヘッダをインクルードしています。

これもQtを使用することとは無関係ですが、以前vectexをBlenderに組み込んだとき、source/blender/render/intern/source/texture.cの中の関数は名前の重複を避けるために頭に「vtex_」を付けるという修正をしていましたが、こちらのvectex_agg.cppの中の関数は修正するのを忘れていました。
この状態で、Blenderに内臓したvectexの他にテクスチャプラグインのvectexを同じシーンファイル上で使用すると、プラグインのvectexから内臓vectexのコードが呼ばれてしまうという恐ろしい状況になっていました。
これを避けるため、vectex_agg.cppの中の構造体、関数の名前をオリジナルのvectexとは違う名前に変更しています。
typedef struct VtexBackend
{
    QSvgRenderer svg_render;
    double xmin;
    double ymin;
    double xmax;
    double ymax;
} VtexBackend;
AGGを使用した元のvectexでは、SVGファイルをXMLパーサで読み込んだSVGデータの本体をagg::svg::path_rendererという形で保持していました。同じものはQtSVGではQSvgRendererというクラスが対応しています。
EXT void *vtex_create_backend(char *filename)
{
    MSG0("[vtex_create_backend]\n");
   
    VtexBackend *backend = new VtexBackend();

    QString svgfname = filename;
    if (backend->svg_render.load(svgfname) != true)

    {
        fprintf (stderr, "Cannot open input file %s\n", filename);

        delete backend;
        return (NULL);
    };
   
    QSize doc_size = backend->svg_render.defaultSize();

   
    MSG2("- dimension x:%d y:%d\n", doc_size.width(), doc_size.height());

    backend->xmin = 0;
    backend->ymin = 0;
    backend->xmax = doc_size.width();
    backend->ymax = doc_size.height();

   
    return (backend);
}

この関数は、先ほど修正した構造体を作成し、引数で受け取ったSVGファイル名を使ってQSvgRendererのインスタンスを作成します。
Qtのサンプルファイルを見ると、try{}、catch{}などが見当たらないので、とりあえず戻り値を使って成否の判定をしました。
そして、読み込んだSVGファイルの中で指定されているドキュメントサイズを調べ、その値をVtexBackendの中に保存しています。
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)
{
    char buff1[256];
    int n;

    VtexBackend *backend = (VtexBackend *) backend_data;

    int width  = tile_size + border;
    int height = tile_size + border;
   
    unsigned char          *buf = (unsigned char *)MEM_mallocN(width * height * 4, "vectex:tile");

    double size = MAX2(backend->xmax - backend->xmin, backend->ymax - backend->ymin);

     QImage image(buf, width, height, QImage::Format_ARGB32);
    
    QPainter painter(&image);
    painter.setRenderHint(QPainter::Antialiasing);

   
    QColor col;
    col.setRgbF(base_r, base_g, base_b, 1.0);
    painter.fillRect(image.rect(), col);
   
    if (resolution > width) {
        QTransform transform;
        transform.reset();
        transform.translate(- tile_size * tile_col, - tile_size * tile_row);
        transform.scale(resolution / tile_size, resolution / tile_size);
        painter.setTransform(transform);
    }
   
    backend->svg_render.render(&painter);

       
    if(G.f & G_DEBUG) {
        n = sprintf(buff1, "/tmp/test%d_%d_%d.png", resolution, tile_col, tile_row);
        QString fname = buff1;
         image.save(fname, "PNG", -1);
     }

    
    return (buf);
}
元のファイルには古いコードをコメントアウトしたものがそのままになっていたりしていますが、ここではそれらは省いています。
ここではまず、MEM_mallocN()というBlender内部の関数を使用して、キャッシュに保存されるテクスチャ画像用のメモリを作成しています。
オリジナルのvectexでは1ピクセルあたりRGBの3Bytのみを確保するようになっていますが、今回はRGBAというようにアルファ値の分を加えた4Byteを確保しています。
この変更のため、texture.cの中でこのメモリ領域から作成されたテクスチャの色を読み取る処理の部分にも、同じように変更が必要になります。

Qtでは、画像処理を行う機能をQPainterというクラスが行います。
QPainterクラスを作成する際には、どの画像データに対して処理を行うのかを指定する必要があります。
そして、画像データ本体を保持するクラスがQImageです。
QImageのインスタンスを作成するには既存のJpeg、PNGなどの画像ファイルを指定したり、単純にRGBA32などのフォーマットと画像サイズのみを指定して作成することもできますが、今回は作成済みのメモリ領域を指定することで*bufというポインタからも同じメモリ領域にアクセスできるようにしています。

その後の処理は、アンチエイリアスの指定、引数で受け取った色での画像のクリア、変換行列を使用したスケール、オフセットの変更と続きます。
変換行列の部分は、AGGの同等の処理とは仕組みがかなり違うため引数をどう指定するべきなのか、かなり戸惑いました。
具体的には、AGGではSVG画像が描画される描画先の方を変換する方式なのですが、Qtでは描画元の方を変換するようになっています。

最後に、VtexBackend構造体に保持しているQSvgRendererに作成したQImageへとSVG描画を行って、画像イメージのメモリ領域を指すポインタbufを関数の戻り値として返しています。

一番最後の部分にデバッグ用のコードとして、作成したタイル画像をPNG画像として保存する機能を付けてみました。
ただし、現在のところ、出力先を/tmp/に固定しているためLinux上でしか動作しないと思います。
pic090709_04.jpg

vectex_destroy_backend()関数内には変更はありません(名前が変更されていますが、ソースコードは省略します)。

○Blender本体のソースコードの修正
vectexを組み込んだときに加えた変更を前提として、それに対してさらに修正を行った内容を説明します。

・source/blender/render/intern/source/texture.c
int vtex_get_tile(VtexInstance *instance, int x, int y, int power)
{
...
    BLI_lock_thread(LOCK_IMAGE);
...
    }
    BLI_unlock_thread(LOCK_IMAGE);

    return (index);
}
Qtの使用には直接関係ありませんが、マルチスレッドレンダリングに対応するために追加したスレッドロック関数の位置を若干変更しています。
以前は、vtex_get_tile()を呼び出しているvtex_sample()関数の中に置いていましたが、スレッドロックの影響を受ける部分はなるべく少ない方がいいと考え直して範囲を狭めてみました。
さらに突き詰めるとvtex_backend_render_tile()関数の中のメモリ確保の部分をロックすれば、レンダリング時にフリーズすることが回避できるようになるようですが、その場合同じ部分のテクスチャを複数のスレッドが重複して作成してしまうという別の問題が起こってしまいました。
重複を避けるための処理を記述すればいいわけですが、それはもう少しvectexのソースコードの仕組みを理解してからの方が良さそうに思います。
void vtex_sample(VtexInstance *instance, float *texvec, int power, int interp,
            double *pr, double *pg, double *pb)
{
...

            /* Calculate pixel offset taking the border into account */
            int offset = (y * (tile_size + VTEX_TILE_BORDER) + x) * 4;
               *pb = (double)(tile[offset]     / 255.0);
            *pg = (double)(tile[offset + 1] / 255.0);
            *pr = (double)(tile[offset + 2] / 255.0);
...
   
            /* Calculate pixel offset taking the border into account */
            int tl = (y      * (tile_size + VTEX_TILE_BORDER) + x)      * 4; /* top left */
            int tr = (y      * (tile_size + VTEX_TILE_BORDER) + x_next) * 4; /* top right */
            int bl = (y_next * (tile_size + VTEX_TILE_BORDER) + x)      * 4; /* bottom left */
            int br = (y_next * (tile_size + VTEX_TILE_BORDER) + x_next) * 4; /* bottom right */
            *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;
        }
    }
}

キャッシュされるタイルテクスチャの画像用メモリ領域は、AGGを使用している元のvectexではRGBの3Byteですが、それをRGBAの4Byteに拡張しています。
現在はまだ何も処理を追加していませんが、後でアルファ値を使用できるように機能を拡張したいと思っています。
そして、この変更とは直接関係ないのかもしれませんが、AGGではメモリ上の色の並びがRGBの順になっていますが、QtではBGRの順番になっています。
書き込みはQtの関数が勝手に行っているので、読み出しを行うこの部分で整合性が取れるように調整しています。

その他に、vectex.h内の構造体、関数名を変更していますので、それらを呼び出している部分で名前を修正していますが省略します。

・source/creator/creator.c -> creator.cpp
...
#include <QtGui>
...
#ifdef __cplusplus
#define EXT extern "C"
#else
#define EXT
#endif

...
extern "C" void    winlay_get_screensize(int *width_r, int *height_r);

/* for the callbacks: */

EXT int pluginapi_force_ref(void);  /* from blenpluginapi:pluginapi.c */
...
int main(int argc, char **argv)
{
    int a, i, stax, stay, sizx, sizy, scr_init = 0;
    SYS_SystemHandle syshandle;
   
    QApplication app(argc, argv, QApplication::Tty);
...
}
Qtを使用するために拡張子を「.c」から「.cpp」に修正しています。
ソースコードの中では、まず、QtGuiのヘッダファイルのインクルードを追加しています。
最後の部分は、Qtを使用するプログラムに必要な処理です。
実際に何をどうするために必要なのか詳しいことは調べていないのですが、Qtで使用している参照カウンタの管理などのために必要なのかもしれません。
中間にある部分は、creator.cの拡張子を「.cpp」に変更したことの副作用という感じのものです。
creator.cppの中で使用している別のファイルの関数のプロトタイプ宣言すべてに対して、C++のファイルから利用される際に「extern "C"」のマクロが展開されるように、このような記述を多数のファイルに追加しなければならなくなってしまいました。
このような変更を以下のファイルに対して行っています。

source/blender/blendkernel/BKE_bad_lebel_calls.h, BKE_node.h, BKE_scene.h
source/blender/imbuf/IMB_imbuf.h
source/include/BIF_drawscene.h, BIF_editsound.h, BIF_toolbox.h, BIF_usilender.h, playanim_ext.h
source/blender/render/extern/include/RE_pipeline.h
source/blender/src/winlay.h

このようなことをしてしまうと、Blenderのソースコードを保守する上で非常に不都合となりますので、もし可能であればなんとかして回避したいところですが、今のところどうすればいいのかよくわかりません。

・source/creator/SConscript
creator.cの変更に合わせてソースファイル名の修正、Qtのライブラリを使用するためにヘッダファイルの位置の指定を追加しています。
...
sources = 'creator.cpp'
...
incs += ' ' + env['BF_QT_INC']
...
これ以外に、今回、vectexパネルにファイルセレクタを使用するためのボタンを追加している関係で、修正を行っているファイルがいくつかあります。
SVGファイルの指定に関しては、現在相対パスによるファイルの指定ができないという問題がありますので、それを修正するときにまとめてとりあげたいと思います。
posted by mato at 03:25| Comment(0) | Blender Vectex | このブログの読者になる | 更新情報をチェックする

2009年07月05日

オープンソースのSVGライブラリ

前回、Blenderにvectexの機能を組み込んで、ようやくvectexの機能自体を改造する準備ができました。
ここで、使用するSVGファイルを指定する際にファイルセレクタを使用するようにしたり、UVマッピング以外のマッピングが使えない制限を改善したりと色々やりたいことがあるのですが、なによりもまず普通のSVGファイルならどんなものでも使用できるようにSVG描画機能そのものを改善したいと思います。

現在のvectexではフォントを使った文字、グラデーションやぼかしなど色々とSVGの仕様に対応していないため、あらかじめ文字をパスに変換しておくなどの対応をしておく必要があります。
これではかなり使いにくいので、できればInkscapeなどを使って作成したイラストのデータをそのまま利用できるようにしたいところです。

そこで次の目標として、vectexのSVG描画エンジンをAGG、Expatを使ったものからオープンソースの別のSVGライブラリに変更する、という改造を進めていきたいと思います。

使用するSVGライブラリには、
・vectexが使用しているメモリ上の画像領域にSVG画像を直接描画できる
・SVG画像の描画の際にオフセット、スケーリングをAGGと同じように変換行列で指定できる
というようなことが必要になりそうです。

調べてみたところ、オープンソースのSVGライブラリとしては、
・GNOMEプロジェクトのlibrsvg
・KDEのKSVG(KHTML)
・QtのQtSVG
・WXWindowのwxSVG
などが見つかりました。
これ以外に、Inkscapeの内部機能を調べて描画する部分だけを使わせてもらうということも可能ではあると思いますが、他のプログラムから利用されることを前提にして作られているライブラリと比べると、色々と大変そうです。

私自身が普段GNOME DESKTOPを使用していることもあり、とりあえずlibrsvgを第一候補として選びました。

○librsvg
調べてみると、librsvgはCairoという2Dグラフィックスライブラリと組み合わせて使うことで、vectexがAGGを使っているのと同じように独自に確保したメモリ領域を使用することができるようです。
また、Cairoの機能として、描画領域のオフセットやスケーリングを指定するような変換行列を使うこともできるようです。

librsvgをvectexに組み込む前に、テストとして簡単なサンプルプログラムを作ってみます。
SVGファイルを開いて、PNGファイルとして保存するというものです。

vectexのコードとして使用することを考えて、mallocを使って自前で確保した画像用メモリを使用したり、変換行列を使ってスケールの変更をしたりしています。

librsvg_test.c
#include <librsvg/rsvg.h>
#include <librsvg/rsvg-cairo.h>
#include <cairo-svg.h>
#include <stdio.h>
#include <stdlib.h>

int
main (int argc, char *argv[])
{
    cairo_t *cr3;
    cairo_surface_t *image;
   
    RsvgHandle *r_svg;
    RsvgError rs_err;
    RsvgDimensionData svgdim;
   
    cairo_matrix_t matrix;
   
    int width = 256;
    int height = 256;
   
    unsigned char          *buf = (unsigned char *)malloc(width * height * 4);
   
    rsvg_init();
    r_svg = rsvg_handle_new_from_file("/home/mato/Desktop/sample/tiger.svg", NULL);
    rsvg_handle_get_dimensions(r_svg,&svgdim);

    image = cairo_image_surface_create_for_data(buf, CAIRO_FORMAT_ARGB32, width, height, width * 4);
    cr3 = cairo_create (image);
   
    cairo_set_source_rgb (cr3, 1.0, 0.5, 0.0);
    cairo_paint (cr3);
   
    cairo_matrix_init_identity(&matrix);
    cairo_matrix_scale(&matrix, 0.5, 0.5);
    cairo_transform(cr3, &matrix);
   
    rsvg_handle_render_cairo(r_svg, cr3);
   
    cairo_surface_write_to_png (image, "hello.png");

    rsvg_handle_free(r_svg);
    rsvg_term();

    cairo_destroy (cr3);
    cairo_surface_destroy (image);

    free(buf);
   
    return 0;
}

SConscript
env = Environment()
env.MergeFlags('!pkg-config --cflags --libs librsvg-2.0')
env.Program('librsvg_test.c')

このサンプルプログラムをコンパイルするためのScons用のSConscriptファイルでは、librsvgのコンパイルオプションを指定するためにpkg-configという機能を使っています。
librsvgはライブラリの依存関係がかなり面倒なのですが、このツールを使うとMakefileやSConsvriptがかなりすっきりとしたものになります。

実際にコンパイルするときには必要な様々なライブラリのインクルードファイルの場所、ライブラリの場所、ライブラリ名などが展開されます。

Blenderにライブラリを組み込む際には、このサンプルファイルのコンパイル時に表示されるメッセージやライブラリ自体のMakefileなどを参考にしてSconsの設定ファイルを修正していくことになります。

ちなみに、このサンプルプログラムはソースコード中でSVGファイルの名前を指定していますので、実際にコンパイル、実行する場合にはこの部分を適当に書き換える必要があります。
また、librsvgは当然として、それ以外にもcairo、GLIB、GKT+などいくつかのライブラリの開発用パッケージをインストールしておく必要があります。

サンプルプログラムが正常に動作することが確認できて、librsvgがvectexのバックエンドとして使えそうな感触をつかむことができました。
pic090704_01.jpg

さっそく、Blenderに組み込んでみました...が、色々あってこのlibrsvgを使ってvectexを改造する計画は保留することにしました。
実は、librsvgは絵の再現性については文句なしなのですが、vectexで使うにはかなり致命的な問題があることに、後から気が付きました。

一応、AGG、Expatを置き換える形でlibrsvgをBlenderに組み込んで動作させるところまで改造を進めてあります。
現状でも、元のvectexでは読み込めなかったテキストやグラデーションなどを使ったSVGファイルを読み込んで、Blenderのレンダリング画像の中に表示させることができるようになっています。
pic090704_07.jpg

問題というのは、SVG画像の細部をある程度以上に拡大する状況で、拡大率に応じてテクスチャの作成に時間がかかるようになり、ある限界以上の解像度になるとプログラムが停止してしまうこともあるというものです。
レンダリングのテストでAGG、Expatのバージョンで1分以内にレンダリングできたものが、librsvgを使った場合20分以上かかるということもありました。

また、ライブラリの依存関係が複雑なこともあって、Blenderのビルドシステムへの組み込みが今ひとつうまくいっていないらしく、動作が非常に不安定な感じになっています。

AGG、Expatを使った元のvectexでは、SVG画像の一部を拡大表示する際に、どこをどれだけ拡大しているかという描画領域の違いによって、描画時間が極端に長くなるようなことは感じられません。
しかし、librsvgのサンプルSVGビュワーやMozilla FirefoxなどでSVGファイルを表示して、画像の一部をどんどんと拡大していくと、拡大率が増えるにしたがって処理速度が低下し、ある限界を超えるとSVGビュワーでは描画不能となり、Firefoxでは拡大ができなくなります。
pic090704_02.jpg pic090704_03.jpg

Adobeの作成したInternet Explore用のSVGプラグインでも同じようなことが起こるようですので、これはlibrsvgに何か問題があるということではなくて、描画品質を最大限に高めるようなSVG描画エンジンを目指すと、こういう形にならざるを得ないという仕様のようなものなのだと思います。

結局、このような仕様のライブラリは、vectexに組み込んでレンダリングの状況に応じて最適な解像度のテクスチャを作成するという用途には、そもそも初めから向いていなかったということが分かりました。

○QtSVG
ということで、次の候補を探しているうちに、QtSVGというものがあることを知りました。
QtというのはLinuxのデスクトップ環境KDEで使われているプログラミングツールキットです。
このQtのサンプルの中にSVGビュワーが含まれています。Qt4のExampleの中のPaintingというカテゴリに入っています。
pic090704_04.jpg

このサンプルプログラムを動かしてみて、拡大表示の動作が非常に高速なことに驚きました。
起動時に既定で読み込まれるSVGファイルがいきなりアニメーションファイルになっています。
そして、Renderingメニューを開くとOpenGLという項目があって、2D描画を行う際にOpenGLを使ったハードウェアレンダリングが可能なようです。
pic090704_05.jpg

マウスホイールで拡大縮小操作ができますが、細部を拡大表示してもほとんどスピードは落ちません。
このSVG描画エンジンをvectexで使用できるとしたら、テクスチャ作成の時間はAGGを使っているオリジナルと同じか、もっと早くなるかもしれません。

対応しているSVGの仕様はSVG 1.2 tinyというものでECMA scriptやDOMなどには対応していないということですが、vectexで使用する場合はあまり関係なさそうです。

librsvgの時と同じようにサンプルプログラムを作ってみました。
qt_test.cpp
#include <QtGui>
#include <QtSvg>
#include <stdlib.h>

int main(int argc, char **argv)
{   
    QApplication app(argc, argv, QApplication::Tty);
    
    QString svgfname = "/home/mato/Desktop/sample/tiger.svg";
    QSvgRenderer svg_render;
    svg_render.load(svgfname);
   
    QSize size = svg_render.defaultSize();
    unsigned char          *buf = (unsigned char *)malloc(size.width() * size.height() * 4);

     QImage image(buf, size.width(), size.height(), QImage::Format_ARGB32);
    
    QPainter painter(&image);
    painter.setRenderHint(QPainter::Antialiasing);
   
    QColor col;
    col.setRgbF(1.0, 0.5, 0.0, 1.0);
    painter.fillRect(image.rect(), col);
       
    QTransform transform;
    transform.reset();
    transform.translate(-128, -128);
    transform.scale(2.0, 2.0);
    painter.setTransform(transform);
   
    svg_render.render(&painter);

    QString fname = "Hello.png";
     image.save(fname, "PNG", -1);
    
    free(buf);
 
    return 0;
}

qt_test.pro
######################################################################
# Automatically generated by qmake (2.01a) ? 6? 26 23:46:27 2009
######################################################################

TEMPLATE = app
TARGET =
DEPENDPATH += .
INCLUDEPATH += .

# Input
SOURCES += qt_test.cpp

QT += svg

Qtを使ったプログラムはコンパイルの際に、普通とはちょっと違ったQMakeというコマンドを使うようです。
qt_test.proというのは、そのQMakeで使用するコンパイルの設定ファイルです。
設定する内容はSconsのSConscriptと似たような感じのものですが、QMakeというコマンドはこのファイルを使って直接コンパイルするのではなくて、GCCなどの別のコンパイラで使えるMakefileを作成することになります。
pic090704_06.jpg

vectexで使用することを想定して、mallocで確保したメモリの使用、変換行列でのスケールなどを行っています。
QMakeという独自のツールを使っているので、Blenderのビルドシステムとの相性がちょっと気になりますが、普通のMakefileが作成されているのでインクルードファイルやライブラリ名などを調べるのは問題なくできそうです。

本来は、QtはGUIを使ったプログラミングを行うためのものですが、QApplicationのコンストラクタで指定することでコンソールアプリケーションとして使用できるようです。

ただし、Qtを使ったプログラムでは、main()関数の中でQApplicationのインスタンスを必ず作成しなければならない、ということが気になります。
はたして、これをBlenderに組み込むことができるのでしょうか...。

かなり無理があるような気もしますが、main()関数のあるファイルcreator.cの拡張子を「.c」から「.cpp」に変えることで無理やり組み込むことができました。

次回の記事で、詳しい内容について書きたいと思います。
posted by mato at 01:10| Comment(0) | Blender Vectex | このブログの読者になる | 更新情報をチェックする
×

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