モデル拡大法
概念篇で書いた方法:
モデルの点を法線の方向に沿って移動し、マテリアルは単色にして、背面描画すること。
これを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
}
問題点と解決方法
概念篇でも書いたようにキューブの場合は問題がある。その理由は:
赤い頂点は複数の三角と関わり、それぞれの法線は違う。だから、法線の方向に沿って移動したら、三角たちが離れてしまう。では、同じ法線にすればいい:
こうして、全ての頂点の法線を変えて、移動する時は問題が起こらない。
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