読者です 読者をやめる 読者になる 読者になる

自習室

こもります

UnityでありもののMeshをワイヤフレームで描画する

.Unity .C# .Shader

はじめに

Unityには、glBegin(GL_LINE_LOOP)的な、直接ラインを引く手法はありません。メッシュを食わせて、それらしいShaderでラインっぽい描画にする必要がああります。

以前の記事 UnityでAudio Responsive なVisualizerをつくる - 自習室 では、メッシュのすべての辺を細長い長方形として扱って、GLを直接叩いて描画する力業な対処を行っていました。

今回は別の手法として、MeshTopology.Lines を使う手法についてまとめます。

ソースコードは一部 @_kzr 先生の keijiro/unity-emgen at test · GitHub 中の物を参考にさせていただいております。

f:id:AMANE:20140225135140p:plain

メッシュをライン描画用に MeshTopology.Lines の形式に組み替える

Meshはありものを使うことにします。デフォルトで用意されている、Cube, Capsule, Cylinder, Plane, Sphere をいつもやるように MeshFilter コンポーネントに食わせて、いつもやるようにMeshRenderer で描画すれば良いようにします。

MeshTopology

デフォルトのMeshは MeshTopology.Triangles で作られています。以下のコードで、使われているメッシュのトポロジーを調べることが出来ます。

// MeshFilterには、CubeやCylinderなどのMeshを与えておく
MeshFilter meshFilter = GetComponent<MeshFilter>();
MeshTopology topo = meshFilter.Mesh.GetTopology(0);
Debug.Log(topo);  //"triangles" と出力される

MeshTopologyについてはこちら

https://docs.unity3d.com/Documentation/ScriptReference/MeshTopology.html

これは、OpenGL で言うところの glBegin(GL_TRIANGLES) などで指定する図形のタイプの事で、おなじみ床井先生のサイトGLUTによる「手抜き」OpenGL入門で言うと、

  • GL_TRIANGLES
  • GL_QUADS
  • GL_LINES
  • GL_LINE_STRIP
  • GL_POINTS

が選べる、という事を意味しています。

Cubeの場合で検証

f:id:AMANE:20140225003155p:plain

monodevelopデバッグして、Mesh.vertices の中身を見てみると、Cubeの場合、上の図の順番で頂点が定義されています。

f:id:AMANE:20140225003339p:plain

さらに Mesh.triangles の中身を見てみると、{0, 3, 1, 0, 2, 3 ...} となっているのですが、3つずつで三角形を作っています。この図で言うと、

  • Mesh.vertices[0]
  • Mesh.vertices[3]
  • Mesh.vertices[1]

の3点で一個目のポリゴンを形成している、と言うことです。この3頂点は外から見たとき時計回りの順で指定されているので、カリングしても描画されます。

Mesh.SetIndices()

頂点 mesh.vertices はそのまま利用します。

ポリゴン mesh.triangles は、三角形を作るべく3つずつ頂点のインデックスを格納している配列です。トポロジーMeshTopology.Linesにしたメッシュでは、このインデックス配列に2つの頂点で1つの組(線分)となるようにセットしていきます。

mesh.trianglesから順番にインデックスを取り出して、ライン用のインデックス配列を作り直しているコードがこちらです

public int[] MakeIndices()
{
    int[] indices = new int[2 * triangles.Length];
    int i = 0;
    for( int t = 0; t < triangles.Length; t+=3 )
    {
        indices[i++] = triangles[t];        //start
        indices[i++] = triangles[t + 1];   //end
        indices[i++] = triangles[t + 1];   //start
        indices[i++] = triangles[t + 2];   //end
        indices[i++] = triangles[t + 2];   //start
        indices[i++] = triangles[t];        //end
    }
    return indices;
}

こうして作成したインデックス配列を、もう一度meshにセットしてあげれば、トポロジーがLinesになったメッシュの完成です。

triangles = meshFilter.mesh.triangles;
meshFilter.mesh.SetIndices(MakeIndices(), MeshTopology.Lines, 0);

シェーダ

MeshTopology.Lines で正しく組まれたMeshは、シェーダで2頂点間に一直線に引かれるフラグメントを作ります。従って、シェーダは非常にシンプルな物になります。

Shader "Custom/MakeLine" {
    Properties {
        _Color ("Color", Color) = (1, 1, 1, 1)
    }
    SubShader {
        Tags { "RenderType"="Transparent" }
        Blend SrcAlpha One
        Pass {
            CGPROGRAM
            
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            
            struct v2f {
                float4 pos : SV_POSITION;
            };
            
            float4 _Color;
            
            v2f vert(appdata_base v)
            {
                v2f o;
                o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
                return o;
            }
            
            half4 frag (v2f i) : COLOR
            {
                return _Color;
            }
            ENDCG
        }
    } 
    FallBack Off
}

Tags{}Blend を正しく指定すると、ラインですが透過も可能です。ここでは重ねていくとサチって発光しているような表現になるBlendを施してみました。

また今回の手法でも、ちゃんと ProjectSetting > Quality > Anti Aliasing することで、アンチエイリアスがかかります。

最後に

サンプルを動かすプロジェクトをGitHubに上げました。

izmhr/DrawLineFromMesh · GitHub

今回の手法だと、カメラからの距離にかかわらずすべての辺が1pxで描かれることになり、遠くなると線が密集してつぶれてしまいます。またもちろん、ライティングの効果は反映されません。これへの対処としては、一番はじめにも挙げた UnityでAudio Responsive なVisualizerをつくる - 自習室 も良いと思います。