2009年10月07日

メモリリーク

今回の更新はバグフィックスのみです。
修正したバグはメモリリークが起こっていたことです。

実行ファイルはこちらになります。

Linux 64bit


Linux 32bit


Windows 32bit


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

AGGバージョンも更新しています。
実行ファイルは以前の記事からダウンロード可能です。
AGGバージョンのソースファイルのパッチ(Blender_modified_patch091005.zip)


以下、今回の修正内容について詳しく説明します。
元々のプラグインのvectexでは、メモリの管理について以下のように行っています。
vectexは大量のタイル画像を作成しますが、それらのタイル画像はTile構造体の*imageというポインタでアクセスできるようになっています。
typedef struct Tile
{
    unsigned char *image;
    unsigned int   age;
} Tile;
そしてそのTile構造体にはInstance構造体から*tile_setというポインタでアクセスします。
typedef struct Instance
{
    void         *backend_data;
    int           tile_set_max_power;
    Tile         *tile_set;
    int           tile_memory;
    unsigned int  timestamp;
    unsigned char current_base_color[4];
    char          current_file_name[64];
} Instance;
Instance構造体へのアクセスのために、PluginのCast構造体に*instance_dataというポインタを登録しています。
typedef struct Cast
{
    int           enable;
    int           mipmap;
    int           interpol;
    /*int           usealpha;*/
    int           label_dummy0;
    int           tex_level;
    int           mem_max;
    int           mem;
    int           label_dummy1;
    unsigned char base_color[4];
    int           label_dummy2;
    char          file_name[64];
    int           label_dummy3;
    void         *instance_data;
} Cast;
このブログを始めてすぐのころにBlenderのプラグインの仕組みを調べていたときには気づかなかったのですが、通常Cast構造体に登録されるのはPluginパネルに表示されるボタンの状態を保持するためのデータであって、大量の画像へのポインタを保持するInstance構造体へのポインタのようなものを登録するのはかなり特殊な使い方と考えていいようです。

BlenderのプラグインAPIはこのような使い方を想定して作られていないので、Blenderの終了時などにプラグインを使用しているテクスチャが削除される処理が行われるとき、Cast構造体自体が削除されてもそこに登録されたポインタが指す実体については削除されないことになります。

このような理由からオリジナルのvectexではBlenderの終了またはプロジェクトを閉じる処理が行われたとき、作成したタイル画像のデータ、そしてそれを管理するInstance構造体などのデータが使用しているメモリ領域がきちんと解放されずにメモリリークを起こしています。

現在のOSではアプリケーションの終了時にはそのプロセスに割り当てられたメモリ領域全体が破棄されるような仕組みになっていて、実際にはこのメモリリークが大きな問題になることはなさそうです。
ただし、アプリケーションを終了せずにプロジェクトを閉じる操作を何度も続けると問題がおこるかもしれません。
具体的には、vectexを使用してレンダリングを実行し、Blenderを終了しないまま新規プロジェクトを作成し、新しいプロジェクトでvectexを使用してレンダリングを実行、新規プロジェクトを作成...というように操作を続けていくとメモリが不足する可能性があります。

では、vectex内臓版のBlenderではこのあたりのことがどうなっているのかというと、
作成されるタイル画像は、VtexTile構造体の*imageポインタでアクセスされます。
typedef struct VtexTile
{
    unsigned char *image;
    unsigned int   age;
    float pad;
} VtexTile;
VtexTile構造体にはVtexInstance構造体の*tile_setポインタでアクセスされます。
typedef struct VtexInstance
{
    void         *backend_data;
    int           tile_set_max_power;
    int           tile_memory;
    unsigned int  timestamp;
    unsigned char current_base_color[4];
    char          current_file_name[160];
    short imaflag;
    short vtex_imaflag;
    float pad;
    struct VtexTile         *tile_set;
} VtexInstance;
さらにVtexInstance構造体にはBlenderのテクスチャのデータ全般を管理しているTex構造体に追加した*vtex_instance_dataからアクセスできるようになっています。
typedef struct Tex {
...   
    /* vectex */
    float vtex_resolution_scale;
    int vtex_enable, vtex_mipmap, vtex_interpol, vtex_tex_level, vtex_mem_max, vtex_mem;
    unsigned char vtex_base_color[4];
    char vtex_file_name[160];
    short vtex_imaflag;
    short vtex_version;
    char pad1[4];
   
    struct PackedFile * vtex_packedfile;
    struct VtexInstance *vtex_instance_data;
...
} Tex;
Blenderの終了時には、テクスチャのデータを保持するTex構造体は、blender/source/blender/blenkernel/intern/texuture.c (line:417)にあるfree_texture()という関数でメモリの解放を行っています。

Tex構造体に追加したポインタ*vtex_instance_dataの実体もここでメモリの解放を行う必要があります...が、これまでメモリの解放処理を書かないまま放置してありました。
このため、vectex内臓版Blenderでもアプリケーション終了時やプロジェクトを閉じた時に、オリジナルのプラグインとまったく同じようにメモリリークを起こしていました。

本来ならTex構造体にポインタ*vtex_instance_dataを追加した時点で気づいていなければいけなかったのですが、今頃になって気づきました。
プラグインと違ってBlender自体のソースコードを自由に変更できる状況にありながら、数ヶ月もの間メモリリークを放置していたとは、なんとも情けない話です...。


今回の更新でのソースコードの修正内容です。
前回からの修正内容(changes091006.zip)

まず、free_texture()関数にメモリの解放の処理を書く必要があります。
blender/source/blender/blenkernel/intern/texuture.c (line:417)
void free_texture(Tex *tex)
{
    free_plugin_tex(tex->plugin);
    if(tex->coba) MEM_freeN(tex->coba);
    if(tex->env) BKE_free_envmap(tex->env);
    if(tex->vtex_instance_data) vtex_destroy_instance(tex);
    BKE_previewimg_free(&tex->preview);
    BKE_icon_delete((struct ID*)tex);
    tex->id.icon_id = 0;
   
    if(tex->nodetree) {
        ntreeFreeTree(tex->nodetree);
        MEM_freeN(tex->nodetree);
    }
}
ここで使用しているvtex_destroy_instance()という関数はこれまでvectex()関数と同じblender/source/render/intern/source/texuture.cに置いていましたが、blender/source/blender/blenkernelという全く別のディレクトリにあるファイルから参照できるようにする必要があります。
しかし、そもそも現状ではvtex_destroy_instance()関数は、外部のファイルから使用できるようにするためにプロトタイプ宣言をヘッダファイルで公開していません。
blender/source/blender/render/intern/include/texture.hというヘッダファイルにこの関数のプロトタイプ宣言を書いて、blender/source/blender/blenkernel/texuture.cから参照できるようにすることもできますが、関数の用途と配置について考えるとむしろvtex_destroy_instance()関数そのものを.../blenkernel/texture.cの方に移してそれを.../render/intern/source/texture.cの方から使用するというようにした方がいいようです。

実際にこの関数を移動することを考えた場合、この関数で使用しているいくつかの関数も同じようにファイルを移動させる必要がでてきます。
vtex_destroy_tile_set()関数と、その関数で使われているvtex_get_base_index()関数です。

vtex_destroy_instance()関数では、これ以外にもvtex_destroy_backend()関数も使われていますが、こちらはもともとblender/source/vectex/vectex.hで公開されているので、そのヘッダファイルをインクルードするだけで使用できます。

.../blendkernel/texture.cに移動したので、.../render/intern/source/texture.cからはこれらの関数は削除しました。
これらの修正は移動のみの修正のため、ソースコードの引用については省略します。

移動した関数のプロトタイプ宣言をヘッダファイルに追加します。
blender/source/blender/blenkernel/BKE_texture.h
...
struct VtexInstance;
...
...
int vtex_get_base_index(int power);
void vtex_destroy_instance(struct Tex *tex);
void vtex_destroy_tile_set(struct VtexInstance *instance);

...
移動した関数をコンパイル時に正しく認識できるように、SConsの設定ファイルSConscriptファイルの内容を修正します。
blender/source/blender/blenkernel/SConscript
...
if env['WITH_BF_VECTEX']:
    incs += ' ../vectex'


