PostProcessingStackV2のシェーダで_CameraDepthNormalsTextureから深度と法線を取り出す方法

![210122.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61266/e5da3bba-750f-9f09-dbab-42f28eabde1e.png)
PostProcessingStackV2のシェーダ内で、深度と法線を使いたい。
けどググっても日本語の情報があまり無かったのでメモとして記事化しておきます。
# 事前準備
### Cameraの設定変更
カメラのdepthTextureModeをDepthNormalsにしておきます

```cs:Main.cs
public class Main : MonoBehaviour
{
    [SerializeField]
    private Camera _mainCamera;
    // Start is called before the first frame update
    void Start()
    {
        _mainCamera.depthTextureMode = DepthTextureMode.DepthNormals;
    }
}
```
もしかするとこれは不要かも？
けど作業中_CameraDepthNormalsTextureが空になることが何度かあったので念の為。

### PostProcess設定用のcsを準備
カスタムPostProcessの追加方法については公式マニュアルの
[Writing custom effects | Post Processing | 3.0.1](https://docs.unity3d.com/Packages/com.unity.postprocessing@3.0/manual/Writing-Custom-Effects.html)
を見てもらうとして、とりあえず深度と法線を表示する用として下記のスクリプトを追加

```cs:DepthNormals.cs
#if UNITY_POST_PROCESSING_STACK_V2
using System;
using UnityEngine;
using UnityEngine.Rendering.PostProcessing;

namespace ScreenPocket.PostProcess
{
    [Serializable]
    [PostProcess(typeof(DepthNormalsRenderer), PostProcessEvent.AfterStack, "ScreenPocket/DepthNormals")]
    public sealed class DepthNormals : PostProcessEffectSettings
    {
        [Range(0f, 1f), Tooltip("DepthNormal effect intensity.")]
        public FloatParameter blend = new FloatParameter {value = 0.5f};
        [Range(0f, 1f), Tooltip("Depth or Normal. 0:Depth ~ 1:Normal")]
        public FloatParameter depthOrNormal = new FloatParameter {value = 0.5f};
    }

    public sealed class DepthNormalsRenderer : PostProcessEffectRenderer<DepthNormals>
    {
        public override void Render(PostProcessRenderContext context)
        {
            var sheet = context.propertySheets.Get(Shader.Find("ScreenPocket/PostProcess/DepthNormals"));
            sheet.properties.SetFloat("_Blend", settings.blend);
            sheet.properties.SetFloat("_DepthOrNormal", settings.depthOrNormal);
            //ビュー空間の法線をワールド空間に変換するための行列
            var viewToWorld = Camera.main.cameraToWorldMatrix;
            sheet.properties.SetMatrix("_ViewToWorld", viewToWorld);
            
            context.command.BlitFullscreenTriangle(context.source, context.destination, sheet, 0);
        }
    }
}
#endif

```
depthOrNormalを操作することで深度と法線が見られるようにしておきます。
また、シェーダ内でView空間の法線をWorld空間に変換したいので変換用Matrixもシェーダに渡しておきます
# シェーダコード
で、ここからが本番のシェーダコード。
勿体ぶってもアレなので、表示するためのコードを書いておきます。

```hlsl:DepthNormal.shader
Shader "ScreenPocket/PostProcess/DepthNormals"
{
    HLSLINCLUDE 
        #include "Packages/com.unity.postprocessing/PostProcessing/Shaders/StdLib.hlsl"

        TEXTURE2D_SAMPLER2D(_MainTex, sampler_MainTex);
        TEXTURE2D_SAMPLER2D(_CameraDepthNormalsTexture, sampler_CameraDepthNormalsTexture);
        float _Blend;
        float _DepthOrNormal;
        float4x4 _ViewToWorld;

        inline float DecodeFloatRG( float2 enc )
        {
            float2 kDecodeDot = float2(1.0, 1/255.0);
            return dot( enc, kDecodeDot );
        }

        float4 Frag(VaryingsDefault i) : SV_Target
        {
            float4 mainColor = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoord);
            float4 color = SAMPLE_TEXTURE2D(_CameraDepthNormalsTexture, sampler_CameraDepthNormalsTexture, i.texcoord);

            float3 normal;
            float depth;
            //DecodeDepthNormal(color, depth, normal);
            //↑本当ならこうしたいけど #include "UnityCG.cginc" しないといけないので↓で置き換える
            depth = DecodeFloatRG(color.zw);
            normal = DecodeViewNormalStereo(color);
            //View空間からWorld空間に変換
            normal = mul((float3x3)_ViewToWorld, normal);
            
            color = float4(lerp(float3(depth,depth,depth), normal, _DepthOrNormal),1);
            
            return lerp(mainColor, color, _Blend);
        }
    ENDHLSL
    
    SubShader
    {
        Cull Off ZWrite Off ZTest Always
        Pass
        {
            HLSLPROGRAM
                #pragma vertex VertDefault
                #pragma fragment Frag
            ENDHLSL
        }
    }
}
```
コメントにも記載した通り、#include "UnityCG.cginc" して、DecodeDepthNormal()するべきではあるのですが、PostProcessingStackV2のシェーダコードはHLSLであることと、結局必要なのはDecodeFloatRG()だけであることから、DecodeDepthNormal()を参考にDecodeFloatRG()をそのまま持ってきて解決しています。
また、前述した様に法線がView空間の物になっているので、行列乗算してWorld空間の法線に変換しています。
この変換処理を省くと、カメラが動くたびに法線の向きが変わってしまいます。気になる方はコメントアウトして確認してみると良いかと。

# 出力結果
Blend = 0　の場合　元画像
![スクリーンショット 2021-01-22 003125.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61266/41244341-a3e1-dd78-49fe-a1238245509a.png)
Blend = 1、DepthOrNormal = 0 の場合　深度を表示
![スクリーンショット 2021-01-22 003140.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61266/4886dbc0-cc90-1be3-9543-8a4ae4c3f5fc.png)
Blend=1、DepthOrNormal = 1 の場合　World法線を表示
![スクリーンショット 2021-01-22 003204.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61266/ecf7618d-50cd-d017-0c35-eb00e7bc0837.png)
という事で、深度と法線の表示が達成可能となります。

# 追記
Z軸を反転させたい場合は↓の記事などを参考に反転させるとよいかと
[【Unity】【シェーダ】ビュー空間の法線を_CameraDepthNormalsTextureから取得する - LIGHT11](https://light11.hatenadiary.com/entry/2020/02/03/210422)
# 参考資料
[Postprocessing with Normal Texture | Ronja's tutorials](https://www.ronja-tutorials.com/post/018-postprocessing-normal/)
[William Chyr | Unity Shaders – Depth and Normal Textures (Part 3)](https://williamchyr.com/unity-shaders-depth-and-normal-textures-part-3/)