C++幼女先輩

プログラミング成分多め

UNITYでDepthをリニアでEXR形式で出力

概要

Depthマップ欲しい事は多いと思います Depthの取得自体は簡単ですが、Depthの計算上 Z値とリニアで変化しないため感覚的にわかりにくいので リニアにしたい DepthをPNGファイルにすると 256段階に丸められてしまうため、浮動小数点形式で保存したいので EXR形式で保存したい Depthの取得は、別パスにて画面全体から取得する。UNITYではポストプロセスで行うのが楽

UNITYちゃん

今回は題材にUNITY Chanを使う 何を使ってもいいし、UNITYの3DMeshでもいい

ポストプロセス

デプスは画面全体を取得する必要があるので、ポストプロセスで取得する ポストプロセスを使うには カメラに OnRenderImage関数の入ったスクリプトをつける

using System.IO;
using UnityEngine;
using UnityEngine.Rendering;

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

    public Material mat;

    [ImageEffectOpaque]
    public void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        Graphics.Blit(source, dest, mat);
    }
}

これで、マテリアルに設定したシェーダーを使い ポストプロセスを実行すると

シェーダーでDepth取得

マテリアルに設定するシェーダーを作成する

まず、デプステクスチャを有効にするため、シェーダーの変数宣言のあたりに

UNITY_DECLARE_DEPTH_TEXTURE(_CameraDepthTexture);

を。これでデプステクスチャに _CameraDepthTexture でアクセスできる

Shader "test/CopyDepth" {
  SubShader {
    ZTest Always
    Cull Off
    ZWrite Off
    Fog{ Mode Off }
    Tags{ "RenderType" = "Opaque" }

    Pass {

      CGPROGRAM
      #pragma vertex vert
      #pragma fragment frag
      #include "UnityCG.cginc"
      #pragma only_renderers d3d9 d3d11 glcore gles gles3 metal xboxone ps4 
      #pragma target 3.0

      UNITY_DECLARE_DEPTH_TEXTURE(_CameraDepthTexture);
      
      struct v2f {
    float4 pos : SV_POSITION;
    float2 uv : TEXCOORD0;
      };

      v2f vert (appdata_img v) {
    v2f o = (v2f)0;
    o.pos = UnityObjectToClipPos(v.vertex);
    o.uv = MultiplyUV(UNITY_MATRIX_TEXTURE0, v.texcoord.xy);
    return o;
      }

      float frag(v2f i) : SV_TARGET{
        return SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);
      }
    ENDCG
    }

  }
}

上記シェーダーをポストプロセスに設定すると、Depthっぽいものが表示される (画面表示はこの段階では崩れているが) f:id:murasame-labo:20170805005638p:plain

ただしいDepthを取得

Depthテクスチャ用に RenderTextureを生成し、ポストエフェクト時にそこにコピーする

using System.IO;
using UnityEngine;
using UnityEngine.Rendering;

// deplicated
[ExecuteInEditMode]
[RequireComponent(typeof(Camera))]
public class SceneScript : MonoBehaviour {

    public Material mat;
    private RenderTexture _rtd;


    // Use this for initialization
    void Start() {
        GetComponent<Camera>().depthTextureMode |= DepthTextureMode.Depth;

        _rtd = new RenderTexture(2048, 2048, 32);
    }

    
    [ImageEffectOpaque]
    public void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
       Graphics.Blit(source, _rtd, mat);
    }
}

見てもわからないが、ポストエフェクトで画面に出力せず、 rtdへコピーされているので rtdの値を出力すればよい

EXRで保存

float形式で保存するために EXR形式を使う UNITYでは EXRへの変換は簡単にできる Aを押したらファイルに保存する

    void Update () {
        if (Input.GetKey(KeyCode.A)){
            RT2EXR(_rtd,  "rtd.exr");
        }
    }

    void RT2EXR(RenderTexture rt, string filename)
    {
        Texture2D tex = new Texture2D(rt.width, rt.height, TextureFormat.RGBAHalf, false);
        RenderTexture.active = rt;
        tex.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0);
        tex.Apply();

        byte[] bytes = tex.EncodeToEXR();
        Object.Destroy(tex);

        File.WriteAllBytes(filename, bytes);

    }