env.BlenderLib ( libname = 'bf_blenkernel', sources = sources, includes = Split(incs), defines = defs, libtype=['core','player'], priority = [65, 20] )
このように一つの関数を複数の場所のファイルから参照できるようにすると、インクルードファイルの位置だけでなく、コンパイル後に作成されるライブラリの有線順位priorityについても変更が必要になることがあります。
今回は、blender/source/blender/vectexディレクトリのSConscriptファイルのpriorityの数値をblender/source/blender/blenkernelのSConscriptと同じになるように修正しました。
もともとはblender/source/blender/renderのSConscriptに合わせていましたが、そのままではWindowsのmingwでのコンパイルでエラーが出てしまったため、より有線順位が高いと思われるblenkernelの方に合わせることにしました。

blender/source/blender/vectex/SConscript
...
env.BlenderLib ( libname='bf_vectex',
        sources=sources, includes=Split(incs),
        defines=Split(defs),
        libtype=['core','player'], priority = [65, 20], compileflags = Split(cflags))

今回の修正内容はこれだけです。

この修正を行うにあたってLinuxでメモリリークの有無をしらべることのできるツールを探し、Valgrindというものを使ってみました。
このツールはGUIのないコマンドラインで使用するタイプのものですが、メモリリークを調べるだけという簡単な用途で使うなら、使い方はそれほど難しくありません。

通常の操作で

myprog arg1 arg2

というようにコマンドラインで使用するプログラムを

valgrind --leak-check=yes myprog arg1 arg2

というようにするだけで、端末画面にエラーメッセージが表示されます。
動作スピードがかなり遅くなりますが、BlenderのようなGUIプログラムでも通常と同じようにプログラムが起動し、普通に操作することができます。

ただし、Blederを調べようとすると大量のエラーメッセージが表示されて、これ以上メッセージを表示しないというように言われてしまいます。

valgrind --leak-check=yes --error-limit=no ./blender

というように--error-limit=noというオプションを付けることで、この問題を回避できるようです。
今回の修正前の状態では、valgrindの出力に以下の様にvectexのメモリリークが表示されました。
...
==8900== 348,284 bytes in 5 blocks are possibly lost in loss record 369 of 372
==8900==    at 0x4C278AE: malloc (vg_replace_malloc.c:207)
==8900==    by 0x14DCD8A: MEM_mallocN (mallocn.c:258)
==8900==    by 0xAF43F9: vtex_create_instance (texture.c:920)
==8900==    by 0xAF5D2E: vtex_check_instance (texture.c:1286)
==8900==    by 0xAF5FEB: vectex (texture.c:1344)
==8900==    by 0xAFE27D: multitex (texture.c:3155)
==8900==    by 0xB00D35: do_material_tex (texture.c:3686)
==8900==    by 0xB41188: shade_lamp_loop (shadeoutput.c:1566)
==8900==    by 0xB6F42F: shade_material_loop (shadeinput.c:93)
==8900==    by 0xB6FAE6: shade_input_do_shade (shadeinput.c:154)
==8900==    by 0xB6B9C4: shade_tra_samples (zbuf.c:3889)
==8900==    by 0xB6CC5F: zbuffer_transp_shade (zbuf.c:4191)
...

今回の修正後、vectexのメモリリークの表示はなくなったようです。
(起動後の操作によって出力の内容も変わるので、操作内容によっては別の結果が出る可能性があります。)
posted by mato at 00:09| Comment(2) | Blender Vectex | このブログの読者になる | 更新情報をチェックする

2009年09月18日

OSAへの対応

OSAはレンダリング時のアンチエイリアスを行う機能です。
OSAの設定はRenderパネルの「Render」ボタンのすぐ下の「OSA」ボタンと「5」「8」「11」「16」という数字のボタンで行います。
pic090918_01.jpg

これまでvectexでは独自の計算でアンチエイリアスの処理を行っていて、Blenderが元々用意しているOSAの計算処理を使用していませんでした。
今回は、vectexのアンチエイリアス処理にBlenderのOSAの機能を使用するように改良しました。

これに合わせて、前回の修正でvectexパネルを表示させたときに同時に表示されるようにしたMap Imageパネルの表示内容を修正し、イメージテクスチャを使用したときと完全に同じ内容にしました。
今回、Map Imageパネルに追加したボタンは「Gauss」「Rot90」「Min」ボタン、「Filter」数値入力ボタン、「Normal Map」ボタン、「Normal Space」トグルボタンです。
pic090918_02.jpg

実行ファイルはこちらになります。

Linux 64bit

Linux 32bit

Windows 32bit


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

AGGバージョンも更新しています。
実行ファイルは以前の記事のURLからダウンロード可能です。
AGGバージョンのソースファイルのパッチ(Blender_modified_patch090916.zip)

この記事を書くまで気づかなかったのですがGaussボタンをオンにしたときの処理は今回の更新では実装していませんでした。
このため現在の状態ではGaussボタンを押しても効果がありません。

今回の機能の更新はかなり地味な感じですが、vectexの中枢部分の改良ともいえる重要なものです。
OSAを使用したvectexのレンダリング画像は、特に細かい模様が連続しているような部分でかなり向上していると思います。
下の画像では、左からオリジナルのvectexの補間計算、今回可能になったvectex+OSA、PNG画像でOSA使用という条件でレンダリングしています。
pic090918_04.jpg


ここから、ソースコードの解説になります。
これまでソースコードの修正はプラグインから移植してきたvectexのオリジナルのコードに少しずつ機能追加をするという感じで進めてきましたが、今回はイメージテクスチャの描画部分のソースコードを丸ごとコピーしてきて、その中で画像データからピクセルの色情報を取得する部分を探してvectexのコードに置き換えるというような感じになっています。

イメージテクスチャでvectexの描画を行うvectex()関数に相当するのは、blender/source/blender/render/intern/source/imagetexture.cにあるimagewrap()関数、imagewraposa()関数です。
OSAの機能が有効な場合にはimagewraposa()関数が呼ばれ、そうでない場合にはimagewrap()関数が呼ばれます。

imagewraposa()関数を使用するためには、同じファイルに書かれているboxsample()、boxsampleclip()、clipy_rctf()、clipx_rctf()、square_rctf()、clipy_rctf_swap()、clipx_rctf_swap()、ibuf_get_color()などの関数が必要になります。

今回はそれらすべてのファイルをtexture.cにコピーし、関数名にvtex_という文字列を加えるなどして別の関数にした上で、vectexの機能を使用できるように修正を加えました。
imagewraposa()関数については、名前をvectex_osa()として関数の引数もvectex()関数と同じになるように修正しています。

その上で、OSAが有効な場合はvectex_osa()関数が呼ばれ、OSAが無効な時とvectexパネルでMipmap(Old)、Interpol(Old)がオンになっている時にはvectex()関数が呼ばれるようにしています。

これでは、かなりのソースコードが重複していて無駄な感じがしますが、これは今後の開発の方向性などを考えた上で行っています。

今回でBlender2.49上での開発を終了し、次からはBlender2.5をベースとしていくつもりです。
ただし、単純に今の状態でそのまま移行するのではなく、vectexの実装の仕方をもう少しBlenderのプログラミングの方針に合うように修正したいと思っています。
現在vectexはイメージテクスチャとは独立したテクスチャタイプになっていますが、実際のところソースコードのかなりの部分が重複するため、イメージテクスチャの中に統合してしまうのがより合理的なように思います。

今回、imagewraposa()関数をコピーしてvectex_osa()という関数に作り替えることで、次回以降イメージテクスチャのコードの中にvectexを移していくための足掛かりとしたいと思いました。

具体的なソースコードの修正内容について見ていきます。
前回からの修正内容(changes090918.zip)

まず、ユーザーインターフェイスの部分です。
vectexパネルからMap Imageパネルと重複するボタンを削除し、MipMap、Interpolボタンの名前と説明を修正しました。

