チリペヂィア

リンクフリー。サンプルコードなどは関連記事内でライセンスについて明示されない限り商用利用なども自由に行って構いませんが、自己責任でお願いします。またこれら日記内容の著作権自体は放棄していません。引用部分については引用元の権利に従ってください。

カスタムエディタのEditorクラスでRenderTexture

なんでインスペクタにDrawLineメソッドないんじゃー!!
Handles.DrawLineでよかった。とりあえずRenderTargetのサンプルとして読んでください(目逸らし)

そう言えばUnity5になってから無料版でもRenderTargetが解禁されています。

って事はEditorのレンダリングで使っても良いと。

というわけでプレビューのところに対角線を一本引いてみようと思います。*1

こんなクラスがあるとして

using UnityEngine;

public class DrawLineTestComponent : MonoBehaviour {

}


そこで、こういうエディタを定義してプレビュー機能を盛り込んでいきます。

[CustomEditor(typeof(DrawLineTestComponent))]
public class DrawLineTestEditor : Editor {

    public override bool HasPreviewGUI()
    {
        return true;
    }

    public override GUIContent GetPreviewTitle()
    {
        return new GUIContent("DrawLineTest");
    }

    public override void OnPreviewGUI(Rect r, GUIStyle background)
    {
        /// ...
    }
}


まずシェーダを用意します。GL.Lineで色を付けて描画するにはシェーダとマテリアルをセットする必要があります。で、それは洋モノの開発ネット上で散見されるお決まりのパターンがあるのでそのまま持ってきます*2

    static Material mat;

    void OnEnable()
    {
        if (mat == null)
        {
            // シェーダーの名前は適当に冗長化した方が良いかも。
            mat = new Material("Shader \"Lines/Colored Blended\" {" +
              "SubShader { Pass { " +
              "    Blend SrcAlpha OneMinusSrcAlpha " +
              "    ZWrite Off Cull Off Fog { Mode Off } " +
              "    BindChannels {" +
              "      Bind \"vertex\", vertex Bind \"color\", color }" +
              "} } }");
            mat.hideFlags = HideFlags.HideAndDontSave;
            mat.shader.hideFlags = HideFlags.HideAndDontSave;
        }
    }


いよいよRenderTextureを生成して描画します。一気にいきます。

    RenderTexture previewImage;

    // OnDisableで削除
    void OnDisable()
    {
        if (previewImage != null) previewImage.Release();
    }

    public override void OnPreviewGUI(Rect r, GUIStyle background)
    {
        int w = (int)r.width;
        int h = (int)r.height;
        
        if (previewImage == null)
        {
            previewImage = new RenderTexture(w, h, 0, RenderTextureFormat.ARGB32);
            previewImage.hideFlags = HideFlags.HideAndDontSave;
            previewImage.Create();
        }
        else if ((previewImage.width != w) || (previewImage.height != h))
        {
            previewImage.Release(); // サイズ変更する時は事前にRelease()
            previewImage.width = w;
            previewImage.height = h;
            previewImage.Create();
        }

        if (previewImage.IsCreated() == false) return; // ???

        RenderBuffer regColorBuffer = Graphics.activeColorBuffer;
        RenderBuffer regDepthBuffer = Graphics.activeDepthBuffer;

        Graphics.SetRenderTarget(previewImage);

        GL.Clear(false, true, Color.clear);
        mat.SetPass(0); // マテリアル設定
        GL.PushMatrix();
        GL.LoadPixelMatrix(0, w, h, 0); // 画面座標っぽく座標指定できて便利
        GL.Begin(GL.LINES);
        GL.Color(Color.white);
        GL.Vertex(new Vector3(0, 0)); // 画面左上から
        GL.Vertex(new Vector3(w, h)); // 右下まで対角線いっぱいに直線描画
        GL.End();
        GL.PopMatrix();

        Graphics.SetRenderTarget(regColorBuffer, regDepthBuffer);
        GUI.DrawTexture(r, previewImage);
    }


見たまんまなのであんまり説明は要らないかなぁ…

SetRenderTarget()でターゲット変更する前に、Graphics.activeColorBuffer/DepthBufferで元の描画先を保存しておいて後で戻すのが推奨かな。それとGUI的な用途であればGL.LoadPixelMatrix()が超便利なのでオススメ。

結果

んー。まぁ描画品質は若干お察し。本気でやるなら余白ありの透過テクスチャ使って、余白分太めにポリゴン描画して、テクスチャサンプリングでアンチエイリアシングするとかそんな感じになると思うんだけどそこまでするかどうかは微妙かなぁ。

*1:実はまず最初、こちら(Unity の Editor 拡張でインスペクタにグラフを描画する方法を色々調べてみた - 凹みTips)のサイトを参考にDrawRect()をGUIUtility.RotateAroundPivot()する方法を試したんですが、うちの環境で対角線を引こうとすると、なぜか半分くらいの長さになってしまうケースが発生。いろいろ試してみたところ縦長ウィンドウ表示にすると半分程度まで縮んでしまい、逆に横長ウィンドウに潰すとだいたいちゃんと表示できる:でもちょっと長さが足りない、という感じだったので、回転する前のインスペクタ描画に渡される矩形の幅で勝手にクリッピングされてしまうんじゃないかと推測。試しに矩形幅で描画したらほぼ同じ描画結果になったんよね…DrawTextureなら良かったんかねぇ…→6/20追記:回転前の矩形から逆算してクリッピングに引っかからないように分割描画してみたらうまくいった。インスペクタの狭い部分でGUIUtility.RotateAroundPivot()する作戦は今となってはアレかもしれない。やっぱHandle使うべきか…

*2:初出誰やねん状態