2017年4月26日

Unity 頂点カラーに対応したシェーダーを作る


デフォルトのマテリアルでは頂点カラーがうまく表示されなかったので、シェーダー側で対応させてみる…



※このページの内容の動作確認にはUnity5.3を使用しています。
メッシュの各頂点に動的に色をつけたい場合、最も手っ取り早くて実用的な方法は専用のシェーダーを作ることですが、一応他にも方法はあります。パフォーマンスはかなり悪くなりますが、たとえばテクスチャを作成してメッシュに貼り付けるという方法。それから、頂点カラーをスクリプトから変更するやり方もあります。こんな感じ↓

Color32[] colors = {
 new Color32(255,   0,   0, 255),
 new Color32(  0, 255,   0, 255),
 new Color32(  0,   0, 255, 255)
};
Mesh mesh = GetComponent<MeshFilter>().mesh;
mesh.colors32 = colors;

上記はメッシュの頂点が3つだけ(つまり三角形)の場合の例ですが、テクスチャを用意する方法よりは手軽に設定できて便利ですね。

ただ、このコードを実行しても結果はこんな感じになります。

頂点カラーが適用されずマテリアルで設定した色のまま

これはなぜかというと、シェーダー側で頂点カラーに対応してないからです。Mesh.colorsのドキュメントをよく読んでみると

// (Note that most built-in Shaders don't display vertex colors. Use one that does, such as a Particle Shader, to see vertex colors)

「ほとんどの組み込みシェーダーでは頂点カラーが表示されないことに注意。頂点カラーを見るには、Particle Shaderなど頂点カラーが表示できるものを使うこと」

と書かれています。この記述のとおり、Standard Shaderは頂点カラー非対応で、頂点カラーにはParticles関連のシェーダーくらいしか対応していません。

というわけで、とりあえずStandard Shaderを頂点カラーに対応させてみます。Create > Shader > Standard Surface Shaderで新規のシェーダーを作成し、次のように変更しました。

Shader "Custom/VertexColorStandard" {
 Properties {
  _Color ("Color", Color) = (1,1,1,1)
  _MainTex ("Albedo (RGB)", 2D) = "white" {}
  _Glossiness ("Smoothness", Range(0,1)) = 0.5
  _Metallic ("Metallic", Range(0,1)) = 0.0
 }
 SubShader {
  Tags { "RenderType"="Opaque" }
  LOD 200
  
  CGPROGRAM
  // Physically based Standard lighting model, and enable shadows on all light types
  #pragma surface surf Standard fullforwardshadows vertex:vert

  // Use shader model 3.0 target, to get nicer looking lighting
  #pragma target 3.0

  struct Input {
   float2 uv_MainTex;
   fixed4 vertexColor;
  };

  void vert(inout appdata_full v, out Input o) {
   UNITY_INITIALIZE_OUTPUT(Input, o);
   o.vertexColor = v.color;
  }

  sampler2D _MainTex;

  half _Glossiness;
  half _Metallic;
  fixed4 _Color;

  void surf (Input IN, inout SurfaceOutputStandard o) {
   // Albedo comes from a texture tinted by color
   fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
   o.Albedo = c.rgb * IN.vertexColor;
   // Metallic and smoothness come from slider variables
   o.Metallic = _Metallic;
   o.Smoothness = _Glossiness;
   o.Alpha = c.a;
  }
  ENDCG
 }
 FallBack "Diffuse"
}

なげえ!…でも変更点はそんなに多くありません。

#pragma surface surf Standard fullforwardshadows vertex:vert

まずは#pragmaの行にvertex:vertを追加し「頂点に関する処理はvert関数内でやるよ!」ということを指定しています。関数名さえ一致していれば別にvertex:getvinfoでもvertex:chotenでも何でも大丈夫だと思います。おそらくvertが一般的なのかな。

struct Input {
 float2 uv_MainTex;
 fixed4 vertexColor;
};

void vert(inout appdata_full v, out Input o) {
 UNITY_INITIALIZE_OUTPUT(Input, o);
 o.vertexColor = v.color;
}

次にInput構造体に頂点カラーの値を保管しておくためのvertexColorを追加。さらにvert関数も追加。この関数内で頂点の色を取得しInputに一旦保存(o.vertexColor = v.color)、あとのsurf関数内でその値を使用するという流れです。ちなみにappdata_fullという構造体の中には頂点の位置、接線、法線、テクスチャ座標(×4)、そして色といった頂点データがてんこもりに入っています。今回はその中のcolorの値を取得したわけです。

o.Albedo = c.rgb * IN.vertexColor;

実際にsurf関数内で変更したのはこの行だけ。直前で計算したこの座標の色(c.rgb)に、さっきvert関数で受け取った頂点カラー(IN.vertexColor)を掛け合わせてブレンドしています。

あとはこのシェーダーでマテリアルを作成しゲームオブジェクトに追加すると、冒頭のコードがきちんと反映されるようになります。マテリアルで設定した色やテクスチャと混ざるようになるので、使い方次第では面白いことができそう。


それと、ググったらコミュニティで頂点カラーの強さやアルファ(透明)にも対応したシェーダーが公開されていました。いや~こりゃ便利ですな!

…でもそうなると今回の作業の意味は… ま、まあ何事も勉強ということで…

0 件のコメント:

コメントを投稿