【Unity】【シェーダ】カメラから見た深度を描画する

カメラから見た時の深度を描画する方法です。

_CameraDepthTextureを使う方法

マニュアルによると、_CameraDepthTextureをシェーダ内で定義すればデプステクスチャが得られるようです。

docs.unity3d.com

シェーダは次のようにします。

Shader "ShowDepthTexture"
{
    SubShader
    {
        Cull Off
        ZTest Always
        ZWrite Off

        Tags { "RenderType"="Opaque" }

        Pass
        {
            CGPROGRAM
           #pragma vertex vert
           #pragma fragment frag
            
           #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };
            
            sampler2D _CameraDepthTexture;
            float4 _CameraDepthTexture_ST;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _CameraDepthTexture);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                return UNITY_SAMPLE_DEPTH(tex2D(_CameraDepthTexture, i.uv));
            }
            ENDCG
        }
    }
}

これをポストエフェクトとして使うので、スクリプトを下記のように書きます。

ただしここで、カメラがデプステクスチャを生成するためにCamera.depthTextureModeをDepthTextureMode.Depthとしておく必要があります。

using UnityEngine;

public class DepthTexture : MonoBehaviour {

    [SerializeField]
    private Shader _shader;
    private Material _material;

    void Start () {
        // たとえばライトのShadow TypeがNo Shadowsのときなどに
        // これが設定されていないとデプステクスチャが生成されない
        GetComponent<Camera>().depthTextureMode |= DepthTextureMode.Depth;

        _material = new Material(_shader);
    }

    private void OnRenderImage(RenderTexture source, RenderTexture dest)
    {
        Graphics.Blit(source, dest, _material);
    }
}

これを設定しなくても_CameraDepthTextureが取れる場合があるのですが、 下記のフォーラムによると、PCやコンソールのみのようなので、きちんと設定しておく必要があります。

https://forum.unity.com/threads/no-depthtexture-on-forward-rendering-when-shadows-disabled.519230/

Unity uses the depth texture for directional light shadow receiving on desktop and consoles, which is why it exists when there is a light source.

ディレクショナルシャドウが作る影にデプステクスチャを使用しているとのことです。

結果

これをカメラにアタッチして、

f:id:halya_11:20180508010240p:plain

このシーンを再生すると、

f:id:halya_11:20180508012844p:plain

深度が描画されました。

参考1: CommandBufferを使った実装

任意のタイミングのデプステクスチャを得たい場合はCommandBufferを使えばよさそうです。

light11.hatenadiary.com

using UnityEngine;
using UnityEngine.Rendering;

[RequireComponent(typeof(Camera))]
public class DepthTexture : MonoBehaviour
{

    [SerializeField]
    private Shader _shader;

    private void Awake()
    {
        Initialize();
    }

    private void Initialize()
    {

        var camera = GetComponent<Camera>();

        // たとえばライトのShadow TypeがNo Shadowsのときなどに
        // これが設定されていないとデプステクスチャが生成されない
        camera.depthTextureMode |= DepthTextureMode.Depth;

        if (camera.allowMSAA)
        {
            // MSAAがONになっていると正常に動作しない
            return;
        }
        var material = new Material(_shader);
        var commandBuffer = new CommandBuffer();
        int tempTextureIdentifier = Shader.PropertyToID("_PostEffectTempTexture");
        commandBuffer.GetTemporaryRT(tempTextureIdentifier, -1, -1);
        commandBuffer.Blit(BuiltinRenderTextureType.CurrentActive, tempTextureIdentifier);
        commandBuffer.Blit(tempTextureIdentifier, BuiltinRenderTextureType.CurrentActive, material);
        commandBuffer.ReleaseTemporaryRT(tempTextureIdentifier);
        camera.AddCommandBuffer(CameraEvent.AfterForwardOpaque, commandBuffer);
    }
}

参考2: より安価な実装

より負荷の小さい実装が下記で紹介されていましたので参考としてリンクしておきます。

appleorbit.hatenablog.com

なるほど。