スポンサーサイト

--年--月--日 --:--

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

シェーダー開発ツール 5

2006年11月01日 15:56

色々と仕事の方であったので開発を中断していたシェーダー開発ツール。

2週間ぶりぐらいに開発を再開しました。
んが!なかなか気力が沸きませんよこれ(´・ω・`)

たぶん、考え事とかあるからだと思うんだけど・・・。
手が動かないです。
ってことで、今日は気分転換も兼ねてブログの更新。

前回から変わったことはあまり無いんだけどね。
追加したことって言えば、、、

・自分で定義した「変数」を、他の「変数」の計算プログラムの中で参照できるようにした。

・「変数」管理リストウィンドウを追加


ぐらいかなー。
基本はほとんど出来上がってるから、そんなに難しくなかった。
Closure 大活躍w


そんなワケで、スクリーンショット。

ShDev_061101.jpg



あと追加しないといけないことは、、、

・「変数」を実際にシェーダー定数として設定する仕組み(GUI含む)

・POST 処理系のシェーダー(HDR とか シャドウマップ とか Depth of Field とか)を
 書くための、フロー制御のエディット機能

 
かな。

どっちもGUI作るのが面倒('A`)
基本的に あるけ が自分で使うように作ってるので、
自分が使いやすければ適当に作ってもいいんだけど、
やっぱり見栄えは重要だと思うのでw

そんなこんなでGUI作るのはいっつも悩んでます。

一応基本って言うかそういうのはあって、
ProDG みたいに全部スプリットウィンドウで処理できるようにしてます。
だから、大まかなGUIは使用者が構築できるようになってて、
細かい部分だけ作ればいいんだけど、それでも悩みますw

デザインが決まってるGUI作るのは楽だし楽しいんだけど、
どうしようか悩んでる時は本当眠くなりますw


あと大まかな機能2つ入れれば完成だし、がんばりますよ( ・`ω・´)
・・・11月中に完成させたいなー('A`)
 

blog_rank
スポンサーサイト

動的にシェーダー定数をプログラム

2006年10月20日 16:12

ってことで、上記タイトルの機能を あるけ が趣味で作ってるシェーダー編集エディターに追加しました。

既存のシェーダーの開発ツールとしては nVidia の FX Composer や ATI の RenderMonkey があります。
どっちのツールも定数レジスタを設定することは出来るけどプログラムすることは出来ませんでした。
射影行列を作ったり、ライトの位置を簡単な円運動させたり、そういうことは出来なかったわけです。

もちろん、定数を設定するだけなら出来ます。
たとえば、HDRの閾値を入力したり。

しかし、実際にゲームで使う場合、定数レジスタに設定する値は複雑な演算を行ったり、
計算で求めたベクトル同士の内積の値を突っ込んだりします。
そういう細かな定数レジスタの設定は、出来ませんでした。

あるけ はプログラマなので、別にスライダーなどで定数レジスタが弄れなくても、簡易的なプログラムが書け、それで定数レジスタを制御できる方がうれしいです。


・・・ってことで、Closure の機能をバリバリ使ってタイトルの機能を追加しました。
アプリケーションの中でプログラムを入力すると、構文解析を行い、エラーが無ければプログラムを構築します。

どんなことが出来るか?というと、、、


char pProgram[] = "\
 VEC vTest  = init(1,0,0); \
 MAT mtTest  = rotY( rad(cnt()) ); \
  \
 return vTest * mtTest;\
"
;



こんな感じのプログラムがあるとします。
cnt() っていうのは1フレームに1ずつ増えるカウンタを返す関数、rad() は与えられたパラメーターを Degree から Radian にする関数、init() はベクトルの初期化関数、rotY() はY軸回転の行列を返す関数です。

このテキストをほいさーって感じに Closure のジェネレーターに突っ込んでやると、これを評価する Closure が返ってきます。
後は、値が欲しい時に Closure を評価してやれば、この Closure が計算し、「Y軸周りに回転するベクトル」を取得することが出来るわけです。


Closure を使ってるので、他のプログラムの中でこのプログラムを呼び出すことも可能です。
スクリプトに見えますが、インタプリタで動いてるわけでなく、式を保存しているので Closure を評価するだけで式の実行を行います。

