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 | このブログの読者になる | 更新情報をチェックする

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 | このブログの読者になる | 更新情報をチェックする

2009年06月12日

Blenderにvectexを組み込む(2)

前回サーバにアップロードできなかったvectex機能内臓の改造版Blenderの実行ファイルです。
Linux32bit(Blender_modified_Linux32090611.tar.gz)、Linux64bit(Blender_modified_Linux64090611.tar.gz)、Windows32bit(Blender_modified_win32090611.zip)版の3種類です。

Linux版、Windwos版ともにPythonは2.5を使用しています。Windows版はMinGWでのコンパイルのため、FFMpeg、Quicktimeの機能が入っていません。
ソースコードが前回のものと少しだけ変わっていますので、実行ファイルを作成した際に使用したものをこちら(Blender_modified_src090611.zip)に置いておきます。

※追記(2009/07/09) 実行ファイルを修正版と差し替えました。
修正内容は、ベースのBlenderのバージョンを2.49aに変更、ファイルセレクタの追加、Linux版のPythonのバージョンを2.6に変更、などです。
ソースコードは、古い方もそのまま残します。修正版のソースコードはこちら(Blender_modified_src090709.zip)です。

※追記(2009/07/17) 実行ファイルを修正版と差し替えました。
修正内容は、バンプマッピングのサポート、SVGファイル名の相対パスへの対応、その他、若干のバグ修正、です。
修正版のパッチファイルはこちら(Blender_agg_vectex_patch090717.zip)です。

今回の記事の内容は、前回の記事でBlenderに組み込んだvectexのソースコードを修正して、plugin版とほぼ同じように動くようにするまでの流れを書いてみます。

○CLAMPマクロ
前回の記事でBlenderにvectexのソースコードを組み込んでみましたが、この状態でSconsを使ってコンパイルすると以下のようなエラーが出ます。
pic090611_01.jpg

1071行目付近で構文エラーが起こっているようです。
ソースコードを見てみるととくに問題はなさそうで、どこが悪いのかしばらく悩みました。  
pic090611_02.jpg

1071行目のコードにあるCLAMP()という命令文は、すべて大文字になっていることからも推測できますが、関数ではなくてマクロです。
ここで使用されているマクロはutil.hでテクスチャプラグイン用に用意されているものです。
#define CLAMP(val, low, high) ((val>high)?high:((val<low)?low:val))
そして、Blenderのソースコードを調べてみると、source/blender/blenkernel/BKE_utildefines.hというファイルに同じ名前のマクロが見つかりました。
pic090611_03.jpg

マクロの内容を見てみると、if文を使っていてutil.hに書かれているものとは少し展開のされ方が違っているようです。
若干違ってはいますが、すぐ下のCLAMPIS(a,b,c)というものの方が展開のされ方が近いようですので、コンパイルエラーが起こっている部分のCLAMPマクロをCLAMPISに置き換えてみました。
void vtex_sample(VtexInstance *instance, float *texvec, int power, int interp, double *pr, double *pg, double *pb)
{
        ...

        /* extend: copy the color of edge pixels */
        x = CLAMPIS(x, 0, resolution - 1);
        y = CLAMPIS(y, 0, resolution - 1);
        /* clip: alpha=0 outside edges */
        ...
}
○dxt、dyt
これで、とりあえず最後までSconsのビルドが進むようになりました。
早速、作成されたBlender実行ファイルを動かしてみると、Textureパネルのテクスチャ選択メニューにvectexの項目が追加されていて、vectexのパネルを表示することができます。
しかし、テキスト入力の部分でSVGファイルを指定して読み込もうとすると、フリーズして動かなくなりました。
pic090611_04.jpg

ここから先は、ソースコードを眺めているだけだとなかなか問題の原因を突き止めるのは難しくなります。
ソースコードのあちこちにprintf()などを組み込んで、どこまで正常に動作しているのか確認することでもできますが、デバッガを使うとより簡単にプログラムの動作を調べることができます。
(私が実際にこの部分のソースコードを修正したときには、vectex.hにあるMSGマクロをあちこちに書き込んでコンソールに出力される内容で動作確認しています。その後、さらにややこしい問題に直面し、この方法でのデバッグには限界を感じてデバッガを使用することにしました。)

BlenderをSconsでビルドする場合、
scons BF_DEBUG=1
というようにBF_DEBUGオプションを指定することで、デバッグ用のシンボルを埋め込んだ実行ファイルを出力できます。
pic090611_05.jpg

私の使っているUbuntuはGnomeデスクトップのものでKDE環境ではないのですが、KDbvgというデバッガを使ってみました。
デバッガを起動するとファイルメニューから実行ファイルを読み込むことができますので、デバッグオプションを付けてビルドしたBlenderの実行ファイルを読み込みます。自動的にBlenderの最初に起動する部分のソースコードが表示されますが、必要に応じて他のソースコードも読み込むことができます。
pic090611_06.jpg pic090611_07.jpg

