HDRPでポストエフェクトやる

<追記 2019-10-05>
HDRPがカスタムポストエフェクト対応したそうです。この記事の3日後にマージされとる……😂
https://github.com/Unity-Technologies/ScriptableRenderPipeline/pull/4484
</追記ここまで>

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">HDRPでポストエフェクトできた、ワーイ <a href="https://t.co/PM01n6y4g3">pic.twitter.com/PM01n6y4g3</a></p>&mdash; amagi (@amagitakayosi) <a href="https://twitter.com/amagitakayosi/status/1168732436441681920?ref_src=twsrc%5Etfw">September 3, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

UnityのHDRPを使うと高品質なレンダリングが可能になりますが、まだユーザーがポストエフェクトを作成できません。
Unity公式フォーラムでは「2019.3で対応するつもりだよ～」とアナウンスされていますが、今のところ具体的なスケジュールはまだ未定のようです。
https://forum.unity.com/threads/custom-post-processing-effects-with-new-hdrp-workflow.689317/

昨日↑のスレを見返した所、Another Angle Gamesのnehvaleem氏がHDRPをforkしてポストエフェクトを自作するチュートリアルを投稿してくれていました！
https://medium.com/another-angle/custom-post-processing-using-hdrp-in-unity-2019-x-60302a2fa1e0

この記事では、↑のチュートリアルに沿って解説します。
↑のチュートリアルではシェーダー周りはノータッチだったり色々ハマりどころもあったので、その辺もカバーしていきます。

## 前提
- Unityのバージョン: 2019.2
- HDRPのバージョン: 6.9.1

## 手順
- HDRPをfork
- HDRPプロジェクトを新規作成
- forkしたHDRPをプロジェクトに突っ込む
- 自作PostProcessComponentを作る
- 自作PostProcessComponentをロードする
- PostProcessingSystemをいじる

### HDRPをfork
HDRPはScriptableRenderPipelineの中にあるので、まずはこちらをcloneします。
けっこう時間かかるんで気長に待ちましょう。
https://github.com/Unity-Technologies/ScriptableRenderPipeline

```
git clone https://github.com/Unity-Technologies/ScriptableRenderPipeline
cd ScriptableRenderPipeline
```
HDRPは、Unityのバージョンに合わせて異なるバージョンを使う必要があります。
バージョン毎にタグが切られてるのでcheckoutしましょう。
2019.2の場合は6.9.1です。

```
git checkout 6.9.1
```

### HDRPプロジェクトを新規作成

次に、Unityで新規プロジェクトを作成します。
プロジェクト作成ダイアログで `HDRP` を選択してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29342/ec912494-8706-657a-3314-e050941c3a38.png)

HDRPのサンプルシーンが作成されます。きれいですね～

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29342/a27eb1d8-7ad1-ef33-6517-632bc9fee67e.png)

従来のUnityではPost Processing Stackをインポートしてポストエフェクトをかけていましたが、HDRPではPost Processing Stackが最初から組み込まれています。
Hierarchyビューで `Scene Post-process`を選択してインスペクターを見ると、既にいくつかポストエフェクトが設定されていることがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29342/c2ae2644-10e4-56ab-db04-ecb98417e75b.png)

### forkしたHDRPをプロジェクトに突っ込む

まず、標準で入っているHDRPを削除します。
`Window` > `Package Manager`から`High Definition RP`を選択し、`Remove`をクリック。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29342/95866703-e423-3aa7-c98b-055c041a6388.png)

次に、先程cloneしたHDRPをインストールします。
Package Managerの`+`ボタンから`Add package from disk...`をクリック。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29342/7f6cc2af-2ccf-02f7-5fc5-5f54bcc3dcea.png)

先程cloneしたレポジトリから、 `ScriptableRenderPipeline/com.unity.render-pipelines.high-definition/package.json` を選択し、`開く` をクリックすると、HDRPのインストールが始まります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29342/f3e63edd-6510-aa8b-aaa2-4ff505539f38.png)

### 自作PostProcessComponentを作る

それでは、ついにポストエフェクトを作っていきます。

HDRPのポストエフェクトは、IPostProcessComponentを実装したクラスとシェーダーの2ファイルで構成されています。

- IPostProcessComponent: エフェクトのパラメータ等を管理
- シェーダー: エフェクト処理

まずIPostProcessComponentのクラスを作りましょう。
HDRPレポジトリの `Runtime/PostProcessing/Components` に `Glitch.cs` を作成します。

```cs
using System;
using UnityEngine.Rendering;

namespace UnityEngine.Experimental.Rendering.HDPipeline
{
    [Serializable, VolumeComponentMenu("Post-processing/Glitch")]
    public sealed class Glitch : VolumeComponent, IPostProcessComponent
    {
        // BoolParameterを使うと、Inspector上ではチェックボックスが表示される
        public BoolParameter isEnabled = new BoolParameter(false);

        // ColorParameterを使うと、Inspector上ではカラーピッカーが表示される
        [Tooltip("Overlay color for glitch")]
        public ColorParameter overlayColor = new ColorParameter(new Color(0,0,0,0), false, true, true);

        // ClampedFloatParameterを使うと、Inspector上ではスライダーが表示される
        [Tooltip("Intensity of glitch")]
        public ClampedFloatParameter intensity = new ClampedFloatParameter(0.03f, 0f, 0.2f);

        // 実装必須
        public bool IsActive()
        {
            return isEnabled.value;
        }
    }
}
```

次に、HDRPレポジトリの `Runtime/PostProcessing/Shaders` に `Glitch.shader` を作成します。