関数型プログラムの悪い欠点で、あまり長いプログラムはスタック食いつぶすだろうから書けないけどねw
シェーダー開発ツールはこの機能を入れてもうちょっと機能を追加したら、公開しようかな?って思ってます。


ためしに作ったようなものだけど、他のゲーム会社とかでも使われたら最高だね~w


blog_rank

C++言語で関数型プログラミング 第2章

2006年09月22日 13:56

pelt の記事を挟んじゃったけど、クロージャーの記事の続きです。

前回は Lambda が非常に長ったらしいテンプレート引数を持ったクラスだってことと、テンプレートが複雑に絡み合って動作していることを書きました。



今回は Lambda がどうやって動いてるかを具体的に説明したいと思います。

まず Lambda の動作。
Lambda とは無名関数を扱うためのテンプレートです。
printf( "%d\n", (_1 * 10)(5) )
と書いたとき、50 と表示されるわけです。

何が無名なのか?
それは _1 を後で 5 と置き換えてるところです。
要するに、この 5 と書いてるところは何でもいれていいわけです。

実際、このまま使うだけでは大した威力もありません。
これがテンプレート関数と結びつくと色々面白いことが出来ます。

template int Test( T lambda )
{
  return lambda(7);
}

として
 Test( _1 * 10 )
を呼んでやると

結果が 70 と帰ってきます。
要するに、式を渡すことでパラメーターが帰るようになるわけです。

画期的だと思いませんか?
従来はパラメーターを渡すことで中で式に代入し、結果が返ってきました。
これだとパラメーターが同じ場合、式を取り替えるのは呼ぶ関数を変更することになります。

たとえば、曲線の補間処理を行う時、従来は曲線タイプを保持して計算処理を選択・実行していたところ、式自体を保持できるので曲線式自体をパラメーターとして保持できます。
曲線タイプを保持するより、よほど柔軟に処理できますよね。
ユーザーが自分で定義した曲線も処理できます。
(実際は数式しか保存できないので、曲線を定義するのは難しい)

他には、クイックソートの比較条件などを渡してやるなど、色々応用範囲はあります。



さて、実際どうやって実現してるかというと、、、TypeList というテンプレートがあります。
これ自体は

template< typename T, typename U >
struct TypeList
{
  typedef typename T Head;
  typedef typename U Tail;
};

と、まぁ、これだけの構造体なのですが、これを使用することで無限にテンプレートの引数を増やすことが出来ます。
要するに、TypeList を入れ子状にするわけです。

 TypeList< int, TypeList< float, double > >

などのように。
こうすれば、何個でもテンプレートの引数を増やすことが出来ます。

上のタイプは型の定義しかしていませんが、これに実体を持たせたものが Lambda で使用されています。



template< typename T, typename U >
struct Tuple
{
  typedef typename T Head;
  typedef typename U Tail;
  
  Head  h;
  Tail  t;
};


実体を持つことで実際にデータを保持することが出来、柔軟に(自動的に)型が決定される Lambda に使われるわけです。

さて、どうやって実装されているのか?
ためしに実装したのがありますのでこれを元に説明します。


ってことで、下にファイルを添付します。

LambdaSample1.txt


実行してみるとわかると思いますが、テンプレートが式を保存出来てることがわかると思います。

さて、この技術の肝となる部分は、、、

#define LAMBDA_OPERATOR_BINARY_TL( en, op )               \
  template< ActionType Act, typename Arg, typename B > inline     \
  Lambda< en, Tuple< Lambda, B > >             \
  op( const Lambda &a, const B &b )             \
  { return Lambda, B > >( a, b ); }   \

#define LAMBDA_OPERATOR_BINARY_LT( en, op )               \
  template< typename A, ActionType Act, typename Arg > inline     \
  Lambda< en, Tuple< A, Lambda > >             \
  op( const A &a, const Lambda &b )             \
  { return Lambda > >( a, b ); }   \

#define LAMBDA_OPERATOR_BINARY_LL( en, op )                        \
  template< ActionType ActA, typename ArgA, ActionType ActB, typename ArgB > inline   \
  Lambda< en, Tuple< Lambda, Lambda > >             \
  op( const Lambda &a, const Lambda &b )            \
  { return Lambda, Lambda > >( a, b ); }  \

