Gポイントポイ活 Amazon Yahoo 楽天

無料ホームページ 楽天モバイル[UNLIMITが今なら1円] 海外格安航空券 海外旅行保険が無料!

log

2012.12.07 C++:ミップマップに関するお話

ファイル 419-1.jpgファイル 419-2.jpgファイル 419-3.jpgファイル 419-4.jpg

今回はポロンを作ってみましたが、まぁ別に話すことも無いので笑。打って変わって今回は
ミップマップについて少しお話してみようと思います。

ミップマップを使用すると、通常通りだと斜めのポリゴンがぼけた感じになってしまいます。
これはなぜこうなるか説明するために、まずサンプリング方法について説明してみます。
一般的に使用されるサンプリングはバイリニアサンプリングというやつですが、これは
描画するピクセル位置のUV点の周囲4ピクセルを使用して滑らかにサンプリングする
という手法です。

バイリニアサンプリングだけだと画面上に描画するポリゴンサイズよりテクスチャサイズが
小さい場合、サンプリングするピクセルが飛び飛びになるため、遠景の描画がガタガタに
なる訳で。これを解決するためにミップマップが存在します。

ミップマップは描画するポリゴンサイズ(というより描画するピクセルで使用するUV範囲)に
応じて使用するテクスチャのサイズを変更することで、サンプリングが飛び飛びになる問題を
防ぐ手法です。ピクセルで使用する
UV範囲が正方ならば綺麗に描画されるのですが、UV範囲が長方形の場合は範囲が
大きい方を基準に小さいテクスチャを選ぶため、もう片方の範囲が小さい軸方向では
ボケてしまいます(おそらくUV範囲が小さい方を基準にした場合はもう片方の軸方向で
サンプリングが飛び飛びになるため、とりあえずこの仕様になっていると思われる)。
さてこれを解決するにはどうすればいいだろう?

解決方法の1つに異方性フィルタリングでサンプリングする方法がある。このサンプリングの
アルゴリズムについて詳しくのっているサイトが見つからなかったため、仕様の予測でしか
無いのですが、バイリニアサンプリングでは4ピクセルで補間するのに対し異方性フィルタ
リングでは描画するピクセルのUV範囲に応じてm*nの範囲のピクセルを使用して補間
することで正確な描画を行っているんだろうと思います。

ただこのサンプリングだと昔のGPUでは重いため、一般的に使用される解決方法が
「んじゃミップマップレベルをシフトすればいいじゃない?」というものです。
描画するテクスチャのミップレベルのシフトは固定パイプライン、HLSL共にMipMapLodBias
で出来ます。MipMapLodBiasに-1を設定することで描画するテクスチャが1段階大きい
テクスチャにシフトするので、ボケた感じがこれである程度解消できます。

ちなみに画像の2番目が通常通りのミップマップ、3番目がMipMapLodBiasに-1を設定した
場合のものです。サムネイルをクリックで拡大してみることで、効果が分かるかと思います。
ネットで調べてみると-0.5に設定してる人もいましたが、あまり効果が無かったので自分は
-1にしています。

実は今まではMipMapLodBiasにマイナスを設定できるとは考えてもいなかったので、
設定していなかったのでした笑。試してみると、ボケが解消されたというより、テクスチャの
ガタカタが解消されました。それよりもポポイの口のラインがようやく少し見えるようになった
のが嬉しい!遠景の地面辺りは相変わらずボケボケなので、異方性フィルタリングに変更
しようかなとも思いますが、とりあえずはこの辺で終わりにします。

ふぅ、今回の記事を書くのに少し疲れた;

追記:
異方性フィルタリングも試してみました(画像の4枚目)。
試してみて気づきましたが、テクスチャがガタついてたのは縮小フィルタにニアレストポイント
を指定していたからで、遠景のボケを少しでも防ぐために設定していたのが仇となっていて、
中景のテクスチャがガタガタになってたのでした;