blender/source/blender/src/buttons_shading.c
(line:800-)
static void texture_panel_vectex(Tex *tex)
{
...
    uiDefButI(block, TOG, B_TEXPRV, "MipMap(Old)", 10, 55, 150, 18, &tex->vtex_mipmap, 0, 1, 0, 0, "Uses mipmap averaging(Use original plugin's mipmap calculation)");
    uiDefButI(block, TOG, B_TEXPRV, "Interpol(Old)", 160, 55, 150, 18, &tex->vtex_interpol, 0, 1, 0, 0, "Interpolates nearby pixels(Use original plugin's interpolate calculation)");
...
/*   
    uiDefButBitS(block, TOG, TEX_USEALPHA, B_TEXPRV, "UseAlpha",    10, 35, 100, 20, &tex->imaflag, 0, 0, 0, 0, "Click to use Image's alpha channel");
    uiDefButBitS(block, TOG, TEX_CALCALPHA, B_TEXPRV, "CalcAlpha",    110, 35, 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, 35, 100, 20, &tex->flag, 0, 0, 0, 0, "Click to invert the alpha values");
*/
...
}

Map Imageパネルについては、vectexが有効な場合に非表示にしていた部分を削除して、イメージテクスチャのときと同じ内容で表示されるように修正しました。
これは前回行った修正内容を取り消して、元のソースコードと同じになったということで、ソースコードの引用は省略します。

次に、vectex()関数を呼び出しているmultitex()関数の修正です。
vectex()関数が呼ばれる条件を制限し、今回追加したvectex_osa()関数を呼び出すコードを追加しています。
blender/source/blender/render/intern/source/texture.c
(line:3116-)
static int multitex(Tex *tex, float *texvec, float *dxt, float *dyt, int osatex, TexResult *texres, short thread, short which_output)
{
...
    case TEX_VECTEX:
        /*retval= vectex(tex, texvec, dxt, dyt, osatex, texres);*/
        if(tex->vtex_mipmap || tex->vtex_interpol || !(osatex)) retval= vectex(tex, texvec, dxt, dyt, osatex, texres);
        else retval= vectex_osa(tex, texvec, dxt, dyt, osatex, texres);

        break;
...
}

vectex()関数には本来は変更は必要ないのですが、vtex_osa()関数と共通するコードのうちの一部を別の関数として分離してみました。
vectex()関数の頭の方で、tex->vtex_instance_dataの状態をチェックしてプレビュー画面の色を黒、赤にしたり、SVGファイル名が変更されたときにキャッシュされたタイル画像を作成し直している部分です。
チェックした内容によってはvectex()関数から抜ける必要があるため、その場合は関数の戻り値をNULLとし、正常な場合はインスタンスデータを返すようにしています。
(line:1264-)
VtexInstance * vtex_check_instance(Tex *tex, TexResult *texres)
{

    if (! tex->vtex_enable)
    {
        /* Plugin is disabled, check if there is allocated memory to cleanup */
        if (tex->vtex_instance_data != NULL)
        {
            vtex_destroy_instance(tex);
            tex->vtex_mem = 0;
        }
       
        /* Return a black pixel */
        texres->tin = texres->ta = 1.0;
        texres->tr = texres->tg = texres->tb = 0;
           

        return NULL;  

    }
...
    VtexInstance *instance = (VtexInstance *) tex->vtex_instance_data;      
...
    }
   
    return instance;
}
分離したコードを呼ぶ出す部分です。
(line:1333)
static int vectex(Tex *tex, float *texvec, float *dxt, float *dyt, int osatex, TexResult *texres)
{
...
    BLI_lock_thread(LOCK_IMAGE);

    VtexInstance *instance = vtex_check_instance(tex, texres);
    if (instance == NULL){

        BLI_unlock_thread(LOCK_IMAGE);
   
        return (TEX_INT | TEX_RGB);  
    }
    BLI_unlock_thread(LOCK_IMAGE);
...
}


以下は、imagetexture.cからコピーしてきた関数なので、diffコマンドではすべてが追加されたようになっています。
かなり長いので、短く引用しながらコメントしていきます。

