2010年04月

2010年04月28日

背景ポリゴンに食い込んでいるポイントを選択 modo 401 SP3

明日からゴールデンウィークだな。

今回はちょっとしたスクリプトを作ってみた。それは、背景のポリゴンメッシュに食い込んでいるポイントを選択するものだ。下の画像はクリックすると大きくなるよ。

fig01

動作原理は最初に選択したポイントを元にそこからエッジを次々探して、エッジが背景のポリゴンと交差したらそこから先のポイントは前のポイントと反対側に行ったものとして仕分けしていく。選択されるのは最初に選択されたポイントと反対側のポイントだ。

今のところ1つのエッジが2枚以上のポリゴンと交差しちゃう場合は考えていない。それと交差するポリゴンは三角形に分解しているので、平らじゃない多角形ポリゴンについての判定は実際の表示と異なってくるし、SDSにも対応していない。

それではまた次回。

次の定期更新は5月6日の予定。

modoカテゴリー別ページ  



take_z_ultima at 11:30|この記事のURLComments(0)TrackBack(0)modo | CG

2010年04月27日

Maxのライトを調べてみた その11 3ds Max 2010

前回に引き続き「環境効果&効果」ロールアウトだ。

ボリュームライトは下の画像のようにライトの中に影を落とせる。

fig01

これが出来る影のモードは下の通り。スポットライトと指向性ライトで可能だ。

  スキャン
ライン
mental
Ray
アドバンスドレイトレース × −
mental ray
シャドウ
マップ
× ○
エリア
シャドウ
× −
シャドウ
マップ
○ ○
レイトレース
シャドウ
× ○

このライトの中に現れるシャドウについてのクオリティの調整は、環境と効果パネルの「ボリューム」ロールアウトにある。

fig02

「サンプルボリューム%」はボリュームライトの中で何回影をサンプリングするかを決める値だ。下のGIFアニメはサンプルボリューム%を1〜29まで変化させた時の影の様子だ。

fig03

この画像から推測されるのは、ボリュームライトの中にサンプルボリューム%で設定した数だけ視線に垂直で視点からの距離がボリュームライトの照射している奥行きの範囲で分布する仮想的な平面を作成して、そこにシャドウマップの影を生成して重ね合わせる感じかな。下の画像は右にあるのがカメラで黄色がスポットライトのライトボリュームで、その中にある球体の影がグレーの部分。点線の部分が空間をスライスする仮想の平面。この平面にグレーの部分の断面形状の影が生成されて、重ね合わされ、立体的な影としてレンダリングされるっぽい。

fig11

数が多いほど滑らかで精度が高い影が出るわけだけど、そのぶんだけレンダリングに時間がかかるようになる。

このままだと綺麗な影を出すのに計算量が増えてしまうので、それを補うためにフィルターをかける事が出来る。フィルタシャドウの「低・中間・高・ライトサンプル範囲を使用」はフィルターのかけ方を切り替えるスイッチだ。

「低」はフィルターなし。下の画像はサンプルボリューム%を4にしたものだ。影の境界にジャギーが出ている。

fig04

「中間」はシャドウのイメージがピクセルごとに平均化される。

fig05

マニュアル原文には

This produces a very significant improvement in cases where you’re getting banding types of artifacts.

って書いてあって、下の画像のような帯状のアーティファクトの模様が影に現れちゃう時に設定すると効果があるそうだ。

fig09

これが「中間」にしてみた結果。完全に消すには至って無いけど、輪郭がぼやけて段々が多少目立たなくなっている。もちろんサンプルボリューム%の数値を上げればより綺麗になるよ。

fig10

ちなみに日本語マニュアルには

人工品のバンディング タイプを得る場合、これによってかなりよい結果が得られます。

って書いてあったよ。これ読んでわかったら凄いw

「高」についてはマニュアル原文には

Adjacent pixels and the diagonal pixels are sampled, and each are given different weights.

と書いてある。日本語マニュアルには

