5
4

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-01-21

210122.png
PostProcessingStackV2のシェーダ内で、深度と法線を使いたい。
けどググっても日本語の情報があまり無かったのでメモとして記事化しておきます。

事前準備

Cameraの設定変更

カメラのdepthTextureModeをDepthNormalsにしておきます

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
を見てもらうとして、とりあえず深度と法線を表示する用として下記のスクリプトを追加

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もシェーダに渡しておきます

シェーダコード

で、ここからが本番のシェーダコード。
勿体ぶってもアレなので、表示するためのコードを書いておきます。

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
Blend = 1、DepthOrNormal = 0 の場合 深度を表示
スクリーンショット 2021-01-22 003140.png
Blend=1、DepthOrNormal = 1 の場合 World法線を表示
スクリーンショット 2021-01-22 003204.png
という事で、深度と法線の表示が達成可能となります。

追記

Z軸を反転させたい場合は↓の記事などを参考に反転させるとよいかと
【Unity】【シェーダ】ビュー空間の法線を_CameraDepthNormalsTextureから取得する - LIGHT11

参考資料

Postprocessing with Normal Texture | Ronja's tutorials
William Chyr | Unity Shaders – Depth and Normal Textures (Part 3)

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4