当たり判定シェーダ


~第2回Cgコーディングコンテスト参加作品~






■2002 Oct. 16追加

Cg Coding Contest で画像がおかしかったので、GeForce3 で動くバージョンを作りました。
原因は、テクスチャの設定にNULLを与えた時に黒色が与えていると思い込んでいたところです。
src.lzh (バイナリとデータとソース:GeForce3動作確認済み)

■はじめに

やっとCgコーディングコンテストの作品にたどり着きました。
他の人がやっていないネタとして、当たり判定をシェーダでやってみました。
弾が当ったところが、燃え広がります。

今回の作品もインターラクティブ性の高いものです。
左クリックで弾を発射します。それ以外にも適当な時間間隔で弾が降ってきます。
右ドラックやホイールでカメラが動かせます。
炎だらけになったら、スペースバーで炎が消えますので、適当に楽しんでください。

まぁ、いつものように適当にファイルが入っています。
といっても、炎を作ったときと1ファイルしか増えていません。

CollisionV.cg衝突マップ作成。
BurnV.cg炎を作る。
BurnP.cg炎を作る。
BlackV.cg焼け跡の黒い部分を作成する。
vs.cg地面の描画。
ps.cg地面の描画。
draw.cppメインの描画部分。
draw.h描画の各関数の定義。
bg.cpp空の描画。
load.cppメッシュのロード。
load.hロードのインターフェイス。
main.h基本的な定数など。
main.cpp描画に関係しないシステム的な部分。
fire.bmp (火種の元絵)
earth.bmp (燃えている場所の元絵)
sky.bmp (空)
map.bmp (地面)

あと、地面のモデルと実行ファイル及び、プロジェクトファイルや、説明文が入っています。

■やってること

影ボリュームと同じ考え方で「衝突マップ」を作成して、以降、あたった部分を炎で燃え上がらせます。

最初地面を下から見た画像を作成します。

次に、今見たカメラアングルで弾の表面を白く描画します。
弾を描画するときに、適当な長さで弾を引き伸ばして弾が地面を通り抜けたときでも判定漏れが無いようにしています。
また、実際にはこのプロセスで弾の表面だけを表示したら判定があまくなったので、裏と表の両方の面を描画しました。

その後に、弾の裏面だけを黒く塗りつぶします。
裏を塗りつぶすことによって、地面と弾が接触した部分だけが白く浮かび上がります。

白く浮かび上がった「衝突マップ」をその時点までの結果に加算して更新します。

弾1つに関するプロセスは以上です。後は全ての弾に関して同じ事を繰り返して、 今までに飛んだ全ての弾の「衝突マップ」を作り上げます。
あとは、衝突のあった部分を炎で燃え上がらせています。

■ソースファイル

それではプログラムを見ていきましょう。
最初に深度情報をレンダリングします。
高速化も考えて、D3DRS_COLORWRITEENABLEを使って色の描画を禁止して、深度情報だけを書き出します。

draw.c
0403:     //-----------------------------------------------------------------------------
0404:     // 衝突マップの作成
0405:     //-----------------------------------------------------------------------------
0406:     lpD3DDev->SetRenderTarget(pTextureSurface, lpZbuffer);
0407:     lpD3DDev->Clear(0,NULL,D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0,0,0),1.0f,0);
0408:     
0409:     // 深度情報の保存
0410:     D3DXVECTOR3 eye    = D3DXVECTOR3(0.0f,-10.0f,  0.0f);
0411:     D3DXVECTOR3 lookAt = D3DXVECTOR3(0.0f,  0.0f,  0.0f);
0412:     D3DXVECTOR3 up     = D3DXVECTOR3(0.0f,  0.0f,  1.0f);
0413:     D3DXMatrixLookAtLH(&mView, &eye, &lookAt, &up);
0414:     D3DXMatrixPerspectiveFovLH(&mProj
0415:         ,D3DX_PI/3.5f
0416:         ,1.0f
0417:         ,0.01f,100.0f
0418:         );
0419:     mat = mView * mProj;
0420:     lpD3DDev->SetRenderState(D3DRS_CULLMODE, D3DCULL_CW);
0421:     lpD3DDev->SetRenderState(D3DRS_COLORWRITEENABLE, 0);
0422:     pVertexProgramContainer->SetShaderActive();
0423:     pPixelProgramContainer->SetShaderActive();
0424:     DrawModel(lpD3DDev, mat, 2);
0425:     lpD3DDev->SetRenderState(D3DRS_COLORWRITEENABLE, 0xf);
0426:     lpD3DDev->SetRenderState(D3DRS_ZWRITEENABLE, FALSE);

次に弾の描画です。
表面を白、裏面を黒と順番に描いた後に、できたテクスチャを衝突マップに加算合成で追加します。
これが弾の数だけループされます。