近接したピクセルと対角のピクセルがサンプリングされ、各々が異なった重さになります。

と書いてある。どういう意味だろう?このモードにすると、低や中間で出ていた空中に置いた平面に落ちた影が何枚も重なっているような感じの影は出ない。

fig06

どうやらその幾重にも重なった影の輪切りの平面の対応するポイントを繋いだ線でグラデーションをかけて影を出すのがこのモードのようだ。下の画像のグレーの部分が仮想平面に生成した影。そして青い点がそれぞれの平面上の影の対応する位置。この対応している2点間の影を補完処理すれば、その間に仮想平面を増やさなくてもボリュームシャドウは滑らかに繋がる事になる。

fig12


たぶんこんなとこじゃないかな。あくまで想像なんだけどね。

fig08「範囲を使用」は基本的に「中間」と同じような動作だけど、ブラーで平均する範囲をライトのモディファイヤパネルにあるシャドウの「サンプル範囲」を使って決める。サンプル範囲が大きくなればそれだけ影の輪郭がぼやける。

このモードは影の輪郭がぼやけるし、意外とレンダリングに時間がかかるなぁ。「中間」モードではサンプル範囲が指定できなかったからそれを補うような感じで使えばいいのかな。

fig07

試してみた感想から言えば、「高」にするのが一番速くて綺麗な感じがするね。ただし減衰が入って来たりするとどうなるんだろう?

とか思ったけど時間が無くなったのでまた次回。

maxまとめページ 



take_z_ultima at 11:30|この記事のURLComments(0)TrackBack(0)3ds Max | CG

2010年04月26日

裏面のポリゴン選択設定 modo 401 SP3

3DTVをヨドバシカメラで見てきた。なかなかの臨場感だねぇ。でも目のピント調整機能とか被写界深度なんかが左右の視差と齟齬を生じるせいなのか、最初のうちは違和感があるね。慣れてくると気にならなくなってくるけど。
自分としては部屋の壁全面に雄大な風景を表示してみたいなぁ。

さて、シャツの襟みたいに生地の裏側が表に出てくるような形状のポリゴンを編集する場合、ポリゴンを裏側から選択したり出来ないとちょっとめんどくさい。そんな時のために初期設定パネルの入力:動作設定に「ポリゴンの選択」と「両面ポリゴン選択」のオプションがある。この設定はなんとなくはわかるんだけど、実際どんなもんなのかが判然としないところがある。

fig01

そこでひとつずつ切り替えてどう選択できるか試してみたのが下の表だ。左の欄は(ポイント・エッジ・ポリゴン)選択モード、それぞれワイヤーフレーム表示とシェーディング表示の時について調べてみている。ただし後から出てくるけど、この表が有効なのはサブディビジョンサーフェスモードがOFFの時だけだ。

  ポリゴン
選択
ワイヤー シェード



表面と
ワイヤー
フレーム

 表・裏・影  表
表面と裏面  表・裏・影  表・裏
表面のみ  表  表
常に
レイキャスト
 表  表


表面と
ワイヤー
フレーム
 表・裏・影  表
表面と裏面  表・裏・影  表・裏
表面のみ  表  表
常に
レイキャスト
 表  表



表面と
ワイヤー
フレーム
表・裏・影
(エッジ)

(ポリゴン)
表面と裏面 表・裏・影
(エッジ)
表・裏
(ポリゴン)
表面のみ
(エッジ)

(ポリゴン)
常に
レイキャスト

(ポリゴン)

(ポリゴン)

 

  • 表:表側から選択できた
  • 裏:裏側から選択できた
  • 影:手前にポリゴンがあっても後ろにあるエレメントが選択できた

fig02

  • (エッジ):エッジを選択することで選択できた

    fig04


  • (ポリゴン):ポリゴンの内側をクリックすることで選択できた

    fig03