#define LAMBDA_OPERATOR_BINARY( en, op )    \
  LAMBDA_OPERATOR_BINARY_TL( en, op )    \
  LAMBDA_OPERATOR_BINARY_LT( en, op )    \
  LAMBDA_OPERATOR_BINARY_LL( en, op )    \
  


LAMBDA_OPERATOR_BINARY( PLUS, operator + )
LAMBDA_OPERATOR_BINARY( MINUS, operator - )
LAMBDA_OPERATOR_BINARY( MULTIPLE, operator * )
LAMBDA_OPERATOR_BINARY( DIVIDE, operator / )
LAMBDA_OPERATOR_BINARY( SURPLUS, operator % )


この部分です。

やってることは、オペレーターの実装をしているんですけど、戻り値に Tuple を使用し新しい型を定義して入れ子で返してるのがわかると思います。
要するに、Lambda を + や * などのオペレーターで合成するたびに新しい型を戻り値の型として返してるワケです。


あるけ がテストで書いた、この Lambda 、機能としては全然ダメです。
戻り値が一番最初に突っ込んだ型に特定されちゃうこと
(たとえば上のファイルでいうと、(10 + sample1)() の場合、必ず戻り値は int になってしまう)
が、上げられます。


しかし、ほぼ BOOST の Lambda も同じようなトリックで動いています。
上の式に Unary(単項) と Binary(二項) の定義を追加し、引数を評価できるようにトリックを入れてやれば BOOST の Lambda が出来上がります。


次回は、それじゃ あるけ が作った TCClosure はどういった原理で動いてるの?を説明します。

あるけ が作った TCClosure は戻り値の型はかなり柔軟に対応されています。
また、今回 あるけ が書いた Lambda や BOOST の Lambda と違い、「戻り値の型」だけ指定してやれば式を「変数として保存する」ことも可能です。

BOOST の Lambda では式の保存を行うことはテンプレート関数やクラス以外で無理でした。

次回はもう少し具体的な話が出来ると思います。




blog_rank

ペルトマッピングあれこれ その1

2006年09月08日 23:24

プライベートのことで色々あったせいか、ちょっとブログ書く元気がありません。。