0428:     D3DXMatrixTranspose( &m, &mat );
0429:     pCollVC->SetShaderConstant( viewproj_matrix_iter, &m  );
0430: 
0431:     for(i=0;i0432:         if(!bullet.IsActive(i)) continue;
0433:         D3DXVECTOR4 &x = bullet.GetPosition(i);
0434:         D3DXVECTOR4 &v = bullet.GetVelosity(i);
0435: 
0436:         lpD3DDev->SetRenderTarget(pTextureSurface, lpZbuffer);
0437:         lpD3DDev->Clear(0,NULL,D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0),1.0f,0);
0438:         // 表面の描画
0439:         lpD3DDev->SetTextureStageState(0,D3DTSS_COLOROP,    D3DTOP_SELECTARG1);
0440:         lpD3DDev->SetTextureStageState(0,D3DTSS_COLORARG1,  D3DTA_DIFFUSE);
0441:         lpD3DDev->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
0442:         pCollVC->SetShaderActive();
0443:         lpD3DDev->SetPixelShader(0);
0444: 
0445:         pCollVC->SetShaderConstant( trans_iter, &x  );
0446:         pCollVC->SetShaderConstant( velocity_iter, &v  );
0447:         pCollVC->SetShaderConstant( color_iter, D3DXVECTOR4(1.0f,1.0f,1.0f,1.0f)  );
0448:         bullet.Draw(i, lpD3DDev);
0449: 
0450:         // 裏面の描画
0451:         lpD3DDev->SetRenderState(D3DRS_CULLMODE, D3DCULL_CW);
0452:         pCollVC->SetShaderConstant( color_iter, D3DXVECTOR4(0.0f,0.0f,0.0f,1.0f)  );
0453:         bullet.Draw(i, lpD3DDev);
0454:         lpD3DDev->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
0455: 
0456:         // 今までの衝突マップに加算合成 
0457:         lpD3DDev->SetRenderTarget(pBurnTextureSurface, NULL );
0458:         lpD3DDev->SetTextureStageState(0,D3DTSS_COLOROP,    D3DTOP_SELECTARG1);
0459:         lpD3DDev->SetTextureStageState(0,D3DTSS_COLORARG1,  D3DTA_TEXTURE);
0460:         lpD3DDev->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
0461:         float size = 512.0f;
0462:         TLVERTEX Vertex[4] = {
0463:             // x    y   z rhw tu tv
0464:             {   0,    0,0, 1, 1, 0,},
0465:             {size,    0,0, 1, 0, 0,},
0466:             {size, size,0, 1, 0, 1,},
0467:             {   0, size,0, 1, 1, 1,},
0468:         };
0469:         lpD3DDev->SetTexture( 0, pTexture);
0470:         lpD3DDev->SetVertexShader( FVF_TLVERTEX );
0471:         lpD3DDev->SetPixelShader(0);
0472:         lpD3DDev->DrawPrimitiveUP( D3DPT_TRIANGLEFAN, 2, Vertex, sizeof( TLVERTEX ) );
0473:         lpD3DDev->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
0474:     }
0475:     lpD3DDev->SetRenderState(D3DRS_ZWRITEENABLE, TRUE);
0476:     lpD3DDev->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);

あとは、弾を描画する頂点シェーダプログラムです(地面に関しては普通のレンダリングをします)。
弾のレンダリングの時に、表も裏も同じシェーダプログラムで描画します。

CollisionV.cg
0006: // ----------------------------------------------------------------------------
0007: // 入力データ
0008: // ----------------------------------------------------------------------------
0009: struct appdata  {
0010:     float4 position  : POSITION;
0011:     float4 texcoord0 : TEXCOORD0;
0012: };
0013: // ----------------------------------------------------------------------------
0014: // 頂点シェーダプログラム
0015: // ----------------------------------------------------------------------------
0016: vf20 main(appdata I
0017:         , uniform float4x4 viewproj_matrix
0018:         , uniform float4 trans
0019:         , uniform float4 velocity
0020:         , uniform float4 color
0021: ) {
0022:     vf20 O;
0023:     
0024:     // 座標変換
0025:     float4 v = (0<=dot(I.position, velocity))
0026:                     ? I.position
0027:                     : (I.position-velocity);
0028:     v=v+trans;
0029:     O.HPOS = mul(viewproj_matrix, v);
0030:     
0031:     // 頂点色は定数をコピーする
0032:     O.COL0   = color;
0033: 
0034:     return O;
0035: } 

頂点座標はブラーの時と同じ考え方で引き伸ばします。 進行方向と同じ向きの法線を持つ頂点に関してはそのまま、 裏向きの法線を持つ頂点に関しては後ろに伸ばして座標変換します
といっても、入力データに法線情報をもたせるのが(データが増えるので)いやだったので、 弾が玉で丸いので、ローカル座標での位置と速度方向から引き伸ばす頂点を算出しています。
一般には、法線情報をもたせて引き伸ばすと良いでしょう。
頂点色は定数として色の情報を設定しておき、定数色をそのまま出力します。

■最後に

以上で、衝突判定をシェーダで行うことができました。
キューブマップや双放物面マップを使えば、任意の凸多面体に関して衝突判定が行えるので、 地面だけでなくもっと色々な幅が広がると思います (というか、時間が無くてそれらのマップを使えませんでした)。
今回の当たり判定では判定に関する応答ができないので、弾はあたった後もまっすぐに進んでしまいます。
まあ、その辺はご愛嬌ということで…





もどる

imagire@gmail.com