この記事では2Dでのアウトラインの実装方法を紹介します。
目次
2Dアウトラインシェーダーの実装方法
素材データ
今回使用する素材データはこちらです。
このデータは「ちびコマドット絵作成機」で作成しました。
シェーダーの作成
画像をスプライトとして登録し、以下のシェーダーコードを適用します。
shader_type canvas_item;
// アウトラインの色
uniform vec4 outline_color : hint_color;
void fragment() {
// スプライトの色を取得
vec4 sprite_color = texture(TEXTURE, UV);
// アルファ値を4倍した値で引く
float alpha = -4.0 * sprite_color.a;
// 4方向の平均を足し込む
alpha += texture(TEXTURE, UV + vec2(0.1, 0.0)).a;
alpha += texture(TEXTURE, UV + vec2(-0.1, 0.0)).a;
alpha += texture(TEXTURE, UV + vec2(0.0, 0.1)).a;
alpha += texture(TEXTURE, UV + vec2(0.0, -0.1)).a;
// アウトラインの色とアルファ値を合成
COLOR = vec4(outline_color.rgb, alpha);
}
アウトラインの色は、シェーダを適用したスプライトをクリックし、インスペクターの「CanvasItem > Material > Shader Param > Outline Color」から色を設定します。
ここでは赤色としました。
シェーダーを適用すると以下のようにスプライトの見た目が変化します。
なぜこうなるのかを説明します。
重要となるのは以下の部分です。
// アルファ値を4倍した値で引く
float alpha = -4.0 * sprite_color.a;
// 4方向の平均を足し込む
alpha += texture(TEXTURE, UV + vec2(0.1, 0.0)).a;
alpha += texture(TEXTURE, UV + vec2(-0.1, 0.0)).a;
alpha += texture(TEXTURE, UV + vec2(0.0, 0.1)).a;
alpha += texture(TEXTURE, UV + vec2(0.0, -0.1)).a;
これはピクセルの透明部分(alpha=0.0)と不透明(alpha>0.0)の境界となる部分を抽出する処理となります。
もう少しわかりやすく説明するために以下のスプライトを使用するとします。
これは、黒のマスが不透過(alpha=1.0)、白のマスが透過部分(alpha=0.0)であるとします。
この画像の中央部分とその上下左右の透過値を先程の式に当てはめます。
すると、以下のような計算となり、アルファ値は0.0となり透過します。
- 中央(1.0) x -4 = -4.0
- 上下左右(それぞれ1.0 が4つ) = 合計 4.0
- -4.0 + 4.0 = 0.0
また、完全に透過している部分は「0.0 x -4 = 0.0」で上下左右も0.0なので、合計した値は「0.0」で透過することになります。
それに対して、このような透過している部分と不透過の部分の境界にあたる部分で先程の計算式を当てはめると、
「0.0 x -4 = 0.0」に「0.0(上) + 1.0(左) + 0.0(右) + 0.0(下)」を足し込むことになり、不透過(1.0)となります。
これによりアウトライン(境界)の抽出が可能となります。
なお内側の色が水色になってしまっているのは、アルファ値がマイナスになっているため、マイナス値を描画しないようにすればこの部分は消すことができます。
以下、clamp関数で 0.0〜1.0 に丸めるコードの例です。
// アウトラインの色とアルファ値を合成
COLOR = vec4(outline_color.rgb, clamp(alpha, 0.0, 1.0));
またピクセルシェーダの限界として、もとのテクスチャの描画領域を超えた部分は描画できない(上記画像であれば足元の部分)ので、このあたりが気になる場合は、スプライトのサイズを大きめにして余白をとっておくのが良いかもしれません。
元のスプライト描画を含めた正式なアウトラインの描画処理
先程の例では、元のスプライトが描画されないので、シェーダーコードを以下のように変更します。
shader_type canvas_item;
// アウトラインの幅
uniform float width : hint_range(0.0, 30.0);
// アウトラインの色
uniform vec4 outline_color : hint_color;
void fragment() {
// スプライトサイズの割合に変換する
float size = width * 1.0 / float(textureSize(TEXTURE, 0).x);
// スプライトの色を取得
vec4 sprite_color = texture(TEXTURE, UV);
// アルファ値を4倍した値で引く
float alpha = -4.0 * sprite_color.a;
// 4方向の平均を足し込む
alpha += texture(TEXTURE, UV + vec2(size, 0.0)).a;
alpha += texture(TEXTURE, UV + vec2(-size, 0.0)).a;
alpha += texture(TEXTURE, UV + vec2(0.0, size)).a;
alpha += texture(TEXTURE, UV + vec2(0.0, -size)).a;
// アウトラインの色とアルファ値を合成
float blend_a = clamp(alpha, 0.0, 1.0); // 元のスプライトとの合成アルファ値
float outline_a = abs(alpha); // 輪郭線のアルファ値.
vec4 final_color = mix(sprite_color, outline_color, blend_a);
COLOR = vec4(final_color.rgb, clamp(sprite_color.a + outline_a, 0.0, 1.0));
}
アウトラインの幅の定義と抽出するピクセルの位置の調整、そしてもとのスプライトとのブレンド処理が含まれています。
なお、アウトラインの幅はシェーダーパラメータから設定するため、シーンのノードから対象のスプライトを選択して、「CanvasItem > Material > Shader Param > Width」の値を設定する必要があります。
ここでは「2」を設定しました。
するとこのようにアウトライン表示がされるようになります。
完成プロジェクト
今回の内容を実装したプロジェクトファイルを添付しておきます。
参考
今回のシェーダーコードは以下の動画からお借りました
・Outline Shader in Godot (tutorial)