独り言日記(2008/06)
独り言日記
続・直接照明が間違ってるっぽい(2008/06/30)
まだ若干光の量がおかしいですが、修正。Pentium4 2.8GHz/Mem512MBのWinXPマシンにて。2スレッドでレンダリングです。光源は天井の面光源のみ。背景は真っ黒にしてます(後ろ2面は壁なし)。
↓リファレンス用のパストレースでのレンダリング。2000sampling/pixel。2343秒。
↓直接照明との分離で計算(改善後)。50sampling/pixel。192秒。
以前は天井部分での間接照明などがノイズが強すぎたのですが、これで普通に。レイトレ計算の再帰処理を再帰を使わないように展開していたのですが、これが直接照明を分離したときに一部計算が不正になってました(間接照明部の光の計算をスキップしていた)。直接照明を分離した場合はすっきりしないのもあって、再び再帰でのレイトレースに戻しました。
ということで、ベッドルームのレンダリング、和室のレンダリングはもっと少ないサンプリング数で品質を上げることができるかな。可能であれば1/10以下の速度でレンダリングできればいいのですが。
直接照明が間違ってるっぽい(2008/06/30)
独自レンダラにて点光源などのライトの場合は直接照明と間接照明を分離しているのですが、どうもいろいろシーンを試してみてこの部分が間違ってるっぽい。2008/06/25の和室シーンの明るいピクセルが点在する部分があるところもこれ関係のようでした。
以下もおそらく正しくはないのですがShadeホームデザインから持ってきたシーンをレンダリングしたもの。Dell Precision PWS490(Intel Xeon5110 1.6GHz(DualCore))/Mem2048MBのWinVista(64ビット)の環境にて。
↓400x300 pixel。50sampling/pixel。1232秒。
↓640x480 pixel。5000sampling/pixel。317720秒(88時間)。
レンダリング時間の割にはあんまりフォトリアルっぽくなくて(ノイズもまだ消えてなくて)拍子抜けでした。う〜ん、直接照明の分離の整理後再度リベンジせねば。後、サンプリング数によって仕上がりの色合いがかなり変わってますね。ノイズの粗さ以外にも影響しているようで。
両方とも同じシーンで、点光源を9つと背景のhdrによるIBLのライティングです。点光源のようなオブジェクトとして扱っているライト(スポットライト、平行光源、面光源も含む)はシャドウレイの計算を行ってますが、RRT本の13章によると二次拡散反射以降でもシャドウレイを考えた輝度計算が必要なのかな。もうちょっとノイズも、少ないサンプリング数で抑えることができそうではあります。ということで、IBL以外のライトについて調整を入れなければ(その後は、双方向パストレ(MLT)での最適化、などやることがまだまだあります)。
400x300ピクセルの粗いレンダリングのものと640x480ピクセルの割と高いサンプリング数のものを比較すると、
400x300 pixel ==> 640x480 pixel で2.56倍 50 sampling ==> 5000 sampling で100倍
であわせると256倍の時間がかかる、という計算をしてみました。レンダリング時間を見ると、317720 / 1232 = 257.8ですので大雑把に見ると時間の推測はできてそうであります。プログレッシブなレンダリングをする場合は、粗いレンダリング時の時間を計測しておき、それから最終レンダリング時間を推測する、なんてできそうかなぁと思ってます(上記は2つのレンダリングで分けて比較してますが)。
しかし、まだまだ完成しそうにないですね(^_^;;。
自然教育園(2008/06/28)
よく散歩に行くコースなんですが、目黒の自然教育園です。本日行ってきました。携帯カメラ(Softbankの814T)で撮影したのを色の自動補正を行った後、320x240に縮小してます。
実際は(自分自身の目視では)もっと暗いです。そして、空はこんなに白く飛んでいるようには見えてません、曇りでしたしね。
おそらく、背景を白く飛ばないようにすると木々は逆に暗くなる(逆光で暗くなる表現)と思います。
ということで、今までGIっぽいレンダリングをしてみて「背景を白く飛ばす(露出をあげる)」というのは、写真でも似たようになるかなと。結局は露出というのは光の表現する範囲をシフトすることに等しくなりそうではあります(それを全部納めてしまおうというのが、ダイナミックレンジを持った形式)。
GIレンダラで使えそう?なものを撮ってみました。
↓木のベンチ。
園内はカラスが多かったです。カラスがいたので収めてみました。しかし、東京のカラスは大きいなぁと。
↓入り口のイスと机
ここはあまり人がいない(といっても土日はそれなりにいるかも)ので落ち着くにはいい場所です。ちょっと歩いたら自分以外周りに誰もいない空間ができる、しかも見渡す限り木々、ってのはトリップできるというかそんな感じが好きなんでリピータになってるなぁ。元々 木や花は好きでして、小学生時代は図鑑を丸暗記してました。とっくに忘れてしまって白紙になってるのですが(^_^;;。思えば物心ついたころの趣味で記憶にある一番最初は木や花を育てることだったです。
自然教育園では、名前の通りいろんな花や木が栽培されてますので飽きないんですよね。
Shadeホームデザインのサンプルをレンダリング中(2008/06/27)
Shadeホームデザインにはサンプルシーンがいくつか入っているのですが、これを独自レンダラにてレンダリング中です。小さいサイズかつ少ないサンプリング数でレンダリングしてその時間を計測、その後、本番でのレンダリングサイズとサンプリング数でレンダリング中。
予定では85〜100時間くらいかかる・・・。先日の夜から仕掛けてたのですが、次の日の昼でもまだがんばってPCの中の小人さんがレンダリング中です。と、このレンダリング時間を推測するテストでもあります。単純にレンダリングサイズ・サンプリング数、は比例すると考えて計算してます。ただし、最大反射回数を変化させた場合は比較できません。さて、予定時間でレンダリングできるかどうか。
テストしてみて、空間分割をまだ詰めないといけないなぁと。ちょうどShadeホームデザインのサンプルシーンは大きい家に小さなオブジェクトを配置、というのがたくさんあるのでテストし甲斐があります。
後、フォトリアルっぽくする場合は背景のhdrの露出(輝度)を無駄に大きくするといい感じになる、という技を覚えたので、これを使ってどれだけ空気感が出るか楽しみです。PCがぶっ壊れないか心配ですが(汗)。<DualCoreのマシンをフル稼働中。たぶん4日くらいかかるかな。
もう少し詳細に大きなサイズにてレンダリング(2008/06/25)
昨日の日記の画像では分かりにくいので、もう少し大きくかつサンプリング数を上げてレンダリングしてみました。
Dell Precision PWS490(Intel Xeon5110 1.6GHz(DualCore))/Mem2048MBのWinVista(64ビット)の環境にて。
3000 sampling/pixel。640x480 pixel。反射回数最大12。光源は背景としてのhdrと、天井での面光源です。
これで15411秒(約4.5時間)です。時間がかかります。後、角の部分で1ピクセル単位で白くなってる部分がいくつか存在してます。原因はなんなのだろう。
Shadeホームデザイン(2008/06/24)
Shadeホームデザインを買いました。ガイドブックを立ち読みしてみると、「畳」の表現があったので使えそうということで。デフォルトでは910mmが1グリッドになっていて私がやりたいことにうってつけでした。
説明書も何も見ずに和室にオブジェクトをおいてみました。
説明書やガイドブックをみなくても、それなりに使えそうです。障子やふすまもオブジェクトとしてありますね。ただ、「床の間」(和室に壺や掛け軸などを置く、ちょっと段差のある空間です)表現ができるかどうかが分からなかったので、もう少し掘る必要はありそうです。後、中段の位置に板を置くとかできるだろうか、ふすまを半開きにしたいのだけどできるだろうか、とか。いずれにしても、Shadeから形状は持ってこれるみたいですので、工夫次第でいろいろできそう。
しかしこれ、面白いですね。触ってみて秘密基地を作るような面白さは得られるなぁと。オブジェクトもいろいろあるので(でもやはり足りないと言えば足りないかな)、レンダラのテストにも使えそうです。寸法はグリッドに合わせればいいので、ラクチンです。
上記シーンをShadeに読み込み、そのまま独自レンダラに渡してみました。
↓光源はhdrのみ(露出を5に)。1000sampling/pixel。64ビットVistaマシン(CoreDuo)にて1645秒。
↓光源はhdr(露出を5に)と天井に面光源を配置。1000sampling/pixel。64ビットVistaマシン(CoreDuo)にて1897秒。
う〜む、閉じたシーンのためノイズはやはり多くなるか。奥のガラスのあるところ以外は全部閉じています。
Shadeでもレンダリングしてみたのですが、(それはそれで正しいんでしょうけど)表面材質の色がそのまま出ちゃいます。これが強いとCG臭さが増してしまうのかなぁとなんとなく思ってます。かといって、開発中の独自レンダラだとなかなか思うような色合いにならないかもしれない。上記レンダリングはマテリアル設定は一切詰めていないものです。勝手に吸収が加わるため、おそらく似たような感じになるかも、という懸念事項が出てきそうではあります。
でも、空気感を保つとなると捨てるべき部分はあるのかもしれないですね。カスタマイズできるようにするのが一番妥当かな。ユーザとして面倒なことは極力省きたいのですが、それで表現力が奪われるとなると問題。どうするかなぁ。
寸法について勉強(2008/06/23)
今まで建物の寸法とかぜんぜん理解せずにモデリングしていたのもあったので、これを機会に基礎知識を勉強中です。
Wikipediaの「畳」のページ。
http://ja.wikipedia.org/wiki/%E7%95%B3
これによると、日本家屋は3尺 x 6尺(910mm x 1820mm)を畳一枚分として、すべてはこれを基準に行うとよいらしい。
で、このページの和室画像がすごく気に入ったのでなんとかGIでレンダリングできないか、と試行錯誤しています。建築っぽいのはShadeがやりやすいですね。ただ、グリッド単位を「尺」または「910mm(3尺)」に切り替えたい、と思うのは私だけでしょうか(^_^;;。それもあって本気でShadeHomeDesign買おうかとも思ったのですが、日本建築には対応してるんでしょうかね。また調べてみよう。日本風のじめっとした感じ、ってレンダラで再現できたら最高だなぁと思ってます。
ということで、とりあえずShadeからシーンデータを吐き出して独自レンダラに渡してます。見せることができるレベルじゃないのですが、以下のような光の入りっぷりまでこぎつけました(今は光が入り込むかのテストなので、マテリアルや障子などまだまだ作成できてません)。光は背景のhdrのみです。手前の縁側らしきもの以外は全部閉じてます。
分かったこととして、かなり強くないと室内に光が入らないということ。で、Wikipediaの畳の写真を見てもたしかに縁側の外の背景はかなり白く飛んでます。ですので、オリジナルのhdrの露出を4(2^4=16倍)にしてようやく上記のような感じに(普通の露出だと真っ暗です)。400 sampling/pixelですが、まだまだノイズは消えてませんね。それと、まだ数段階光を強くする必要がありそう。思い込みも障害になるというかもっと観察しないといけないなぁと思いました。
実際にこんな場所にたった場合は、おそらく背景はくっきり見えているのですが、目の自動補正機能も加わってか その補正された結果を本来のものと錯覚しているのかもしれないですね。う〜ん、光は奥が深い・・・。
続続・ミップマップ(2008/06/22)
ミップマップについて、PBR本(分厚い英語の本です)を見るともっと詳しく書いてました。先日日記に書いたやり方は自分で考えた方法なんですが、もっときれいにいけるのかな。その他のテクスチャ補間方法についても載ってますねぇ。
ということで、より詳しい方法を知りたい方はPBR本こと「Physically Based Rendering」というのを買うのがよいかもしれません(ただし英語)。この本、物理的に重いので読むのが疲れるんだなぁ。
続・ミップマップ(2008/06/20)
ミップマップ実装の続きです。width x heightのサイズから縦横のサイズが1になるまで1/2倍していった複数のテクスチャを生成し、これを今度はスクリーンに投影したときのサイズを元に、「どれだけの距離を離れると個々のミップマップテクスチャサイズになるか」を求めていきます。
ビュー座標での視点からスクリーンまでの距離をnearZ、スクリーンの幅をscrWidthとします。
ミップマップテクスチャのサイズをimgWidth[n] x imgHeight[n]とすると、
fZ[n] = (float)scrWidth * nearZ / (float)imgWidth[n];
で求めた「fZ[n]」がミップマップテクスチャサイズに相応する視点からの距離(Z値)になります。テクスチャサイズが大きくなるほどfZ[n]は0に近づき、逆にテクスチャサイズが小さくなるほど遠ざかるのが分かります。また、「imgWidth[n] = scrWidth」でちょうどスクリーンサイズと同じ場合は、「fZ[n] = nearZ」になり、スクリーン位置になります。スクリーンのピクセル値とスクリーンまでの距離に反比例しているのが分かります。
これで、遠ざかった場合にミップマップテクスチャを切り替える、ということ以外でも、スクリーンのサイズが大きくなったら(または小さくなったら)ミップマップのテクスチャを切り替える、ということもできるようになります。このfZ[n]はミップマップテクスチャの数分保持するようにします。これは前処理で計算してしまいましょう。
では、どのミップマップテクスチャを採用するかです。レイトレでは、スクリーンの1点に対してレイを飛ばし、そのレイがどの物体とぶつかるか計算します。ワールド座標上の位置が求まりますので、これをビュー座標に変換。Z値をview.zとします。
int num; for(loop = 0; loop < ミップマップテクスチャ数; loop++) { if(view.z < fZ[loop]) break; } if(loop < ミップマップテクスチャ数) num = loop; else num = ミップマップテクスチャ数 - 1;
これだけです。ここでのnumがミップマップテクスチャ番号に相当します。これくらいだと負荷はかからないですね。後は、その選択したテクスチャ上の色をUV値を元にゲットすればOKです。
ミップマップの境界を分かりやすいように色分けしてみました。奥に向かって、緑、青、赤、緑、青、赤、となっている部分がミップマップテクスチャを切り替えている部分になります(色がついていない手前の部分はオリジナルのテクスチャ)。
OpenGLでのミップマップ実装では、これに加えて前後の2つのテクスチャを合成してより滑らかになるようにするモードがあります。これは、上記のコードのview.zとfZ[num]、fZ[num - 1]の離れ具合をみて、2枚のミップマップテクスチャから取ってきた色を線形補間すればいいというのはだいたい想像がつくかと思います。
これで、視点に近い部分でテクスチャが拡大する場合はバイリニアフィルタやトライリニアフィルタで補間、視点から遠い部分はミップマップ、ということでテクスチャがつぶれるのを緩和できますね。上記説明ではあんまり例がよろしくないのですが、たとえば髪の毛なんかのようにアルファで抜く場合にはミップマップの効果は絶大かと(遠くもそうですが、近くのテクスチャでもつぶれては困る場合≒ディテールが細かいテクスチャ、ってことになるのかな)。
ミップマップ(2008/06/20)
ミップマップをレンダラに搭載してみました。微妙すぎて分かりにくい画像ですが(^_^;;。
↓ミップマップ使用前
↓ミップマップ使用後
奥に行くにつれて無限平面上の文字はつぶれていくのですが、ジリジリする部分がぼやけていい感じになる、というもの。サンプルとしてはあんまりよろしくないですね。マス目にしてモアレを発生させたほうがわかりやすかったかも。
初歩的なやり方ですが以下のように搭載しています。
- 1枚のテクスチャのサイズをwidth x heightとします。
- 縦横ともに1/2倍した縮小画像を生成し、保持します。サイズはwid = (width >> 1)、 hei = (height >> 1)
- これをさらに1/2倍した縮小画像を生成し、保持します。wid = (wid >> 1)、hei = (hei >> 1)
- で、縦または横のピクセルサイズが1になるまで繰り返します。
オリジナルサイズwidth x heightから、縦または横のサイズが1になるまでの複数枚のテクスチャが存在している状態です。計算すれば分かりますが、使用するメモリ量はオリジナルのテクスチャの1.5倍以下には必ず収まります。後、縦横ともに1/2ずつ縮小しますので、512 x 512 pixelなら
- 512 x 512 pixel(オリジナル)
- 256 x 256 pixel
- 128 x 128 pixel
- 64 x 64 pixel
- 32 x 32 pixel
- 16 x 16 pixel
- 8 x 8 pixel
- 4 x 4 pixel
- 2 x 2 pixel
- 1 x 1 pixel
の10テクスチャを持てばいいことになります。
レイトレースするときの 交差位置をビュー座標変換したときの距離(Z値)により、上記のうち採用するテクスチャをどれか1つ選択してあげるとミップマップ表現の出来上がり、です。Z値が大きいほど小さいテクスチャを選択してあげることになります。逆に視点に近いほど大きいテクスチャを選択するようにします。この判断は、まずレンダリング時のスクリーンにテクスチャをピクセル値の実寸で貼り付けたとして、そのときの近クリップ面の距離との比率を求めておきます。その比率を保持したまま、テクスチャサイズを縮小していったときの視点からの距離をテクスチャ数分保持しておきます。
と、ちょっと長くなりそうなのでまた日を改めて説明することにします。このあたりは面倒そうには見えてしまいますね。でも、そんな面倒でもない部分だったりもします。
Gdiplus::Bitmapの「GetPixel」は使わないようにする(2008/06/19)
GDI+の画像読み込みの件は自己解決しました。Gdiplus::Bitmapの「GetPixel」で色を取得していたのですが、これがボトルネックになってました。
Gdiplus::BitmapData bitmapData; pBitmap->LockBits(NULL, Gdiplus::ImageLockModeRead, PixelFormat24bppRGB, &bitmapData);
として、BitmapDataから直接ビットマップ情報を取得できるようにし、
RGBTRIPLE *pRGBPos = (RGBTRIPLE *)bitmapData.Scan0 + x + (y * bitmapData.Stride / sizeof(RGBTRIPLE));
としたときの、(pRGBPos->rgbtRed, pRGBPos->rgbtGreen, pRGBPos->rgbtBlue)が、(x, y)の位置でのRGBになってますね。
解放は以下で行います。
pBmp->UnlockBits(&bitmapData);
これで、1600 x 1200 pixelのjpegファイル読み込みが1700msから125 msに。これなら実用的に使えそうです。
無限平面(2008/06/19)
ポリゴンメッシュ以外の要素として「無限平面」を入れてみました。
地面はフリー素材をお借りしてテクスチャにしています(1600 x 1200 pixel)。空間分割ではポリゴンメッシュと無限平面は別に扱うため、無限平面を導入したところで空間分割の最適化を阻害することはありません。これでどこまでも続く地平線が実現できますね。オブジェクトが無限平面に落とす影もできているのも分かるかと思います。
ですが、やはりというか背景のhdrとしっくりあわせるのは難しいなぁと。背景には実際に物体をおかないと違和感はぬぐえないのかもしれませんね。
後、課題がいくつか出てきました。
- 遠くのテクスチャがかなり汚い → ミップマップ実装は必須っぽい
- テクスチャ画像読み込みはGDI+を経由して行っているのですが、テクスチャサイズが大きいと読み込みが遅い。
2つめの問題はどうしようか。bmpならフォーマットが分かるので独自で読み込んだほうが遙かに速くなるのですが、jpeg/gifなどはやっかいかもしれないです。しかし、カリカリチューンしてほしいなぁ<GDI+。
レイトレでの面反転(2008/06/17)
レンダラにて、視点からレイを飛ばしたときに交差した面が表を向いているか裏を向いているかを判断する際に、視線ベクトル(rayDir)と交点での法線(normal)にて
LN = -(rayDir.x * normal.x + rayDir.y * normal.y + rayDir.z * normal.z); if(LN < 0.0f) { // 裏返っているので法線を逆転 normal = -normal; LN = -LN; }
みたいにしていました。交点位置での法線は、三角形の各頂点法線より求めた値です。が、シーンによっては以下のように変になってました。
黒い部分が裏返ってしまっているところ。ローポリだと起こりやすいのかな。原因は、スムージングしたときの法線の流れが急なために視線ベクトルと法線の作る角度が90度以上になるタイミングがあるからのようです。
これを解決するために「視線ベクトルと面法線の内積」にて裏返っているか判定するようにしました。
これでなんとか解決したような。しかし、これ今まで気づかなかったです。
投影でのUVの補正(2008/06/16)
先日の日記でのムービーを見ていて、背景描画(球投影)部分にて不都合に気がついた方もおられると思います。ちらつく瞬間がありますよね?実はネタ解説をするためにわざとそうしていました。
6/12の日記にて「ただし、境界部分でぐにゃっと延びるのに注意」というのを書きました。球体にした背景とのテクスチャ対応位置はUとVによって0.0〜1.0の値を割り当てています。
変になっている部分をキャプチャしてみます。
画像ではU方向に0.1ずつ増加していってます。ところが0.95〜1.05となる部分は1.0オーバーですので、表現としては0.95〜0.05となってしまいます。こうすると、逆向きにテクスチャは貼り付いてごらんのとおりな折り返しが見える形になってしまいます。
では、これをどう判断して補正するか?ということですが、差分をとりましょう。0.95 - 0.05は0.9になります。円筒や球投影の場合はぐるっと一回転で0.5以上の差が起きることはありません(もし可能なら次元がゆがんでることになる)。
ではプログラムで補正してみます。uvA[]にそれぞれ(u, v)の要素を持っており4頂点のポリゴンでのUVが入っているとします。
float minV, maxV; int i, minPos, maxPos; while(1) { minV = maxV = uvA[0].u; minPos = maxPos = 0; for(i = 1; i < 4; i++) { if(minV > uvA[i].u) { minV = uvA[i].u; minPos = i; } else if(maxV < uvA[i].u) { maxV = uvA[i].u; maxPos = i; } } if(maxV - minV < 0.5f) break; uvA[minPos].u += 1.0f; }
これで横方向はぐねっと折り返されることはなくなります。
このような補正は背景だけでなく、球投影または円筒投影したUVをオブジェクトの頂点に割り当てる場合などにも利用できます。縦方向も補正する必要がある場合は、v値も同様にするとよいでしょう。
プログレッシブなレンダリング描画(2008/06/15)
上からのスキャンライン的な見せ方だと途中経過が分かりにくいので、プログレッシブ式も選べるようにしました。
ムービーの画質が悪いので動画の撮り方を改めて模索中です。
キャプチャ時はCamStudioで無圧縮にて「Microsoft Video 1(品質100)」で撮ってます。avi形式で出されるので、次にFlash 8 VideoEncoder(Flash8に付属のもの)で ビデオコーデック「On2 VP6」、画質を「カスタム」、最大データレート2000 kbpsとしてみました。最大データレートをあげると品質アップに貢献するようですね(この「画質」が「高」の場合は700kbpsです。これでも足りない)。上記ムービーにてflv形式で2.2MBです。まだ粗いか。
ところで、某所でPS3のMGS4の動画を見たのですが、リアルタイムレンダですごい品質いいですねぇ(MGSでは、ムービー部もリアルタイムでしたよね、昔から)。あと、技術的な挑戦もしてるんだなぁと(始めの市街戦のスネークの服とか気に入りました。後、髪の毛の表現)。これとSIREN NTはPS3の作品として注目してます。PS3を買う予定はないのですが研究材料としてはいいなぁと。
髪に関しては表に出せない新しく考えた技術(?)があるのですが(実験では実現できてます)、いずれはお見せできれば。ピクセルシェーダーが使えないボードでもリアルタイムでここまでできる、ってのは、、、独自ツールで表現できればと。
とある機会にCGと無縁な方々に「レンダリング」について話をしたことがあるのですが、どうもその概念が伝わらないような気がしました(リアルタイムとレンダリングした画像の区別がついていない)。彼らにとっては「なんでわざわざ遅いレンダリングをして画像出す必要があるの?」と思っているのかもしれません。研究というか開発としてはレンダリング理論を掘るのは楽しいのですが、いざ「理解されるものは?売れるものは?」となると悲しいかな、ベクトルはそっちじゃないのかなぁと(それを言っちゃあおしまいですけど(^_^;;)。
PS3の画像を見てみて(PS2でもWiiでもいいんですが)、一般から見るとあれが「フォトリアル」って言っても通じるんだろうなぁと。
しかし、未だにPCIExpressが使えないマシン(AGP現役ですよ、店頭で探してももう対応ボードがないし(^_^;;)やノートPCしか持ってないというのは、、、う〜ん遅れているなぁと<自分。
プレビューでの球投影な背景(2008/06/12)
hdr画像をプレビューしたくてこんな感じにしてみました。ムービーとして撮ってみました。後半はレンダリングを実際に行ってます(100samples/pixelの精度)。こうするとhyperShotみたいだ・・・(レンダリングは別窓ですが)。
背景が球投影といえども、それっぽくするのであれば別にGPU(ピクセルシェーダ)やキューブマップなど使わずにさくさく動くのはできたりします。上記はPentium4 2.8GHz/Mem512MBの環境で操作してます。が、プレビュー部分はCeleron 1GHzのノートPCでも変わらない速度なはず。
これのトリックは、フェイク環境マップを実装したことがある方ならすぐ理解できるかと思います。やり方は簡単。
- 背景のhdrを縮小して、OpenGLなどのハードに渡せるように2の累乗にして保持(RGBAにすること。露出やガンマも計算して入れてしまいます)。これをテクスチャとしてバインドします。
- スクリーンをn x n分割します。上記では4x4分割しています。スクリーン上でグリッドが作られているイメージです。
- それぞれに頂点座標を与えて「視点−スクリーン上の点」が作るベクトルをワールド座標変換します。
- 視点から「頂点の部分のみ」レイトレースし、背景とぶつかったときのテクスチャ上のUVを求めます。4x4分割した場合は、(4 + 1) x (4 + 1)回の衝突判定をするだけです。
- もうおわかりですね、これを四角形ポリゴンの集まりと見なしてZ書き込み・Z参照を無効にして描画すればいいだけ。ただし、境界部分でぐにゃっと延びるのに注意(これも補正で緩和できますよ)。
背景がhdrとかだったりする場合、実際にビューポートから見えている領域は背景をあらわす全球のうちごくごく一部です。ですので、ゆがみは少ないです。プレビューなんでちょっとやそっとのゆがみは気にせずに、というのであればこれくらいで十分使えるかと(わざわざキューブマップを作る必要もないですし)。後、「球投影マップだからといって球形状を作る必要もない」ってのも特徴です。
これをもうちょっと発展させると、反射・屈折もどきがピクセルシェーダーなしに実装できそうですね。
ちなみにhdr画像は6MBのファイルサイズで2000 x 1000 pixelのものです。OpenGLに持ってくるときは1024 x 1024に縮小してます。レンダリングを開始したときに改めてhdrファイルをごそっと読み込んでいるのですが、読み込み負荷はほとんどかかってないのが分かるかと思います(計測しても1秒もかからないです)。
ということで、何が言いたいのかというと「Shadeでもできるはず」です(^_^;;。なんかhdrの読み込みが(というよりもファイル読み込みが全般的に)遅いのはなんとかしてほしいかもしれません。そのへんと不安定さがかなり「使いたくねぇ」の原因かなぁと思ってます。50MBのhdr(6000 x 3000 pixel)でも3〜4秒くらいで読めるはずですよ。
続・ツール造りの途中経過(2008/06/12)
結構時間が空いてしまいました。ようやくレンダラおよびツール造りの余力が出てきたので再び開発中です。
レンダリング機能(今はライブラリとして分離してます)を、ツールに取り込んでテストしてみました。シーンは何の意味もない適当なものです。
カメラ移動などはツール上で行えるので(メタセコと行き来するのが多いのでメタセコの操作にあわせてます)、これにてレンダリングのテストが格段に楽になりました。
マテリアル設計ではツールによって方言を作ってしまいやすいのですが、今のレンダラもやっぱり少しクセがあります。このあたりもあんまりアクが強くないようにしたいものですが・・・。簡単にそれっぽいリアルさを出したいがためにちと細工してるのがあるのですが、どう構成しようか(上画像のプレビューの色設定はちと間違えてしまいました。レンダラの感じにあわせるつもりです)。
あと、マテリアル調整用プレビューレンダラ(マテリアルが一覧でプレビューできるやつ)も実装できるようにしたほうがいいかな。GIだと、背景のhdrによってもがらっと色合いが変わりますので レンダリングしてあげたのをマテリアル調整部で見せないと仕上がりの確認が面倒になりそうです。今はレンダラ内部では、1つのアプリケーションで1シーンのみ保持できるだけなので、複数シーンを持つことができるようにすれば対処できそう。
その他、背景hdrはどこまでプレビューとレンダラで近づけることができるか(プレビューでは球状にすりゃそれっぽくなると思うけど)、これも調整がいりますねぇ。
この部分は牛歩前進で進めることができればと思ってます(その都度、レンダラのテストもすることになるのでそれもあわせて)。
ツール造りの途中経過(2008/06/03)
これは合間合間に作ってるやつですので、いつ完成するやらですが・・・。
GIレンダラのテスト用ツールを作ってます。mqoデータをボンボン放りこんでシーンに配置し(個々のオブジェクトごとに拡大縮小・回転・移動が可能)、マテリアルを設定して、レンダリングする、というエディタです。
メタセコのデータはインポートできるようにしました。後、wxWidgetsのwxAUIのテストなども含めての実験プログラムでもあります。
やるべきこととして、
- 背景(hdr)のリアルタイムプレビュー&背景ウィンドウ
- カメラウィンドウ
- マテリアルエディットウィンドウ
- レンダリング設定ウィンドウ
- Mac OS X対応(ロードマップには入れてますが、Win版ができてからの後追い)
などなど。ほとんどのウィンドウはwxAUIにて分離するように(ドッキングもできるように)する設計としています。リスペクトとしてはMaxwellStudioみたいなシーンエディタなんですが、MaxwellStudioは操作性にちと難ありな部分があるため、このようなものはやっぱり作るのが手っ取り早いのかなぁと。
しかし、ツリーコントロールは3Dソフトでは使えるようで使いにくいウィジットではありますね。上のキャプチャのオブジェクトとマテリアルのリストはツリーコントロールを使っているんですが(まだ階層化処理は入れてません)、表示のOn/OffやロックのOn/Off、などのアイコンを入れたいなぁと思ったりしています。が、そうするとツリーコントロール自身を自作(フルスクラッチで独自描画)しないと間に合わないかなぁという感じが。今は開発時間の問題もあるので、とりあえずツリー部(一覧)と詳細部に分けて管理しようかなと。
しかし、メタセコで作っていたアイギスのモデリング、まだまだ粗があるので(時間を置くといろいろ見えてきますね)こっちも調整したいところではあります。
デカルト座標でのベクトルを極座標に変換(2008/06/03)
ツールなんかを作っていると日常的にベクトルを極座標に変換する、ってことが発生します。たとえば、視点と目標点の2つを結ぶ視線ベクトルがあったとして、これから極座標でのθとφを求めたい場合。
※ θは-π/2 〜 + π/2の間違いでした。
上記はよくある極座標の図です。
極座標(θ,φ)からデカルト座標(X', Y', Z')への変換は、θとφが与えられている場合
X' = sinθ * cosφ Y' = cosθ Z' = sinθ * sinφ
な感じになります(Y-upの場合)。ただし、θ = 0のときは、(X', Y', Z') = (0, 1, 0)とします。
この逆を行うのですが面倒なので、制限をかけて極座標を算出してみます。
制限事項は、アップベクトルを(0, 1, 0)として視点と目標点から作るベクトルは真上または真下をみることができないようにしました。
視点をeyePos、目標点をeyeTargetとします。視点は目標点を中心に動くものとします(目標点は固定)。
XZ平面でのφ値を計算
視線ベクトルが(0, 1, 0)または(0, -1, 0)となる状態が存在しないという制限を入れた場合は、視線ベクトルをXZ平面に投影した場合には必ず長さができます。この2D上での角度を求めるとφは計算できそうです。
#define PI 3.1415926535 #define PI2 (PI + PI) vector2 v1, v2, vv; double a_c, a_s; v1.x = eyeTarget.z; v1.y = -eyeTarget.x; v2.x = eyePos.z; v2.y = -eyePos.x; vv = v2 - v1; vv = normalize2(vv); // ベクトルvvを正規化(長さを1.0にする) a_c = acos(vv.x); a_s = asin(vv.y); if(a_s > 0.0) phiV = (float)a_c; else phiV = (float)(PI2 - a_c);
ここで求めたphiVがφの値(0.0 〜 2π)になります。
YZ平面でのθ値を計算
θは-π/2 〜 +π/2の値を取りますが、まずは3次元上のベクトルから2次元に持ってくる(XYZ要素の1つを除外する)必要が出てきます。φが求まってますので、(2π - φ)の回転を加えることで1つの要素(ここではXが常に0になるように計算)を相殺します。
vector3 eV, v3; a_c = cos(PI2 - phiV); a_s = sin(PI2 - phiV); eV = eyePos - eyeTarget; v3.x = eV.x * a_c - eV.z * a_s; // Xは必ず0になるので計算不要 v3.y = eV.y; v3.z = eV.x * a_s + eV.z * a_c; vv.x = v3.z; vv.y = v3.y; vv = normalize2(vv); // ベクトルvvを正規化(長さを1.0にする) thetaV = (float)acos(vv.x); if(vv.y < 0.0f) thetaV = -thetaV; // 符号反転
ここで求めたeV(3次元) → vv(2次元)の変換にて、2D平面上に軸を回転させています。この後、acos(vv.x)でvvとvector2(1, 0)の作る角度を求めています。厳密には cosθ = (vv.x * 1.0f + vv.y * 0.0f)の式になりますが省略してます。省略しない場合は以下のような計算式。
// ベクトルvvと(1, 0)が作る角度cosθの計算 float fVal = vv.x * 1.0f + vv.y * 0.0f; // θを求めたいため、acos(逆余弦)を使って算出。戻り値は0.0 〜 π thetaV = (float)acos(fVal);
で、求まったthetaV、phiVが極座標での値になります。
これを何に使ってるのかというと、マウス操作にて目標点の周りをぐるぐる回るという部分。パラメータは視点と目標点のみで、これらからθ、φを上記のように計算できます。θ、φにマウスでの移動量を加えたあとに
θを元にX軸中心の回転行列を求める → xMat φを元にY軸中心の回転行列を求める → yMat m = xMat * yMat eV = (0, 0, 1) vector4 v4 = vector4(eV.x, eV.y, eV.z, 1) * m
で求まった(v4.x, v4.y, v4.z)が新しい目標点から視点を見たときの方向ベクトルになります。
おっと、このソースでは回転がない場合は(0, 0, 1)がデフォルトの方向になりますね。上の極座標からデカルトの変換では(0, 1, 0)をデフォルトにしていたのですが説明に食い違いがでちゃいました。上記の説明に合わせるなら、極座標からデカルトへの変換は
X' = sinθ * cosφ Y' = sinθ * sinφ Z' = cosθ
となります。
len = length(targetPos - eyePos); // ベクトルの長さを求める eyePos2.x = v4.x * len + eyeTarget.x; eyePos2.y = v4.y * len + eyeTarget.y; eyePos2.z = v4.z * len + eyeTarget.z;
で求まったeyePos2が新しい視点位置です(目標点と視点が作る距離は変わらず)。
Future's Laboratory 技術格納庫 2004-2013 Yutaka Yoshisaka.