実はいろいろやっているうちにどんどん混乱してきて、「表面のみ」モードを検証してたら下のGIFアニメのようにワイヤーフレーム時に裏側や影の部分が選択できたり出来なかったり、なんて事が起きたりした。
下のGIFアニメは「表面のみ」にして上が開いた立方体と球体を同じレイヤに並べて、カーソルを動かしてみたものなんだけど、立方体の向こう側の面は裏側で選択出来ないけど、球体は両面とも選択出来ている。
この違いはどこから来ているんだろう。
で、気が付いたのは球体がデフォルトでSDSだってこと。どうやらSDSの場合は「表面のみ」は通用しないみたいだ。TABキーを押してSDSを解除したら球体の裏側は選択出来なくなった。

fig06

気軽な気持ちで始めてみたけど思わぬ事態に遭遇して何度も表を書き換えてるうちに時間がなくなっちゃったよ。ツール類との組み合わせを含めてもう少し検証してみないと、これらのオプションの本当のところは掴みきれない感じだ。

それではまた次回。

modoカテゴリー別ページ  



take_z_ultima at 11:30|この記事のURLComments(0)TrackBack(0)modo | CG

2010年04月23日

Maxのライトを調べてみた その10 3ds Max 2010

fig01今回は標準ライトの環境効果&効果ロールアウトだ。

標準状態でここに追加出来るのはボリュームエフェクトとレンズエフェクトだけだ。

fig02

ボリュームエフェクトはライトのチンダル現象を模したもので、ONにするとライトの経路が光って見えるようになる。

fig04

レンズエフェクトはライトの光条やリングなど、光源が映りこんだ時にレンズに生じる効果を模した画像を生成する。

fig05

「追加」ボタンを押してボリュームライトを選ぶとライトの効果としてボリュームライトが設定される仕組みだけど、裏では「環境と効果」パネルの効果のリストに「ボリュームライト」が追加され、その効果の対象としてそのライトが追加される仕組みになっている。だからライトの方のパネルでボリュームライトを追加するたびに「環境と効果」パネルの方のリストには1回ごとボリュームライトが追加される。
この対象となるライトは1つのボリュームライト効果に対して複数指定が出来る。だからボリュームライト効果を削除すると複数のライトに影響が出ることも考えられる。
そこでライトの方のパネルの「削除」ボタンは、そのライトをボリュームライトの効果対象から除くだけで、「環境と効果」パネルに追加されたボリュームライトの効果自体は削除しない。
なので、ライト側の方で追加と削除を繰り返すと、環境と効果パネルの方ではどんどんボリュームライトが増えちゃうから気をつけてね。

fig06

ライトのパネルでボリュームライトを追加した後、リストに追加されたボリュームライトを選んで「設定」ボタンを押すと「環境と効果」パネルが開いてボリュームライトの調整が出来るようになる。

fig03

フォグカラーはボリュームライトの色を決める。そして減衰カラーは近接減衰と遠方減衰の色を決める。この色が有効になるためにはライトの近接減衰か遠方減衰が有効になっていて、さらにこのパネルの「減衰カラーを使用」がONになってないとならない。

fig07

そして減衰カラーの強度は「密度」と「減衰乗数」によって決まる。下のGIFアニメはフォグカラーを白、減衰カラーを青、「密度」を2、「減衰乗数」を0〜30で変化させたものだ。「減衰乗数」の数値を上げると減衰カラーがハッキリしてくる。

fig08

また、減衰カラーはフォグカラーとも密接な関係があって、下の画像は減衰カラーを青にしてフォグカラーを白→赤→緑→青→白に切り替えてみたものだ。ここからわかる事は減衰色の成分がフォグカラーの成分に無い時は減衰の色が出ないってことだ。だから減衰色はフォグカラーの一番減衰されずに残る色を決めるフィルターだと考える事も出来る。そしてそれをどのくらい減衰させないで残すかは減衰乗数で決まるわけだ。減衰カラーの効果はフォグカラーがRGB全成分を持っている白だと計算しやすいね。

fig09