さて、そこで拡大/縮小/ミップフィルタ全てに異方性フィルタを設定する場合と縮小フィルタ
だけニアレストポイントのままにする場合を調べてみましたが。
前者の場合はテクスチャは全ての場合と比べて一番滑らかになりましたが、結局遠景は
同じようにボケた感じでした。対して、後者は遠景のボケはあまりありませんが中景の
キャラのテクスチャがガタガタになってしまいました。
ちなみに4枚目の画像は前者の方法のものです。後者の方は3枚目と4枚目の中間の
ような感じだと思って下さい。2,3枚目は縮小フィルタにニアレストポイントを設定していた
時のものなので、4枚目よりも遠景がはっきりしてるのが分かるかと思います。

結論としては遠景の見た目よりも近景やキャラの見た目が大切だと思うのと、速度の面でも
バイリニアの時とほとんど差がなかったため、これからは3フィルタ全てに異方性フィルタを
設定する方向性でいこうと思います。

2012.07.29 C++:球面調和ライティング

ファイル 411-1.jpg


実は前々から球面調和関数によるGI(Irradiance Environment Mapping)の
コードを実装させていたのですが…バグが多くてお蔵入りしていたものが今回ようやく
人にお見せ出来る形になったのでさっそく動画にしてみました。

普段なら球面調和関数について…とここで解説を入れたいところですが。。実装しておきな
がら自分でもあまりよく分かっていない部分が多いんですよね;ものすごく大雑把に説明
してみますが、球面調和関数というのはフーリエ変換と似たようなもので、単純な成分の
組み合わせで複雑なデータを表現できるというやつです。自分のプログラムでは2次の
球面調和関数の成分までで計算しているため、細かいGIは表現出来ませんが2次までの
計算なら4x4の行列に球面調和ライティングのイラディアンスを格納できるため、シェーダ
から行列計算でライティングが出来てしまいます。

またイラディアンスをどういう形で保持しておくかは、通常はシーン全体のボクセルに持た
せるのが普通だと思いますが、自分のプログラムでは地面の各頂点ごとに持たせました。
描画する場合はキャラクターの位置の地面のポリゴンの3頂点からイラディアンスを求めて
シェーダに転送する、といった感じです。
ただこの手法だと地面のポリゴンが荒いと上の画像のように不自然な感じになります。
上の画像のキャラクターは花の赤色の影響を受けていますが、花は後ろにあるのにキャラ
クターの正面に影響を与えているので、これはちょっと不自然です。…まぁこれはポリゴンを
細かく分割すれば済む話なので問題無し、でいいかな。

個人的に球面調和関数はスキンメッシュの自前実装と同じ位難しかったでした。技術的に
難しいことをしている上に詳しい説明の資料が乏しいので、かなりてんてこ舞いでした笑。

2012.07.20 C++:カスタムUIその3

ファイル 410-1.jpg

カスタムUIを更にアップデート。今度はパラメータを受け取って表示するテキストを使用
できるようになり、後はGetDCでD3Dのデバイスコンテキストを取得するのではなく
D3Dのウィンドウを直接子のウィンドウすることでフレームレートの低下を防ぐ仕様に
なりました(それでもGDIを使用しているためか少しFPSが低下してますが)。

で…これだけだと前回とほとんど見栄えが変わらないなということでボクセルでGUIの
文字を書いてみたり苦笑。これだけのことなのに結構時間がかかった訳ですが。原因は
D3Dのウィンドウを子ウィンドウにしてしまうと色々不都合が起こることに気が付き、そこ
からの修正作業の時間が大半でした。

D3Dのウィンドウを子ウィンドウにする場合、疑似フルスクリーンでフルスクリーン描画する
には一旦D3DのウィンドウをSetParent()で最上位ウィンドウとして切り離してからポップ
アップウィンドウのスタイルに変更しなければならなかったり。また、DirectInputの協調レベルを指定する際のウィンドウハンドルは最上位ウィン
ドウにしなければならず、ウィンドウモードと疑似フルスクリーンモードの切り替えの際に
協調レベルを再設定しなければならなかったり。。更にやっかいだったのがウィンドウ
スタイルの切り替えや親ウィンドウの切り替えや表示モードの切り替えの実行順番が少し
でも違うと一部の命令が動作せず表示がおかしくなったりしたのでした。