まず、vectex_osa()関数からです。
引数を変更して元のImage *ima、ImBuf *ibufを削除、int osatexを追加しています。
当然のように、関数内でそれら削除した引数を使用している部分はコメントアウトするか、削除してあります。
最初にあるImBuf、ミップマップのチェックを行っている部分はコメントアウトしています。
次に、vectex()関数からインスタンスデータのチェックを呼び出す処理、解像度計算を行う処理などをコピーしています。
(line:2151-)
int vectex_osa(Tex *tex, float *texvec, float *dxt, float *dyt, int osatex, TexResult *texres)
{
...
    /* quick tests */
    /*if(ibuf==NULL && ima==NULL)
...
       return retval;*/
   
    /* mipmap test */
    /*if(tex->imaflag & TEX_MIPMAP) {
...
    }*/
   
    BLI_lock_thread(LOCK_IMAGE);

    VtexInstance *instance = vtex_check_instance(tex, texres);
    if (instance == NULL){
        BLI_unlock_thread(LOCK_IMAGE);
   
        return (TEX_INT | TEX_RGB);  
    }
    BLI_unlock_thread(LOCK_IMAGE);
...
    else {
        /* OSA is not active; use the default image resolution */
        ideal_res = VTEX_TILE_SIZE;
        power     = VTEX_TILE_SIZE_POWER;
        pixel_size = 1.0 / VTEX_TILE_SIZE;
    }

そして、ImBufの中に格納されている画像サイズの代わりに使用する値resolutionをここで定義しています。
フィールドレンダリング関連の処理はImBuf構造体に関係しているためコメントアウトしています。
vectexでは今のところフィールドレンダリングには未対応です。
    int resolution = 1 << power;
...
    /*if(ibuf->flags & IB_fields) {
        if(R.r.mode & R_FIELDS) {*/            /* field render */
            /*if(R.flag & R_SEC_FIELD) {*/        /* correction for 2nd field */
                /* fac1= 0.5/( (float)ibuf->y ); */
                /* fy-= fac1; */
            /*}
            else {*/                /* first field */
                /*fy+= 0.5f/( (float)ibuf->y );
            }
        }
    }*/
...

ここで、画像サイズを取得している部分を先ほど定義したresolutionに置き換えています。
vectexでは画像のサイズは縦横同じになるため、MIN2マクロでの比較処理が不要になります。
そして、解像度が可変のためレンダリング時には各ピクセル毎に計算し直されることになります。
この関数内でImBufが使用されるのは、画像サイズの取得が用途のほとんどです。
それ以外は、boxsample()関数に引数で渡されるとき、ミップマップの処理の際にどのレベルのミップマップが有効になっているのかを調べる処理に使われているだけです。
...
    if(tex->imaflag & TEX_FILTER_MIN) {
        /* make sure the filtersize is minimal in pixels (normal, ref map can have miniature pixel dx/dy) */
         /*float addval= (0.5f * tex->filtersize) / (float) MIN2(ibuf->x, ibuf->y);*/
         float addval= (0.5f * tex->filtersize) / (float) resolution;
...

間がかなり開いて、フィールドレンダリングの処理をコメントアウトしています。
関数内での処理の流れで言うと、ここからミップマップを使用している場合の処理が始まっています。
ミップマップの処理をする際に対象のレベルを保持するために使用するImBuf構造体の定義をコメントアウトしています。
pixsizeの計算に画像サイズが使用されている部分をresolutionに置き換えています。
...
    /* warning no return! */
    /*if( (R.flag & R_SEC_FIELD) && (ibuf->flags & IB_fields) ) {
        ibuf->rect+= (ibuf->x*ibuf->y);
    }*/

    /* choice:  */
    if(tex->imaflag & TEX_MIPMAP) {
        /*ImBuf *previbuf, *curibuf;*/
        float bumpscale;
       
        dx= minx;
        dy= miny;
        maxd= MAX2(dx, dy);
        if(maxd>0.5) maxd= 0.5;

        /*pixsize = 1.0f/ (float) MIN2(ibuf->x, ibuf->y);*/
        pixsize = 1.0f/ (float) resolution;
       
        bumpscale= pixsize/maxd;
        if(bumpscale>1.0f) bumpscale= 1.0f;
        else bumpscale*=bumpscale;

Blenderの通常のミップマップの処理はテクスチャ画像の解像度が低い部分で有効になるように計算されています。
一方でvectexはテクスチャの解像度が高い場合でも動作するミップマップのようなものと考えることができます。
そのため、今回は既存のミップマップのコードをすべてvectexに置き換えるようにソースコードを修正しています。
以下の部分では、既存のミップマップの処理をコメントアウトし、新規に追加するvectexのミップマップ処理のために必要な変数max_res、min_resを定義しています。
次の部分ではミップマップの補間処理をスキップするかどうかを判別する条件式を置き換えています。
ミップマップの処理ではミップマップレベルが一つ下のものと現在のレベルのものを合成するという形で補間を行います。
もし、ミップマップの段数が少なくて一つしかなければ、補間計算はできません。
ミップマップの補間計算をスキップするかどうかの条件は、元のコードでは previbuf!=curibuf ですが、vectexでは ideal_res != max_res となります。
そして、ここでも画像サイズが使用されている部分をresolutionに置き換えています。
        /*curmap= 0;
        previbuf= curibuf= ibuf;
        while(curmap<IB_MIPMAP_LEVELS && ibuf->mipmap[curmap]) {
            if(maxd < pixsize) break;
            previbuf= curibuf;
            curibuf= ibuf->mipmap[curmap];
            pixsize= 1.0f / (float)MIN2(curibuf->x, curibuf->y);
            curmap++;
        }*/
       
        int max_res = 1 << power;
        int min_res = max_res >> 1;


        /*if(previbuf!=curibuf || (tex->imaflag & TEX_INTERPOL)) {*/
        if(ideal_res != max_res || (tex->imaflag & TEX_INTERPOL)) {
            /* sample at least 1 pixel */
            /*if (minx < 0.5f / ibuf->x) minx = 0.5f / ibuf->x;
            if (miny < 0.5f / ibuf->y) miny = 0.5f / ibuf->y;*/
            if (minx < 0.5f / resolution) minx = 0.5f / resolution;
            if (miny < 0.5f / resolution) miny = 0.5f / resolution;

        }

この後、非常に重要な部分、ピクセルデータを取得するvtex_boxsample()関数を呼び出して、最終的な計算結果をtexres構造体に格納する部分になります。
しかし、かなり長い上にミップマップを使用しているか、そうでないかで条件分岐し、その両方についてさらに、バンプマップを使用する場合とそれ以外というようにif、else文が重なり合っていて、コードがかなり見づらくなっています。

まず、ミップマップが有効で、バンプマップを使用している場合です。
vtex_boxsample()関数で引数の内容が違っています。
元々は2つのミップマップcuribufとprevibufのそれぞれのピクセルデータを取得して、それらをdx:dyの比率で足し合わるという処理を行っていました。
vectexではpowerの値から作成されたタイル画像のインデックス値が計算できるので、ImBuf構造体の代わりにint powerの値を渡しています。
そしてvectexの計算に必要なVtexInstance *instance、Tex *texが引数に追加されています。
2つのミップマップから得られた値を足し合わせる比率の計算方法も違っています。
最後に、バンプマップの強度を補正するbumpscaleの値の計算にミップマップの補間をする処理を付け加えています。
        if(texres->nor && (tex->imaflag & TEX_NORMALMAP)==0) {
            /* a bit extra filter */
            //minx*= 1.35f;
            //miny*= 1.35f;
           
            /*vtex_boxsample(curibuf, fx-minx, fy-miny, fx+minx, fy+miny, texres, imaprepeat, imapextend);*/
            vtex_boxsample(instance, power, fx-minx, fy-miny, fx+minx, fy+miny, texres, imaprepeat, imapextend, tex);
            val1= texres->tr+texres->tg+texres->tb;
            /*vtex_boxsample(curibuf, fx-minx+dxt[0], fy-miny+dxt[1], fx+minx+dxt[0], fy+miny+dxt[1], &texr, imaprepeat, imapextend);*/
            vtex_boxsample(instance, power, fx-minx+dxt[0], fy-miny+dxt[1], fx+minx+dxt[0], fy+miny+dxt[1], &texr, imaprepeat, imapextend, tex);
            val2= texr.tr + texr.tg + texr.tb;
            /*vtex_boxsample(curibuf, fx-minx+dyt[0], fy-miny+dyt[1], fx+minx+dyt[0], fy+miny+dyt[1], &texr, imaprepeat, imapextend);*/
            vtex_boxsample(instance, power, fx-minx+dyt[0], fy-miny+dyt[1], fx+minx+dyt[0], fy+miny+dyt[1], &texr, imaprepeat, imapextend, tex);
            val3= texr.tr + texr.tg + texr.tb;

            /* don't switch x or y! */
            texres->nor[0]= (val1-val2);
            texres->nor[1]= (val1-val3);
           
            /*if(previbuf!=curibuf) {*/  /* interpolate */
            if(ideal_res != max_res) {  /* interpolate */
               
                /*vtex_boxsample(previbuf, fx-minx, fy-miny, fx+minx, fy+miny, &texr, imaprepeat, imapextend);*/
                vtex_boxsample(instance, power -1, fx-minx, fy-miny, fx+minx, fy+miny, &texr, imaprepeat, imapextend, tex);
               
                /* calc rgb */
                /*dx= 2.0f*(pixsize-maxd)/pixsize;
                if(dx>=1.0f) {
                    texres->ta= texr.ta; texres->tb= texr.tb;
                    texres->tg= texr.tg; texres->tr= texr.tr;
                }
                else {
                    dy= 1.0f-dx;
                    texres->tb= dy*texres->tb+ dx*texr.tb;
                    texres->tg= dy*texres->tg+ dx*texr.tg;
                    texres->tr= dy*texres->tr+ dx*texr.tr;
                    texres->ta= dy*texres->ta+ dx*texr.ta;
                }*/
               
                /* Do mipmapping: interpolate final pixel from the sampled pixels */
                double max_weight = (double)(ideal_res - min_res) / (max_res - min_res);
                double min_weight = 1 - max_weight;
                texres->tr = texres->tr * max_weight + texr.tr * min_weight;
                texres->tg = texres->tg * max_weight + texr.tg * min_weight;
                texres->tb = texres->tb * max_weight + texr.tb * min_weight;
                texres->ta = texres->ta * max_weight + texr.ta * min_weight;

               
                /*val1= dy*val1+ dx*(texr.tr + texr.tg + texr.tb);
                vtex_boxsample(previbuf, fx-minx+dxt[0], fy-miny+dxt[1], fx+minx+dxt[0], fy+miny+dxt[1], &texr, imaprepeat, imapextend);*/
                val1=  max_weight*val1+ min_weight*(texr.tr + texr.tg + texr.tb);
                vtex_boxsample(instance, power -1, fx-minx+dxt[0], fy-miny+dxt[1], fx+minx+dxt[0], fy+miny+dxt[1], &texr, imaprepeat, imapextend, tex);

                /*val2= dy*val2+ dx*(texr.tr + texr.tg + texr.tb);
                vtex_boxsample(previbuf, fx-minx+dyt[0], fy-miny+dyt[1], fx+minx+dyt[0], fy+miny+dyt[1], &texr, imaprepeat, imapextend);*/
                val2=  max_weight*val2+ min_weight*(texr.tr + texr.tg + texr.tb);
                vtex_boxsample(instance, power -1, fx-minx+dyt[0], fy-miny+dyt[1], fx+minx+dyt[0], fy+miny+dyt[1], &texr, imaprepeat, imapextend, tex);

                /*val3= dy*val3+ dx*(texr.tr + texr.tg + texr.tb);*/
                val3= max_weight*val3+ min_weight*(texr.tr + texr.tg + texr.tb);
               
                texres->nor[0]= (val1-val2);    /* vals have been interpolated above! */
                texres->nor[1]= (val1-val3);
               
                /*if(dx<1.0f) {
                    dy= 1.0f-dx;
                    texres->tb= dy*texres->tb+ dx*texr.tb;
                    texres->tg= dy*texres->tg+ dx*texr.tg;
                    texres->tr= dy*texres->tr+ dx*texr.tr;
                    texres->ta= dy*texres->ta+ dx*texr.ta;
                }*/
               
                int resolution_cur = 1 << power;
                int resolution_prev = 1 << (power - 1);
                float resolution_mix =(float) resolution_cur * max_weight + (float) resolution_prev * min_weight;

                pixsize = 1.0f/ resolution_mix;

                bumpscale= pixsize/maxd;
                if(bumpscale>1.0f) bumpscale= 1.0f;
                else bumpscale*=bumpscale;

            }
            texres->nor[0]*= bumpscale;
            texres->nor[1]*= bumpscale;
        }
こちらはバンプマップを使用していない場合の処理です。
法線計算のために本来のテクスチャ座標の他にX、Yそれぞれの方向にずらした位置の色データを計算するため、それぞれのミップマップに対して3回ずつ行っていたvtex_bosxample()関数の呼び出しが1回ずつに減っています。
代わりにvectex()関数からコピーしてきた tex->vtex_tex_level が有効な場合の処理が増えています。
これはテクスチャの解像度を色分け表示するための処理で、vectexパネルのTex Levelボタンがオンになったときに使用されます。
        else {
            maxx= fx+minx;
            minx= fx-minx;
            maxy= fy+miny;
            miny= fy-miny;

            /*vtex_boxsample(curibuf, minx, miny, maxx, maxy, texres, imaprepeat, imapextend);*/
            vtex_boxsample(instance, power, minx, miny, maxx, maxy, texres, imaprepeat, imapextend, tex);

            if (tex->vtex_tex_level)
            {
                /* Apply hue color based on image detail level */
                double h = (power - 1) * 30.0;
                double cr, cg, cb;
                vtex_hsv2rgb(h, 1.0, 1.0, &cr, &cg, &cb);
                texres->tr *= cr;
                texres->tg *= cg;
                texres->tb *= cb;
            }

           
            /*if(previbuf!=curibuf) {*/  /* interpolate */
            if(ideal_res != max_res) {  /* interpolate */
                /*vtex_boxsample(previbuf, minx, miny, maxx, maxy, &texr, imaprepeat, imapextend);*/
                vtex_boxsample(instance, power -1, minx, miny, maxx, maxy, &texr, imaprepeat, imapextend, tex);
               
                if (tex->vtex_tex_level)
                {
                    /* Apply hue color based on texture detail level */
                    double h = (power - 2) * 30.0;
                    double cr, cg, cb;
                    vtex_hsv2rgb(h, 1.0, 1.0, &cr, &cg, &cb);
                    texr.tr *= cr;
                    texr.tg *= cg;
                    texr.tb *= cb;
                }

               
                /*fx= 2.0f*(pixsize-maxd)/pixsize;
               
                if(fx>=1.0) {
                    texres->ta= texr.ta; texres->tb= texr.tb;
                    texres->tg= texr.tg; texres->tr= texr.tr;
                } else {
                    fy= 1.0f-fx;
                    texres->tb= fy*texres->tb+ fx*texr.tb;
                    texres->tg= fy*texres->tg+ fx*texr.tg;
                    texres->tr= fy*texres->tr+ fx*texr.tr;
                    texres->ta= fy*texres->ta+ fx*texr.ta;
                }*/
               
                /* Do mipmapping: interpolate final pixel from the sampled pixels */
                double max_weight = (double)(ideal_res - min_res) / (max_res - min_res);
                double min_weight = 1 - max_weight;
                texres->tr = texres->tr * max_weight + texr.tr * min_weight;
                texres->tg = texres->tg * max_weight + texr.tg * min_weight;
                texres->tb = texres->tb * max_weight + texr.tb * min_weight;
                texres->ta = texres->ta * max_weight + texr.ta * min_weight;

            }
        }
    }
ここからミップマップを使用しない場合の処理です。
バンプマップを使用する場合は法線計算のためvtex_boxsampleを3回呼び出しています。
バンプマップを使用しない場合、vectexのTex Level表示のための処理をしています。
    else {
        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 (minx < 0.5f / resolution) minx = 0.5f / resolution;
            if (miny < 0.5f / resolution) miny = 0.5f / resolution;

        }

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

            if (tex->vtex_tex_level)
            {
                /* Apply hue color based on image detail level */
                double h = (power - 1) * 30.0;
                double cr, cg, cb;
                vtex_hsv2rgb(h, 1.0, 1.0, &cr, &cg, &cb);
                texres->tr *= cr;
                texres->tg *= cg;
                texres->tb *= cb;
            }

        }
    }