デバッガを使用すると無限ループなどでフリーズするような場合でも、ソースコードのどの部分で処理が止まっているのかを調べることができます。
デバッガからBlenderを起動して、vectexを使用するまで通常通りに操作します。フリーズしたら、デバッガの「ブレーク」という機能を使用してプログラムを一時停止します。このとき表示される部分がフリーズを起こしている部分になります。
pic090611_08.jpg

vectex()関数の中のfor()文で変数powerの値を変更している部分でフリーズが起こっていることが分かりました。
そして、その原因はvectex()関数の引数として読み込んでいるdxt[]、dyt[]の値が正しく得られていないことにあるようです。

ソースコード、texture.cの中でvectex()関数のすぐ下にはplugintex()という関数があります。
テクスチャプラグインのvectexを実行しているときには、このplugintex()という関数が呼ばれているはずなので、この関数の中でdxt[]、dyt[]がどのように処理されているのかを調べると、原因について何か分かるかもしれません。
pic090611_09.jpg

plugintex()のソースコードを見てみると、この部分でosatexという変数の値に応じてpluginTexDoit()関数にdxt、dytの値をそのまま渡すか、代わりに0を渡すかを切り替えているようです。
dxy、dytはfloat値の配列(ポインタと同じようなもの)ですから、osatexが真(!=0)のときアドレスを渡し、偽(=0)のときはアドレスが無効であることを伝えるために0を渡しているようです。
今回作成したvectex()関数でも、同じようにosatexの値に応じてdxt、dytの値を変更するコードを追加することで、テクスチャプラグインのvectexと同じように「if (dxt && dyt)」の部分を動作させることができるはずです。
static int vectex(Tex *tex, float *texvec, float *dxt, float *dyt, int osatex, TexResult *texres)
{
    ...
    if (osatex)
    {
    }
    else
    {
        dxt = 0;
        dyt = 0;
    }

    ...
}
ということで、こんな感じのコードを追加しました。もう少し違う書き方もできるはずですが、とりあえずなるべく元々あるソースコードには変更を加えずに、分かりやすくすることを第一に考えていますので、これはこれでいいことにします。
このようにosatexという変数が必要になったため、vectex()関数の引数に「int osatex」を加えてあります。
pic090611_10.jpg pic090611_11.jpg

○osatex
これで、テクスチャプレビューにSVG画像が表示され、普通にレンダリングもできるようになりました。
ところが、色々とカメラ設定を変えてレンダリングしてみると、ちょっと問題があることに気が付きます。
vectex付属のサンプルファイルtest-animated.blendをレンダリングしてみると、アニメーションの最後の方で本来なら虎の目の部分にカメラが近づいたときに「Blender Vector Texture!」という文字がはっきり表示されるはずですが、画像がぼけたようになり文字がまったく読めません。
pic090611_19.jpg pic090611_20.jpg

デバッガで調べてみると、vectex()関数の中ではosatexの値が常に0でそれ以外の値になることがありません。
plugintex()関数の中でのosatexの値の変化を見てみると、テクスチャプラグインを読み込んでテクスチャプレビューを表示するときにはosatex=0となりますが、レンダリングを実行するとosatex=512などになり、0以外の値になっています。
pic090611_14.jpg

ところが、vecetx()関数の中でのosatexの値はレンダリング時でも0のままです。
grepコマンドを使っていろいろと調べた結果、TEX_PLUGINとOSAが関係していそうなコードが/source/blender/blenkernel/intern/material.cに見つかりました。
pic090611_15.jpg

ELEM3()というのはマクロのようです。
grepコマンドで調べると、/source/blender/blenkernel/BKE_utildefines.hに定義が見つかります。
pic090611_16.jpg

似たようなマクロがたくさんありますが、一番最初の引数が2番目以降のもののいずれかと同じかどうかを調べるためのもののようです。
先ほどのmaterial.cの中のコードは、テクスチャのタイプがイメージかプラグインか環境マップのときにma->texcoに512とのビットマスクのORを設定するというような内容で、テクスチャプラグインでosatex=512となっているのはこのコードが行っているようです。
もしそうであれば、このコードのテクスチャタイプにTEX_VECTEXを追加することで、vectex()関数にosatexが渡されるときにテクスチャプラグインを使っているときと同じ値が入るようになりそうです。
static void do_init_render_material(Material *ma, int r_mode, float *amb)
{
            ...
            if(r_mode & R_OSA) {

                if ELEM4(mtex->tex->type, TEX_IMAGE, TEX_PLUGIN, TEX_ENVMAP, TEX_VECTEX) ma->texco |= TEXCO_OSA;
            }
            ...
}

この修正を行ってビルドしたBlenderでは、レンダリング時にosatex=512という値が得られるようになりました。
test-animated.blendをレンダリングしてみると、虎の目の中に置かれているテキストがはっきりとレンダリングされるようになっています。
pic090611_17.jpg pic090611_18.jpg

