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っぽいものが表示される
(画面表示はこの段階では崩れているが)

ただしい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ファイルとして出力できた

リニア化
上記のままでは、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 ) ;
}
これでリニアになったはずである(多分)

まとめ
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
}
}
}