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年以上新しい記事の投稿がないブログに表示されております。