【Unity】頂点カラーを使って綺麗にアウトラインを描画する
Abstract
普通の頂点シェーダで頂点の法線を使ってモデルの押し出し法をすると、モデルによっては綺麗にスケールされない
実際のモデルと同じメッシュで、頂点に対して方向を平均した法線を使えばこの問題は起きない
しかし、↑の手法ではMeshRendererとMeshFilterをランタイムでコピーする必要があり、スマートでない
モデルの頂点カラーに法線の情報を付与しておき、頂点シェーダでそれを変更することで、モデルの形状に依らない綺麗なアウトラインを描画できるようになった
結果
https://gyazo.com/c0791fa748ac34f6096182b0f97bf16a
モデルへの頂点カラーの付与
選択されたオブジェクトのメッシュを走査し、頂点の法線を取得し、頂点カラーに設定する
頂点の法線は長さ1の3次ベクトルだが、頂点カラーの範囲はRGB(0.0~1.0)であるので、以下の変換を行い正規化する
$ color_r = normal_x * 0.5 + 0.5
$ color_g = normal_y * 0.5 + 0.5
$ color_b = normal_z * 0.5 + 0.5
code: normal2vcolor.py
import bpy
from mathutils import Vector
current_obj = bpy.context.active_object
mesh = current_obj.data
if mesh.vertex_colors:
vcol_layer = mesh.vertex_colors.active
else:
vcol_layer = mesh.vertex_colors.new()
print("*"*40)
for loop_index, loop in enumerate(mesh.loops):
loop_vert_index = loop.vertex_index
color = (color * 0.5) + Vector((0.5,) * 3)
print("painting vert",loop_index, "to color ", color0, color1, color2) mesh.vertex_colors.active = vcol_layer
mesh.update()
bpy.ops.object.mode_set(mode='VERTEX_PAINT')
FBXでの書き出し
Y-up, Z-front座標系に直すことに注意
https://gyazo.com/1366e838b9a40eefedca04dbba9fd209
Unityでのシェーダー
以下に、アウトラインだけを表示するシンプルな頂点/フラグメントシェーダを示す
vert関数で、0~1の頂点カラーのコンポーネントを、元の値に戻している
$ normal_x = -(color_r-0.5)*2
$ normal_y = (color_g - 0.5) * 2
$ normal_z = (color_b - 0.5)*2
法線の座標系がxだけ直っていなかったので、xを補正した
code:shader
Shader "Custom/VertexColorOutline" {
Properties {
_OutlineWidth ("OutlineWith", Range(0.001, 1)) = 0.003
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
Pass {
// 頂点カラーを使ったアウトラインシェーダ
Cull Front
ZWrite On
CGPROGRAM
float _OutlineWidth;
float _UseVertexColor;
struct appdata {
float4 color: COLOR;
float4 vertex: POSITION;
float4 normal: NORMAL;
};
struct v2f {
float4 pos: SV_POSITION;
float4 color: COLOR;
};
v2f vert (appdata v) {
v2f o;
if (_UseVertexColor) {
float4 color = v.color;
// colorが0 ~ 1なので color = norm *.5 + .5されている
// これを-1 ~ 1になおす
color.xyz -= 0.5;
color.xyz *= 2;
// 書き出し時にUnityの座標系に直しているが、右手座標系から左手座標系になっていないのでxだけ補正する
color.x *= -1;
float4 normal = float4(normalize(color).xyz,0);
float4 add = normal * _OutlineWidth;
o.pos = UnityObjectToClipPos(v.vertex + add);
} else {
o.pos = UnityObjectToClipPos(v.vertex + normalize(v.normal) * _OutlineWidth);
}
o.color = v.color;
return o;
}
fixed4 frag (v2f i) : SV_Target {
return i.color;
}
ENDCG
}
}
}
やってみて
予想通りの結果になった
しかし、もともと適用したかったモデルではいい結果を得られなかった
凹みがある形状の場合、頂点の法線がきれいに外側を向かない
上記の例のようなシンプルで、平均法線の向きが理想的な場合のみ良い結果をえられるっぽい