google とかから検索で飛んできてくれた人やこんなページでもブックマークしてくれている奇特な人に大変申し訳ないです( ;Д⊂ヽ


ってことで、ブログを閉鎖します!


・・・・っていうのはウソですw


書く元気がないからって何もやってないわけじゃないですよ(・∀・)
あ、けど Closure のことはもうちょっと元気が出た時にでも書きますね。
あれは説明するのも労力が必要なので( ;Д⊂ヽ


今日は今やってることなんかをちょっとまとめるために書きます。


今は PeltMapping っていう処理をやっています。
これは、モデルデータのUVを「アジのヒラキ」みたいに展開(Unfold)する処理です。
3D Studio Max 8 の UV Pelt Mapping 機能ですね。


2000年ぐらいの Siggraph に出てた論文を元に作っています。
・・・っていうけど、原理は恐ろしいほど強引で簡単なんですけどねw

とりあえず、参考になるURLを上げておきます。
http://www.cgl.uwaterloo.ca/~krmoule/talks/pelt/intro.html

ちなみに、Model Pelting のプラグインは Maya のはフリーでありますし、 XSI のも多分あるでしょう。確かめてないけどw

なので、デザイナーさんが使う分にはメインストリームのソフトウェアでサポートされているので1から作る必要はそんなにないわけです。


ならなぜ作るのか?
もちろん自分が作ったツールでUVを気にしないでモデルに対して陰影のテクスチャを張ったりペイントしたりをやりたいからです!
テキストエディターを作ったのと同じ理由ですね(´ω`)



さて、原理はというと、上でも「簡単」で「強引」と書きましたが、物凄い簡単で物凄い強引です。
まず、「アジのヒラキ」を作る場合やらないといけないことはなんでしょうか?

切れ目を入れることですね。

PeltMapping も同じです。
UVを展開する時に展開元となる切れ目のエッジを選択します。

その後何をするかというと、、、
物理演算を行いながら実際に皮を剥ぐようにメッシュを展開していくんですねw
強引過ぎますw 考えた人単純すぎw
けど、理にかなってます。


やり方は、、、
 1:UVの展開を行うために切れ目を作成します。
 2:メッシュの頂点間を結ぶ線(エッジ)を全てばねモデルにします。。
 3:1の切れ目を引っ張ります
 4:後は布シミュレーションを行うようにばねモデルを解決しちゃえばOKです。

こう書くと簡単そうですね~。


実際は結構面倒な処理があります( ;Д⊂ヽ



まずはエッジの生成処理。

maya とかじゃ線を選択して切断コマンドしちゃえば出来ちゃいますが、自力でやる場合案外面倒です。


何が面倒なのか?
エッジでメッシュを切断した場合、当たり前ですけど切断部分の点が出来ます。
これはエッジに沿って切った場合も同様です。


切断後に出来た点をメッシュが崩れないように切断部分のポリゴンを再連結する処理がまず面倒でした。

あるけ は最近頭がなまってるせいか、あまりいい考えが思い浮かばず強引にやることにしました。
まず、切断点の頂点(これはメッシュが保持している既存の頂点になっています)のインデックスを使って、カットするエッジを用意します。
(用意の仕方はとりあえず置いておきます。まだ実装してないので( ;Д⊂ヽ
多分GUIを用意することになるでしょう。2点間の最短エッジを結ぶとかメッシュを調査して自動的にカットする部分を見つけるとか)


次に一筆書きの順番になるように切断する点を整列します。

このとき、往復で考えるところがミソです。
たとえば、
 1->2, 2->3, 3->4
と切断点が指定された場合
 1->2, 2->1, 2->3, 3->2, 3->4, 4->3
と往復でカットするエッジを配列に入れておくわけです。

この例を一筆書きの順番に並べてみると
 1->2, 2->3, 3->4, 4->3, 3->2, 2->1
になります。


こんなの簡単じゃんとか思う人いると思います。
それじゃカットするエッジが十字にクロスしてた場合を考えて見ましょう。

   4
   |
 3-2-5
   |
   1

こんな感じにですね。

この場合、一筆書きで書こうとすると、(CCW でいきます)
 1->2, 2->5, 5->2, 2->4, 4->2, 2->3, 3->2, 2->1
となります。


これを生成するところまでが第一関門。


まぁ、単純に考えて直線の為す角度を利用すればいいわけです。
為す角度っていうと内積。
けど、ただ内積を取るだけじゃ CW 方向なのか CCW 方向なのかわかりません。
そこで、あるけ はエッジに座標系を持たせました。

この座標系、一意に求まらないとダメです。
ここでもちょっと躓きました。
なんでかっていうと、1->2 のベクトルをたとえばZ軸として座標系を作成しようとしても
それは 1->2 のベクトルの周りに無限に存在することになっちゃいます。
これじゃ、使い物になりません。
Z軸だけが固定だけじゃX軸だと思って使ってても、Y軸が反転すればX軸も反転しちゃうので・・・。


それじゃどうするのか?
1->2 ベクトルを使うポリゴンを全て列挙して、そのポリゴンの面法線を加算して単位化して使いました。

こうすれば2つ(1->2 ベクトルと面法線の平均のベクトル)のベクトル(しかも必ず交差する)により座標系を固定できるんです。


後は為す角度を求めるだけです。
求め方を上記の例(クロスさせたやつ)をもとに説明しましょう。
1->2 ベクトル(以降Aベクトル)と 2->5 ベクトル(以降Bベクトル)を例にとって見ますと、
まず内積で余弦が出るので、acos( A・B ) で角度が求まります。
次に A ベクトルの座標系 M のX軸ベクトル(これをxベクトルとします)と B ベクトルで内積を取ります。
内積の方向でどっち周りなのかがわかるわけですね。
これで acos( A・B ) が CW なのか CCW なのかがわかるわけです。


さて、後は単純に角度を調べて、CCW に一番近いエッジを選んでいけばOKです。
・・・ってワケにも実は行きませんw
A ベクトルと B ベクトルの内積の結果と x ベクトルとの内積の符号、場合により x ベクトルの内積の大きさと座標系 M のY軸ベクトル y との内積の符号も使わないと実際の方向は出ないわけです。

たとえば、A・B == A・C となる C ベクトルがあったとします。
そうすると、C != B となるベクトルであった時、必ずどちらかが先に処理されるように求めなければいけません。
X ベクトルとの内積の符号だけみれば回転方向はわかりますが、回転方向が同じで A・B == A・C だった場合、先に処理された方が隣接辺と判定されてしまうでしょう。

そこで、X ベクトルとの内積の大きさと Y ベクトルとの内積の符号も使用します。
実際、A・B == A・C の時だけ判定すればいいのであまり無いかもしれません。
A・B == A・C の場合に X ベクトルとの内積と、Y・B と Y・C の符号を使って為す角度を求めます。
ちなみに、 A・B == A・C かつ X・B == X・C の場合 B == C となるので、この場合はエッジの指定方法またはポリゴン事態がおかしなデータになります。

これで一筆書きのカットするエッジリストが手に入ります。



とりあえず今日はここまで。
また気が向いたら続きを書きますw




blog_rank

C++言語で関数型プログラミング 第1章

2006年08月25日 18:07

前回、どこまで書いたっけかなw

・・・・・前回の解説じゃちょっと飛んでるところも多いね、こりゃ。

まず、用語説明から。
 ・TCClosure  クロージャー(Closure)を実現するために あるけ が作ったテンプレートクラス
 ・Lambda   無名関数を扱えるようにした BOOST に入ってるトリッキーなテンプレートクラス
こんなもんかな。

他にはカリー化とかも知っておいた方がいいんだろうけど、あるけ 自体実は良くわかってないので省きますw



さて、前回は式のオブジェクト化を行えば Closure が出来そうなこととか、Lambda がどうやって動いてるのか?を簡単に説明しました。

Lambda 自体はすごく綺麗に動的に関数の生成を行えるように作られています。
しかし、実際問題として、Lambda を有機的に使うことはすごく難しいです。
なぜなら、この前の説明で作った ((_1 * 100 + _2 + 10)(1,2)) の左辺値を変数に格納しようとしたら、

boost::lambda::lambda_functor_base::sig::type boost::lambda::lambda_functor::operator ()(A &,B &) const'
 with
  Act=boost::lambda::arithmetic_action,
  Args=boost::tuples::tuple    arithmetic_action,boost::tuples::tuple    lambda_functor,
    boost::tuples::tuple>,
    const boost::lambda::detail::parameter_traits_    IF::RET>::type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::
    null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,
    boost::tuples::null_type>>>,boost::lambda::lambda_functor>,boost::tuples::null_type,
    boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,
    boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type>>>,const boost::lambda::detail::
    parameter_traits_::RET>::type,boost::
    tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::
    null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type>,
  SigArgs=boost::tuples::tuple    null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,
    boost::tuples::null_type>,
  T=boost::lambda::lambda_functor_base,boost::
    tuples::tuple    lambda_functor,
    boost::tuples::tuple    arithmetic_action,boost::tuples::tuple    lambda::placeholder<1>>,const boost::lambda::detail::parameter_traits_    IF::RET>::type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::
    null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,
    boost::tuples::null_type>>>,boost::lambda::lambda_functor>,boost::tuples::null_type,
    boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::
    tuples::null_type,boost::tuples::null_type,boost::tuples::null_type>>>,const boost::lambda::detail::
    parameter_traits_::RET>::type,boost::
    tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::
    null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type>>,
  A=int,
  B=int

まぁ、こんな冗談みたいな長さのテンプレートを宣言しないと格納できないわけです(;´Д`)'`ァ'`ァ

これが Lambda を使用する場合のもっともとっつきにくい部分でしょう。
使える場面が少ないのと、使う場合は必ずテンプレート関数にしないといけないからです。
暗黙のテンプレート引数として使えば、別にどんな型だろうが使う側は知らなくてもOKですからね。


次は、Lambda がどうやって動いてるのかを説明しようと思います。
今日は忙しいのでさわりの部分だけw


blog_rank



最近の記事


上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。