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 | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
×

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