マニュアルには「フォグカラーが赤で減衰カラーが緑だと紫の影が〜」みたいに書いてあるけど、無理だよな・・・。

フォグはライトから離れるほど密度が高くなり、ライトの方で光量の減衰の設定が無ければ、フォグによるグローはどんどん強くなる。それだとちょっとライトから離れたらフォグによるグローがとんでもない事になってしまうのでそれを防ぐために設けた上限値が「最大ライト%」だ。デフォルトでは90%らしい。
そして下限値が「最小ライト%」でこれは0より大きくするとライトが当たってない部分まで底上げされてフォグが出るようになる。これはシーン全体に対して起こるので、無限に見渡せるところをレンダリングすると、フォグカラーと同じ明るさになってしまう。だからこれが使えるのは何らかの壁やオブジェクトがあって無限遠まで見通せないようなシーンだけだ。

下のGIFアニメは最小ライト%を2にして背景に板を一枚置いてそれを遠ざけてみたものだ。壁がちょっと遠ざかると周りが真っ白になって全部飽和しているのが確認できる。ちょっとでも最小ライト%の値が0を超えるとこういうことになるので、背景に何も無い場合は0以外ダメだよ。

fig10

今日はちょっと時間が足りなくなったのでまた来週。

maxまとめページ 



take_z_ultima at 11:30|この記事のURLComments(0)TrackBack(0)3ds Max | CG

2010年04月22日

modoからMaxのOLEサーバーにアクセスする その7 modo 401 SP3 3dsMax 2010

前回の続きでコードの覚え書きだ。

このプログラムはAutoWrapを介してMAXの公開されたMAXScriptファンクションに引数としてVARIANTの配列を渡して、戻り値としてVARIANTのデータを貰ってくる構造になっている。そのためLuaから送られてきた引数をVARIANTの配列にまとめてAutoWrapに渡し、戻ってきたデータをVARIANTからluaのデータに変換してLuaスクリプトに戻してやる必要がある。

前回はluaの型とそれを調査するファンクション、VARIANTの型、メンバー変数名の対応を書いたけど変換ファンクション書き忘れてたので再掲。

luaの型:ブーリアン
調査ファンクション:lua_isboolean( )
変換ファンクション:lua_toboolean( )
VARIANTタイプ:VT_BOOL 
VARIANTの変数名:boolVal 
データの型:short

luaの型:数値
調査ファンクション:lua_isnumber( )
変換ファンクション:lua_tonumber( )
VARIANTタイプ:VT_R8
VARIANTの変数名:dblVal
データの型:double

luaの型:文字列
調査ファンクション:lua_isstring( )
変換ファンクション:lua_tostring( )
VARIANTタイプ:VT_BSTR
VARIANTの変数名:bstrVal
データの型:BSTR

luaの型:テーブル
調査ファンクション:lua_istable( )
VARIANTタイプ:VT_ARRAY|VT_VARIANT 
VARIANTの変数名:parray
データの型:SafeArray

この中でテーブルだけは特殊でデータの入れ物なので対応するSafeArrayという入れ物を作ってさらにテーブルの中身をVARIANT型に変換してSafeArrayに入れてやる必要がある。

SafeArrayを作るにはSafeArrayCreate( )を使う。

WINOLEAUTAPI_(SAFEARRAY *)
 SafeArrayCreate(
   __in VARTYPE vt,
   __in UINT cDims,
   __in SAFEARRAYBOUND * rgsabound
 )

vtはSafeArrayに入れるデータの型で今回はVT_VARIANT。
cDimsはSafeArrayの次元数。
rgsaboundは各次元の一番小さいインデックス番号と要素数の配列。

次元数と要素数はluaから渡ってくるテーブルを調べないとわからないのでまずその調査をする。それがlen_table( )で、Luaステータス L と調査したいテーブルが入ってるスタックのインデックス index 、調査する次元 level  、テーブルの最大次元数 maxlevel 、各次元の要素数の配列 dims を渡して呼び出すと、テーブルの最大次元数と要素数の配列が返って来る。

