Comments
Description
Transcript
独り言日記
独り言日記 GPU でのレイトレース(2008/11/30) 時間があれば GPU ネタで実験してることを書き連ねていくことにします。このあたりは今まで まじめにやってなかったのもあり、試行錯誤兼覚書になりますのでご了承を。 GPU についての分かりやすい論文。 http://gpurt.sourceforge.net/DA07_0405_Ray_Tracing_on_GPU-1.0.5.pdf この論文では UniformGrid による空間分割を GPU にて行うようにしてます。結果を見ると CPU とあんまり差はないようですが、当時 GeForce6 シリーズ、今は 9 シリーズが一般に普及してるっ ぽいので、もうちょっと速いのかな。 一度 kd-tree による空間分割を GPU に入れてみようといじってたのですが、スタックって GPU で はどう実装するんだろう、ってのがあったので初心に帰って UG で確認してみることにしました。 たしか、kd-tree を GPU 実装する実験をされている方はいたと思いますので(海外の方で)、それ もいずれはチェックする必要はありそうですね。 こちらは輝度計算(一般で言うシェーダ部分)を CPU にて行いたいので、GPU にすべてを納めな い方法を考えていく必要があります。将来のカスタマイズを考えると、これごと GPU に入れると 汎用性がなくなるんじゃないかなぁと。そこでのボトルネックの解決策は「いかにまとめて GPU に処理を送って並列化を有効利用するか」にあります。いずれにせよメモリ転送(ハードとの I/O)がボトルネックになるのは分かりきってます。このあたりは昔掘ってたのがあるので、その 理論を適用。幸い今は PCI-Express が一般的で DMA 転送のボトルネックはかなりマシになってい るはず。 ところで、今は DirectX9 で実験してるのですがここでリソースを占有すると、グラフィック (DirectX での 3D 描画)との併用は物理的に無理なような・・・。OpenGL とならいけるのだろう か。CUDA は分離して処理できる、みたいなことが書いてましたね。 Larrabee が発表されるまでに実験結果が出ればいいや、というゆるゆるスケジュールにてこのあ たりは進めていきます(ちなみにこれは興味本位の趣味の実装ですので、完全オープンに行きま す)。 Mac での画像保存(2008/11/28) ちょっと間が空きましたが、Mac(Carbon) での画像保存方法です。 読み込み時と同じく、Carbon.framework/QuickTime.framework をプロジェクトのビルド時に参照で きるようにする必要があります。 #include <Carbon/Carbon.h> #include <QuickTime/ImageCompression.h> #include <QuickTime/QuickTimeComponents.h> 1 /** * 画像ファイルを保存する * @param[in] pFileName ファイル名 * @param[in] wid 保存する画像の幅 * @param[in] hei 保存する画像の高さ * @param[in] pRGBABuff 1 ピクセルにて RGBA 4 バイトの情報が格納されているバッファ bool SaveImageToFile(const char *pFileName, const int wid, const int hei, const unsigned char *pRGBABuff) { int x, y; unsigned char *pBuff; unsigned char *pSrcPos, *pDstPos; char *pPos; int fileType; if(!pFileName) return false; if(!wid ││ !hei ││ !pRGBABuff) return false; // 一度カラファイルを作成。こうしないと、FSPthMakeRef が失敗するため。 FILE *fp; fp = fopen(pFileName, "wb"); if(!fp) return false; fputs("t", fp); fclose(fp); // ファイル名を POSIX 形式から FSRef に変換 FSSpec spec; FSRef ref; Boolean isDir; if(FSPathMakeRef((const UInt8 *)pFileName, &ref, &isDir) != noErr) return false; // FSRef から FSSpec の変換 FSGetCatalogInfo(&ref, kFSCatInfoNone, nil, nil, &spec, nil); //--------------------------------------------------// // ファイルの拡張子より、保存ファイルの種類を調べる // //--------------------------------------------------// pPos = strrchr(pFileName, '.'); if(!pPos) return false; pPos++; fileType = kQTFileTypeBMP; if(strcmp(pPos, "bmp") == 0) { fileType = kQTFileTypeBMP; } else if(strcmp(pPos, "jpg") == 0 ││ strcmp(pPos, "jpeg") == 0) { fileType = kQTFileTypeJPEG; } else if(strcmp(pPos, "png") == 0) { fileType = kQTFileTypePNG; } else if(strcmp(pPos, "tiff") == 0 ││ strcmp(pPos, "tif") == 0) { fileType = kQTFileTypeTIFF; } else { return false; } //--------------------------------------------------// // 画像を RGBA のバッファに保持 // //--------------------------------------------------// pBuff = (unsigned char *)malloc(wid * (hei + 1) * 4); CGContextRef bitmapContext = CGBitmapContextCreate(pBuff, wid, hei, 8, wid * 4, CGColorSpaceCreateDeviceRGB(), kCGImageAlphaPremultipliedLast); // ビットマップの RGBA を格納するバッファ unsigned char *pBmpDat = (unsigned char *)CGBitmapContextGetData(bitmapContext); if(!pBmpDat) { CGContextRelease(bitmapContext); free(pBuff); return false; } // RGBA 情報をコピー。 // memcpy(pBmpDat, pRGBABuff, wid * hei * 4); // と同等。 pSrcPos = (unsigned char *)pRGBABuff; pDstPos = pBmpDat; for(y = 0; y < hei; y++) { for(x = 0; x < wid; x++) { *(pDstPos + 0) = *(pSrcPos + 0); // Red 2 *(pDstPos + 1) = *(pSrcPos + 1); *(pDstPos + 2) = *(pSrcPos + 2); *(pDstPos + 3) = *(pSrcPos + 3); } } // Green // Blue // Alpha pDstPos += 4; pSrcPos += 4; //--------------------------------------------------// // 画像タイプ別に保存 // //--------------------------------------------------// GraphicsExportComponent exporter; OpenADefaultComponent( GraphicsExporterComponentType, fileType, &exporter); GraphicsExportSetInputCGBitmapContext(exporter, bitmapContext); GraphicsExportSetDepth(exporter, 24); GraphicsExportSetOutputFile(exporter, &spec); GraphicsExportDoExport(exporter, NULL); CloseComponent(exporter); CGContextRelease(bitmapContext); free(pBuff); return true; } 注意点としては、FSPathMakeRef 関数はファイルが存在していない場合はエラーを返す、という部 分です。新規ファイルとして保存する場合は、そのままではエラーになってしまいます。ここで は、エラーにならないようにあらかじめダミーファイルを作成しています。 拡張子を見て、bmp/jpeg/png/tiff など分けてますが、「kQTFileTypeBMP」などの種類は http://homepage.mac.com/vanhoek/MovieGuts%20docs/36.html のページより、 kQTFileTypePicture ← pict 形式 kQTFileTypeGIF kQTFileTypePNG kQTFileTypeTIFF kQTFileTypePhotoShop ← psd 形式 kQTFileTypeSGIImage ← sgi 形式 kQTFileTypeBMP kQTFileTypeJPEG kQTFileTypeJFIF kQTFileTypeTargaImage ← tga 形式? kQTFileTypeFlash ← swf 形式? が 画 像 と し て あ る み た い で す。Windows の GDI+ と 対 応 フ ォ ー マ ッ ト を あ わ す な ら、 gif/png/tiff/bmp/jpeg、かな。 他の FreeImage などのライブラリを使うのも手ですが、意外と OS ネイティブの画像読み込みや保 存は、Windows の GDI+ も Mac の QuickTime も速いのでいいですよ。 ヴィルヘルム・ハンマースホイ(2008/11/25) 電車の中の広告にて「ハンマースホイ」という単語を見て、ジャッキーチェンの映画に出てくる ような脇役「ホイさん」か!とか一瞬妄想したのですが、、9/30 ∼ 12/7 に上野にて展覧会をやって るようです。 3 http://www.shizukanaheya.com/top.html 知らなかった作家さんなんですが、絵として来るもんがありました。GI 好きなら好きな作風です ね。フェルメールっぽいなぁと思って調べてみると、やっぱり影響を受けてるみたいです。 もうすぐで展覧会も終わりなので、見に行くかな。しかし、Wikipedia ではなんでも情報がある なぁ、すばらしい。 続・GPU での速度実験(2008/11/24) GeForce 9600GT で速くなった、と思ったのですがよくよく考えると GPU 計算はバックグラウンド (ハード側)で行われるため、完全に計算が終了まで待つとすると、結局は「IDirect3DDevice9 ::GetRenderTargetData」の完了まで待たないといけないのかもしれません。 ということで、再度「IDirect3DDevice9::GetRenderTargetData」が終わるまで待つようにして確認し ました。後、テクスチャサイズが大きい(1024x1024Pixel とか)とかなり遅いので、256x256pixel で処理するようにしました。 GeForce6600GT の WinXP 環境では CPU に比べて 2 倍強、GeForce 9600GT の WinVista 環境では CPU に比べて 5 倍くらい。微妙に速くなりました。う∼ん、でももうちょっと速くならないものかな。 CUDA のサンプルではメモリ転送速度を測るものがあったので、そのへんも最適化されてるんで しょう、たぶん・・・。素直に CUDA に移るべきか。ちらっと CUDA のサンプルソース見ました がややこしそうだ (^_^;;。 その前に、一度、GPU のみで計算する簡易レイトレーシングと、部分的に GPU にて計算する簡易 レイレーシングで比較したほうがいいですね。 GPU での速度実験(2008/11/23) GPU の実験です。レイトレで使用する三角形交差判定を 100 万回 x 1000 セット、合計 10 億回の 計算を行うものをぶん回してみました。 なお、CUDA じゃなくて DirectX9 の HLSL で実験です。CPU でループでまわした場合と GPU の 場合で比較してます。 ■ GeForce6600GT + Win XP / Intel Pentium4 2.8GHz / Mem 512MB CPU GPU 96326 ms 64477 ms 結果として、1.49 倍。 ■ GeForce 9600GT + Win Vista / Intel Xeon 5110 1.6GHz(Core 2 Duo) / Mem 2GB CPU GPU 4 96876 ms 32 ms 結果として、3027.37 倍。 GeForce6600GT ではあんまり効果がないということで放置していたのですが、GeForce9600GT で は如実に速度アップしました。 念のため、計算された結果を CPU の場合と GPU の場合で比較しましたが値としては一致。という ことで、ちゃんと計算はされているようです。 ただ、これはあくまでも GPU 上での計算のみで、 IDirect3DDevice9::GetRenderTargetData IDirect3DSurface9::LockRect ∼ IDirect3DSurface9::UnlockRect での GPU → CPU に結果転送を行う部分にてボトルネック作ってしまいます。試しに 1 セットご とにて上記の GPU → CPU の処理を入れると、GeForce 9600GT マシンにて「30110 ms」というこ とで、CPU に比べて速度アップは 3 倍程度になってしまいました。う∼ん、やはりメモリ転送が ボトルネックか・・・。 ただ、GPU 計算自身はたしかに GeForce9 シリーズでは速いですね。ちなみに作業用テクスチャは 1024x1024pixel のものを 3 つ(三角形の各頂点を 3 テクスチャに納めてます)、交差判定の結果格 納用のテクスチャを 1024x1024pixel、としました。テクスチャサイズを小さくしてみたり、 IDirect3DDevice9::CreateOffscreenPlainSurface のフラグを調整したりでなんとかできないかなぁ。 ここさえクリアできれば、ハイブリッドにてオフラインレンダラでも高速化させるための方法論 が投与できそうではあります。 後、これくらいパフォーマンスがあると空間のトラバースも GPU に入れてしまってもまだ余裕が ありそうですね。 Mac のヘルプシステムを作ろうかどうしようか(2008/11/21) Mac ではヘルプを実装するために HelpBook(AppleHelp の一機能って立ち居地でしょうか)とい う仕組みがあるのですが、私としては正直使いにくいです。気になる点は以下。 ・インデックス(ツリー)を表示してほしい ・可搬性がない(SDK のマニュアルとしたい場合、単体起動させてほしい) ・キーワード一覧がほしい ・検索入力がデスクトップ上のメニュー部にあるので、目線の移動が必要。同じウィンドウ 上で検索させてほしい。 いや、実現できてるよ、ってのがあったらすみません。 Windows/Mac OS X でマニュアル部を統一しようと思っていろいろ調べはしてるのですが、この 使い勝手がドキュメント好きの私としてはどうも受け付けません。Windows の HTML ヘルプに 5 慣れてしまったからかもしれませんが、、、。かといって、PDF は重いし検索がよろしくないのが 気になります。 Windows の HTML ヘルプは「HTML Help Workshop」にて作成できます。以下の Office リソース キットに入ってます。昔っから英語版しかないですが、日本語のヘルプ作成も OK です。 http://www.microsoft.com/japan/office/ork/2003/tools/BoxA02.htm 一応、Windows の HTML ヘルプはネットと絡めて CGI を HTML ヘルプに入れ込む、なんてこと も可能です(実体は単なる HTML ですので)。 昔っからずっと HTML ヘルプは使い続けてるのですが、ヘルプとしての使いやすさはやっぱり HTML ヘルプが一番かなぁ。 wxWidgets のサンプルでヘルプを作るのもあるのですが、ちょっとごわっとしたのが気になりま す。でも、wxWidgets を使うのがマルチプラットフォームのヘルプとして一番実現しやすそうか な。 6 以下の HelpLogic というのはよさげなんですが、 Mac/Win どちらでも動作するものは結局は HTML と JavaScriptっぽいですね。 http://www.ebutterfly.com/helplogic/ といろいろ調べてもいい案が浮かばないので、作ったほうがいいのかなぁ。次回のレンダラでの ヘルプ搭載は現状維持でいきますが、アプリケーションをこれ以外でも作る予定のある都合に て、ヘルプの使い勝手は避けられない問題です。 ユニットバスのレンダリング、リベンジ(2008/11/21) もう一度シーンを見直して再度サンプリング数を高くしてレンダリングしてみました。 Win Vista / Intel Xeon 5110 1.6GHz(Core 2 Duo) / Mem 2GB 7 640x480 pixel、20000sampling/pixel にて約 33.45 時間。 2 日マシンを立ち上げっぱなしで CPU 酷使です (^_^;;。 以下のような部屋構成になってます。 光は外からの IBL のみ。3 つの窓からの光が入ります。 ちょっとサンプリング数を変えて実験してみました。 100 sampling/pixel 1000 sampling/pixel 8 明らかにサンプリング数により光の入りっぷりが異なってます。拡散反射の場合は半球上にラ ンダムにレイが反射することになりますが、光が極端に偏ってる場合(閉じた部屋の窓から光が 入る場合など)はサンプリング数を上げないと最終結果に近づかない、ということなりそうです。 ここで遅いレンダリング時間をあえて出しているのは、 「では、この質を保ちつつどこまで時間短 縮できるか」というものの記録を行っていきたいから。閉鎖空間でフォトン使用にてどこまでで きるのだろうか、これも実験事項です。MLT などの最適化アルゴリズムを実験する、これも必要。 GPU(CUDA) などのハードウェアの恩恵を受ける(将来は Larrabee)、これも実験がいりますね。今 の GPU に載せる方法はおそらくあまり効果はないのかなと思ってます(オフラインレンダラの大 半にて)。ハードは容量が限られてますので、単純な軽い実験用パストレならいいのですがマテ リアルが複雑になるものとかは、良くて数並列程度になってしまいそう。いろいろ誤解がありそ うなので近いうちに実験はしてみようとは思ってます(それでも、CPU と GPU の両方を使って 2 並列、とかも可能でしょうからね)。そう考えるとどうしても Larrabee の方に期待が傾くのです が、どうなんでしょうね。 あっと、一応いいわけですが、あくまでもレイトレースベースでのレンダラでの GPU 使用でどこ まで速くできるのだろう、の探求は行おうとは思ってます。もちろん、レイトレを使わずにつめ ていく方法もあり、GI がリアルタイムで動作している、というプロジェクトもちらほらあります。 ぱっと見はなかなか違いが難しいのですが、、、。室内シーンをより高速に、ができればいいです ねぇ。個人的には超リアルな CG で Myst みたいなゲームがしたいです (^_^;;。 はずかしながら CUDA はチェックしてなかったので以下のサイトは最近知りました。 http://www.nvidia.co.jp/object/cuda_home_jp.html# 何倍になった、というのがプロジェクトごとに書いてますので、何に強いか弱いか、ってのは把 握できますね。単純な計算はそれだけハードリソースが少なくて済むので並列数も増やせる、レ ンダラみたいにちょっと大きな規模になるとそれだけハードリソースを食うので並列数も少な くしないといけない、ということかもしれませんね。 で、上のユニットバスのレンダリング例だと、サンプリング数を少なくして求める絵を出す、と いうのは難しいということなのかもしれません(視点からのパストレースでは)。一次レイから の反射は重要なのでそこだけサンプリング数を増やして、それ以上はカットしていく、もしくは それ以降はフォトンを集める、とか、いろいろ方法はありそうですので絵と比べながら速度アッ 9 プできれば。今はそのためのリファレンス用レンダラでもあります。 後、これくらいサンプリング数が多ければ右の蛇口が壁に反射してコースティクスを生んでいる のも見えますね。 COLLADA 形式(2008/11/20) http://www.khronos.org/collada/ が COLLADA の総本山ですかね。単なる XML ですので(バイナリもあるみたいですが)対応し やすそう。 カメラデータやアニメーション、物理関連のパラメータも持てるじゃないですか。でも、レンダ リング系のパラメータはないですねぇ。シェーダがあるのでどっちかというとリアルタイム系 のフォーマットっぽい。 これも将来対応予定とできれば。シーン保存はこれか fbx で満足できそうだなぁ。 Mac での画像読み込み(2008/11/20) Mac で画像を読み込むには QuickTime の機能を使うと Windows での GDI+ みたいな読み込み・書 き込みを行うことができます。確認できたところで、jpeg/png/bmp/gif が OK。たぶん pict も OK か。 と こ ろ が、簡 単 に 扱 え る と い う も の で は あ り ま せ ん。Mac OS X で は、POSIX 形 式 の 「/Users/xxx/tmp/test.png」みたいなファイルパス指定を、旧 Mac での「:Users:xxx:tmp:test.png」みた いな形式に変換してくる必要があります。ドライブ(と Mac で言っていいのか)の指定がある場 合があるので、簡単に「/」を「:」に置き換えるだけじゃダメです。そう考えると、UNIX 系で使 われるファイルパス指定はスマートですね(ドライブという概念はマウントすることになるので 特別視する必要はないわけですし)。 さて、Mac ではファイルパス変換のため、「POSIX → FSRef → FSSpec」なる変換を行います。 ソースを書いたほうがはやいのでそっちで説明します。 なお、Carbon.framework/QuickTime.framework をプロジェクトのビルド時に参照できるようにする 必要があります。 #include <Carbon/Carbon.h> #include <QuickTime/ImageCompression.h> #include <QuickTime/QuickTimeComponents.h> /** * ファイルから画像情報を読み込み * @param[in] pFileName ファイル名 * @return 読み込みに成功すれば true が返る */ bool LoadImageFromFile(const char *pFileName) { CGImageRef image; FSSpec spec; 10 OSErr err; GraphicsImportComponent gi; int wid, hei; unsigned char *pBuff; int x, y; unsigned char *pSrcPos, *pDstPos; bool ret; if(!pFileName) return false; // ファイル名を POSIX 形式から FSRef に変換 FSRef ref; Boolean isDir; if(FSPathMakeRef((const UInt8 *)pFileName, &ref, &isDir) != noErr) return false; // FSRef から FSSpec の変換 FSGetCatalogInfo(&ref, kFSCatInfoNone, nil, nil, &spec, nil); // グラフィックのインポート処理開始 err = GetGraphicsImporterForFile((const FSSpec *)&spec, &gi); if(err != noErr) return false; // グラフィックスを得る ret = false; if(!GraphicsImportCreateCGImage(gi, kGraphicsImportCreateCGImageUsingCurrentSettings)) { // 画像の幅と高さを取得 wid = (int)CGImageGetWidth(image); hei = (int)CGImageGetHeight(image); &image, // 1 ピクセルでのビット数 //int bpp = CGImageGetBitsPerPixel(image); // 1 ラインでのバイト数 //int scanBytes = CGImageGetBytesPerRow(image); // ビットデータの取得 (RGBA を取得するとする ) pBuff = (unsigned char *)malloc(wid * (hei + 1) * 4); CGContextRef bitmapContext = CGBitmapContextCreate(pBuff, wid, hei, 8, wid * 4, CGColorSpaceCreateDeviceRGB(), kCGImageAlphaPremultipliedLast); // ビットマップを描画 CGContextDrawImage(bitmapContext, CGRectMake(0,0,wid, hei), image); // ビットマップの RGBA バッファ unsigned char *pBmpDat = (unsigned char *)CGBitmapContextGetData(bitmapContext); if(pBmpDat) { pSrcPos = pBmpDat; for(y = 0; y < hei; y++) { for(x = 0; x < wid; x++) { // *(pSrcPos + 0) が Red // *(pSrcPos + 1) が Green // *(pSrcPos + 2) が Blue // *(pSrcPos + 3) が Alpha } } } pSrcPos += 4; } ret = true; CGContextRelease(bitmapContext); free(pBuff); CGImageRelease(image); CloseComponent(gi); } return ret; ファイル名は POSIX 形式(「/Users/xxx/test.png」みたいにスラッシュ指定)で指定します。日本語 など全角がある場合は UTF-8 で与えるようにしてください。 11 読み込まれると、上記のコメント化している「*(pSrcPos + 0) ∼ *(pSrcPos + 3)」にてピクセルの Red/Green/Blue/Alpha 値が 0 ∼ 255 の値にて取得できます。 Carbon の「GraphicsImporter」と い う も の で 画 像 を 読 み 込 む の で す が、そ れ に 渡 す た め に 「FSPathMakeRef」関数で POSIX から FSRef に変換、 「FSGetCatalogInfo」関数で FSSpec に変換、と いう手順を踏む必要があります。 その後、GraphicsImporter より CGImage を取得、 「CGContextDrawImage」関数にてビットマップに 描画、でようやくピクセル値にアクセスできます。 ・・・正直、こんだけややこしいのならカプセル化してくれよ、と思うのは私だけでしょうか (^_^;;? Carbon のオンライン情報見てもなかなかたどりつけない部分ですので(私は断片的な情報を寄せ 集めてようやくたどり着きました)、参考になれば幸いです。読み込み速度に関しては、遅くはな いです、それなりの速度といったところ。 しかし、OS の持つ命令群は古い仕様と新しいものが混在してますね。パスカル文字列なんてひさ しぶりに見ましたし。Cocoa だともっと簡単なのかな。でも、覚える気ゼロです(苦笑)。だって、 ObjectiveC は他 OS に生かせる汎用性がないんだもの。なんで Java なり C をネイティブに選択し なかったのか、まずい対応だよなと今でも思ってます。政治的なことではありそうですが NeXT の血なんで、分からなくはないのですが・・・。 で、画像あたりと QuickTime 命令をいじっていて Inside Macintosh を思い出しました。昔の Mac プ ログラマなら持っていたであろう分厚いプログラム本です。今は情報が Apple のサイトに Web 展 開されてますが、どうも探しにくいです(少し前まではもうちょっとアクセスしやすかったので すが、、)。 次回は、画像ファイル保存について説明していきます。 wxWidgets(wxAUI) でのズレ対策(2008/11/19) タイミングによって、wxAUI でのフローティングウィンドウを移動するとマウスリリース時に他 のウィンドウも移動することがある、という現象に出くわしてまして、wxWidgets 内のソースを チェック中です。Windows でも Mac OS X でも再現(こちらの環境では Mac のほうが再現率は低 いです)。平行してレンダリングを走らせている場合など、負荷が高いときに起こりやすい感じ でした。 aui/framemanager.cpp の「wxAuiManager::OnFloatingPaneMoved」関数内にて if (pane.IsFloating()) { pane.floating_pos = pane.frame->GetPosition(); if (m_flags & wxAUI_MGR_TRANSPARENT_DRAG) pane.frame->SetTransparent(255); } のところにて、 12 if (pane.IsFloating()) { // 以下を追加 { int cou, loop; cou = m_panes.GetCount(); for(loop = 0; loop < cou; loop++) { wxAuiPaneInfo& p = m_panes.Item(loop); if(!p.IsFloating()) continue; if(p.name == pane.name) continue; p.floating_pos.x = p.frame->GetPosition().x; p.floating_pos.y = p.frame->GetPosition().y; } } pane.floating_pos = pane.frame->GetPosition(); } if (m_flags & wxAUI_MGR_TRANSPARENT_DRAG) pane.frame->SetTransparent(255); のようにすることで再現しなくなりました。この関数では、キャプション部をドラッグしてひと つのウィンドウを移動させた場合、移動が完了した(マウスリリースになった)タイミングで呼 ばれています。 どうも、ドラッグ対象のフローティングウィンドウ以外でも floating_pos の値が書き換えられる場 合があり、この「wxAuiManager::OnFloatingPaneMoved」関数の最後で呼んでいる Update 関数内で floating_pos と frame->GetPosition で取得できる位置が違う場合に「frame->SetSize(floating_pos.x, floating_pos.y...)」としてる部分でウィンドウがあらぬ位置に移動してしまうようでした。 みくよん(2008/11/15) みくよん書籍版を買ってきました。 ニコニコ動画でこれを見て感動して、当時同人誌でのなぎみそ SYS さんの「みくよん」を探して 13 たのですが売り切れてて手に入らなかったのです。で、11 月に書籍化するというのが紹介されて ましてようやく手に入りました。 こういう文字の少ないものでも内容が伝わるのは絵の表現力としてすごいなぁと思ったりして ます。 後は来月出るうろたんだーの CD を買う予定です。ニコ房、ミク房(うろたんだーは KAITO です が、VOCALOID 全体にはまってます)まっしぐらなんですが気にしない!! みくよんを買ったのは秋葉原のとらのあなで、行ったときは同人ゲーム売り場も見てるのです が、東方とミク関連が増えてますね。音楽 CD はほしいのがあるなぁ。今年は MacBook で散財し たので自重しなくては、、、。 続続続・多角形とレイとの交差判定(2008/11/15) 話が飛びましたが、凹凸多角形とレイの交差判定の最後です。2D 上に投影した凸多角形は 11/11 にやった内容で、外積にて内包されているか判定、というところまでやりました。 凹凸多角形の場合、交点のある位置を赤丸としたときの図です。 それぞれの交点の横方向にラインを引きます。ここでは 3 つの交点を例に挙げてます。 ■ LineA 多角形の稜線より、LineA と交差する位置を求めます。赤丸よりも左または右にある(上図では青 四角の)交点の数をカウントしましょう。左は 1 個、右は 1 個です。このときは赤丸は多角形に 内包しています。 14 ■ LineB 多角形の稜線より、LineB と交差する位置を求めます。赤丸よりも左または右にある(上図では青 四角の)交点の数をカウントしましょう。左は 1 個、右は 1 個です。このときは赤丸は多角形に 内包しています。 ■ LineC 多角形の稜線より、LineC と交差する位置を求めます。赤丸よりも左または右にある(上図では青 四角の)交点の数をカウントしましょう。左は 2 個、右は 2 個です。このときは赤丸は多角形の 外にあります。 こんな感じで見てみると、ある法則が見えてきます。「左にある横方向のラインとの交差数が奇 数の場合は内包、偶数の場合は外」、同様に「右にある横方向のラインとの交差数が奇数の場合は 内包、偶数の場合は外」、ということでどっちかひとつを見れば OK。 プログラムで説明します。2D 上に投影されたときの頂点を vector2 posA[] の配列であらわす、頂 点数 vCou、平面上での交差位置を vector2 cPos とします。 float x1, y1, x2, y2, tmp, xx; int cou; cou = 0; for(loop = 0; loop < vCou; loop++) { x1 = posA[loop].x; y1 = posA[loop].y; if(loop + 1 < vCou) { x2 = posA[loop + 1].x; y2 = posA[loop + 1].y; } else { x2 = posA[0].x; y2 = posA[0].y; } if(y1 > y2) { // y1 < y2 となるように入れ替え tmp = x1; x1 = x2; x2 = tmp; tmp = y1; y1 = y2; y2 = tmp; } if(y2 - y1 < 1.0f) { if(x1 <= cPos.x) cou++; continue; } if(cPos.y >= y1 && cPos.y <= y2) { // y = cPos.y の位置でのスキャンライン上の交差位置を求める xx = (x2 - x1) * (cPos.y - y1) / (y2 - y1) + x1; if(xx <= cPos.x) cou++; } } if(cou & 1) { 交点 cPos は多角形 posA[] に内包される } else { 交点 cPos は多角形 posA[] の外にある } ということになります。思ったよりも簡単になりますね。 ということで、このように一連の処理を行うことで、凹凸多角形がある場合のレイとの交差判定 も可能になります。ただ、まだまだ最適化できる点は残してますので(if で分岐してるところと か)詰めてみるものいいかもしれません。 多角形の法線の計算や、凸多角形かどうかの判定などは前処理でやってしまってもいいですね。 15 三角形の場合は、Tomas Moller のほうがコストが低いのでそっちに分けてしまうとか。 まだまだネタはあるのですが小出しにしていければと思ってます。Mac での画像読み込みや FSRef から FSSpec 変換などはまだでしたね。これも徐々に。 SPEED フォーマット(2008/11/12) 「ジャパンホームショー 2008」というイベントが東京ビッグサイトにて行われてまして、本日は一 日行ってきました。目が疲れた・・・。 私の目的は CAD 系システムってどんなもん、というのを見るためと「SPEED フォーマット」に関 しての情報収集です。SPEED とは建築 CAD 向けのオープンフォーマットの規格、で建築関連 CAD メーカー各社が集まって考えたフォーマットとのこと。これのお披露目会をやってました ので見てきました。オープンフォーマットなのでライセンス料はかからないようですね。 個人的には SPEED フォーマット自身の説明を期待していたのですが、アプリケーションに組み込 んだ実例、といったメーカーさんの製品での操作の紹介が主ではありました。 本日サイトが新しくオープンしてたので URL を。 http://www.speed-net.jp/ 「SPEED とは」のページから仕様書をダウンロードできるのですが、最後のページ「面の表裏」は 右手系だと「逆周りじゃね?」と思うのは私だけでしょうか (^_^;;。反時計周りが表のような。 レンダラの絡みで最近はいろんなフォーマットを見てるのですが、これといった決め手のあるも のはなかなかないのかなと思ってます。仕様書から判断できる SPEED フォーマットは形状のみ (マテリアルはあります)ですね。カメラやレンダリング、アニメーション系の機能はないような 感じです。ユーザが拡張できる、という話はしていたのですが、fbx なんかそうですが個々に拡張 してしまうと結局方言を作ることになるので初期仕様がある程度完成していないとなかなか使 う側は難しいのかも。まだオープンにされていない仕様や今後機能追加していく部分があると は思いますので、どのように進化するのかは注目しております。 後、某メーカー(SPEED フォーマット対応してます)のブースにて CAD ソフト見せてもらったの ですが、窓の開閉・ドアの開閉ができてました。Shade ホームデザインで開閉してほしいなぁと 思ったりしてます(半開きの窓を Shade 上で作らないといけない、なんてなんか無駄な気がするの で)。 CAD ソフトは専門外なのであんまり何ができるのか理解できてないのですが、なるほど、3DCG との間にはたしかに溝がありますね。基本は図面なのか。マテリアル設定から先のレンダリン グまでの作業は 3DCG 寄りの分野といった感じではありました。 続続・多角形とレイとの交差判定(2008/11/11) 続きです。 16 ■面が凸多角形か判定 2D 上に投影されたときの頂点を vector2 posA[] の配列であらわす、頂点数 vCou、平面上での交差 位置を vector2 cPos とします。 まずは、多角形が凸型かどうかの判別です。 上図のように、頂点が n 個ある多角形の頂点をはさむ辺の外積を取り、0.0 以下かそれ以外か、で カウントします。プログラム書いたほうがはやいので、プログラムを。 cou = 0; for(loop = 0; loop < vCou; loop++) { if(loop == 0) { v1 = posA[loop] - posA[vCou - 1]; } else { v1 = posA[loop] - posA[loop - 1]; } if(loop + 1 < vCou) { v2 = posA[loop + 1] - posA[loop]; } else { v2 = posA[0] - posA[loop]; } if((v1.x * v2.y - v1.y * v2.x) < 0.0f) cou--; else cou++; } 頂点をはさむ辺の外積を計算して、変数 cou にてカウントしてます。この cou が vCou または -vCou の場合は、凸多角形と判断できます。外積で求まるのは平面に垂直な直交軸が手前に伸びている か奥に伸びているか、です。これがすべて同じ向きかを調べるわけです。 ■凸多角形の場合は外積により内包判定。凸多角形の場合の判定はここで終了。 凸多角形と分かった場合は、その凸の中に交点があるか外にあるか、を判定します。 17 今度も外積を使用します。交点位置と個々の辺の両端を結ぶベクトルを求めます。それを元に 先ほどと同じように外積で求めた値が 0.0 以下かそれ以外かでカウントします。 cou = 0; for(loop = 0; loop < vCou; loop++) { v1 = posA[loop]; if(loop + 1 < vCou) { v2 = posA[loop + 1]; } else { v2 = posA[0]; } e1.x = cPos.x - v1.x; e1.y = cPos.y - v1.y; e2.x = v2.x - cPos.x; e2.y = v2.y - cPos.y; } if((e1.x * e2.y - e1.y * e2.x) < 0.0f) cou--; else cou++; ここでの cou が vCou または -vCou の場合は凸多角形でかつ交点 cPos が内包されている、と判断 できます。cou がそれ以外でかつ凸多角形の場合は交差しない、と判断できます。 後は、凹凸多角形ですね。これはスキャンライン的に判断します。といっても、交点位置は分 かってるので稜線との交点を求めて判断するだけではあります。よく塗りのアルゴリズムで使 われるのですがキーワードは偶数・奇数です。これは次回にて。 続・多角形とレイとの交差判定(2008/11/11) 先日の続きです。 18 上記のように、凹凸多角形がありそれにレイが交差するとします。計算する流れとしては ・多角形から面法線を求める(平面式との交差計算で使用) ・平面とレイの交差距離を求める。これで交差位置も求まることになる。 ・面の頂点および交差位置は同一平面上にあると仮定して、2D 上に投影 ・面が凸多角形か判定 ・凸多角形の場合は外積により内包判定。凸多角形の場合の判定はここで終了。 ・それ以外の凹凸多角形の場合の内包判定。スキャンラインにて判定を行う。 のような感じです。 ■多角形から面法線を求める 面を構成する頂点は同一平面上にあると仮定します。モデラーなどで同一平面にそろえない場 合もあるので極力平均を取った法線を求めます。 以下の vector3 は (x,y,z) 要素を持つものとします。 // vCou を面を構成する頂点数、 // pWPos が頂点座標の格納された先頭ポインタとする int loop; double dx, dy, dz; vector3 v1, v2; vector3 n = vector3(0, 0, 0); vector3 *pPos = (vector3 *)pWPos; // 頂点の配列先頭 19 for(loop = 0; loop < vCou; loop++) { if(loop == 0) { v1 = (*pPos) - (*(pWPos + vCou - 1)); } else { v1 = (*pPos) - (*(pPos - 1)); } if(loop + 1 < vCou) { v2 = (*(pPos + 1)) - (*pPos); } else { v2 = (*pWPos) - (*pPos); } // dx dy dz 外積より面法線を求める = (double)(v1.y * v2.z - v1.z * v2.y); = (double)(v1.z * v2.x - v1.x * v2.z); = (double)(v1.x * v2.y - v1.y * v2.x); if(fabs(dx) < (1e-5) && fabs(dy) < (1e-5) && fabs(dz) < (1e-5)) { pPos++; continue; } n.x += (float)dx; n.y += (float)dy; n.z += (float)dz; pPos++; } normalize(&n, &n); // 法線の正規化 法線は、1 つの頂点をはさむ 2 辺の外積にて求まります。念のため、すべての頂点の法線を求めて それを加算、最後に正規化して長さが 1.0 となるようにしてます。面の向きはどちらでも OK で す。求めるのは平面式でのレイからの距離を求めるのに使用しますので。 ■平面とレイの交差距離を求める 法線 n が求まりましたので、平面式は nx(X - x1) + ny(Y - y1) + nz(Z - z1) = 0 となります。(nx, ny, nz) が面の法線、(x1, y1, z1) が面を構成する頂点。ここでは、頂点座標の一番 はじめのものを使うとします。 レイの開始位置を rayPos、レイの方向ベクトル(正規化済みとする)を rayDir とし、仮に以下の 式を用意します。 c.x = rayDir.x * t + rayPos.x; c.y = rayDir.y * t + rayPos.y; c.z = rayDir.z * t + rayPos.z; で求めた c が平面との交点位置、t がレイの開始点からの距離とします。この c を (X, Y, Z) = (c.x, c.y, c.z) として平面の式に代入します。t 以外は定数ですので、これにて t が求まります。計算は以 下な感じ。 double dx, dy, dz, A, B; dx = (double)(rayPos.x - (pWPos->x)); dy = (double)(rayPos.y - (pWPos->y)); dz = (double)(rayPos.z - (pWPos->z)); B = (double)(n.x * dx + n.y * dy + n.z * dz); A = (double)(n.x * rayDir.x + n.y * rayDir.y + n.z * rayDir.z); if(fabs(A) < 1e-5) return false; t = (-B / A); if(t < 0.0f) return false; ここで計算した t がレイの開始位置からの距離です。計算途中の A が限りなく 0 に近い場合は平 20 面の法線とレイが垂直に近い場合(面とレイが平行で交差せず)、t が 0 より小さい場合はレイよ り手前にあることになります。 ここで求めた t を上で書いた c の式に当てはめると、交差位置が求まりますね。理想としては、多 角形の頂点と交点位置は同一平面上にあるとよいことになります。 ここで交点は求まったのですが、まだ「平面上にあるけど、多角形内に内包されるか」は判断で きてません。ということで、内包判定を行います。 ■面の頂点および交差位置は同一平面上にあると仮定して、2D 上に投影 とある一点が内包されているかどうか、は 2D 上に投影することで計算しやすくなります。三次 元上では XYZ の 3 要素があるのですが、これから 1 つの要素を消して XY だけで考えると分かり やすいですよね。で、上の説明で「多角形の頂点と交点位置は同一平面上にあるとよい」という のを入れました。こういう条件だと、平面から見ると 2D で判別できそうです。 で、投影方法なんですが、平面式の計算で法線 n を求めました。これが Z 軸にイコールになるよ うに変換し、Z を相殺しましょうか。 そこで出てくるのが「正規直交基底」です。まずは計算式から。 vector3 vX, vY, vZ; vZ = n; if(fabs(vZ.y) < 0.9f) { vY = srVector::SVector3(0, } else { vY = srVector::SVector3(1, } vec3Cross(&vX, &vY, &vZ); // vec3Cross(&vY, &vX, &vZ); // vec3Normalize(&vX, &vX); // vec3Normalize(&vY, &vY); // 1, 0); 0, 0); 外積計算。vX に vY x vZ の結果が入る 外積計算。vY に vX x vZ の結果が入る 正規化 正規化 法線ベクトル n が (0, 1, 0) に近くなければもうひとつの軸を (0, 1, 0) とする、 (0, 1, 0) に近ければも うひとつの軸を (1, 0, 0) とする、で外積計算をすることで3つめの軸が求まります。そして求まっ たベクトルと vZ( 法線ベクトル ) を再度外積計算することで、完全に直交する 3 つのベクトルが求 まります。これを正規化して (vX, vY, vZ) として保持。 この正規直交基底は異なる座標間の変換ではよく使われますので覚えておいて損は無いです。 (fx, fy, fz) を変換前の頂点座標とすると、 dx = (double)(vX.x * fx + vX.y * fy + vX.z * fz); dy = (double)(vY.x * fx + vY.y * fy + vY.z * fz); dz = (double)(vZ.x * fx + vZ.y * fy + vZ.z * fz); の計算にて、元の頂点座標が同一平面にある場合は dz はすべて同じ値になります。ということ で、Z 成分を除去できます。同様に、交点 c も当てはめて Z 成分を除去します。 概念としては、以下のような XY 座標で交点位置が投影される状態となります。 21 後は 2D 処理です。 と、ここでいったん区切ります。 多角形とレイとの交差判定(2008/11/10) 本日はネタフリだけ。ひさびさに実用的(?)な話題です。まず、レンダラ(とビュワー)にて obj ファイル形式のインポータと、多角形ポリゴン対応をしてみました。obj の仕様上は 1 つの面 に対して何頂点でも指定可能な感じでしたので、これを機に。レンダラ内部では多角形対応して いましたが、ビュワーにて対応中です。 22 レンダラ内部では多角形を三角形分割して保持し、それで三角形とレイとの交差判定を行ってま す。 多角形との交差判定では以下のような方法が代表的でしょうか。 ・多角形を三角形分割して扱う ・多角形のまま扱う 大部分は三角形分解が可能だと思うのですが、なにぶん余計なメモリを確保しておかないといけ ないというのもあるので、後者の場合を考えていきます(前者は昔日記に書きましたのでスルー です)。レンダラで扱う場合はどれが適切なんでしょうね、処理時間が大事なので三角形分解し て扱うほうがいいのかなと考えてますが、たしかにメモリに無駄は生じるかも。 以下のような関数で、凹凸多角形の交差判定を行うとします。 23 /** * 多角形とレイとの交差判定 * @param[in] rayPos レイの開始位置 * @param[in] rayDir レイの方向ベクトル(正規化済み) * @param[in] vCou 面を構成する頂点数 * @param[in] pWPos 面のワールド座標での頂点座標の配列 * @param[out] pRetT レイの開始位置から交差位置までの距離 * @return 交差する場合は true */ bool PolygonIntersect(const vector3 &rayPos, const vector3 &rayDir, const vector3 *pWPos, float *pRetT); int vCou, この機能、ビュワーにてマウスクリックされたときに多角形ポリゴンが選択されているか、の判 別に使いたいんです。手順としては、平面とレイとの交差判定で交差位置を求め、平面上に頂点 および交差位置を投影、で 2 次元上での内包判定を行うとします。スクリーンに投影した 2D 上の 多角形とスクリーン上のマウスクリック点で判別してもいいのですが、一応レンダラでも使える ようにレイとの交差判定で考えることにします。 なお、凸多角形の場合は外積計算だけで内包されているかは判定できます。問題となるのは凹が 存在する多角形の場合。ということで、しばらくはこのネタで引っ張ります。 Shade のフォーラムページ(2008/11/07) 私はてっきり削除されたものかと思ってたのですが、どうもまだ生きてるらしい、という情報を いただきました。 Shade online のページから「コミュニティ&ギャラリー」のトップメニューを選択。左に表示され ているメニュー「過去の投稿作品(閲覧のみ)」の「画像投稿機」を選択。これで、Shade online フォー ラムのメニューが左に出てきて「最近の投稿」を選択するとフォーラムが出てきます。 なんという隠し通路。 artist side には登録・投稿したいかなぁと思うこともあるのですが、個人的にはどうも点数をつけ られるのが受け付けられなくって今は ROMってます。競争原理が働くのでクリエイティブな方 には有効かもしれませんが、正直素人な私が嬉々としてアップして点数つけられてへこむのもイ ヤだしなぁと。 obj ファイルフォーマット(2008/11/06) 3D ではよく使われるであろう obj ファイルフォーマットについて。後できちんと調べて実装しよ うと思って Shade のフォーラムに情報として書き込んだのですが、なんだかフォーラム自体が消 えてますのでもう一度こっちに貼っておきます。 http://local.wasp.uwa.edu.au/‾pbourke/dataformats/obj/ http://local.wasp.uwa.edu.au/‾pbourke/dataformats/mtl/ お気に入りにいれときゃいいですね (^_^;;。面倒なので後で調べようと思って忘れてしまうこと が多いです<自分。 しかし、フォーラムがなくなると(で、artist side が出来たのだと思うけど)作品メインじゃない 自分は活躍の場がないなぁと。まあいいや、自分のサイトで自作自演で盛り上がっておきます。 24 さて、obj はいろいろ機能があるようですが頂点のリストが 1 つしかないです。個々のオブジェク トはグループという概念でまとめることができるのですが、グループごとに再度頂点リストを再 配置しないといかんですねぇ。 ただ、構成としては単純なので実装はしやすいです。マテリアルのほうは細かい機能が多いので すが、ツールのインポータでは対応してない機能が多いのかもしれませんね。とりあえず、Shade/ メタセコ /3D アトリエにて obj を出して試してみました。マテリアルに関してはほんとに基本部 分のみといった感じ。 そういえば、ご存知の方もいるとは思いますが Shade ホームデザインのコンテンツが増えてます。 最近、なぜかイーフロからの案内メールがこない状態でしてはぶられた感があるのですが、artist side の書き込みでアップデートされてるのを知りました。今は 1.0.2.2 です。私としては、ささっ と配置したいというのもありこれはありがたいです。今まで気づいてなかったのですが「琉球 畳」素材が!!前からあったかどうかは記憶にございません (^_^;;。 で、Shade ホームデザインは個人的によく使ってます。Shade の shd に吐き出したときのにマテリ アルがまとまってないのがもったいないですが、よく考えればプラグイン作ってまとめちゃえば いいかもしれないですね。 後、Autodesk からのメールを見て fbx 2009.03 なるものが出てることを知りました。これも何が変 わったのか試してみたいなぁ。ちなみに fbx SDK に targa 形式の読み込み・書き込み関数がありま して、そのソースを元にレンダラにて targa 形式のインポータを作らせてもらいました。フォー マットとしては割と簡単です。 fbx SDK にて知ったのですが「COLLADA」ってのがあるのですね。3D のモデルフォーマットら しい。fbx もちょっと過去の仕様が点在して分かりにくいところがあるので、なんかもうちょっ と整然としたかつ拡張性のある統一フォーマットはないかなぁと思ったりしてます。 スレッドを最適化(2008/11/04) サンプルレンダリングは、以下の 400x300pixel、100 sampling/pixel のもの。 25 ■ Mac OS X 10.5.5 / 2.1GHz Intel Core 2 Duo / Mem 1GB 最大スレッド数 Ver 0.5.1.3 備考 Ver 0.5.1.4 1 Thread 135 秒 135 秒 --- 2 Thread 99 秒 74 秒 旧来から見て 1.34 倍の 速度アップ 16 Thread 74 秒 74 秒 --- ■ Win XP / Pentium4 2.8GHz(HyperThread 環境 ) / Mem 512MB 最大スレッド数 Ver 0.5.1.3 備考 Ver 0.5.1.4 1 Thread 161 秒 161 秒 --- 2 Thread 148 秒 137 秒 旧来から見て 1.08 倍の 速度アップ 16 Thread 153 秒 159 秒 --- ■ Win Vista / Intel Xeon 5110 1.6GHz(Core 2 Duo) / Mem 2GB 最大スレッド数 Ver 0.5.1.3 備考 Ver 0.5.1.4 1 Thread 132 秒 132 秒 --- 2 Thread 70 秒 70 秒 --- 16 Thread 70 秒 72 秒 --- Win では一部で速度アップですが、マルチコア環境ではほぼ変わらず。Win 環境のマルチコア以 26 外では 2 スレッド時に若干速度アップしました。ただ、スレッド数を上げても変わらないことか ら(HyperThread で若干速いですが)、コア数にあわせたスレッドを使うほうが効率がよさそうで すね。 Mac 環境で速度アップ。スレッドのスケジューリングを見直しました。Win 環境では CoreDuo に てスレッドを 1 から 2 の使用に変えることで約 1.88 倍、Mac 環境では CoreDuo にてスレッドを 1 から 2 の使用に変えることで約 1.82 倍、ということでマルチコアの恩恵としてはこんなところか な(以前完全 2 倍になっていたのですが、シーンによるのかも)。 一応、無駄なくスレッドのスケジューリングしたつもりですが、スレッドに関して分かったこと が数点あります。 ■無駄な Sleep を置かない こ れ は 当 た り 前 で す ね。 Sleep を 使 う ん じ ゃ な く て、 Windows の 場 合 は WaitForSingleObject/WaitForMultipleObjects で待たせること。Mac は sem_wait(POSIX の命令。指 示があるまではこの場で待機)で待つように。 ■ Windows の WaitForXXX では常に INFINITE タイムアウトによる待ちからの脱出はスレッドプログラミングでは必要ないのかなと思いまし た。別スレッドからちょっかいを出したい場合(または一定の時間で抜け出たい場合)はシグナ ルを送って「立たせる」ようにすれば、待ってる間は他のスレッドにて時間は配分されます。後、 Mac の POSIX スレッドではタイムアウトの概念が使えないようですので、 OS 依存をしないために INFINITE で。 ■ Mac では定期的に sched_yield を呼ぶ 「sched_yield」は、別のスレッドに対して制御を譲る命令です。GUI 系と並列で動かしている場合 は、イベントの息抜きがいる場合があります。定期的に呼ぶ必要はないですが、sem_wait の後く らいに一度呼んであげるのがいいのかもしれません。 ■ Mac での WaitForMultipleObjects の代わり Win での「WaitForMultipleObjects」は、複数のシグナルを監視してどれかひとつでもイベントが起 これば次にいく、といったものです。残念ながら POSIX スレッドではこの機能はないです。で、 どうすれば実現できるかということですが、、、。1 つの「まとめ役のセマフォ」を作成し、複数シ グナル(セマフォ)の 1 つがおきたらついでに「まとめ役のセマフォ」も起こしてあげます。 sem_t *parentSem; // まとめ役のセマフォ sem_t *semA[16]; // 個々のセマフォ。これで 16 スレッド管理するとする ... sem_open にて名前付きセマフォの作成。 ... // n 番目のスレッドでの処理。 // 以下の処理が 0 ∼ 15 まで別々のスレッドとして存在するとします。 while(1) { // 何か処理 ... // n 番目のスレッドのイベントを立ち上げ sem_post(semA[n]); } // まとめ役のセマフォでのイベントを立ち上げ sem_post(parentSem); これをチェックする親のスレッド関数にて以下のようにチェックできます。 27 // 個々のスレッドのイベントを感知 while(1) { // 何か処理 ... // まとめ役のセマフォでのイベントが来るまでずっと待機。 sem_wait(parentSem); int ret; for(int i = 0; i < 16; i++) { // i 番目のスレッドのイベントが立っているかチェック ret = sem_trywait(semA[i]); if(ret < 0 && if(ret != 0) continue; break; } // 他のスレッドに処理を渡す sched_yield(); } errno == EAGAIN) continue; // i 番目のスレッドのフラグが立った!! これで擬似的に Windows で言う WaitForMultipleObjects(16, semA, false, INFINITE); を実現していることになります(semA に格納された 16 個のシグナルの 1 つが立ち上がったら抜 ける。INFINITE なので、シグナルがおきるまでずっと待つ)。 ここで、 while(1) { // 何か処理 ... int ret; for(int i = 0; i < 16; i++) { // i 番目のスレッドのイベントが立っているかチェック ret = sem_trywait(semA[i]); if(ret < 0 && if(ret != 0) continue; break; } // 他のスレッドに処理を渡す sched_yield(); } errno == EAGAIN) continue; if(i < 16) { // i 番目のスレッドのフラグが立った!! } のようにすればこんなことしなくてもいけるんじゃないか、と思ってはいけません。上記はご法 度です。イベントがこなければ while(1) にて延々と回り続けるので CPU リソースを消費すること になりますので。じゃあ、Sleep を while の最後に置こう、とかするとこれも CPU の空白時間がで きるのでアウトです(Sleep の間は他のスレッドは有意義に使えるのですが、この while 自身のルー プにて待ちが発生し、結果的に遅くなります)。そういった意味でも Sleep は使わずにスケジュー リングできることが大事かなと思います。 後、Mac でのセマフォについて役に立つかどうか、覚書がありますのでまた別途日記にて書いて おくようにします。 28 ちなみに、現行ベクターで公開しているレンダラのバージョン (Ver 0.4.1.3) よりもすでに手を加え ていってますので、年末くらいに上記の最適化も加味したものが公開できるかと思います。 後、上画像を見て、まだ最適化できる点があるのに気づきましたでしょうか?「レイトレと変わ らないじゃないか」と思った方、正解です。1 ピクセルに対して 100 サンプリングする必要もない 箇所がありますよね。拡散反射しない場所はわざわざサンプリングする必要もないわけで、この あたりはがさっと最適化できそうではあります。ただし、二次三次で拡散反射の面にぶちあたる とやっぱりサンプリングは分岐するので、 「局所的な偏り」をなんとか検出する必要がありそうで す。 Mac での POSIX におけるセマフォの上限(2008/11/03) Mac にて、あるスレッド数以上を処理しようとするとアプリが不安定になることがありまして、調 べて見ると「sem_open」による名前付きセマフォのオープン数の上限に引っかかっていたようで した。1 つの処理にて 3 つのセマフォを使っていたのですが、これの 16 スレッド分(16x3=48 セ マフォのオープン)では不安定に。処理を最適化して 1 つの処理にて 2 つのセマフォを使う(16 x2=32 セマフォのオープン)ことで安定。 この考えでいくと、たとえば将来 32 コアなんて CPU が出た場合はアウトになりますね。それと も、セマフォ数の上限自身もあがるのかな。と、これは OS での設定になるかも。 いずれにせよ、CPU だけじゃなくて OS としてどれくらいのスレッド数の作成(+セマフォなど の機能)に耐えうることができるのか、両方から攻めていく必要がありそうです。 セマフォの上限は OS がどこかで握ってそうですね。これも調査せねば。 29