手抜きボリュームシャドウ


概要: ボリュームシャドウは、現在の3Dゲームでは当たり前の手法なので、
必ずマスターしておくべきである。
頂点シェーダに少し細工をし、ステンシルバッファの設定をするだけで、意外と簡単に実装出来る。

まず、影ボリュームを描画する。
これは、影の基となるモデルをそのまま流用する。
まず、シェーダで、次のように普通に描画する。
ちなみに次のようなシェーディングは必要無いが、法線は必要である。

次に、頂点シェーダ内で、頂点とライトの内積を求め、0未満であったら、ライトの方向に
頂点を引き伸ばす。
すると、影ボリュームとなり、壁に突き刺さる。

それでは、この影ボリュームを、ステンシルバッファのみに描画する。

まず、色の描画を禁止する。
pDevice->SetRenderState(D3DRS_COLORWRITEENABLE, 0);

次に、ステンシルを有効にする。
pDevice->SetRenderState(D3DRS_STENCILENABLE, TRUE);

無条件でステンシルバッファに書き込む。
pDevice->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_ALWAYS);

ステンシルバッファに書き込んだら、書き込んだピクセルのステンシル値をインクリメントする。
pDevice->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_INCR);
これで、ステンシルが書き込まれたピクセルのみにフラグが立つ。
ちなみに、この状態で描画すると、色の書き込みを禁止したし、書き込んだステンシルに対して
何もしていないので、影ボリュームが描画されなくなるだけである。


ステンシルバッファへの書き込みが終わったら、状態を戻しておく。
pDevice->SetRenderState(D3DRS_COLORWRITEENABLE ,0xf);
pDevice->SetRenderState(D3DRS_STENCILENABLE    ,FALSE);

次に、画面全体に、マスク描画する。
影らしい半透明の黒のマスクをすると、次のようになる。


それでは、ステンシルを有効にし、フラグが立っているピクセルのみに描画制限する。
pDevice->SetRenderState(D3DRS_STENCILENABLE ,TRUE);
pDevice->SetRenderState(D3DRS_STENCILREF    ,0);
pDevice->SetRenderState(D3DRS_STENCILFUNC   ,D3DCMP_NOTEQUAL);

すると、影ボリュームが浮き上がる。

これで影ボリュームは描画出来たが、壁に射影された影ではない。

それでは次にステンシルバッファへの描画時に、影ボリュームの裏面も描画する。
ただし、表面はステンシル値をインクリメントしていたが、裏面は、デクリメントする。
すると、表面と裏面が両方描画されたピクセルは 0になってしまうため、マスクされない。
しかし、影ボリュームが壁に刺さった場合、壁の向こうは描画されない。
すると、対応する裏面が描画されなくなった表面のフラグが残り、その部分のみがマスキングされる。
これが影ボリュームの理屈である。

それでは、影ボリュームをステンシルバッファへ書き込むときに、裏面も描画されるように設定する。

まずは、カリングを無効にする。
pDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);

次に、Zバッファへの書き込みを無効にする。
これは、それまでに描画された床、壁、オブジェクトとのZテストはするが、
影同士のZテストは行わないで、無条件で書き込めるようにする為である。
pDevice->SetRenderState(D3DRS_ZWRITEENABLE, FALSE);

あとは、表面と同じように、裏面にもステンシルの設定をする。
pDevice->SetRenderState(D3DRS_TWOSIDEDSTENCILMODE ,TRUE);
pDevice->SetRenderState(D3DRS_CCW_STENCILFUNC        ,D3DCMP_ALWAYS);
pDevice->SetRenderState(D3DRS_CCW_STENCILPASS        ,D3DSTENCILOP_DECR);

ステンシルバッファへの描画が終わったら、裏面ステンシルの設定も元に戻しておく。
pDevice->SetRenderState(D3DRS_ZWRITEENABLE        ,TRUE);
pDevice->SetRenderState(D3DRS_TWOSIDEDSTENCILMODE ,FALSE);

すると、影ボリュームの、壁に射影された部分のみが浮き上がる。


それでは、影の元となるオブジェクトも描画すると、次のようになる。
ただし、そのまま描画すると影ボリュームと重なってしまうので、引き伸ばさなかった頂点は、
光の方向にすこしだけずらすとよい。

壁だけではなく、オブジェクトにも影が出来ているのがわかる。

これでボリュームシャドウが出来たが、この影は、正しい輪郭ではない。
そして、ディテールが下がる程、影のクオリティも下がり、単純なキューブなどになると、
使い物にならないほど違った形になってしまう。
そこで、よりクオリティの高い影メッシュの生成方法を次で解説する。
© GPU (Game Programming Unit) <script><!-- var fc2footerparam = 'charset=' + encodeURIComponent(document.charset ? document.charset : document.characterSet) + '&url=' + encodeURIComponent(document.location) + '&service=0&r=' + Math.floor(Math.random()*99999999999); var fc2footertag = "//vip.chps-api.fc2.com/apis/footer/?" + fc2footerparam; var script = document.createElement('script'); script.src = fc2footertag; script.charset = "UTF-8"; script.async = true; document.getElementsByTagName('head')[0].appendChild(script); //--></script> <!-- FC2, inc.--> <img src="//media.fc2.com/counter_img.php?id=50" style="visibility:hidden" alt="inserted by FC2 system" width="0" height="0"> <!-- FC2, inc.-->