これで exrファイルとして出力できた f:id:murasame-labo:20170805010655p:plain

リニア化

上記のままでは、Depthの値はZとリニアではないので 直観的にわかりやすく リニアに変換する UNITYには便利な組み込み関数があるので それでリニアにできる

// Z buffer to linear 0..1 depth
inline float Linear01Depth( float z )
{
  return 1.0 / (_ZBufferParams.x * z + _ZBufferParams.y);
}

これでzバッファの値をリニアに変換できる _ZBufferParams 等はシェーダーのビルトイン変数といい、UNITYがConstantBufferに送っている

    // x = 1 or -1 (-1 if projection is flipped)
    // y = near plane
    // z = far plane
    // w = 1/far plane
    float4 _ProjectionParams;

    // x = width
    // y = height
    // z = 1 + 1.0/width
    // w = 1 + 1.0/height
    float4 _ScreenParams;

    // Values used to linearize the Z buffer (http://www.humus.name/temp/Linearize%20depth.txt)
    // x = 1-far/near
    // y = far/near
    // z = x/far
    // w = y/far
    // or in case of a reversed depth buffer (UNITY_REVERSED_Z is 1)
    // x = -1+far/near
    // y = 1
    // z = x/far
    // w = 1/far
    float4 _ZBufferParams;

    // x = orthographic camera's width
    // y = orthographic camera's height
    // z = unused
    // w = 1.0 if camera is ortho, 0.0 if perspective
    float4 unity_OrthoParams;

具体的には上記のような、near far等の値が入っている

シェーダーを少し変更する

float frag(v2f i) : SV_TARGET{
  return Linear01Depth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv).x ) ;
}

これでリニアになったはずである(多分)

f:id:murasame-labo:20170805011402p:plain

まとめ

using System.IO;
using UnityEngine;
using UnityEngine.Rendering;

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

    public Material mat;
    private RenderTexture _rtd;


    // Use this for initialization
    void Start() {
        GetComponent<Camera>().depthTextureMode |= DepthTextureMode.Depth;

        _rtd = new RenderTexture(2048, 2048, 32);
    }

    
    [ImageEffectOpaque]
    public void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        Graphics.Blit(source, _rtd, mat);
    }

    // Update is called once per frame
    void Update () {
        if (Input.GetKey(KeyCode.A)){
            RT2EXR(_rtd,  "rtd.exr");
        }
    }

    void RT2EXR(RenderTexture rt, string filename)
    {
        Texture2D tex = new Texture2D(rt.width, rt.height, TextureFormat.RGBAHalf, false);
        RenderTexture.active = rt;
        tex.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0);
        tex.Apply();

        byte[] bytes = tex.EncodeToEXR();
        Object.Destroy(tex);

        File.WriteAllBytes(filename, bytes);
    }
}
Shader "test/CopyDepth" {
  SubShader {
    ZTest Always
    Cull Off
    ZWrite Off
    Fog{ Mode Off }
    Tags{ "RenderType" = "Opaque" }

    Pass {
      CGPROGRAM
      #pragma vertex vert
      #pragma fragment frag
      #include "UnityCG.cginc"
      #pragma only_renderers d3d9 d3d11 glcore gles gles3 metal xboxone ps4 
      #pragma target 3.0

      UNITY_DECLARE_DEPTH_TEXTURE(_CameraDepthTexture);

      struct v2f {
    float4 pos : SV_POSITION;
    float2 uv : TEXCOORD0;
      };

      v2f vert (appdata_img v) {
    v2f o = (v2f)0;
    o.pos = UnityObjectToClipPos(v.vertex);
    o.uv = MultiplyUV(UNITY_MATRIX_TEXTURE0, v.texcoord.xy);
    return o;
      }

      float frag(v2f i) : SV_TARGET{
        return Linear01Depth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv).x ) ;
      }
      ENDCG
    }
  }
}