ということで本来なら数時間で出来そうなものですが、デバッグでかなり無駄に時間が
かかりました;正直GDIやメッセージ処理周りには意味不明な仕様やバグのようなもの
が多すぎるなという印象を受けました。カスタムウィンドウやボタンも全てDirectXで管理
した方が問題が起こらず安心出来そうですが、、ボタンのON/OFF時の描画もHLSLで
管理しないといけないので、それはそれで面倒くさそうだなあ…笑。

2012.07.16 C++:カスタムUIその2

ファイル 409-1.jpg

前回の続きで今度はコンボボックスとポップアップメニューを実装させてみました。
それとDirectXのバックバッファからGDIのデバイスコンテキストを取得して、GDIで
描画するといったことも試してみましたが、、これがまた60FPSから一気に15~20
FPSまで下がってしまいました。重くなることは分かってましたが、ここまで重くなるのか。。

前回よりもエディタっぽくなりましたが。GDIを使うとDirectXを使用した録画ソフトを
使用出来ない(反映されない)のが厄介なところです。GDI系の録画ソフト重いからなあ…。

2012.07.12 C++:カスタムUI

ファイル 408-1.jpg

エディタがあったら便利だなと前から思っていたので、まず最初にウィンドウとボタンを
作成・管理できるクラスを作ってみました。呼び出すところは以下のような感じになってます。

// ウィンドウを作成
Window1.CreateCustomWindow( "Window1", 500, 500, NULL, Draw1 );
// ウィンドウ内にボタンを作成
Window1.AddButton( "Button0", 20, 20, 98, 16, NULL );

Draw1という引数部分でウィンドウ描画用の関数を渡すことで、それぞれのウィンドウで
別々の描画が出来るようにしています。UDKやUnityみたいにエディタ上で簡単に
ゲームが作れるようになるのが一番理想的なんですが…まだまだ遠いですね苦笑。

2012.07.10 C++:DirectX11の話

前々から色々と試したいと思っていたDirectX11を今回ようやく試してみることにしま
した。とりえずここここを参考に、ポリゴンとテクスチャの描画やコンピュートシェーダを
実装してみたので、今まで使用していたDirectX9との違いと利点についてまとめて
みようと思います。

・デバイスロスト処理が不要
 他のサイトでも色々説明されてますが、デバイスロストしなくなりました。
 ただしGPUのドライバ更新時はロストするとかしないとか(まぁ実行中にドライバ更新
 する人なんてまずいないと思いますが)。
・D3Dデバイスが3つに分離
 従来のD3DデバイスがD3Dデバイス、デバイスコンテキスト、スワップチェインの3つに
 分離しました。具体的にはD3DデバイスがVRAMリソースの作成、デバイスコンテキスト
 がGPUに描画コマンドを発行、スワップチェィンがウィンドウへの描画を実行といった
 感じに担当が分かれています。何故こんな面倒なことになったのかというとこれは自分
 の予想なのですが、コードを見渡してみるとロード周りはD3Dデバイスを使用し、描画
 周りはデバイスコンテキストとスワップチェインを使用するといった風に見事に分かれて
 いるため、マルチスレッドで次のシーン読み込みを行っている場合従来のようにD3Dデバ
 イスをクリティカルセクション等で制御する必要がなくなっているのです。つまり、処理を
 高速化させるためにこういった分離がされているのだろうなと推察しました。
