カスタムパスで3Dの流体シミュレーションをするサンプルシーンを見てみました。
参考:https://github.com/alelievr/HDRP-Custom-Passes?tab=readme-ov-file#liquid
パーティクルの法線をカスタムカラーに描画して、カスタムカラーにガウスぼかしをかけます。ぼかした法線を使って液体を表現します。
シーン
シーンにはカスタムパスがあります。ガウスぼかしのRadiusを設定できます。流体を描画するためのマテリアルがアタッチされています。
VFXグラフもあります。ここで球形のパーティクルを発生させています。
カスタムパス
カスタムパスでは、SetupメソッドでRTHandleを割り当てています。これはガウスぼかしで一時的なレンダーターゲットとして使われます。
blurBuffer = RTHandles.Alloc(
Vector2.one * 0.5f, TextureXR.slices, dimension: TextureXR.dimension,
colorFormat: GraphicsFormat.R16G16B16A16_SNorm,
useDynamicScale: true, name: "BlurBuffer"
);
一辺が2の正方形の平面メッシュを作成します。最後に流体を描画するときに使います。
quad = new Mesh();
quad.SetVertices(new List< Vector3 >{
new Vector3(-1, -1, 0),
new Vector3( 1, -1, 0),
new Vector3(-1, 1, 0),
new Vector3( 1, 1, 0),
});
quad.SetTriangles(new List{
0, 3, 1, 0, 2, 3
}, 0);
quad.RecalculateBounds();
quad.UploadMeshData(false);
Executeメソッドでは、CustomPassUtils.DrawRenderersメソッドを使って、VFXグラフで発生されたパーティクルをカスタムカラーバッファに描画します。
var overrideDepth = new RenderStateBlock(RenderStateMask.Depth);
overrideDepth.depthState = new DepthState(true, CompareFunction.LessEqual);
CoreUtils.SetRenderTarget(ctx.cmd, ctx.customColorBuffer.Value, ctx.cameraDepthBuffer);
CustomPassUtils.DrawRenderers(ctx, layerMask, overrideRenderState: overrideDepth);
カスタムカラーバッファにガウスぼかしをかけてカスタムカラーバッファに描画します。ガウスぼかしにはCustomPassUtils.GaussianBlurメソッドを使います。
// Blur the custom buffer:
var resRadius = radius * ctx.cameraColorBuffer.rtHandleProperties.rtHandleScale.x;
CustomPassUtils.GaussianBlur(ctx, ctx.customColorBuffer.Value, ctx.customColorBuffer.Value, blurBuffer, 25, resRadius);
CustomPassUtils.GaussianBlurメソッドの引数には一時的なレンダーターゲットやピクセル単位での半径も渡します。半径はRTHandleをサンプリングするときに使うスケールがかけられています。
VFXグラフのVFXシェーダーグラフではパーティクルの表面の法線にノイズをかけています。これを流体の表面の法線として使います。ぼかすことでパーティクルの境界が曖昧になり、表面がなめらかにつながって見えます。
VFXグラフ
VFXグラフでは、パーティクルを絶え間なく発生させます。速度や寿命が設定されています。
パーティクルは「Gravity」ブロックによって落下し、「Collide with Plane」ブロックで平面に衝突します。「Turbulence」ブロックではパーティクルの速度にノイズを適用します。
向きや大きさも設定されます。VFXシェーダーグラフには、Sphereメッシュやposition属性が渡されています。position属性はパーティクルの位置です。
velocity属性の値を「Set Scale」ブロックに入力しています。加速するほどにパーティクルが伸びます。
VFXシェーダーグラフ
VFXシェーダーグラフでは、まずUVにTimeを足した値と引いた値の2つが作られます。
それぞれの値でパーリンノイズを別々の方向にスクロールさせます。
足した方のXと引いた方のYを使って、さらに違った方向にスクロールするノイズを作ります。
この3つを3次元ベクトルに合わせます。
「Position」ノードの値からパーティクル中心の位置を引いています。
どちらもワールド空間(Absolute)なので、パーティクルの中心からSphereメッシュの頂点やフラグメントへのベクトルになります。これを正規化して、Sphereの法線を計算しています。
法線とノイズの値をLerpノードでブレンドします。マスタースタックに入力します。
ノイズをブレンドしないとハイライトが見えなくなります。
カスタムパスではこの結果をカスタムカラーバッファに描画したあと、ガウスぼかしをかけています。
液体を描画する
カスタムパスでカメラの前に平面メッシュを移動しそこに流体を描画します。まずMatrix4x4.TRSメソッドで、平面を変形する行列を作成します。
// Move the mesh to the far plane of the camera
float forwardDistance = ctx.hdCamera.camera.nearClipPlane + 0.0001f;
var transform = ctx.hdCamera.camera.transform;
var trs = Matrix4x4.TRS(
transform.position + transform.forward * forwardDistance,
transform.rotation,
Vector3.one);
カメラの位置からカメラの向いている方へ、nearClipplane + 0.0001fだけ移動した位置になっています。向きはカメラと同じで、スケールは1です。
レンダーターゲットをカメラカラーバッファとカメラデプスバッファに設定して描画します。
CoreUtils.SetRenderTarget(ctx.cmd, ctx.cameraColorBuffer, ctx.cameraDepthBuffer);
ctx.cmd.DrawMesh(quad, trs, transparentFullscreenShader, 0, pass);
シェーダーグラフ
水流の描画にはカスタムパスでアタッチされたマテリアルが使われています。マテリアルにはLitシェーダーグラフが設定されています。
シェーダーグラフとマテリアルで、Surface Typeが「Transparent」に設定されていて、屈折が使われています。
シェーダーグラフでは、法線が描画されたカスタムカラーバッファの値がマスタースタックのNormalに入力されます。法線は「Transform」ノードでTangent空間に変換されます。
カスタムカラーバッファの値は「Length」ノードにも入力されています。大きさを計算することで明度を評価できます。
水流の部分が白くなります。
透明度がかけられてマスタースタックのAlphaに接続されています。
最後にカスタムデプスバッファの値からカメラのNear Planeを引いた値がマスタースタックのDepth Offsetに入力されています。
カメラの近くでは水流エフェクトが表示されなくなります。Depth Offsetの値を大きくするとこの範囲が広がります。
これで3Dの液体を表示できました。
水流を描画する平面メッシュ
平面メッシュはカスタムパスのSetupメソッドで作成したものです。
quad = new Mesh();
Mesh.SetVerticesメソッドで頂点位置を設定しています。XY平面に平行で一辺が2の正方形になっています。
quad.SetVertices(new List< Vector3 >{
new Vector3(-1, -1, 0),
new Vector3( 1, -1, 0),
new Vector3(-1, 1, 0),
new Vector3( 1, 1, 0),
});
Mesh.SetTrianglesメソッドで三角形を定義します。頂点のインデックスが時計回りに設定されます。
quad.SetTriangles(new List{
0, 3, 1, 0, 2, 3
}, 0);
左手座標系なので面はこちら側を向きます。この正方形がカメラの正面に置かれます。流体はここに描画されます。
カメラのNearクリッププレーンを遠ざけると、平面の位置も遠ざかるようになっています。
float forwardDistance = ctx.hdCamera.camera.nearClipPlane + 0.0001f;
CameraコンポーネントのNearの値を大きくすると、水流が描画される範囲が狭くなりました。
デフォルトのPlaneのスケールを0.2にして、2ユニット四方の正方形を作りました。
カメラと同じ位置に持ってきます。カメラに表を向けるように向きを90度回転させています。
CameraコンポーネントのNearを下限の0.01にしました。Planeをカメラの前方に0.01動かしても、Planeはカメラに映りませんでした。スクリプトと同様に0.0001だけさらに前に動かすと表示されました。
Nearの値を上げると、水流エフェクトの端が見えるようになります。
Planeの位置もそれに応じて変更します。
Planeとエフェクトの端が揃っているように見えます。