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で新規のシェーダーを作成し、次のように変更しました。

  1. Shader "Custom/VertexColorStandard" {
  2. Properties {
  3.   _Color ("Color", Color) = (1,1,1,1)
  4.   _MainTex ("Albedo (RGB)", 2D) = "white" {}
  5.   _Glossiness ("Smoothness", Range(0,1)) = 0.5
  6.   _Metallic ("Metallic", Range(0,1)) = 0.0
  7. }
  8. SubShader {
  9.   Tags { "RenderType"="Opaque" }
  10.   LOD 200
  11.   
  12.   CGPROGRAM
  13.   // Physically based Standard lighting model, and enable shadows on all light types
  14.   #pragma surface surf Standard fullforwardshadows vertex:vert
  15.  
  16.   // Use shader model 3.0 target, to get nicer looking lighting
  17.   #pragma target 3.0
  18.  
  19.   struct Input {
  20.    float2 uv_MainTex;
  21.    fixed4 vertexColor;
  22.   };
  23.  
  24.   void vert(inout appdata_full v, out Input o) {
  25.    UNITY_INITIALIZE_OUTPUT(Input, o);
  26.    o.vertexColor = v.color;
  27.   }
  28.  
  29.   sampler2D _MainTex;
  30.  
  31.   half _Glossiness;
  32.   half _Metallic;
  33.   fixed4 _Color;
  34.  
  35.   void surf (Input IN, inout SurfaceOutputStandard o) {
  36.    // Albedo comes from a texture tinted by color
  37.    fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
  38.    o.Albedo = c.rgb * IN.vertexColor;
  39.    // Metallic and smoothness come from slider variables
  40.    o.Metallic = _Metallic;
  41.    o.Smoothness = _Glossiness;
  42.    o.Alpha = c.a;
  43.   }
  44.   ENDCG
  45. }
  46. FallBack "Diffuse"
  47. }

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

  1. #pragma surface surf Standard fullforwardshadows vertex:vert

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

  1. struct Input {
  2.  float2 uv_MainTex;
  3.  fixed4 vertexColor;
  4. };
  5.  
  6. void vert(inout appdata_full v, out Input o) {
  7.  UNITY_INITIALIZE_OUTPUT(Input, o);
  8.  o.vertexColor = v.color;
  9. }

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

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

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

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


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

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

0 件のコメント:

コメントを投稿