~下の画像をクリックすると、影が動くアニメーションが見られます(注意:3.21MB)。■はじめにここ1年、テクスチャに影の情報を格納してライティングする手法が流行し始めています。DirectXのサンプルで言えば、BumpSelfShadowのサンプルがそれにあたります。簡単な影のプログラムに挑戦してみましょう。今回のプログラムは、次のものです。今回は、初期化に少し時間がかかります。(遮蔽マップ:DirectX9(頂点シェーダ1.1,ピクセルシェーダ2.0))まぁ、いつものように適当にファイルが入っています。出力されるフレームワークのファイルは紹介を省かせていただきます。アプリケーションのソースあと、実行ファイル、モデル及び、プロジェクトファイルが入っています。実際のレンダリングの時には、そのテクスチャをかぶせることにより影の陰影を作り出します。いい感じで補間すれば、セルフシャドウの影をリアルタイムに動かすことができます。遮蔽マップの作成方法はちょっと面倒です。その部分に光があたるかどうかを計算しなくてはなりません。今回は、デカールテクスチャをxz平面にべたっと張っているモデルで計算しました。デカールテクスチャ上でまっすぐな直線は、モデルのローカル座標系のxz平面でもまっすぐに伸びます。視点から光の方向を見たときに、遮蔽物があるかどうかで光があたるかどうかを判定します。それぞれのテクセルを視点として、適当な方向に光線を飛ばします。その光線が途中でより高いテクセルにさえぎられれば、光は届かないので影になります。中間の角度はぞれらの影を補完する方法で影を計算します。1つのテクスチャに4つの方向を格納して、計8枚のテクスチャを作成します。初期化の段階でテクスチャを作成します。実際の表示のときは、そのテクスチャを読み込むだけにするのが良いでしょう。遮蔽マップを作成するのですが、その前にそれぞれの位置での高さを格納した高さマップを作成します。遮蔽マップは、それぞれの位置から光線を飛ばしたときにその光がさえぎられるかぶつかるかを高さマップから検索します。位置座標にテクスチャ座標の値を符号化スケーリングした値を出力します。y座標は上下がひっくり返るので、符号が変わります。後は、色成分にローカル座標での高さを出力します。必要ならば、0から1の範囲に高さが収まるようにスケーリングをしましょう。}ピクセルシェーダでは、色をそのまま出力します。zテストやカリングはしないようにしてレンダリングします。高さマップを作成したら、次は高さマップから遮蔽マップを作成します。遮蔽マップの作成は、高さマップからの1対1の対応として変換できます。ずらしたテクセルの高さが自分から伸びた視線よりも高いかどうかを比較する作業になります。最初にClear命令を使って、画面を白く塗りつぶしておきます。次に、半透明合成をするように設定します。半透明合成の方法は出力した色での乗算合成です。最終的に光が射す部分は白く、どこかでさえぎられる部分は黒くなります。次に、がんがん描画していきます。RGBAの4枚のレイヤーに色を出力するので、描画回数はマップの枚数x4になります。次に、それぞれの成分だけに書き込みするように、書き込みマスクD3DRS_COLORWRITEENABLEを設定します。あとは、少しづつテクスチャをずらしながら描画します。す量は、レイヤーごとに設定されたずらす方向(視線)のベクトルray_dir[i]にその描画回数に比例する量でづらしていきます。比較のために、テクセルから光線を飛ばしたときの光線が飛ぶ高さをfRayHeightに格納します。}遮蔽マップを計算する視線の方向は、32方向のそれぞれの方向の単位ベクトルになります。sinとcosの基本的な使い方で全周を32等分するだけです。};あとは、シェーダでのお話になります。シェーダでは、ずらした位置のテクスチャの座標と比較の元になる現在のテクセルのテクスチャ座標を出力します。}ピクセルシェーダが大事な比較のルーチンです。光線がその場所にあたらなければ白を出力します。}■シーンの描画さて、遮蔽マップができたら、マップを使って地面を描画しましょう。基本的に、頂点シェーダは難しくありません。普通にテクスチャを張ります。ここで、デカールのテクスチャと、遮蔽マップに同じテクスチャ座標を使います。それぞれの座標を別々に出力する必要があります。}ピクセルシェーダでは、遮蔽マップから明るさを求めて、その色を拡散光に用います。}明るさの求め方は、それぞれの遮蔽マップの強さを重みで線型合成します。重みは、光のあたる方向に依存するそれぞれのマップの影響度です。重みで足しあわされるだけの計算になります。重みの計算は、ライトの方向から、計算します。下のプログラムで、dirがモデルのローカル座標系でのXZ平面でのライトの方向(正しくは、光源が存在する方向)になります。今回の遮蔽マップでは、光の入射する高さは情報として入らないので、2次元のベクトルになります。次に、内積を使って、射影マップを作成したときの視線の方向ray_dirとライトがどのくらい向いているのかを調べます。内積を使っているので、ライトのベクトルと視線ベクトルが同じ向きを向いているときは、1になります。それていけば内積の値は段々と小さくなります。影響が0になるようにします。今回は、32方向に視線を飛ばすので、内積の値がcos(2π/32)よりも小さいときに影響が0になるようにします。そこで、内積の値からcos(2π/32)を引いて、その値が正の部分にだけ遮蔽マップの影響が出るように値を調整します。最後に、影響の総和を1になるように、重みの値を合計して、全ての重みをその合計で割るという規格化をします。COVER_MAPS);後は、射影マップを設定して、シェーダを使って描画するだけです。}■最後に影がぼけないと方向による切り替えが不自然にくっきりしますね。最初から複雑なプログラムにしてもしょうが無いので、これぐらいにとどめましょう。バージョンアップしていきましょう。次は水平線マップですかね。もどるimagire@gmail.com。