[Unity] アウトライン Outline - 詳細篇 モデル拡大法


モデル拡大法

概念篇で書いた方法:
モデルの点を法線の方向に沿って移動し、マテリアルは単色にして、背面描画すること。

これをShaderで実現するには:(Outline部分Only)

Pass{
    //背面描画する
    Cull Front

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

    float4 _OutlineColor;
    float _Outline;

    struct Input
    {
        float4 vertex : POSITION;
        float3 normal : NORMAL;
    };

    struct Output
    {
        float4 pos : SV_POSITION;
        fixed4 color : TEXCOORD0;
    };

    fixed4 frag(Output o) : SV_Target
    {
        return o.color;
    }

    Output vert(Input i)
    {
        Output o;
        //モデルの点を法線の方向に沿って移動する
        i.vertex.xyz += normalize(i.normal) * _Outline;
        o.pos = UnityObjectToClipPos(i.vertex);
        //単色にする
        o.color = _OutlineColor;
        return o;
    }

    ENDCG
}

以上のShaderを使った物:

問題点と解決方法

概念篇でも書いたようにキューブの場合は問題がある。その理由は:

赤い頂点は複数の三角と関わり、それぞれの法線は違う。だから、法線の方向に沿って移動したら、三角たちが離れてしまう。では、同じ法線にすればいい:

こうして、全ての頂点の法線を変えて、移動する時は問題が起こらない。

 public static void MeshNormalAverage(Mesh mesh)
    {
        Dictionary<Vector3, List<int>> map = new Dictionary<Vector3, List<int>>();

        #region build the map of vertex and triangles' relation
        for (int v = 0; v < mesh.vertexCount; ++v)
        {
            if (!map.ContainsKey(mesh.vertices[v]))
            {
                map.Add(mesh.vertices[v], new List<int>());
            }

            map[mesh.vertices[v]].Add(v);
        }
        #endregion

        Vector3[] normals = mesh.normals;
        Vector3 normal;

        #region the same vertex use the same normal(average)
        foreach (var p in map)
        {
            normal = Vector3.zero;

            foreach (var n in p.Value)
            {
                normal += mesh.normals[n];
            }

            normal /= p.Value.Count;

            foreach (var n in p.Value)
            {
                normals[n] = normal;
            }
        }
        #endregion

        mesh.normals = normals;
    }

でも法線を変えるとライティングも変わる。だから、Outlineのモデルだけ法線を変え、元のモデルは変わらない。Outlineのためにモデルをクローンする:
Copy components at runtime
クローンするのはMeshRendererとMeshFilter、或いはSkinnedMeshRenderer。Meshをクローンする時ちょっと注意:

Mesh tmpMesh = (Mesh)Instantiate(GetComponent<MeshFilter>().sharedMesh);
outlineObj.GetComponent<MeshFilter>().sharedMesh = tmpMesh;

成果


基本は問題ないが、深度にはやはりまだ問題がある:

実際のキャラクターも試そう:

左はUnityエディターのOutline、右は自分のもの。左の方が正しいと思うが、右の方も悪くない。

深度に関してはまだ研究する必要がある。とりあえず、ここまでの結果:
Github:Unity Outline