void len_table(lua_State *L,int index,int level,int *maxlevel,int **dims){
   int count=0;
   bool table_exist=false;
   lua_pushnil(L);
   while (lua_next(L, index) != 0) {
      ++count;
      if(lua_istable(L,-1)){
         table_exist=true;
         len_table(L,lua_gettop(L),level+1,maxlevel,dims);
      }
      lua_pop(L, 1);
   }
   if((*dims==NULL)||((!table_exist)&&(*maxlevel<level))){
      if(*maxlevel<level) *maxlevel=level;
      if(*dims!=NULL) free(*dims);
      *dims=(int *)malloc((level+1)*sizeof(int));
      ZeroMemory(*dims,(level+1)*sizeof(int));
   }
   if((*dims)[level]<count) 
      (*dims)[level]=count;
   return;
}

luaのテーブルはスタックトップにキーを積んでlua_next(Luaステータス,テーブルが入ってるスタックインデックス )を呼び出すと、積んだキーの次のキーのデータとキーのペアがスタックトップ(−1)と次のスタック(−2)に積まれる。
スタックトップをポップして消してスタックトップにキーを持って来て再びlua_next( )を呼び出すとさらに次のキー:データのペアがスタックに現れる。これを繰り返して行けばテーブル内の要素全てを順に呼び出す事が出来る。要素が全て無くなったらlua_next( )は0を返すし、最初のキーはスタックトップにnilを積んでlua_next( )を呼び出せば出てくる。
len_table( )はこの仕組みを使って次元数と要素数を数えていくんだけど、テーブルの中にさらにテーブルがあるような入れ子構造になっているので、その探索を自分自身を呼び出す再帰的な方法で行っている。

下のコードはindexで示したスタックにあるテーブルの要素数を変数countで数え、要素の中にテーブルがあれば、そのテーブルに対してさらにlen_table( )を呼び出してそのテーブルの要素をカウントさせる。現在カウントしているテーブルの次元は変数levelでわかるようになっていて、len_table( )を呼ぶ時に今のLevel+1を渡すことで次元が切り替わるようにしている。そして数えた要素数は配列dimsの該当する場所に格納する。

 int count=0;
 bool table_exist=false;
 lua_pushnil(L);
 while (lua_next(L, index) != 0) {
    ++count;
    if(lua_istable(L,-1)){
       table_exist=true;
       len_table(L,lua_gettop(L),level+1,maxlevel,dims);
    }
    lua_pop(L, 1);
 }

とは言え、テーブルの次元数がいくつになるかわからないので配列のサイズがわからないからあらかじめ用意するわけにも行かない。 len_table( )は要素の中にテーブルがあるたびに次々呼び出されていくから一番最初に全要素をカウントし終わるのはそのテーブルが入っているテーブルの系列の一番中のテーブルになる。その中にはテーブルが1つも無いのでtable_existフラグがfalseになる。この時そのlevelで配列の要素数を決めてメモリーを確保すればいいわけだけど、もしかしたら他のテーブルの系列の方が深い入れ子になっているかも知れない。過去に検索したテーブルの深度の最大値はmaxlevelに記録されるので、現在のlevelとmaxlevelを比較して、それより大きい時に限るわけだ。それがif文の(*maxlevel<level)のところ。また、dimsはNULLで初期化してあるので、dimsがNULLの時は無条件で配列を作成する。すでに配列を作成してあって、それより大きな要素数が必要になったら古いものは開放して、メモリーを確保しなおして、ZeroMemoryで全部0に初期化する。

 if((*dims==NULL)||((!table_exist)&&(*maxlevel<level))){
     if(*maxlevel<level)
        *maxlevel=level;
        if(*dims!=NULL) free(*dims);
        *dims=(int *)malloc((level+1)*sizeof(int));
        ZeroMemory(*dims,(level+1)*sizeof(int));
 }

配列に格納してある各レベルの要素数は最大値になるように、比較しながら代入するようになっている。

 if((*dims)[level]<count)  (*dims)[level]=count;