ここで再び、フィールドレンダリング関連のコードをコメントアウトしています。
最後にvectexのメモリチェックの処理を追加しています。
...
    /*if( (R.flag & R_SEC_FIELD) && (ibuf->flags & IB_fields) ) {
        ibuf->rect-= (ibuf->x*ibuf->y);
    }*/
...
    /* Before returning, check if the memory limit has been reached */
    BLI_lock_thread(LOCK_IMAGE);
    if (tex->vtex_mem_max != 0 && instance->tile_memory > tex->vtex_mem_max << 20)
    {
        vtex_limit_tile_set(instance, tex->vtex_mem_max << 20);
    }
    tex->vtex_mem = (instance->tile_memory + 524288) >> 20;
    BLI_unlock_thread(LOCK_IMAGE);


    return retval;
}
vectex_osa()関数が終わったので、次はvtex_boxsample()関数を見てみます。
vectex_osa()関数の説明中に書いていますが、引数が元のboxsample()関数から変わっています。
修正している内容は、ImBufから取得していた画像サイズをpowerから計算したresolutionの値に置き換えているだけです。
(line:2048-)
static void vtex_boxsample(VtexInstance *instance, int power, float minx, float miny, float maxx, float maxy, TexResult *texres, int imaprepeat, int imapextend, Tex *tex)
{
...
   
    int resolution = 1 << power;


    rf= stack;
    /*rf->xmin= minx*(ibuf->x);
    rf->xmax= maxx*(ibuf->x);
    rf->ymin= miny*(ibuf->y);
    rf->ymax= maxy*(ibuf->y);*/
    rf->xmin= minx*(resolution);
    rf->xmax= maxx*(resolution);
    rf->ymin= miny*(resolution);
    rf->ymax= maxy*(resolution);


    texr.talpha= texres->talpha;    /* is read by boxsample_clip */
   
    if(imapextend) {
        /*CLAMP(rf->xmin, 0.0f, ibuf->x-1);
        CLAMP(rf->xmax, 0.0f, ibuf->x-1);*/
        CLAMP(rf->xmin, 0.0f, resolution-1);
        CLAMP(rf->xmax, 0.0f, resolution-1);

    }
    else if(imaprepeat)
        /*vtex_clipx_rctf_swap(stack, &count, 0.0, (float)(ibuf->x));*/
        vtex_clipx_rctf_swap(stack, &count, 0.0, (float) resolution);
    else {
        /*alphaclip= vtex_clipx_rctf(rf, 0.0, (float)(ibuf->x));*/
        alphaclip= vtex_clipx_rctf(rf, 0.0, (float) resolution);

        if(alphaclip<=0.0) {
            texres->tr= texres->tb= texres->tg= texres->ta= 0.0;
            return;
        }
    }

    if(imapextend) {
        /*CLAMP(rf->ymin, 0.0f, ibuf->y-1);
        CLAMP(rf->ymax, 0.0f, ibuf->y-1);*/
        CLAMP(rf->ymin, 0.0f, resolution-1);
        CLAMP(rf->ymax, 0.0f, resolution-1);

    }
    else if(imaprepeat)
        /*vtex_clipy_rctf_swap(stack, &count, 0.0, (float)(ibuf->y));*/
        vtex_clipy_rctf_swap(stack, &count, 0.0, (float) resolution);
    else {
        /*alphaclip*= vtex_clipy_rctf(rf, 0.0, (float)(ibuf->y));*/
        alphaclip*= vtex_clipy_rctf(rf, 0.0, (float) resolution);

        if(alphaclip<=0.0) {
            texres->tr= texres->tb= texres->tg= texres->ta= 0.0;
            return;
        }
    }
...

}
vtex_boxsample()関数から呼ばれている関数、vtex_boxsampleclip()です。
こちらも引数を変更しています。
関数の中の修正は、やはり画像サイズをresolutionに変更している部分です。
vtex_get_color()関数の呼び出しは、もともとはibuf_get_color()という名前の関数を呼び出しています。
この関数はImBufに格納されている画像のデータ領域からピクセルの色情報を取得する関数で、今回の修正ではこの部分をvectexのタイル画像からピクセル情報を取得するように修正しています。
(line:1962-)
static void vtex_boxsampleclip(VtexInstance *instance, int power, rctf *rf, TexResult *texres, Tex *tex)
{
...
    int resolution = 1 << power;
...
    /*if(endx>=ibuf->x) endx= ibuf->x-1;
    if(endy>=ibuf->y) endy= ibuf->y-1;*/
    if(endx>=resolution) endx= resolution-1;
    if(endy>=resolution) endy= resolution-1;

...
    if(starty==endy && startx==endx) {
        vtex_get_color(instance, &texres->tr, power, startx, starty, tex);
...
                vtex_get_color(instance, col, power, startx, y, tex);
...
                    vtex_get_color(instance, col, power, x, y, tex);
...
}
vtex_get_color()関数は元の関数を修正したというよりは、vtex_sample()関数から補間処理を省いて、引数を変更したものという感じになっています。
この関数の引数は、元のibuf_get_color()関数とvtex_sample()関数の両方を足して2で割ったような感じになっています。
(line:1719)
static void vtex_get_color(VtexInstance *instance, float *col, int power, int x, int y, Tex *tex)
{
     int premul;
     if (tex->vtex_imaflag & IMA_DO_PREMUL) {
         premul = 1;
     }
     else {
         premul = 0;
     }

    int resolution = 1 << power;
    y = resolution - y - 1;
    int tile_size  = (power < VTEX_TILE_SIZE_POWER) ? resolution : VTEX_TILE_SIZE;
   
    int index = vtex_get_tile(instance, x, y, power, tex);       
    unsigned char *tile = instance->tile_set[index].image;
    if (instance->tile_set[index].age == VTEX_CONST_COLOR)
    {   
        /* Constant color tile */
            if (premul) {
            int val= tile[3];
            col[2] = (float)(((tile[0]*val)>>8) / 255.0);
            col[1] = (float)(((tile[1]*val)>>8) / 255.0);
            col[0] = (float)(((tile[2]*val)>>8) / 255.0);
            col[3] = (float)(tile[3] / 255.0);
        }
        else {
            col[2] = (float)(tile[0] / 255.0);
            col[1] = (float)(tile[1] / 255.0);
            col[0] = (float)(tile[2] / 255.0);
            col[3] = (float)(tile[3] / 255.0);
        }
    }
    else
    {
        /* Clip coordinates to be inside the tile */
        x %= tile_size;
        y %= tile_size;

        /* Calculate pixel offset taking the border into account */
        int offset = (y * (tile_size + VTEX_TILE_BORDER) + x) * 4;
            if (premul) {
            int val= tile[offset + 3];
               col[2] = (float)(((tile[offset]*val)>>8)     / 255.0);
            col[1] = (float)(((tile[offset + 1]*val)>>8) / 255.0);
            col[0] = (float)(((tile[offset + 2]*val)>>8) / 255.0);
            col[3] = (float)(tile[offset + 3] / 255.0);
        }
        else {
               col[2] = (float)(tile[offset]     / 255.0);
            col[1] = (float)(tile[offset + 1] / 255.0);
            col[0] = (float)(tile[offset + 2] / 255.0);
            col[3] = (float)(tile[offset + 3] / 255.0);
        }
    }
}
vtex_boxsample()関数から呼ばれている他の関数vtex_clipy_rctf()、vtex_clipx_rctf()、vtex_square_rctf()、vtex_clipy_rctf_swap()、vtex_clipx_rctf_swap()については、引数まで含めて元の関数からの変更はありません。
ということは元の関数を使えばいいだけなのですが、imagetexture.cの中の関数はヘッダファイルなどで外部に公開されていないので、それを使えるようにするためにはヘッダファイルを新規に作成する必要があって、sconsの設定などにも影響が出る可能性まで考えるとかえって面倒なことになってしまいます。
posted by mato at 23:08| Comment(0) | Blender Vectex | このブログの読者になる | 更新情報をチェックする

2009年09月08日

テクスチャリピート、テクスチャノードへの対応

vectexにイメージテクスチャ相当の機能を追加する作業も終盤に近づいてきました。
今回は、テクスチャのリピート、テクスチャノードへの対応という2つの機能追加を行いました。
pic090908_01.jpg pic090908_02.jpg

さらに、ちょうどBlender2.49bがリリースされたばかりだったので、ベースとしているBlederのバージョンを2.49bに変更しました。

実行ファイルはこちらになります。
Linux 64bit

Linux 32bit


Windows 32bit

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


AGGバージョンも更新しています。
実行ファイルは以前の記事のURLからダウンロードできます。
AGGバージョンのソースファイルのパッチ(Blender_modified_patch090905.zip)


ここから今回の変更内容についてのソースコードの解説になります。

前回からの修正内容(changes090908.zip)
zipファイルを展開すると中に2つのdiffファイルが入っています。
ベースとしているBlenderのバージョンを2.49bに変更した関係で、前回からの修正内容をdiffファイルで出力しようとすると、Blender2.49aから2.49bへの差分情報も含まれることになってしまいました。
それを防ぐためにsvn diffコマンドを2回に分けて使用し、Blenderのバージョンアップの情報を含めないようにしました。

○テクスチャリピート
Blenderでイメージテクスチャを使用するとButtons Windowには「Image」パネルの他に「Map Image」というパネルが表示されます。
このMap Imageパネルの下半分を占めているのがテクスチャのリピート表示に関連するボタンです。
pic090908_03.jpg

このパネルのボタンのうち「Extend」「Clip」「Clip Cube」「Repeat」「Checker」の5つのトグルボタンでどれが有効になっているかによって、テクスチャ座標上での画像の表示のされ方が変わります。
これらのボタンで指定するのはテクスチャ座標で画像範囲の外側をどのように表示するかについてです。

パネルの一番下の「MinX」「MaxX」「MinY」「MaxY」の値をデフォルト状態から変更すると、テクスチャの張られている面よりも表示される画像を小さくすることができ、その場合に画像の外側の表示は、
  1. Extendであれば画像の一番外側のピクセルを延長する。
  2. Clip,Clip Cubeならアルファ値を0としてベースのマテリアルを表示する。
  3. Repeatでは画像の範囲外にも同じ画像を繰り返し表示する。
  4. CheckerではRepeatのように画像を繰り返す部分とClipのようにベースのマテリアルを表示する部分を交互に表示する。
というようになります。
pic090908_04.jpg

今回、Blenderのソースコードで、この処理に関連する部分をvectexでも使えるように修正しました。

まずは、ユーザーインターフェイスのパネルを用意する必要があります。
blender/source/blender/src/buttons_shading.c (line:4676-)
void texture_panels()
{
...
            case TEX_VECTEX:
                texture_panel_vectex(tex);
                texture_panel_image_map(tex, mtex);
                break;
            }
        }
    }
}
これで、Map Imageパネルをvectexパネルの横に表示させることができます。
このとき、イメージテクスチャのときと同じようにMap Imageパネルの表示される位置をvectexパネルの左にするためには、vectexパネルを描画するコードを修正する必要があります。
表示される位置のX座標を640から960に変更しています。
(line:800-)
static void texture_panel_vectex(Tex *tex)
{
...
    if(uiNewPanel(curarea, block, "vectex", "Texture", 960, 0, 318, 204)==0) return;
...
}
Map Imageパネルをそのまま表示してしまうとvectexでは使用しないボタンまで表示されてしまいますので、tex->typeがTEX_VECTEXのときには必要ない部分を表示しないようにMap Imageパネルの描画コードを修正します。
(line:1376-)
static void texture_panel_image_map(Tex *tex, MTex *mtex)
{
...
    /* types */
    if (tex->type==TEX_IMAGE) {
        uiBlockBeginAlign(block);
        uiDefButBitS(block, TOG, TEX_MIPMAP, B_IMAGECHANGED, "MipMap",    10, 180, 75, 20, &tex->imaflag, 0, 0, 0, 0, "Generates and uses mipmaps");
        ...
        uiBlockEndAlign(block);
    }       
...
}

