PostProcessingStackV2のシェーダ内で、深度と法線を使いたい。
けどググっても日本語の情報があまり無かったのでメモとして記事化しておきます。
事前準備
Cameraの設定変更
カメラのdepthTextureModeをDepthNormalsにしておきます
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
を見てもらうとして、とりあえず深度と法線を表示する用として下記のスクリプトを追加
#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もシェーダに渡しておきます
シェーダコード
で、ここからが本番のシェーダコード。
勿体ぶってもアレなので、表示するためのコードを書いておきます。
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 の場合 元画像
Blend = 1、DepthOrNormal = 0 の場合 深度を表示
Blend=1、DepthOrNormal = 1 の場合 World法線を表示
という事で、深度と法線の表示が達成可能となります。
追記
Z軸を反転させたい場合は↓の記事などを参考に反転させるとよいかと
【Unity】【シェーダ】ビュー空間の法線を_CameraDepthNormalsTextureから取得する - LIGHT11
参考資料
Postprocessing with Normal Texture | Ronja's tutorials
William Chyr | Unity Shaders – Depth and Normal Textures (Part 3)