【Unity】アニメ風の演出効果をShader Graphで実現する

# はじめに
この記事では、手書きアニメでよく見られる画面に一瞬激しい効果がかかる演出**風**の演出をUnityで実装するための方法を解説します。

実際に動かしてみた動画 ↓

https://x.com/togucchi/status/1758161597649055869?s=20

# 環境
- Unity 2022.3.12f1
- Universal Render Pipeline 14.0.9
- ShaderGraph 14.0.9

# 方針
Universal Render Pipeline(以降URP）でのポストエフェクトの実装方法は様々ありますが、今回はおそらく現状最もカジュアルに実装可能である **FullscreenPassRendererFeature** を利用します。
この機能は、Shader Graphでフルスクリーンに対して効果をかけるShaderを作成し、それを使ったMaterialをRendererFeatureに刺すことでポストエフェクトが作成できる機能です。

こちらの機能に関しては以下の動画でUnity公式が解説していますので、詳細はそちらをご確認ください。

https://www.youtube.com/watch?v=7Cd7kQl6N50

# 実装

さて、ここからは実際の実装について解説します。

## ShaderGraphの作成

まずはShaderGraphを作成し、Material Typeを **Fullscreen** に設定します。

![スクリーンショット 2024-02-21 午後11.17.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/170854/e5f049b2-3211-3606-6e81-ca8f6f6ecfa0.png)

URPのRendererAssetにFullscreenPassRendererFeatureを追加し、作成したシェーダを使ったマテリアルをアタッチします。これにより作成したシェーダを画面に対して適用することができます。

![スクリーンショット 2024-02-21 午後11.58.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/170854/26cf3ee0-5a35-74d8-bab3-ec1723c02c35.png)


今回目指す演出はアートスタイル等によって様々な作り方があると思いますが、今回の実装は **Lambertのポスタライズ** と **円形のディストーション** を用いて行います。

### Lambertのポスタライズ

この項では、画面全体の色を大胆に調整して画面全体が激しく明滅しているような印象を与えるための実装を行っています。
まずは、ShaderGraphで取得できる画面のNormalバッファを利用してLambert (LdotN) の計算を行います。

![スクリーンショット 2024-02-21 午後11.24.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/170854/d5d842cf-597b-9c8d-b7cc-5608f7b568a8.png)

この時点では以下のような画面が出力されます。

![スクリーンショット 2024-02-21 午後11.25.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/170854/95d18b5d-d83b-6860-51fe-aaa2414bacb5.png)

続いて、この結果をstep関数で2値化し、その結果をもとにlerpでカラーを乗せポスタライズを行います。

![スクリーンショット 2024-02-21 午後11.26.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/170854/71c363eb-1100-aff5-d23f-3cf329655842.png)

最終的に以下のような絵が得られました。指定するカラーを変更することで見た目の雰囲気をガラリと変えることができます。

![スクリーンショット 2024-02-21 午後11.29.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/170854/609042af-498d-0b77-ea5e-a8d9c71e78f4.png)

![スクリーンショット 2024-02-21 午後11.29.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/170854/fb3e6457-0286-9425-5105-90576fba6a68.png)

### 円形のディストーション

この項では、画面全体を大きく歪ませることで画面に躍動感を与えることを目指した実装を行っています。

まず、特定座標を中心として画面を歪めるため、指定座標からの方向ベクトルに応じてUVオフセットをかける処理を実装します。

![スクリーンショット 2024-02-21 午後11.45.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/170854/6bbf4eae-eaa9-8ebc-7c17-40d37ecf8dd2.png)

この時点での画面は以下のようになります。 (画面中心を基準としてUVを歪ませています）

![スクリーンショット 2024-02-21 午後11.46.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/170854/23323c36-8841-01f4-c338-003fd58bba9e.png)

さらに、集中線のような効果を得るため、**Polar Coordinates** ノードを用いた円形のUVとノイズ、またUVと特定座標との距離を用いて特定座標を中心にギザギザが広がるようなシェーダを実装します。

![スクリーンショット 2024-02-21 午後11.47.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/170854/6b99b360-ee8c-2480-f3e3-7edd08ef27ea.png)

最終的には以下のような見た目になりました。

![スクリーンショット 2024-02-21 午後11.49.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/170854/bf4eb519-2b2f-44f0-5be1-e4f822bef2b5.png)


ここまでで一枚絵としての実装（シェーダの実装）は完了となります。

## プロパティのアニメーション制御

ここまでの実装で画面に対して作成したポストエフェクトを適用することができましたが、今回実装する演出は画面に一瞬かけたい効果なので、アニメーション等でパラメータを操作する必要があります。

今回は以下のようなTimelineを作成し、AnimationTrackでシェーダのプロパティを操作できるようにします。

![スクリーンショット 2024-02-22 午前12.01.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/170854/eadcd41d-a226-5295-439d-e863e17d1365.png)


### シェーダプロパティのGlobal化

まずはShaderGraphで定義しているプロパティを外部から変更可能にするため、Globalなプロパティとして制御できるようにします。

（シェーダを使ったマテリアルのプロパティを制御する方法も考えられますが、今回はポストエフェクトであるのと実装の単純化のためGlobalなプロパティを用います）

ShaderGraphではプロパティの設定の **Override Property Declaration** にチェックを入れ、**Shader Declaration** をGlobalにすることでプロパティをGlobalなものとして定義することができます。

![スクリーンショット 2024-02-22 午前12.05.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/170854/3879641d-ba0e-ef3a-28b4-e68823006edb.png)

### Globalなプロパティの変更

次に、Globalなシェーダプロパティをアニメーション制御可能にするため、Inspectorから各種パラメータを操作することができるスクリプトを実装します。

```c#
[ExecuteAlways]
public class FlashPostProcessParameter : MonoBehaviour
{
    public static class ShaderProperty
    {
        public static readonly int DistortionCenter = Shader.PropertyToID("_DistortionCenter");
        public static readonly int DistortionStrength = Shader.PropertyToID("_DistortionStrength");
        
        public static readonly int PosterizationThreshold = Shader.PropertyToID("_PosterizationThreshold");
        public static readonly int PosterizationLightColor = Shader.PropertyToID("_PosterizationLightColor");
        public static readonly int PosterizationDarkColor = Shader.PropertyToID("_PosterizationDarkColor");
        
        public static readonly int FlashAlpha = Shader.PropertyToID("_FlashAlpha");
    }
    
    [SerializeField] private Vector2 _distortionCenter = new Vector2(0.5f, 0.5f);
    [SerializeField] private float _distortionStrength = 0.1f;
    
    [SerializeField] private float _posterizationThreshold = 0.1f;
    [SerializeField, ColorUsage(false, true)] private Color _posterizationLightColor = Color.white;
    [SerializeField, ColorUsage(false, true)] private Color _posterizationDarkColor = Color.black;
    
    [SerializeField] private float _flashAlpha = 0.0f;

    private void LateUpdate()
    {
        Shader.SetGlobalVector(ShaderProperty.DistortionCenter, _distortionCenter);
        Shader.SetGlobalFloat(ShaderProperty.DistortionStrength, _distortionStrength);
        
        Shader.SetGlobalFloat(ShaderProperty.PosterizationThreshold, _posterizationThreshold);
        Shader.SetGlobalColor(ShaderProperty.PosterizationLightColor, _posterizationLightColor);
        Shader.SetGlobalColor(ShaderProperty.PosterizationDarkColor, _posterizationDarkColor);
        
        Shader.SetGlobalFloat(ShaderProperty.FlashAlpha, _flashAlpha);
    }
}
```

そして最後にこのスクリプトをアタッチしたオブジェクトにAnimatorコンポーネントをアタッチし、各種プロパティを制御するAnimation Clipを作成、それらをTimelineに並べることでアニメーション制御が可能になります。

![スクリーンショット 2024-02-22 午前12.09.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/170854/9050c682-7010-5f8c-aa36-439675aaab63.png)

## 結果

これらの実装により、時間軸に合わせて激しい画面効果をかける演出を実現することができました。
（最終的な絵作りにはBloomなどの別の効果を併用しています）

![スクリーンショット 2024-02-22 午前12.11.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/170854/a86f8142-2603-b575-39ec-3dc38b0d81df.png)

![スクリーンショット 2024-02-22 午前12.12.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/170854/a1138717-b3ef-c8ff-45de-8dac0f9ac57d.png)

![スクリーンショット 2024-02-22 午前12.12.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/170854/c60b7020-cdf5-c8a7-1aff-071c55d8a571.png)

![スクリーンショット 2024-02-22 午前12.12.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/170854/e54f3f80-732d-f59d-b71e-99ea1a9b2a68.png)

# さいごに

今回の記事ではFullscreen ShaderGraphの簡単な使い方と、それを用いてアニメ風のポストエフェクトを実現するためのアプローチを解説しました。

ポストエフェクトシェーダは画面の印象を操作することができる面白いものなので、ShaderGraphで気軽に実装できるようになった今、ぜひチャレンジしてみてください！