以上の変更でMap Imageパネルを表示できるようにした時点で、実はテクスチャのリピート機能のうちの半分は使えるようになってしまいます。
前々回の修正でvectex()関数に渡されるテクスチャ座標をイメージテクスチャと同じになるようにしていますが、そこで渡されるテクスチャ座標の計算の処理の中にテクスチャのリピートの処理も含まれています。
具体的には、次の部分でその計算が行われています。
blender/source/blender/render/intern/source/texture.c (line:1905-)

static void do_2d_mapping(MTex *mtex, float *t, VlakRen *vlr, float *n, float *dxt, float *dyt)

do_2d_mapping()関数では、テクスチャ座標で「Flat」「Cube」「Tube」「Sphere」のうちのどれを選択しているかに応じた座標計算、Map ImageパネルでRepeatを選択した場合の座表計算、同じくMap ImageパネルでMinX、MaxX、MinY、MaxYを指定したときの座表計算が行われています。

この計算ではテクスチャ座標texvec(この関数の引数ではfloat *tとなっている)の計算の他に、dxt、dytの値も計算されているので、vectexでテクスチャの解像度の計算をするときにも問題ないようです。

あとは、Map ImageパネルでExtend、Clip、Clip Cube、Checkerを選んだ場合の処理です。
これらについては、ソースコードの次の部分に処理が書かれています。