○.blendファイルの読み込み
元のvectex.cのソースコードには、plugin_instance_init()という関数が使われています。
これは、Blenderのテクスチャプラグインが用意している関数のうちの一つで、vectexではこの関数の中に.blendファイルを開いたときに無効なInstance構造体へのポインタがメモリアクセス違反を起こすのを防ぐコードが書かれています。
pic090611_21.jpg
これがないとvectexを使って作成された.blendファイルを開いた時、すぐにBlenderが落ちるという状況になります。
plugin_instance_init()に相当する機能はBlenderの内部テクスチャには用意されていませんので、自力で同じような処理を作成する必要があります。

Blenderで.blendファイルを開くときの処理が書かれているのは、
/source/blender/blenloader/intern/readfile.c
というファイルです。このファイルにdirect_link_texture()という関数があり、.blendファイルを開いたときにその中でテクスチャが使われていた場合に、必要な処理を記述できるようになっています。
テクスチャプラグインのplugin_instance_init()関数も、この部分で実行されるようになっています。
static void direct_link_texture(FileData *fd, Tex *tex)
{
    ...
    if(tex->vtex_instance_data){
        tex->vtex_instance_data= NULL;

    }
    ...
}
このような感じでInstance構造体へのポインタをクリアするコードを追加しました。

○デフォルト値の設定
Textureパネルで新規に作成するテクスチャタイプを選択すると、作成されるパネルの数値データにはあらかじめ設定されたデフォルト値が入っています。
pic090611_22.jpg pic090611_23.jpg

テクスチャプラグインにはボタンの設定を行うVarStructという構造体にデフォルト値を指定できるようになっています。しかし、Blenderの内部のボタンを作成するuiDefBut()という関数にはデフォルト値を指定する機能はありません。

Blenderのソースコードの中でTex構造体のメンバ変数にデフォルト値を設定している場所を探してみると、default_tex()という関数として用意されています。この部分にvectexのためのコードを以下の用に追加しました。
/source/blender/blenkernel/intern/texture.c
void default_tex(Tex *tex)
{
    ...
    tex->vn_distm = 0;
    tex->vn_coltype = 0;
    /* vectex */
    tex->vtex_enable = 1;
    tex->vtex_mipmap = 1;
    tex->vtex_interpol = 1;
    tex->vtex_tex_level = 0;
    tex->vtex_mem_max = 20;
    tex->vtex_mem = 0;

    ...
}
ただ、ここにvectex用の変数の設定を書いても、Textureパネルからvectexを選択したときに表示されるパネルには結果が反映されません。
この関数に加えた変更は、テクスチャプレビューのパネルにある「Default Vars」というボタンを押すと、vectexパネルの状態をデフォルト状態に変わることで確認できます。
pic090611_24.jpg

Blenderにもとから用意されているプロシージャルテクスチャのパネルは、あらかじめ設定してあるデフォルト値がセットされた状態で開きますが、その設定がどこで行われているのかちょっと悩みました。
grepコマンドで調べると、上記のdefault_tex()関数と、/source/blender/blenloader/intern/readfile.cのdo_versions()という関数の中でTex構造体のメンバ変数に値を設定しているコードが見つかります。

今回Tex構造体に追加したvectex用の変数を、do_version()関数で最新のバージョン2.49よりも読み込んだデータのバージョンが小さい場合にデフォルト値をセットするように記述しました。
static void do_versions(FileData *fd, Library *lib, Main *main)
{
    ...
    if (main->versionfile < 249) {
        Scene *sce;
        Tex *tex;
        for (sce= main->scene.first; sce; sce= sce->id.next)
            sce->r.renderer= 0;

        for(tex= main->tex.first; tex; tex= tex->id.next) {
            /* vectex */
            tex->vtex_enable = 1;
            tex->vtex_mipmap = 1;
            tex->vtex_interpol = 1;
            tex->vtex_tex_level = 0;
            tex->vtex_mem_max = 20;
            tex->vtex_mem = 0;
        }       
    }
    ...
}
これで、Blender249より前に作成された.blendファイルを読み込んだとき、あるいはまったくファイルを読み込まずにBlender実行ファイルに組み込まれたデータを読み込む場合に、デフォルト値が設定された状態でvectexのパネルが開くようになります。

もし、今後2.49よりも新しいバージョンにvectexを組み込む場合は、このdo_version()の記述はそのままにして、代わりにBlender実行ファイルに内臓されているB.blendファイルのデータ(/source/blender/src/B.blend.c)をvectexのデフォルト値が設定されたものと入れ替えることで対応するようにします。
たとえばVer2.50でvectexを組み込む場合、上記の変更をさらに修正して(main->versionfile < 250)のときにvectexのデフォルト値を設定するというコードを書いてしまうと、バージョン2.49としてvectexを使用して保存したファイルを読み込んだときに、保存されているデータの設定値をすべてデフォルト値で上書きしてしまうことになり、正しくデータを読み込めなくなります。
posted by mato at 02:53| Comment(0) | Blender Vectex | このブログの読者になる | 更新情報をチェックする
×

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