Thursday, November 16, 2017

[ゲルストナー波] 水面に物を浮かべる

ゲルストナー波の実装は前々回前回をご覧ください。


Copyright (c) 2011 NVIDIA Corporation. All rights reserved.

NVIDIAのIsland11を改造して作っています。

題の通り物を水面に浮かべたいのですが、ゲルストナー波はHLSLで実装したので現状CPUから水面の動きを取得する方法がありません。しかし、波のパラメータはCPU側でも分かっているので、C++でも同じ計算を実装すれば目的を達成できます。

struct Wave
{
Vec2 dir;
float amplitude;
float waveLength;
};
struct ImmutableCB
{
float waveSteepness;
float waveSpeed;
Wave waves[20];
};
static Vec3 CalcGerstnerWaveOffset(const ImmutableCB& cb, Vec3 location, float time)
{
Vec3 sum = Vec3(0, 0, 0);
for (int i = 0; i < dimof(cb.waves); i++)
{
const Wave& wave = cb.waves[i];
float wi = 2 / wave.waveLength;
float rad = (dot(wave.dir, Vec2(location.x, location.z)) + time * cb.waveSpeed) * wi;
float sine = std::sin(rad);
float cosine = std::cos(rad);
sum.y += sine * wave.amplitude;
sum.x += wave.dir.x * cosine * cb.waveSteepness / (wi * dimof(cb.waves));
sum.z += wave.dir.y * cosine * cb.waveSteepness / (wi * dimof(cb.waves));
}
return sum;
}

ほぼ機械的にHLSLからそのまま持ってきただけです。ImmutableCBはコンスタントバッファになる構造体で、GPUと共有する波のパラメータの集まりです。この波のパラメータは一度決定したら書き換えません。変化するのはtimeだけで、現在時刻だけ毎フレームC++とHLSLで同期すれば任意の地点の海水面の移動量が一意に決定します。

あとは、描画したいメッシュの座標をCalcGerstnerWaveOffsetにlocationとして渡せばVec3の移動量が取得できるので、それを足して描画すれば上のアニメーションが得られます。

更に、ここからキューブを波に沿って傾けることを考えてみます。


Copyright (c) 2011 NVIDIA Corporation. All rights reserved.

HLSLでやったように微分して法線を得る事でもできそうですが、予想される結果として戦艦のような大きなものを局所的な法線で傾けるとおかしくなりそうです。今回は海面から任意の3点を取得してそれの属する平面の法線を使うことにします。これなら、大きな物体は3点をなるべく離れた場所に設定することで傾きすぎないようにするなどの調整ができます。

Vec3 center = pos + CalcGerstnerWaveOffset(immutableCb, pos, terrainFrameCB.g_time);
Vec3 offsetXZ = pos + Vec3(1, 0, 1) + CalcGerstnerWaveOffset(immutableCb, pos + Vec3(1, 0, 1), terrainFrameCB.g_time);
Vec3 offsetNegX = pos + Vec3(-1, 0, 0) + CalcGerstnerWaveOffset(immutableCb, pos + Vec3(-1, 0, 0), terrainFrameCB.g_time);
Vec3 offsetNegZ = pos + Vec3(0, 0, -1) + CalcGerstnerWaveOffset(immutableCb, pos + Vec3(0, 0, -1), terrainFrameCB.g_time);
Vec3 normal = normalize(cross(offsetNegZ - offsetXZ, offsetNegX - offsetXZ));
Vec3 rotAxis = normalize(cross(normal, Vec3(0, 1, 0)));
float rotRad = acos(dot(normal, Vec3(0, 1, 0)));
cube.Draw(MeshXAnimResult(), scale(5, 5, 5) * q2m(Quat(rotAxis, -rotRad)) * translate(center.x, center.y, center.z));
view raw draw_cubes.cpp hosted with ❤ by GitHub
キューブの中心からやや離れた3点を波で動かし、offsetXZ, offsetNegX, offsetNegZの3点を得ました。ここから得た法線と真上に向かうベクトルとのなす角を回転量とし、同様に法線と真上に向かうベクトルとの外積で回転軸を求めています。

No comments:

Post a Comment