Unity
Shader

UnityでGeometryShaderを使ってワイヤーフレーム表現

Unityでモデルをワイヤーフレームで表示しようとした際に、
元のモデルを表示しながらワイヤーフレームでも描画する方法が見つからなかった時に作った物です

FireFrame1.png

以下コード

Shader "Custom/Wireframe" {
    Properties{
        _WireFrameColor("WireFrame Color", Color) = (0,0,0,0)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
    }
    SubShader{
        Tags{ "Queue" = "Transparent" }

        // Pass1
        Pass{
            ZWrite On
            Blend SrcAlpha OneMinusSrcAlpha

            CGPROGRAM

            #pragma vertex vert
            #pragma geometry geom
            #pragma fragment frag

            #include "UnityCG.cginc"
            #pragma target 5.0

            sampler2D _MainTex;
            float4 _WireFrameColor;

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

            struct v2g
            {
                float4 vertex : SV_POSITION;
                float2 uv : TEXCOORD0;
                float4 color : TEXCOORD1;
            };

            struct g2f
            {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
                float4 color : TEXCOORD1;
            };

            v2g vert(appdata v)
            {
                v2g o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                o.color = _WireFrameColor;

                return o;
            }

            [maxvertexcount(6)]
            void geom(triangle v2g input[3], inout LineStream<g2f> outStream)
            {
                v2g p0 = input[0];
                v2g p1 = input[1];
                v2g p2 = input[2];


                g2f out0;
                out0.pos = p0.vertex;
                out0.uv = p0.uv;
                out0.color = p0.color;

                g2f out1;
                out1.pos = p1.vertex;
                out1.uv = p1.uv;
                out1.color = p1.color;

                g2f out2;
                out2.pos = p2.vertex;
                out2.uv = p2.uv;
                out2.color = p2.color;

                outStream.Append(out0);
                outStream.Append(out1);
                outStream.RestartStrip();

                outStream.Append(out1);
                outStream.Append(out2);
                outStream.RestartStrip();

                outStream.Append(out2);
                outStream.Append(out0);
                outStream.RestartStrip();
            }

            fixed4 frag(g2f i) : SV_Target
            {
                return i.color;
            }
            ENDCG
        }

        // Pass2
        Cull Back
        CGPROGRAM
        #pragma surface surf Lambert

        sampler2D _MainTex;

        struct Input 
        {
            float2 uv_MainTex;
        };

        void surf(Input IN, inout SurfaceOutput o)
        {
            o.Albedo = tex2D(_MainTex, IN.uv_MainTex).rgb;
        }
        ENDCG
    }
}

Geometryの中身について

void geom(triangle v2g input[3], inout LineStream<g2f> outStream)でgeometory用の関数を宣言しています

入力

triangle v2g input[3]
ポリゴンを形成する三角形の頂点をInput[3]の仮引数で取得
ここでのInput[]はv2g構造体の情報を持っています

出力

inout LineStream<g2f> outStream
LineStream<g2f>と記述することにより、ストリーム情報から二つの頂点を結ぶ直線を生成してくれます
ほかにも三角形を生成するTriangleStream<>やPointStream<>などがあります

geometory内の処理
g2f out0;
out0.pos = p0.vertex;
out0.uv = p0.uv;
out0.color = p0.color;

g2f out1;
out1.pos = p1.vertex;
out1.uv = p1.uv;
out1.color = p1.color;

g2f out2;
out2.pos = p2.vertex;
out2.uv = p2.uv;
out2.color = p2.color;

上記は出力がg2f構造体になるため、入力をg2fに変換しています
次に

outStream.Append(out0);
outStream.Append(out1);
outStream.RestartStrip();

outStream.Append(out1);
outStream.Append(out2);
outStream.RestartStrip();

outStream.Append(out2);
outStream.Append(out0);
outStream.RestartStrip();

outStream.Append()に出力用の頂点を渡すことによって現在のストリームに頂点を追加することができます
今回は一つのストリームに二つの頂点を追加します
二つ頂点を追加したらoutStream.RestartStrip()で現在のストリームを終了して新しいストリームを開始することができます

実は今回の場合は上の記述を

outStream.Append(out0);
outStream.Append(out1);
outStream.Append(out2);

この三行に置き換えても、出力をLineStreamにしているので勝手に三つの頂点をそれぞれ結んだ直線を生成してくれます

最後に、作ったShaderをオブジェクトのマテリアルに取り付けます

完成

WireFrame.png
こんな感じで表示されれば完成
マテリアルの_WireFrameColorでワイヤーフレームの色を変えることができます
ワイヤーフレームだけの表示にしたい場合は

// Pass2
Cull Back
CGPROGRAM
#pragma surface surf Lambert

sampler2D _MainTex;

struct Input 
{
    float2 uv_MainTex;
};

void surf(Input IN, inout SurfaceOutput o)
{
    o.Albedo = tex2D(_MainTex, IN.uv_MainTex).rgb;
}
ENDCG

コード内の上記の部分を削除するとワイヤーフレームだけになると思います

曲に合わせて頂点の高さや色を変えれば
こんな感じになったり


注意

GeometryShaderを使っている関係で一部端末で動かないかもしれません

おわりに

Shaderは工夫一つで面白い表現ができるので楽しいですよね!
普段何気なく見ていたワイヤーフレームの表現も自分で実際に作ってみると意外と難しくて驚きました。

あと、記事内の説明で間違っている部分があるかもしれません、その場合はコメントを頂けるとありがたいです。