神様は有給消化中です。

技術ネタを書こうかな。時事ネタとか私生活ネタはうけないし。

【Unity】無駄なドローコールなしで深度バッファを取得する方法

UnityでDepth Bufferを使用する方法として公式に紹介されているのは、Camera.depthTextureModeを使用する方法です。
しかし、Camera.depthTextureModeを使用すると、UpdateDepthTextureなるレンダリングパスが増えてしまいます。

f:id:appleorbit:20171103050838p:plain

これは公式ページにも記載されている通り、Unityの仕様のようです。
https://docs.unity3d.com/jp/540/Manual/SL-CameraDepthTexture.html

デプステクスチャは、シャドウキャスターのレンダリングに使用するのと同じシェーダー内パスを使用してレンダリングされます(“ShadowCaster” pass type)。


Unity内部で、Shader Replaceを使ってDepth Bufferを書くためだけにドローコールを発行しているためにこの挙動となっています。
理由は不明ですが、おそらくOpenGL2.0世代のモバイル端末はNative Depth Textureをサポートしていない端末があったので、それらに対応するためなのかなと。
ハイエンド系ならMRTで自前で深度バッファ書くだろ的な。

Unityの仕様はどうあれ、Depth Bufferのためにドローコールを発行するとなると描画負荷に無視できないインパクトが出てきます。
そこで、パスを増やさずにDepth Bufferを取得する方法を模索しました。

Depth Bufferを取得する

結論から言うと、Camera.SetTargetBuffersを使用します。

private Camera m_Camera;
private RenderTexture m_ColorBuffer;
private RenderTexture m_DepthBuffer;

private void Start()
{
  m_Camera = GetComponent<Camera> ();

  // |私は深度バッファを取得するためにドローコールを発行しました><|
  //m_Camera.depthTextureMode = DepthTextureMode.Depth;

  // カラーバッファを生成
  m_ColorBuffer = new RenderTexture (Screen.width, Screen.height, 0);
  m_ColorBuffer.Create ();

  // 深度バッファを生成
  m_DepthBuffer = new RenderTexture (Screen.width, Screen.height, 24, RenderTextureFormat.Depth);
  m_DepthBuffer.Create ();

  m_Camera.SetTargetBuffers (m_ColorBuffer.colorBuffer, m_DepthBuffer.depthBuffer);
}


このままだとShaderから使用できないので、CommandBufferを使って任意のタイミングでセットします。
加えて、カメラの描画先がカラーバッファになってしまっているので、Back buffer(画面)に書き戻します。

private void AddCommand()
{
  // 深度バッファをセットするコマンド
  {
    CommandBuffer command = new CommandBuffer ();
    command.name = "Set depth texture";

    command.SetGlobalTexture ("_DepthTexture", m_DepthBuffer);

    m_Camera.AddCommandBuffer (CameraEvent.BeforeImageEffects, command);
  }

  // カラーバッファをバックバッファ(画面)に描きこむコマンド
  {
    CommandBuffer command = new CommandBuffer ();
    command.name = "blit to Back buffer";

    // (注)
    // カメラの書き込み先がRenderTextureなのに、CameraEvent.AfterEverythingのタイミングで
    // CameraTargetがback bufferを示すのは正しいのだろうか・・・
    // 確認バージョン:Unity5.6.1f1
    command.SetRenderTarget (BuiltinRenderTextureType.CameraTarget);
    command.Blit (m_ColorBuffer, BuiltinRenderTextureType.CurrentActive);

    m_Camera.AddCommandBuffer (CameraEvent.AfterEverything, command);
  }
}

これで、_DepthTextureにShaderからアクセスすることが可能になりました。
こんな感じのShaderで、画面に深度バッファが表示できます。

Shader "Test/BlitDepth"
{
  Properties
  {
    _MainTex ("Texture", 2D) = "white" {}
  }
  SubShader
  {
    Cull Off ZWrite Off ZTest Always

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

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

      struct v2f
      {
        float2 uv : TEXCOORD0;
        float2 uv_depth : TEXCOORD1;
        float4 vertex : SV_POSITION;
      };

      v2f vert (appdata v)
      {
        v2f o;
        o.vertex = UnityObjectToClipPos(v.vertex);
        o.uv = v.uv;
        o.uv_depth = v.uv;
        return o;
      }
      
      sampler2D _MainTex;
      sampler2D _DepthTexture;

      fixed4 frag (v2f i) : SV_Target
      {
        half rawDepth = SAMPLE_DEPTH_TEXTURE(_DepthTexture, i.uv_depth);
        half depth = Linear01Depth(rawDepth);
        return fixed4(rawDepth, rawDepth, rawDepth, 1);
      }
      ENDCG
    }
  }
}

UpdateDepthTextureパスなしでDepth bufferを取得することができました。
カラーバッファを画面に書き戻すコストは増えていますが、ドローコールを重複して発行するコストに比べれば安いとおもいます。
f:id:appleorbit:20171103055104p:plain

【Unity】【uGUI】リストビューに、セルが画面外から差し込まれるアニメーションをつける

