Monday, November 27, 2017

[UE4] マンデルブロ集合マテリアルを作る

GLSL Sandboxにマンデルブロ集合があり、面白そうだったのでちょっと使いやすそうに加工してからUE4のマテリアルにしてみました。



ループがあるのでCustomノードに書きます。発散の判定は絶対値が2を超えた時点とし、発散と判定されるまでの計算回数が多い程輝度が上がるようにしました。



探検できるように300倍に拡大した'Plane'にマテリアルを貼り付けます。

Spectator Pawnで探検します。

離れてみると分かりにくいですが、近くから輪郭部を見ると非常に輝度が高いのがわかります。集合の左側は特に輝度が高く、更に拡大しても複雑なパターンが現れてきそうです。

プロジェクトをgithubにアップしました。

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点を得ました。ここから得た法線と真上に向かうベクトルとのなす角を回転量とし、同様に法線と真上に向かうベクトルとの外積で回転軸を求めています。

Sunday, November 5, 2017

[Vulkan] vkCmdPipelineBarrierで指定するVkPipelineStageFlagBitsとは? [SDC 2017]

Samsung Developer Conference 2017で行われた、VulkanのvkCmdPipelineBarrierの使い方の技術セッションがYouTubeで公開されています。

vkCmdPipelineBarrierに指定するsrcStageMaskとdstStageMaskの指定方法に関して説明されており、Khronosの文書が関数リファレンスのレベルしか言及していない現状で、動作原理を解説する貴重なセッションとなっています。

連続する2つのパスはGPUで並行に実行される可能性がありますが、前のパスの結果に後のパスが依存する場合、vkCmdPipelineBarrierを置くことで後のパスを待たせる必要があります。ただし、GPUコア間の並行性が失われて待機時間が生じます。これがPipeline Bubbleと呼ばれます。

srcStageMaskは前のパスのパイプラインステージ(例:vertex shader等)、dstStageMaskは後のパスのパイプラインステージを表します。前のパスのsrcStageMaskで指定のステージが終わるまで、後のパスのdstStageMaskで指定のステージは始めてはいけないという意味になります。

VK_PIPELINE_STAGE_TOP_OF_PIPE_BITとVK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BITは存在しない一種の仮想のパイプラインステージで、それぞれ全てのパイプラインステージの前と後を意味しています。

保守的には、srcStageMask=VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT、dstStageMask=VK_PIPELINE_STAGE_TOP_OF_PIPE_BITとすることで並列動作を出来なくします。

逆に、srcStageMask=VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT、dstStageMask=VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BITとすることで、2つのパスは並列に動き出すそうです。

残念ながら、どのように依存関係を解決しながら平行動作させるかには言及されていませんでしたが、2つのパスの依存関係の無い場合には並列動作させることでpipeline bubbleを取り除こうという趣旨と思われます。

また、AMDのサイトでもVulkan barriers explained と題してvkCmdPipelineBarrierを解説しています。

おそらく最も使用頻度が多いのが、前のパスで作った絵を次のパスのfragment shaderで使うというシナリオだと思いますが、srcStageMask=VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT、dstStageMask=VK_PIPELINE_STAGE_FRAGMENT_SHADER_BITとすればよいそうです。これなら前のパスが絵を描き終わる前に次のパスの頂点処理を平行させることが出来そうです。

DX12では、ID3D12GraphicsCommandList::ResourceBarrierでリソースのステートを遷移させるには前後のD3D12_RESOURCE_STATESを指定すればタイミングはドライバが決めてくれました。Vulkanでは前後のVkImageLayoutに加えてVkPipelineStageFlagBitsでタイミングまでプログラマが把握して指定しなければいけません。