AboutUnityのHDRPを改造し擬似的な流体のレンダリングを行うパスを追加する2019年09月28日はじめにHDRPのコードを読んで、カスタムしてみたくなったのでカスタムしてみた記事です。HDRPのソースコードを編集してカスタムパスを追加し擬似的な流体のレンダリングを実装します。なったらこの記事の情報はヤクタタズになりそうですね。HDRPを改造してカスタムパスを追加してパーティクルを流体っぽくレンダリングするの出来たー。2019.2.6f1で動くv6.9.1をベースにしていきます。利用するUnityのバージョンに対応したHDRPが必要なので注意が必要です。HDRPのレンダリングの本体はRuntime/RenderPipeline/HDRenderPipeline.csです。Unity-Technologies/ScriptableRenderPipeline907行目からのRenderメソッドがレンダリングしているSRPのコアのメソッドです。...といってもRenderメソッドの最初の方は各種設定やカリングなどで占められています。1452行目でExecuteRenderRequestというメソッドを呼び出しています。このメソッドがレンダリングパイプラインの本体になります。}ExecuteRenderRequestは1491行目から2028行目まで続きます。ExecuteRenderRequestをちょっと長いですが全部貼り付けてみるとこんな感じ。hdCamera);などなど。各パスがメソッドとして切り出されてレンダリングパスが作られているのがわかります。この中に自分で書いたメソッドを混ぜることでレンダリングパスを追加できます。HDRP改造の準備まずは普通にHDRPのプロジェクトを立ち上げます。Unityのバージョンは2019.2.6f1です。6.9.1のScriptableRenderPipelineプロジェクトをダウンロードしてきます。Packages/manifest.jsonを書き換えます。}}これでUnityのプロジェクトを立ち上げて何もエラーが出ることなく起動できれば成功です。ローカルに配置したHDRPが利用されているので、これを書き換えていけばよいです。独自パスでG-Bufferに書き込みをする新しいパス用にLit.shaderをベースに書き換えたシェーダを作ります。"Experimental.Rendering.HDPipeline.LitGUI"}使わないであろうGbufferパスやForwardパスTransparent系のパスを取り除きました。レイトレは使わないのでDXR系もいったん取り除いてしまいます。最後にMyPassとして新しいパスを追加しています。このパスはGBufferのパスをベースにしています。GBufferのパスは次に書かれているものを参考にしました。Unity-Technologies/ScriptableRenderPipelineFragでSurfaceDataを取得したあとに上書きをしています。posInput.deviceDepth;#endif}ちなみにSurfaceDataについては次のとおり。このCSharpのコードからHLSLを生成しているのだと思います。これで余計なパスを取り除きMyPassを追加したシェーダが完成しました。このシェーダ用のパスをレンダリングパスに追加していきます。RenderGBufferの次にRenderMyPassというのを追加してみます。m_SharedRTManager.BindNormalBuffer(cmd);RenderMyPassをRenderGBufferを参考に実装します。}}HDCustomSamplerId.csに列挙体を追加します。...HDStringConstants.csに文字列とIDを追加します。真っ赤な金属のマテリアルとして表示されるようになりました。擬似流体を描画するパスを作る以前に従来のパイプライン用に実装した擬似流体レンダリングをHDRP用に作ってみます。測度ゼロの抹茶チョコプログラム全文まず最初に改造したプログラムをすべて載せてしまいます。解説はあとから行います。MyPass.shaderを作成しました。}}HDRenderPipeline.csを改造しました。コンストラクタ中に後に使うマテリアルの生成処理を入れました。RenderMyPassメソッドの呼び出しを追加します。haveRenderMyPassメソッドを実装しました。}CopyDepthBufferメソッドを作成しました。`CopyDepthBufferの呼び出しに変更しました。`CopyDepthBufferの呼び出しに変更しました。...HDCustomSamplerId.csにカスタムパスを追加しました。...HDStringConstants.csに追記をしました。...HDRPの改造はこれですべてです。MyPass.shaderMyPassメソッドが追加したカスタムパスです。4つのパスを実行しています。今回作成したシェーダはLit.shaderをベースにしています。Unity-Technologies/ScriptableRenderPipeline使わないGbufferパスやForwardパスTransparent系のパスを取り除きました。今回レイトレは対応しないのでDXR系もいったん取り除いてしまいます。デプスは後でGBufferへ書き込むとき一緒に書き込むのでDepthOnlyパスも取り除いています。MotionVectorsぱすがあると描画が乱れるので取り除いています。Shaderのパスが追加されています。追加されたそれぞれのパスについては後で順を追って説明します。今回はHDRPのデプスのコピー処理を改造しました。CopyDepthBufferIfNeededメソッドが呼び出されています。デカール機能がオンになっているかどうかでどちらでコピーされるかが変わるようです。RenderDBuffer内でデプスバッファのコピーが走ります。その後のパスに反映されなくなってしまいます。画像は書き込んだデプスが反映されず、描画がおかしくなっている様子。デプスバッファをコピーするようにしました。CopyDepthBufferメソッドを作成しました。CopyDepthBufferIfNeeded呼び出しの部分をCopyDepthBufferに変更しました。RenderMyPass今回追加したカスタムパスの本体であるRenderMyPassについて見ていきます。このパスは通常のGBufferの描画のあとに呼び出しています。RenderMyPassメソッドの呼び出しを追加しています。最初にこのパスで利用するTemporaryRTを取得しています。...利用しているCommandBuffer.GetTemporaryRTのマニュアルは次のページにあります。depthStencilBuffer.rt.heightを利用しているのには理由があります。左下に小さくレンダリングしていく仕様になっているようです。シーンビューとゲームビューのサイズが違うだけでも確認できます。左下にレンダリングされていることが確認できます。既存のDepthStencilバッファにサイズを合わせることで解決しています。今回はfloat1つ書き込みたいのでR32_SFloatにしています。MyDepthPass最初にデプスを書き込むパスを実行しています。rendererList);さきほど作成したTemporaryRTの"_Depth0RTをレンダーターゲットにして書き込みを行います。どこかでIDを保存しておいて使ったほうがよいと思われます。今回は手抜きしています。書き込みに使うMyDepthPassのShaderは次のとおりです。Unity-Technologies/ScriptableRenderPipelineを参考にしています。_Radius;でプロパティとして渡しています。この_Radiusはパーティクルの大きさと揃えます。パーティクルのビルボードのUVは次のようになっています。UVを0~1から-1~1の範囲に変換しています。そのピクセルについて捨てています。これによって正方形いっぱいの円が描画され、その外側は捨てられます。...円の内側のピクセルについてはzを次のようにして計算しています。...半径1の球上の点を考えてxyから残りのzの大きさを計算しています。その後、zに_Radiusを掛けた分だけ手前に飛び出すようにdepthを計算します。それをクリップスペースに変換しています。計算したdepthを線形の0~1になるよう変換する関数をかけて書き出します。DepthBuffer用に通常のデプスも書き出しています。clipSpacePos.w;}これで次のようなものが出力されます。明るさを補正するとこんな感じ。MyXBlurPass次にこの書き出したdepthに対してバイラテラルブラーをかけます。ここでは大胆にX方向とY方向に分けて処理してしまいます。4);HDUtilsに定義されているDrawFullScreenメソッドを利用しています。"_Depth1RT"をレンダーターゲットとして、画面を埋め尽くす三角形を描画します。MyXBlurPassのShaderのパスは次のとおり。ENDHLSL}ステンシルでさきほどのパスで書き込んだ部分だけ書き込みを行います。Unity-Technologies/ScriptableRenderPipelineを参考にしました。フラグメントシェーダではx方向のバイラテラルフィルタを実装しています。画面サイズとRenderTextureのサイズの違いを補正してくれる関数です。今回は手抜きでバイラテラルフィルタの半径を50で固定しています。近ければ半径を大きくします。あまりにも半径が大きくなりすぎないようにある程度のところでクランプする必要もあります。レンダリングの結果を見ながら適当に決めた値です。カメラのFarの値やその他パラメータによっても変わると思われます。このパスのレンダリング結果は次のとおり。見にくいですがX方向にボケたのがわかります。MyYBlurPassこのパスはY方向にしただけで残りは前のパスと同様です。5);書き込むターゲットを"_Depth0RT"に変更しています。MyGBufferPass最初にDepthからViewスペースの位置を復元するのに必要なパラメータを計算し渡しています。corner);視錐台の4つの頂点を与えることでdepthから座標を復元します。planeではどの位置にあたるかを計算します。planeへ投影したときの点へのベクトルのx成分がわかります。tの値はフルスクリーンのUV座標を使えばよいです。現在のピクセルをfarプレーンへ投影したときの点へのベクトルのy成分がわかります。これで現在のピクセルがfarプレーン上ではどの位置に当たるのかというベクトルが手に入りました。eyeスペースでの座標が求まります。シェーダのMyGBufferPassは次のとおりです。ENDHLSL}GBufferへ書き込むにはENCODE_INTO_GBUFFERを利用します。これは@surfaceDataとbuiltinDataが必要になります。surfaceDataは適当に埋めればよくて、builtinDataはGetBuiltinDataで手に入ります。GetBuiltinDataはFragInputs型のinputとそれから計算されるposInputが必要になります。FragInputsはUnpackVaryingsMeshToFragInputsにVaryingsMeshTypeを渡してやることで得られます。VaryingsMeshTypeを渡してやることになります。ここらへんの処理はGBufferのパスを参考にしました。VertMeshをもとに改造したMyVertMeshを使っています。spaceの位置を復元しています。spaceに変換してdepthを書き込んでいます。clipSpacePos.w;次に法線を計算しています。Spaceでの法線としています。normalize(normal);ViewSpaceの法線をワールドスペースの法線に変換しています。1;なめらかな赤い金属にしました。input.positionRWSとposInput.positionWSを埋めます。Viewスペースの位置にView行列の逆行列をかけています。RWSpos;最後にGBufferへ書き込みをしています。outGBuffer);シーンの設定MyPass.shaderからマテリアルを作成してパーティクルシステムのマテリアルとしてセットします。レンダリング結果リアルタイムでこのようにレンダリングされます。遠くなるとディティールが潰れます。このようなことはなくなるはずです。画面が小さめのときはバイラテラルフィルタをxとyで分離したアーティファクトが出ます。画面が大きめでアンチエイリアスをかけているとアーティファクトも気にならないレベルになります。おわりにHDRPを改造してカスタマイズパスを追加してみました。カスタマイズパスを追加するだけならば割と簡単にできました。安定してからのほうがよいでしょう。それまで待つほうが賢い判断だと思われます。このUnityProjectをGitHubに公開しました。手元で動かす場合にはUnityのバージョンに注意してください。MatchaChoco010/UnityHDRPPseudoFluidRendering追記なんかシーンを切り替えるとエラーが出るようです......。SceneからSampleSceneへ切り替える際にエラーが出てしまいました。SampleSceneを読み込んだら、一度Unityを再起動してみてください。追記2シーン切り替えでのエラーの件、修正しました。Materialの生成を他のマテリアルに習って生成してみたら無事にエラーが修正されました。("HDRP/MyPass"));追記3GitHubのソースコードを記事を書いた時点からいくつかアップデートしました。インスペクタ上で自由にマテリアルを設定できるようになりました。興味のある方はGitHubの方を見てみてください。参考サイトこの記事を書くにあたって参考にしたサイトです。MatchaChoco010。