uGUIで作ったリストビューに、セルが画面外から差し込まれるようなアニメーションを追加する方法を共有。
最終的にこんな感じになります。
f:id:appleorbit:20160402223716g:plain

続きを読む

【Unity】カメラからどれだけ離れても、最低1ピクセルは表示されるように保障する

通常3Dオブジェクトがカメラから離れすぎると、レンダリングサイズが1ピクセルを下回った時点で画面に描画されなくなり、カメラとの位置関係によって描画が切れたり出たりしてチカチカと表示されることがあります。今回はこの問題の解決方法(レンダリングの最低ピクセル保障)を共有します。

続きを読む

【Unity】マテリアルの描画がシェーダーのQueue順に処理されない原因と対策

自作シェーダーなどを使用してレンダリングを行う場合、レンダリング順序をTagsのQueueで指定しますが、特定の条件でこのレンダリング順序が無視される現象が発生したので共有。

簡潔にまとめだけ

まとめると
現象  :シェーダーで指定したレンダリングキューが無視されることがある
原因  :MaterialのCustom Render Queueに不正値が入っているため
発生条件:自作シェーダーを使用 & 自作シェーダー設定前に、他のシェーダーを割り当てていた場合
対策  :Custom Render Queueに-1を設定すればOK

続きを読む

【Unity】Graphicを継承したクラスでMissingReferenceExceptionが発生する

先日、Graphicを継承したクラスで何故かMissingReferenceExceptionが発生することがあったので共有。
結論から言うと、OnDisableをオーバーライドすると発生する様子。

テスト用にこんなクラスをすると、コンパイル時にMissingReferenceExceptionが発生します。

public class HogeUI : Graphic {
    protected override void OnDisable ()
    {
        /* なんらかの処理 */
    }
}

Exceptionの内容はこんな感じ。HogeUIが破棄されたのにアクセスしていますと。

MissingReferenceException: The object of type 'HogeUI' has been destroyed but you are still trying to access it.
Your script should either check if it is null or you should not destroy the object.
UnityEngine.UI.Graphic.OnRebuildRequested () (at /Users/builduser/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/UI/Core/Graphic.cs:396)
UnityEngine.UI.GraphicRebuildTracker.OnRebuildRequested () (at /Users/builduser/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/UI/Core/GraphicRebuildTracker.cs:33)
UnityEngine.CanvasRenderer.RequestRefresh ()


おそらくGraphicクラスのOnDisableで何かしら必要な処理を行っていて、オーバーライドによって走らなくなったために起こっているのかと。

対応は以下のようにしました。

public class HogeUI : Graphic {
    protected override void OnDisable ()
    {
        base.OnDisable(); // 追加

        /* なんらかの処理 */
    }
}


Graphicクラスの関数をオーバーライドする場合は、基本的に親クラスの処理を呼び出したほうがいいのかもしれません。

【Unity】AssetBundle化したPrefabのシェーダー参照が壊れる現象

先日、AssetBundle化したPrefabのシェーダー参照が壊れる現象に遭遇したため解決策を共有。状況としてはこんな感じ。
・自作のUGUIオブジェクトにカスタムシェーダーを適用してPrefab化
・上記Prefabをシーンに追加して実行→正常動作
・上記PrefabをAssetBundleから読みこんで実行→表示が壊れる

FrameDebuggerで確認すると、シェーダー名称は適用したカスタムシェーダーの物になっているが、レンダーステートが明らかにおかしい・・・。

Google先生に聞くと、以下の記事を発見。
NGUIオブジェクトをAssetBundle化すると、シェーダー参照が壊れることがあるらしい。blog.livedoor.jp

古い情報だしNGUIだし、関係ないだろーと思いながらランタイムでシェーダーを再設定するとちゃんと表示できた。何ぞこれ。

private void Awake(){
   Material material = GetComponentInChildren<Material>();
   if(material != null){
      Shader s = Shader.Find("Custom/Hoge");
      if(s){
         material.shader = s;
      }
   }
}

ちなみに、上記のコードを実行した場合、UnitEditorと実機で適用されるシェーダーが変わる。
UnityEditor:/Assets以下に入っているカスタムシェーダー
実機:AssetBundleに入っているカスタムシェーダー
これはShader.Findの検索順に依存していると思われます。

しかし、Shader.Findでシェーダーが見つかるのに参照が壊れる理由が本当に不明。
シェーダーコンパイルやらの関係・・・?うーむ。
何か情報をもっている方がおられたら、コメント欄で教えて下さい。

【Unity】EventSystemsから受け取った座標をRectTransform.localPositionに設定する方法

uGUIのEventSystems(IBeginDragHandler, IDragHandler, IEndDragHandlerなど)から受け取った座標をそのままRectTransform.localPositionに設定すると、Canvasのscaleによってはぶっ飛んだ位置(描画領域外)に移動してしまうことがあります。
EventSystemsから渡される座標はスクリーン座標のため、scaleが影響してぶっ飛んでしまうわけですが、今回はこのぶっ飛び現象を回避するためにスクリーン座標→ローカル座標へ変換する方法を共有します。

続きを読む