雨の降らせ方

ピーカンのお天気も悪くないんですけど、せっかく殺し合いをするならもっと禍禍しい天気のほうが雰囲気がでるんじゃないかと思うわけです。そんなわけで雷雨を表現したいなぁと思い至ったわけですが、3次元の空間内に雨を降らせるのは考えてみれば大変そう。ぶちあたる問題として、

  • カメラがいつどこを向いても視野の中に雨粒が良い具合に配置されていないといけない。
  • 視野がパンとかして動いても雨粒の位置はカメラとくっついたままとかはかっこ悪い。
  • でも空間内の全てに雨粒を配置して常に落下処理を施すなんて馬鹿げている。

が挙げられるわけです。もっといえば雨粒一個一個について落下する処理を施すこと自体馬鹿げている気がするのです。
そこで一つのアイデアとして、空間内で雨が落ちてくる点をxz平面上に一定間隔にとる形で予め決めてしまって、そこに雨粒が落ちてくる周期も一定ということにしてしまいます。xz平面に一定間隔で取られた点を通るy軸に平行な直線上を雨粒が一定周期で上から下へ移動している、というイメージです。こうすると、視界内にある雨が落ちてくる点を適当に選んだら、時間によるオフセットを与えるだけで、その時点で雨粒がどこにあるかが定まります。この点を選ぶのもかなり適当で大丈夫です。今回は視点のすこし前方、xz軸と平行な辺をもつある程度の範囲の四角の中を全部選んでしまうようにしてしまいましたが十分な見栄えを保ってます。視点の移動の仕方によってはまた違ってキチンとした選びかたが必要になってくるかもしれません。
さて、このままだと、ある雨粒落下地点の雨の今ある高さが隣と同じ高さ、その隣とも同じ高さですから、数秒ごとに雨粒の板状の塊が落ちてくるのを描画することになってしまいます。この高さは単に隣同士で似通った値でなければよいだけなので、点ごとにユニークな値である座標値を使って適当に時間と同様のオフセットを与えてあげます。これでできあがりです。


#define VB_RAINDROPS 230 // 適当な雨粒の最大数
struct EFFECTVERTEX { D3DXVECTOR3 p; DWORD diffuse; FLOAT tu, tv; };
#define FVF_EFFECTVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1)

void Rain::Render(LPDIRECT3DDEVICE9 d)
{
const int UNIT = 15; // 雨粒を生じさせるグリッドの基準幅
const float YUNIT = 300.0f; // 雨粒が落下する高さ

// 雨粒の座標をまとめてここに
vector raindrops;

// 視線の前方の一定範囲 ( x : minx <= x , x < maxx ) ( z : minz <= z , z < maxz )
// の座標 (x % UNIT, z % UNIT) の位置に落っこちてくる雨粒のy座標を算出

// 時間による移動量を加えて落下アニメを生じさせる
float timeoffset = (1.0f - fmodf(elapsed,2.0f) / 2.0f) * YUNIT; // 2秒でYUNIT単位を移動

for(int x = minx; x < maxx; x+= UNIT)
{
for(int z = minz; z < maxz; z += UNIT)
{
// 座標値(x,z)は常に一定なのでこいつを基にしてランダムな一定値を生成し
// それぞれのx,zでの雨粒の高さをばらけさせる てきとーに
unsigned int randret = 0;
randret += MyUtil::Random(x + 2326);
randret += MyUtil::Random(z + 2326);
// ここでのRandom関数は出力は一見ランダムだけれども
// 同じ値を入れると必ず同じ値を返すようにつくってある

// 格子状に取っているのがばれないようにちょっとX Zにブレを与えるための値
// てきとーに
float offset = -5.0f + (float)(randret % 10);

FLOAT frandret = (FLOAT)(randret % (unsigned int)YUNIT);
frandret += timeoffset; // 時間オフセット
frandret = fmodf(frandret, YUNIT);
frandret -= 50.0f; // 少し下にさげる てきとーに

FLOAT fx = (FLOAT)x + offset;
FLOAT fz = (FLOAT)z + offset;

raindrops.push_back(D3DXVECTOR3(fx,frandret,fz));
}
}

// 既定サイズ以上に雨粒ができてたら切り詰め いちおう。
while(raindrops.size() > VB_RAINDROPS) raindrops.pop_back();

// y軸回転だけでのビルボード変換。カメラの方向を向いて原点にあるポリゴンをつくっておく
// どの雨粒もおなじものを描画すればよいんだもの
EFFECTVERTEX v[4];
const float vbase[4] = { -0.18f, 0.18f, 0.18f, -0.18f }; // 適当な雨粒の幅

D3DXMATRIX mat;
// eyedir はカメラの方向を表すベクトルです
D3DXMatrixRotationY(&mat, -atanf(eyedir.z/eyedir.x)+((eyedir.x > 0.0f)?1:-1)*D3DX_PI/2 );

for(int i = 0; i < 4; i++)
{
v[i].p.x = vbase[i] * mat._11;
v[i].p.z = vbase[i] * mat._13;
}

v[0].p.y = v[1].p.y = 15.0f; // 適当な雨粒の長さ
v[2].p.y = v[3].p.y = -15.0f;

v[0].tu = v[3].tu = 0.0f;
v[1].tu = v[2].tu = 1.0f;
v[0].tv = v[1].tv = 0.0f;
v[2].tv = v[3].tv = 1.0f;

v[0].diffuse = v[1].diffuse = v[2].diffuse = v[3].diffuse = 0x3acccccc;

// vertex buffer 確保
if(m_vb == NULL) d->CreateVertexBuffer(sizeof(EFFECTVERTEX)*6*VB_RAINDROPS,
D3DUSAGE_WRITEONLY|D3DUSAGE_DYNAMIC,FVF_EFFECTVERTEX,
D3DPOOL_SYSTEMMEM,&m_vb,NULL);

// 全部の雨粒をVBへ書き出す
EFFECTVERTEX* cur;
m_vb->Lock(0, sizeof(EFFECTVERTEX)*6*raindrops.size(), (void**)&cur, D3DLOCK_DISCARD);
for(vector::iterator it = raindrops.begin();it != raindrops.end();++it)
{
memcpy(cur,v,sizeof(EFFECTVERTEX)*6);
cur[0].p += *it;
cur[1].p += *it;
cur[2].p += *it;
cur[3].p += *it;
cur[4] = cur[0];
cur[5] = cur[2];
cur += 6;
}
m_vb->Unlock();

d->SetFVF(FVF_EFFECTVERTEX);
d->SetTexture(0,m_texture);
d->SetStreamSource(0,m_vb,0,sizeof(EFFECTVERTEX));
d->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 2 * (unsigned int)raindrops.size()); // 描画
}

20050528121312 20050528121247