blender/source/blender/render/intern/source/imagetexture.c

int imagewrap(Tex *tex, Image *ima, ImBuf *ibuf, float *texvec, TexResult *texres) (line:104-)
int imagewraposa(Tex *tex, Image *ima, ImBuf *ibuf, float *texvec, float *dxt, float *dyt, TexResult *texres) (line:629-)

これらの関数は、基本的には同じ処理をしていてOSAが有効なときはimagewraposa()関数が呼ばれ、無効な時はimagewrap()関数が呼ばれます。
これらの関数内のテクスチャリピートに関連する部分を探して、vectex()関数の中にも同じような動作をするようにコードを追加します。
blender/source/blender/render/intern/source/texture.c (line:1264-)
static int vectex(Tex *tex, float *texvec, float *dxt, float *dyt, int osatex, TexResult *texres)
{
...
    texres->tin= texres->ta= texres->tr= texres->tg= texres->tb= 0.0f;
...
    float fx, fy;
   
    fx= texvec[0];
    fy= texvec[1];

    if(tex->extend == TEX_CHECKER) {
        int xs, ys;
       
        xs= (int)floor(fx);
        ys= (int)floor(fy);
        fx-= xs;
        fy-= ys;

        if( (tex->flag & TEX_CHECKER_ODD)==0) {
            if((xs+ys) & 1);else return retval;
        }
        if( (tex->flag & TEX_CHECKER_EVEN)==0) {
            if((xs+ys) & 1) return retval;
        }
        /* scale around center, (0.5, 0.5) */
        if(tex->checkerdist<1.0) {
            fx= (fx-0.5)/(1.0-tex->checkerdist) +0.5;
            fy= (fy-0.5)/(1.0-tex->checkerdist) +0.5;
        }
    }
   
    int resolution = 1 << power;
    int x = (int)(fx * resolution);
    int y = (int)(fy * resolution);
    if(tex->extend == TEX_CLIPCUBE) {
        if(x<0 || y<0 || x>=resolution || y>=resolution || texvec[2]<-1.0 || texvec[2]>1.0) {
            return retval;
        }
    }
    else if( tex->extend==TEX_CLIP || tex->extend==TEX_CHECKER) {
        if(x<0 || y<0 || x>=resolution || y>=resolution) {
            return retval;
        }
    }

...
}

tex->extend == TEX_CHECKERのときの処理は、imagewrap()関数のものをそのままコピーアンドペーストしただけです。
その次の部分でresolutionの値を計算してfx、fyに掛けていますが、元のコードでは画像のサイズをImBuf構造体から取得してその値を使用しています。
imagewrap()関数の中では、if(tex->extend == TEX_CLIPCUBE)、else if( tex->extend==TEX_CLIP || tex->extend==TEX_CHECKER)の後に、さらに続いてif(tex->extend==TEX_EXTEND)の処理がありますが、その部分はvectex()関数の中ではなくてvtex_sample()関数の中に移しました。

この部分の処理をどうするべきか試行錯誤している間にvtex_sample()関数の引数でtexvecを2つのfloat値からfloat値の配列に変更しました。
そのためvectex()関数の中のvtex_sample()関数の呼び出しの部分を書き換えていますが、最終的にはその変更は今回の機能追加に直接関係がなかったため上の引用部分では省略しています。