・Shader Model4.0
 SM4.0では従来のGPUの構造とは全然違うので、シェーダ命令数が無限です(
 ちなみにWebGLのGLSLも命令数が無限ぽいのでおそらくSM4.0以上で動作させて
 んだろうと思います。
・フューチャレベル
 WindowsVista以降ではDXGIという新しいシステムによってDirectX9,10,11での
 GPUへのアクセスを共通化できるようになったのでDirectX11SDKからはフューチャ
 レベルというものを使用してD3DデバイスをDirectX9.x,10,10.1,11のいずれかの
 モードで作成出来るようになりました。
 なので実は自分の環境はDirectX11に対応していないのですが、DirectX10.1で
 DirectX11SDKを利用している訳です;
・Compute Shader
 従来のシェーダは1プリミティブがシェーダに渡されて対応した1プリミティブを返すと
 いった感じでしたが、Compute Shaderでは構造化バッファという構造体のバッファを
 利用して任意の位置のデータを拾って来たり、任意の位置にデータを渡したり出来ます。
 つまりCPUで計算するのと同じように処理出来ます。更にこの恩恵はピクセルシェーダに
 も適応され、SM4.1,5.0ではピクセルシェーダでも一部の構造化バッファにアクセス
 出来るようです(この辺はまだ試していないので何とも言えませんが)。
 更に新しいDirectX11.1+SM5.1ではピクセルシェーダだけでなく全てのシェーダから
 もアクセス出来るようになるとのことで、GPUがCPUとほとんど同じような働きが出来る
 ようになるのではないかと思います。
 ちなみにCompute ShaderはいわゆるGPGPU用なので従来のパイプラインと同じ
 流れの中で処理するのではなく、Compute Shaderから開始してCompute Shader
 で終了する、いわば唯我独尊シェーダです笑。

パッと思いつく限りこんな感じです。ただ利点ばかりではなく欠点もそれなりにあるわけで。
例えばD3DX算術関数がサポートされていないので、D3DX算術関数を利用する場合は
D3DX10math.hとd3dx10.libを読み込んDirectX10の機能を借りなければならないです。
更にはHLSLコードも含めて、DirectX9とは仕様ががらりと違うのでDirectX9を一から
覚えるのと同じ位面倒だと感じました。なまじDirectX9の作法が頭に入っていると混乱
するみたいなところがあるので新しく触る方はお気を付けください。

2012.07.08 C++:樹木生成

ファイル 405-1.jpg

地形の動的生成の次はオブジェクトの自動生成だ!ということで樹木の自動生成を試して
みることにしました。とりあえず、樹木の生成で最もよく知られている手法としてL-システ
ムが有名なので、その手法について調べてみました。

今までは樹木の生成についてのアルゴリズムかと思ってましたがどうも形式文法という
アルゴリズムのようで、有限長の集合を文字列パターン+再帰処理によって生成する
というもののようです。なのでコッホ曲線やシェルピンスキーの3角形も表現でき、それ
こそ幾何系のオブジェクトならほとんど表現出来てしまいます。いやぁ、いいなこれ。

さっそく2Dで樹木生成を試してみました。…思いっきり再帰処理しているのがバレバレな
画ですね笑。実際は乱数を使用して不規則性を持たせるんだと思いますが、最初なので
とりあえずはこんなもんです。にしても我ながら、、シンプルすぎるなあ。

2012.06.28 C++:地形生成

ファイル 403-1.jpg

つい最近実装したパーリンノイズを利用して地面の動的生成(のようなもの)を作って
みました。モノクロで試してみると何だか雪っぽいなと思ったので、せっかくなので
雪に見立ててみました。それと地形の動的生成の参考になるかなと思い、ネット上に
公開されていたElevatedのソースコードを色々解析したりしてましたが…ソースが短い
くせにHLSLの処理部分で何をやっているのやら全然わからない感じでした(とほほ)。

ちなみにElevatedはデモの中でもかなり重い部類なのですが、ソースコードのテッセ
レーション部分の分割数を下げるだけでそれなりにサクサク動きます。古いPCだけど
Elevated動かしたいなという方はソースコードを探してみるといいと思います(もちろん、
プログラミングが出来る方限定ですが;)。

2012.06.27 C++:視点をずらすお話

ファイル 402-1.jpg

今回はちょっと豆知識的な話を書こうかと思います。カメラと視点に関する話です。
3Dで普通に射影行列(知識の無い方は、まぁカメラの画角を設定するものだと思って
下さい)を作成した場合、あまり意識する人は少ないかと思いますが視点の位置はスク
リーンのちょうど中心になります。つまり真っ直ぐな道路の向こう側を見ている場合、
道路のパースの先は画面の中心に来るということです。

視点が中心に来ないと不自然な印象になるので、視点の位置をずらすなんてことは
普通はやらないですが、ムービー等で写真や画の一部をトリミングした風に見せたい
こともあったりします。そこで、そんな場合は射影行列をD3DXMatrixPerspectiveOff
CenterLHやD3DXMatrixOrthoOffCenterLHで作成します。今までこの関数一体何
なんだろうと思っていましたが、つまりはこの関数は射影変換した後にスクリーン位置を
オフセット移動させるような行列を作成するという訳です(カメラの位置をずらすという訳
ではなくスクリーン変換した2D画面をずらすというやつです)。

ただ、実をいうとこんな関数を使わなくても今まで通りの射影行列のパラメータを以下
のように少しいじるだけでオフセット移動をさせることが出来ます。
・透視投影の場合:_31,_32の値を調節する。
・正射投影の場合:_41,_42の値を調節する。
ちなみに、とっても分かりにくいですが…画像は以下のように透視投影の_31に1を設定
してスクリーンを半分だけ左にずらしたものです。ただの変な画像ではありません苦笑;
D3DXMatrixPerspectiveFovLH( &ProjMatrix,Fov,Aspect,Near,Far );
ProjMatrix._31 = 1.0f;
とっても簡単です。画面をずらしたいという方は是非お試しあれ。

2012.06.20 C++:地平線の描画

ファイル 401-1.jpgファイル 401-2.jpgファイル 401-3.jpg

今回はCryEngine風の海の表現が出来たらいいなと思い、手始めに地平線のラインを
描画してみました。…えーっと、「何故地平線?」って感じですね;

まずCryEngineの海の手法をザックリと説明します。CryEngineの海は無限平面の海
なのですが、ポリゴンが大量に使用されているのかと思いきや、ワイヤーフレームで表示
してみると実は数十ポリゴンしか使用していないことが分かります。ちなみに画像の2つ目
がCryEngineの元の画像、3つ目がワイヤーフレーム画像です。ワイヤーフレーム画像
のワイヤーはスカイドームのワイヤーと海面下の地面のワイヤーと残るはスクリーンに
平行なワイヤーの3つに分かれていますがその3つめのワイヤーが海面のポリゴンの
ワイヤーとなります。要するにスクリーン内の無限平面の範囲をあらかじめ求めて、
ポリゴン数を減らしている訳です。

この手法をやろうと思うと画面の地平線の位置を求める必要があるなと気が付き、
そういうことで地平線の位置の取得と描画の実装に至った訳です。

一応、地平線の高さの求め方も説明しようと思います。
まず、視点からスクリーン左上への方向のy成分の絶対値をY0、視点からスクリーン
左下への方向のy成分の絶対値をY1、スクリーン左上の高さを0、スクリーン左下の
高さをHとします。スクリーン左端に地平線が入る場合は、スクリーン左端の高さYは
以下で求まります。
・Y = H * Y0 / (Y0+Y1);
この式は衝突判定で衝突距離を求める式P = P0 + N * t;(P0がt=0のときの座標、
NがP0からP1への方向ベクトル、tが衝突時刻)の式と同じような感じで求まった
と考えてください。P0は0で、Y0/(Y0+Y1)がt部分、HがN部分です。
スクリーン内に地平線が存在しない場合は以下のようになります。
・地平線が画面の下:Y = H * Y1/Y0;
・地平線が画面の上:Y = -H * Y0/Y1;
スクリーン内に地平線が存在しているかどうかは左上、左下の方向のy成分が両方とも
正値もしくは負値かどうかで判断します。正値なら地平線が画面の上、負値なら
地平線は画面の下になります。で、同様の方法で右側の地平線の高さも求まります。

さて、それで(…長いですがこれでラスト)。視点が無限平面の上なら地平線の下側が
描画側、視点が無限平面の下なら地平線の上側が描画側となります。ちなみにCryEn
gineでは海面より上か下かで海上/海中と描画が切り替わっています。

無限平面、デフォルトで実装してほしい機能だと思うんですが…あまり需要ないのかな。

追記:
これだけだとカメラが90度回転した状態(首をかしげた状態)だとおかしくなることに
気づきました。それを修正するにはカメラが90度回転しているとき(つまりY0=Y1=0の
場合)には上側と下側で地平線の位置を求めれるようにすればいいはずです。
それと後は正射投影の場合、必ずY0=Y1になるためこれも別の処理を加えないと
いけない気がします。