```hlsl
Shader "Hidden/Glitch"
{
    SubShader
    {
        Tags{ "RenderPipeline" = "HDRenderPipeline" }

        Pass
        {
            ZWrite On ZTest Always Blend Off Cull Off

            HLSLPROGRAM
                #pragma target 4.5
                #pragma only_renderers d3d11 ps4 xboxone vulkan metal switch

                #pragma vertex Vert
                #pragma fragment Frag

                #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
                #include "Packages/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariables.hlsl"

                TEXTURE2D_X(_InputTexture);

                float4 _OverlayColor;
                float _Intensity;

                struct Attributes
                {
                    uint vertexID : SV_VertexID;
                    UNITY_VERTEX_INPUT_INSTANCE_ID
                };

                struct Varyings
                {
                    float4 positionCS : SV_POSITION;
                    float2 texcoord   : TEXCOORD0;
                    UNITY_VERTEX_OUTPUT_STEREO
                };

                Varyings Vert(Attributes input)
                {
                    Varyings output;
                    UNITY_SETUP_INSTANCE_ID(input);
                    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);
                    output.positionCS = GetFullScreenTriangleVertexPosition(input.vertexID);
                    output.texcoord = GetFullScreenTriangleTexCoord(input.vertexID);
                    return output;
                }

                float4 Frag(Varyings input) : SV_Target0
                {
                    UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);

                    float2 uv = input.texcoord.xy;
                    float time = _Time.y;

                    // ここにエフェクトを書く。
                    // 今回は適当にグリッチしてる
                    float4 outColor = LOAD_TEXTURE2D_X(_InputTexture, uv * _ScreenSize.xy);
                    float n = sin(floor(uv.y * 23. + time * 3.)) * sin(floor(uv.y * 17. + time * 39.));
                    if (frac(n * 170.) < 0.1) {
                        uv.x = frac(uv.x + n * _Intensity);
                        outColor = LOAD_TEXTURE2D_X(_InputTexture, uv * _ScreenSize.xy);
                        outColor += _OverlayColor * _OverlayColor.a;
                    }

                    return outColor;
                }

            ENDHLSL
        }
    }
    Fallback Off
}
```


### 自作PostProcessComponentをロードする

`Runtime/RenderPipeline/RenderPipelineResources.cs` を開きます。
`GetAllComputeShaders`の上あたりに、以下のコードを追加します。

```cs
[Reload("Runtime/PostProcessing/Shaders/Glitch.shader")]
public Shader GlitchPS;
```

### PostProcessingSystemをいじる

最後にPostProcessingSystemを編集し、レンダリングの終段でポストエフェクトがちゃんと実行されるようにします。
`Runtime/PostProcessing/PostProcessingSystem.cs` を開き、以下のプロパティを追加してください。

```cs
Material m_GlitchMaterial;
Glitch m_Glitch;
```

次に、コンストラクタにmaterialの初期化処理を追加します。

```cs
// さっき作ったシェーダーからマテリアルを作成
m_GlitchMaterial = CoreUtils.CreateEngineMaterial(m_Resources.shaders.GlitchPS);
```

ついでに `Cleanup()` 内でちゃんと後処理してあげましょう。

```cs
CoreUtils.Destroy(m_GlitchMaterial);
m_GlitchMaterial = null;
```

m_Glitchの初期化は`BeginFrame()`で行います。

```cs
public void BeginFrame(CommandBuffer cmd, HDCamera camera)
{
    // 中略

    var stack = VolumeManager.instance.stack;
    // 中略
    m_Glitch = stack.GetComponent<Glitch>(); // この行を追加
}
```

いよいよ`Render()`を編集します。
460行目あたりに `// TODO: User effects go here` というコメントがあるので、その下に以下のコードをコピペしてください。

```cs
if (m_PostProcessEnabled)
{
    if (m_Glitch.IsActive())
    {

        var destination = m_Pool.Get(Vector2.one, k_ColorFormat);
        DoGlitchPass(cmd, camera, source, destination);
        PoolSource(ref source, destination);
    }
}
```

DoGlitchPassの実装を追加します。
DoFinalPassの上あたりに以下のコードをコピペしてください。

```cs
#region Glitch Pass
void DoGlitchPass(CommandBuffer cmd, HDCamera camera, RTHandle source, RTHandle destination)
{
    // マテリアルにパラメーターを渡す
    m_GlitchMaterial.SetTexture(HDShaderIDs._InputTexture, source);
    m_GlitchMaterial.SetVector(Shader.PropertyToID("_OverlayColor"), m_Glitch.overlayColor.value);
    m_GlitchMaterial.SetFloat(Shader.PropertyToID("_Intensity"), m_Glitch.intensity.value);

    // destinationに描画
    HDUtils.DrawFullScreen(cmd, m_GlitchMaterial, destination);
}
#endregion
```

これでコードの編集は終了です～


## 使ってみよう

`Scene Post-process`のインスペクターで Add overrideをクリックし、Post-processing > Glitchを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29342/7fc11c84-f13b-bbbb-4575-0a12dfb54e8c.png)

パラメーターをポチポチすると動くはず！

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Unity custom post fx in HDRP <a href="https://t.co/d7P5jU9Gqu">pic.twitter.com/d7P5jU9Gqu</a></p>&mdash; amagi (@amagitakayosi) <a href="https://twitter.com/amagitakayosi/status/1168792063019601920?ref_src=twsrc%5Etfw">September 3, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

ワーイ