以上で変数maxlevelにはテーブルの最大次元数が、dimsには各次元の要素数が入ったので、SafeArrayを作るためのSAFEARRAYBOUND(インデックスの最小値と要素数の構造体)の配列を作って、 dimsの値で初期化する。ただし順番が逆なので要素数はdimsに入ってるのと逆順に代入する。

    int maxlevel=0;
    int *dims=NULL;
    len_table(L,i,0,&maxlevel,&dims);
    SAFEARRAYBOUND *sab = new SAFEARRAYBOUND[maxlevel+1];
    for(int k=0;k<=maxlevel;k++){
       sab[k].lLbound=0;
       sab[k].cElements=dims[maxlevel-k];
    }
    pArgs[n-i].parray=SafeArrayCreate(VT_VARIANT,maxlevel+1,sab);
    delete[] sab;

これでluaのテーブルと同じような要素構成でSafeArrayが生成される。「ような」と書いたのはluaのテーブルがテーブルの入れ子になっているだけで他の拘束条件が無いのに対してSafeArrayは同じ次元の要素数は全部同じじゃないとならないからだ。それとテーブルの連想配列は順番がこっちで決められないので使えないよ。出来たSafeArrayはAutoWrapに渡すVARIANT配列のひとつに、VT_ARRAY|VT_VARIANT型として代入する。

そしてテーブルの要素をそれぞれVARIANTにしてSafeArrayにセットしていくのがsetArray( )だ。これもlua_next( )を使ってテーブル内の要素を順に取り出して行き、テーブルが出てきたら再帰的にsetArray( )を呼び出して、テーブル以外が出たらデータ型を調べて該当するVARIANTに変換してSafeArrayPutElement( )を使ってSafeArrayの対応する場所にセットする。その要素の位置を指定するためにlong型の配列を変数indicesを使って受け渡していている。例えばarray[3][5][2]に値を代入するなら配列の中身は{3,5,2}の順になる。

void setArray(lua_State *L,int index,VARIANT *v,int level,long **indices,int *dims,int maxlevel){
   (*indices)[maxlevel-level]=0;
   lua_pushnil(L);
   VARIANT tmp;
   while (lua_next(L, index) != 0) {
       if(lua_istable(L,-1)){
          setArray(L,lua_gettop(L),v,level+1,indices,dims,maxlevel);
       }
       else {
          if(lua_isboolean(L,-1)){
             tmp.vt=VT_BOOL;
             tmp.boolVal=lua_toboolean(L,-1);
          }
          else if(lua_isnumber(L,-1)){
             tmp.vt=VT_R8;
             tmp.dblVal=lua_tonumber(L,-1);
          }
          else if(lua_isstring(L,-1)){
             tmp.vt=VT_BSTR;
             const char * mstr= (const char *)lua_tostring(L,-1);
             mtoV(mstr,&tmp);
          }
          else{
             tmp.vt=VT_EMPTY;
          }
          SafeArrayPutElement(v->parray, *indices, (void *)&tmp);
       }
       lua_pop(L, 1);
       ++((*indices)[maxlevel-level]);
       if((*indices)[maxlevel-level]>=dims[maxlevel])break;
   }
   lua_pop(L, 1);
   return;
}

これでMAXScriptファンクションに渡す引数のひとつとしてSafeArrayが使えるようになった。MAXScript側ではこれをSafeArrayWrapperとして受け取って、dataArrayプロパティから中身にアクセス出来る。MAXScriptファンクションからSafeArrayを返す時も配列をSafeArrayWrapperにセットしてそれごと戻すとVARIANTにSafeArrayが入って返って来る。それをluaのテーブルに復元してluaスクリプトに戻す。

以上がOLE_Client.DLLのコードだ。

それではまた次回。

modoカテゴリー別ページ  



take_z_ultima at 11:30|この記事のURLComments(0)TrackBack(0)modo | 3ds Max
Archives