vtex_sample()関数の中では、次の様にソースコードを修正しています。
(line:1068-)
void vtex_sample(VtexInstance *instance, float *texvec, int power, int interp,
            double *pr, double *pg, double *pb, double *pa, Tex *tex)
{
...
    if (interp == 0)
    {
    ...
        /* extend: copy the color of edge pixels */
        /*x = CLAMPIS(x, 0, resolution - 1);
        y = CLAMPIS(y, 0, resolution - 1);*/
        /* clip: alpha=0 outside edges */
        /* repeat: duplicate horizontally and vertically */
       
        if(tex->extend==TEX_EXTEND) {
            if(x>=resolution) x = resolution-1;
            else if(x<0) x= 0;
        }
        else {
            x= x % resolution;
            if(x<0) x+= resolution;
        }
        if(tex->extend==TEX_EXTEND) {
            if(y>=resolution) y = resolution-1;
            else if(y<0) y= 0;
        }
        else {
            y= y % resolution;
            if(y<0) y+= resolution;
        }

    ...
    }
    else
    {
        /* 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 = texvec[0] * resolution - 0.5;
        double v = (1.0 - texvec[1]) * resolution - 0.5;
        int x = (int)u;
        int y = (int)v;
        double u_ratio = u - x;
        double v_ratio = v - y;
        if (u_ratio < 0.0) {
            u_ratio = 0.0;
        }
        if (v_ratio < 0.0) {
            v_ratio = 0.0;
        }
        if (u_ratio > 1.0) {
            u_ratio = 1.0;
        }
        if (v_ratio > 1.0){
            v_ratio = 1.0;
        }

        double u_opposite = 1 - u_ratio;
        double v_opposite = 1 - v_ratio;

        /* extend: copy the color of edge pixels */
        /*x = CLAMPIS(x, 0, resolution - 1);
        y = CLAMPIS(y, 0, resolution - 1);*/
        /* clip: alpha=0 outside edges */
        /* repeat: duplicate horizontally and vertically */

        if(tex->extend==TEX_EXTEND) {
            if(x>=resolution) x = resolution-1;
            else if(x<0) x= 0;
        }
        else {
            x= x % resolution;
            if(x<0) x+= resolution;
        }
        if(tex->extend==TEX_EXTEND) {
            if(y>=resolution) y = resolution-1;
            else if(y<0) y= 0;
        }
        else {
            y= y % resolution;
            if(y<0) y+= resolution;
        }

...
}

vectexの作者のmgmalheirosさんは、コメントでextend以外の場合にどう処理をするべきかを書いていますので、将来的に機能を拡張する予定だったようです。
ここで、vectexの元のコードではCLAMPマクロを使ってx、y座標の値を0、resolution-1の範囲に収まるようにしていました。
これがBlenderのExtendボタンを押した状態に相当するので、このソースコードをそのまま利用することもできるのですが、今回はimagewrap()関数の中にあったソースコードに置き換えることにしました。
Interpolボタンを押したときの処理ではx、yの値以外にもu_ratio、v_ratio、u_opposite、v_oppositeなどの値を使用しているため、これらの値も範囲内に収まるように修正しました。

テクスチャリピートの機能については、以上のような感じです。

○テクスチャノード
今回はノードエディタの中に、vectexのための専用のパネルを作っていません。
テクスチャノードでvectexを使用するためには、テクスチャノードのメニューで「Add」->「Input」->「Texture」を実行してTextureノードを作成し、そのノードのメニューからあらかじめ作成しておいたvectexを使用したテクスチャを選択する、という手順が必要です。
pic090908_05.jpg pic090908_06.jpg

vectexをテクスチャノードに対応させるためには、まずテクスチャノードでdxt、dytの値を使用できるようにする必要があります。
Blender2.5の方では少し前にテクスチャノードでdxt、dytを使えるようにする修正が加えられました。
以下はその部分のsvn logコマンドの出力です。
------------------------------------------------------------------------
r22583 | kakbarnf | 2009-08-18 05:30:11 +0900 (火, 18  8月 2009) | 3 lines

Slight refactor of texture nodes.

Delegates now receive a TexParams* instead of float *coords. This gives texture nodes access to dxt, dyt, cfra as well as coords. This fixes the time node and allows nice sampling to be implemented.

今回はこの部分の修正をBlender2.5から持ち込んでみました。
この部分のソースコードの修正内容の詳細についてはかなり長くなるので説明は省略します。

ただ、この修正を行っただけではvectexの解像度計算ができるようになりませんでした。
原因を調べると、実際にvectex()関数を呼び出す処理を行っているmultitex()関数、それを呼び出すmultitex_thread()関数、それを呼び出すmultitex_ext()関数、それを呼び出すcolorfn()関数というようにさかのぼって、ようやく理由が分かります。
blender/source/blender/nodes/intern/TEX_nodes/TEX_texture.c (line:43-)
static void colorfn(float *out, TexParams *p, bNode *node, bNodeStack **in, short thread)
{
...
        textype = multitex_ext(nodetex, coord, p->dxt, p->dyt, p->osatex, &texres);
        /*textype = multitex_ext(nodetex, coord, 0, 0, 0, &texres);*/
...
}

この部分でdxt、dytのポインタ値を0として処理しているために、せっかくTexParamsに保存するようにしたdxt、dytの値が最終的に呼び出されるvectex()関数まで届いていませんでした。

Blender2.5でのテクスチャノードでのdxt、dytの拡張は私が使いたかったこととは別の理由で使われているようです。
ちなみに、もともとはdxt、dytのみの拡張でしたが、vectexではそれ以外にもosatexの値も必要なため追加してあります。

blender/source/blender/nodes/intern/TEX_util.h (line:75-)
typedef struct TexCallData {
    TexResult *target;
    float *coord;
    float *dxt, *dyt;
    int osatex;
    char do_preview;
    short thread;
    short which_output;
     int cfra;
} TexCallData;

typedef struct TexParams {
    float *coord;
    float *dxt, *dyt;
    int osatex;
    int cfra;
} TexParams;

...
void ntreeTexExecTree(bNodeTree *nodes, TexResult *texres, float *coord, float *dxt, float *dyt, int osatex, char do_preview, short thread, struct Tex *tex, short which_output, int cfra);
blender/source/blender/blenkernel/BKE_node.h (line:427)
void ntreeTexExecTree(struct bNodeTree *ntree, struct TexResult *target, float *coord, float *dxt, float *dyt, int osatex, char do_preview, short thread, struct Tex *tex, short which_output, int cfra);
blender/source/blender/nodes/intern/TEX_util.c
(line:124-)
void params_from_cdata(TexParams *out, TexCallData *in)
{
    out->coord = in->coord;
    out->dxt = in->dxt;
    out->dyt = in->dyt;
    out->osatex = in->osatex;
    out->cfra = in->cfra;
}
(line:133-)
void tex_do_preview(bNode *node, bNodeStack *ns, TexCallData *cdata)
{
...
    float dxt[3] = {0.0, 0.0, 0.0};
    float dyt[3] = {0.0, 0.0, 0.0};
...
    params.dxt = dxt;
    params.dyt = dyt;
    params.osatex = 0;
...
}
(line:214-)
void ntreeTexExecTree(
     bNodeTree *nodes,
     TexResult *texres,
     float *coord,
     float *dxt, float *dyt,
    int osatex,
     char do_preview,
     short thread,
     Tex *tex,
     short which_output,
     int cfra
) {
    TexResult dummy_texres;
    TexCallData data;
   
    if(!texres) texres = &dummy_texres;
    data.coord = coord;
    data.dxt = dxt;
    data.dyt = dyt;
    data.osatex = osatex;
    data.target = texres;
    data.do_preview = do_preview;
    data.thread = thread;
    data.which_output = which_output;
   
    ntreeExecTree(nodes, &data, thread);
}

blender/source/blender/render/intern/source/texture.c
(line:720-)
static int evalnodes(Tex *tex, float *texvec, float *dxt, float *dyt, int osatex, TexResult *texres, short thread, short which_output)
{
    short rv = TEX_INT;
    bNodeTree *nodes = tex->nodetree;
   
    ntreeTexExecTree(nodes, texres, texvec, dxt, dyt, osatex, 0, thread, tex, which_output, R.r.cfra);
   
    if(texres->nor) rv |= TEX_NOR;
    rv |= TEX_RGB;
    return rv;
}

(line:2120-)
static int multitex(Tex *tex, float *texvec, float *dxt, float *dyt, int osatex, TexResult *texres, short thread, short which_output)
{
...
    if(tex->use_nodes && tex->nodetree) {
        retval = evalnodes(tex, texvec, dxt, dyt, osatex, texres, thread, which_output);
    }
...
}

テクスチャノードを使用しているときには、tex->typeがTEX_VECTEXになっていない状態でテクスチャ座標の計算などが行われます。
この場合、そのままではosatexに正しい値が入りません。
これに対応するためには、以前tex->typeがTEX_VECTEXの場合にosatexに正しい値が入るように修正した場所に再度修正を加える必要があります。
blender/source/blender/blenkernel/intern/material.c (line:639-)
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;
                if (mtex->tex->use_nodes && mtex->tex->nodetree) ma->texco |= TEXCO_OSA;
            }
...
}

前々回の修正でテクスチャ座標でReflを選択したときに、tex->typeがTEX_VECTEXの場合にdxt、dytの値を補正していましたが、その条件式にテクスチャノードが使用されている場合も含める必要があります。
blender/source/blender/render/intern/source/texture.c
(line:2453-)
void do_material_tex(ShadeInput *shi)
{
...
                if ((tex->type==TEX_VECTEX) || (tex->use_nodes && tex->nodetree)) {
...
}

同じような修正が、前回修正したスカイテクスチャ、ランプテクスチャ、ヘイローテクスチャの処理の部分でも必要です。
(line:3019-)
void do_halo_tex(HaloRen *har, float xn, float yn, float *colf)
{
...
        if ((mtex->tex->type==TEX_VECTEX) || (mtex->tex->use_nodes && mtex->tex->nodetree)) {
...
}

(line:3162-)
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_VECTEX) || (mtex->tex->use_nodes && mtex->tex->nodetree)) {
...
                if (((mtex->tex->type==TEX_VECTEX) || (mtex->tex->use_nodes && mtex->tex->nodetree)) && (dxyview)) {
...
                    if (((mtex->tex->type==TEX_VECTEX) || (mtex->tex->use_nodes && mtex->tex->nodetree)) && (dxyview)) {
...
                    if (((mtex->tex->type==TEX_VECTEX) || (mtex->tex->use_nodes && mtex->tex->nodetree)) && (dxyview)) {
...
}

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) || (la->mtex[c]->tex->use_nodes && la->mtex[c]->tex->nodetree)) lar->mode |= LA_OSATEX;
                }
            }
...
}

テクスチャノードの機能をテストしているときに、ノードを使ってスケールを変更するとvectexの解像度計算が正しくならないことに気がつきました。
他の操作でも同じようなことが起こる可能性がありますが、とりあえず気がついたスケールノードのソースコードについてだけ修正をしておきました。
blender/source/blender/nodes/intern/TEX_nodes/TEX_scale.c (line:43-)
static void colorfn(float *out, TexParams *p, bNode *node, bNodeStack **in, short thread)
{
    float scale[3], new_coord[3];
    float new_dxt[3], new_dyt[3];
    TexParams np = *p;
    np.coord = new_coord;
   
    tex_input_vec(scale, in[1], p, thread);
   
    new_coord[0] = p->coord[0] * scale[0];
    new_coord[1] = p->coord[1] * scale[1];
    new_coord[2] = p->coord[2] * scale[2];
   
    np.dxt = new_dxt;
    np.dyt = new_dyt;
    new_dxt[0] = p->dxt[0] * scale[0];
    new_dxt[1] = p->dxt[1] * scale[1];
    new_dxt[2] = p->dxt[2] * scale[2];
    new_dyt[0] = p->dyt[0] * scale[0];
    new_dyt[1] = p->dyt[1] * scale[1];
    new_dyt[2] = p->dyt[2] * scale[2];
   

    tex_input_rgba(out, in[0], &np, thread);
}
テクスチャノードについての修正は、以上のような感じです。
posted by mato at 04:40| Comment(0) | Blender Vectex | このブログの読者になる | 更新情報をチェックする

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

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