こんにちは、エンジニアのオオバです。

お悩みさん
お悩みさん
  • Unityプログラミングで作成したメッシュに色を塗りたい
  • 頂点カラーを使ってみたい
  • 頂点には座標以外の情報を持てるの?
  • オオバ
    オオバ
    本記事ではこれらの悩みを解決します。

    このブログでは、初心者向けに3Dプログラミングの基礎にあたる プログラミングで三角形を1つ作り、色を塗る ということをUnityでやってきました。詳しくは以下の記事を読んでみてください。

    今回も引き続き初心者向けに「頂点」にフォーカスして 3Dプログラミングの基礎を解説します。 3Dは頂点の集まり。頂点を知ることで3Dプログラミングの全容がわかってきます。ぜひ最後まで読んでみてください。

    頂点には座標以外の情報を持つことが可能

    はじめに結論を簡単にまとめます。

    頂点には座標以外の情報を持つことが可能

    ①頂点には座標以外のデータを持てる

    ②頂点に「色情報」を持たせたデータを頂点カラーを呼ぶ

    ③頂点カラーを使ってメッシュに色を塗ることが可能

    結論、 頂点には座標以外の情報を持つことが可能です。 今回は頂点に色情報を持たせた 「頂点カラー」 を使って三角形に色を塗ってみたいと思います。

    最初は 「頂点に色情報をもたせるイメージがわきづらい」 かもしれません。しかし、頂点カラーはとても強力で、この仕組を知ることで3Dプログラミングの幅は広がります。ぜひマスターしておきましょう。

    👉DOTweenの教科書を読んでUnityアニメーションをプログラミングしてみよう!

    三角形メッシュに頂点カラーを適用する3ステップ

    三角形メッシュに頂点カラーを適用する3ステップを紹介していきます。

    三角形メッシュに頂点カラーを適用する3ステップ

    1.C#から各頂点に色情報をセット

    2.シェーダーで頂点カラーを取得する

    3.フラグメントシェーダーで頂点カラーを適用する

    そもそも頂点カラーとは?

    頂点とは、ただの座標ではありません。1つの頂点には複数のデータを格納することができます。これらのデータはシェーダー内で活用されます。

    頂点カラーとは「各頂点ごと」に持ったカラー情報です。

    次の表は頂点に含めむデータの一部の羅列です。データを含ませるかどうかは開発者に委ねられます。

    頂点データ内容
    頂点座標頂点の座標(必須)
    法線ベクトル頂点の方向
    UV座標画像を貼り付けるときに利用する座標
    色情報頂点カラー

    頂点座標は必須で、含めないとエラーが起きてシェーダーが動きません。

    ちなみに今回使用する頂点のデータを以下です。

    では、これから頂点カラーを各頂点に設定していきます。

    1.C#から各頂点に色情報をセット

    Meshインスタンスに頂点カラーをセットしていきます。

    以前の記事で作成した三角形メッシュのソースコードを流用しますので、↑の記事をご確認ください。最終的なソースコードは記事後半「今回使用したソースコード全文」で公開しますのでご安心を。

    Meshクラスの SetColorsメソッド で頂点カラーを設定していきます。

    mesh.SetColors(new Color[] {  
        Color.red,   // 赤  
        Color.green, // 緑  
        Color.blue   // 青  
    });  
    

    【Unity】プログラミングで作ったメッシュを頂点カラーで色を塗る方法_0

    図にすると、各頂点に赤、緑、青が設定されるイメージです。この色情報はこれから紹介するシェーダー内で受け取ります。

    2.シェーダーで頂点カラーを取得する

    頂点カラーは「頂点単位で実行」する頂点シェーダーで処理をします。 頂点シェーダーで受け取った頂点カラーを、ピクセル単位で実行するフラグメントシェーダーに渡すのです。

    準備として、最初に「頂点シェーダー」「フラグメントシェーダー」の間で受け渡すデータを構造体で定義します。

    頂点シェーダーに渡ってくるデータを定義

    頂点シェーダーで頂点カラーを受け取るためには、頂点シェーダーに渡ってくるデータを定義する必要があります。

    ここではアプリから送られるデータということで appdata という名の構造体で定義しました。

    struct appdata  
    {
     float4 vertex : POSITION;  
     fixed3 color : COLOR0;  
    };  
    

    float4 vertex : POSITION;」が頂点座標、「fixed3 color : COLOR0」が頂点カラーです。

    POSITIONやCOLOR0とはセマンティクス

    定義した構造体内の「POSITION」や「COLOR0」について解説します。これらはセマンティクスと呼ばれます。シェーダーの入出力時に使用し、どのような目的を持っているかということを示します。

    例えば「POSITION」とは任意の座標情報、COLOR0とは色情報です。セマンティクスとはシェーダーの文法上必要なのでそういうものなのだと認識しておきましょう。

    頂点シェーダーからフラグメントシェーダーに送るデータを定義

    頂点シェーダーで受け取ったデータをフラグメントシェーダーに送るときにも構造体で定義します。「v2f」という名の構造体でフラグメントシェーダーに送るデータを定義しました。

    struct v2f  
    {
      float4 vertex : SV_POSITION;  
      fixed3 color : COLOR0;  
    };  
    

    float4 vertex : SV_POSITION;」は座標変換後の頂点座標、「fixed3 color : COLOR0;」は頂点カラーです。
    この記事では割愛しますが、頂点はモニタに映し出すために さまざまな座標変換 を行います。その最終結果をフラグメントシェーダーに渡すのです。

    思ったより複雑な座標変換

    頂点シェーダー内で行っている座標変換はどのようなことをやっているのでしょうか。実は次の4回座標変換しています。

    1. ローカル座標をグローバル座標に変換する(モデル座標変換)

    2. カメラを原点としてカメラの移動分の値をメッシュのグローバル座標にオフセットする(ビュー座標変換)

    3. カメラの画角、クリッピングなどの設定から映す範囲を指定する(投影変換)

    4. 描画範囲に合わせた変換(ビューポート変換)

    思った以上にシェーダーは複雑なことをやってくれているということがわかりますね。気になる方は上記の変換名で検索してみてください。

    頂点シェーダーで頂点カラーを取得

    各頂点に設定された頂点カラーは、頂点シェーダーで取得します。 頂点シェーダーはフラグメントシェーダーにデータを渡す処理を記述 しています。

    以下の関数「vert」が頂点シェーダーです。

    v2f vert (appdata v)  
    {
      // 頂点シェーダーからフラグメントシェーダーに渡すデータ  
      v2f o;  
      // 座標変換した座標をフラグメントシェーダーに渡す  
      o.vertex = UnityObjectToClipPos(v.vertex);  
      // 頂点カラーをフラグメントシェーダーにわたす  
      o.color = v.color;  
      return o;  
    }
    

    v2f vert (appdata v) 頂点シェーダー「vert」の引数に、「頂点シェーダーに渡ってくるデータを定義」で定義した型「appdata」が渡ってきます。

    戻り値に「頂点シェーダーからフラグメントシェーダーに送るデータを定義」で定義した型「v2f」を記述します。 つまり頂点シェーダーの戻り値がフラグメントシェーダーに渡すデータとなるのです。

    // 頂点カラーをフラグメントシェーダーにわたす  
    o.color = v.color;  
    

    このように頂点シェーダーは頂点カラーを「そのまま」フラグメントシェーダーに渡しています。

    ここまでを図にまとめてみました。

    【Unity】プログラミングで作ったメッシュを頂点カラーで色を塗る方法_1

    C#で渡したMeshデータが頂点シェーダーに渡り、フラグメントシェーダーまで渡るイメージです。appdata、v2fと型を変えながらシェーダー内をデータが渡っていきます。

    3.フラグメントシェーダーで頂点カラーを適用する

    次にフラグメントシェーダーの処理です。頂点シェーダーで渡ってきた「v2f」をフラグメントシェーダー内で料理していきます。最初に結論を話すと、 フラグメントシェーダーの役割ははピクセルの色を決めること です。

    次のコードはfrag関数はフラグメントシェーダーの中身。三角形メッシュのピクセルの数だけ実行する関数です。

    fixed4 frag (v2f i) : SV_Target  
    {
      return fixed4(i.color, 1);  
    }
    

    fixed4 frag (v2f i) : SV_Target に注目してみましょう。フラグメントシェーダー「frag」の引数に、頂点シェーダーから送られた v2f型 が渡ります。
    戻り値は画面に表示する色データ。つまりfixed4とは、RGBAという色データなのです。

    フラグメントシェーダーの処理 return fixed4(i.color, 1); とはアルファ値1にしたカラーとして画面に出力しているという意味になります。

    struct v2f  
    {
      float4 vertex : SV_POSITION;  
      fixed3 color : COLOR0;  
    };  
    

    「i.color」とは「v2f型」です。上記のとおり「fixed3」は3つのデータ(RGB)しかデータがないため、A(アルファ)を追記している形になります。繰り返しになりますが、「フラグメントシェーダーの役割ははピクセルの色を決めること」 です。RGBAの値を決めることが決めることがフラグメントシェーダーの役割なのです。

    以下の図はここまでをまとめました。

    【Unity】プログラミングで作ったメッシュを頂点カラーで色を塗る方法_2

    頂点データが「appdata」という形で頂点シェーダーに。「v2f」に変換してフラグメントシェーダーへ。最終的には「fixed4(RGBA)」としてピクセルに色が塗られディスプレイに表示されるのです。

    Unityを実行すると、以下のように頂点カラーを適用した三角形が表示されます。

    【Unity】プログラミングで作ったメッシュを頂点カラーで色を塗る方法_3

    ご覧の通り、各頂点の赤、緑、青の間の色は自動で補間されるのです。

    まとめ

    この記事ではプログラミングで作ったメッシュの色を頂点カラーで塗る方法を紹介してきました。簡単に内容をまとめます。

    プログラミングで作ったメッシュの色を頂点カラーで塗る方法

    ①C#で頂点の頂点カラーを指定する

    ②頂点シェーダーで頂点カラーを取得し、フラグメントシェーダーへ渡す

    ③フラグメントシェーダーで頂点シェーダーを適用する

    やっていることはとてもシンプルだったかと思います。 各頂点の色をフラグメントシェーダーで出力しているだけ ですから。

    勘の良い人はすでに気づいていると思いますが、 頂点に格納するデータを使ってさまざまな表現が可能 だということです。今回は最もシンプルな「色情報」でした。この色情報を色として使わずに、例えば 地形の凹凸 に使うことも可能です。

    複雑な頂点のデータ入力は専用のツールを作成するか、3Dソフトを使うといった方法をとります。しかし、 頂点の仕組みを理解するには三角形が一番わかりやすい と考えています。

    今後も3Dプログラミングを続けていく中で、頂点に迷った際は、ぜひ本記事を読み直してみてください。なにごとも基本をおさえておくことが重要。基本がその後の開発スピードに大きな影響を与えるからです。

    この記事があなたのゲーム開発に少しでもお役に立てたら嬉しいです。

    以降は今回使用したソースコードの共有です。コピペしていただき、実際に動かしてみると良いと思います。

    今回使用したソースコード全文

    今回作成したのは、C#ファイル1、シェーダーファイル1の合計2ファイルです。

    使用したC#ファイル

    using UnityEngine;  
    using System.Collections;  
    
    /// <summary>  
    /// 頂点カラーで三角形を描画します  
    /// </summary>  
    [RequireComponent (typeof(MeshRenderer))]  
    [RequireComponent (typeof(MeshFilter))]  
    public class DynamicCreateMesh : MonoBehaviour  
    {
      private void Start ()  
      {
        var mesh = new Mesh ();  
        mesh.SetVertices(new Vector3[] {  
          new Vector3 (0, 1f),  
          new Vector3 (1f, -1f),  
          new Vector3 (-1f, -1f),  
        });  
        mesh.SetTriangles(new int[] {0, 1, 2}, 0);  
    
        // 変更箇所 : 各頂点に色情報を設定  
        mesh.SetColors(new Color[] {  
          Color.red, Color.green, Color.blue  
        });  
    
        var filter = GetComponent<MeshFilter> ();  
        filter.sharedMesh = mesh;  
    
        var renderer = GetComponent<MeshRenderer> ();  
        // 変更箇所 : 参照するシェーダー名  
        renderer.material = new Material(Shader.Find("Unlit/VertexColorShader"));  
      }
    }
    

    C#ファイルは前回の記事をを少し変更しただけなので、内容がイマイチ理解できていない方は、こちらの前回記事を参考にしてみてください。かなり丁寧に解説していますので、基本をおさえることができると思います。

    使用したシェーダーファイル

    Shader "Unlit/VertexColorShader"  
    {
      SubShader  
      {
        Pass  
        {
          CGPROGRAM  
          #pragma vertex vert  
          #pragma fragment frag  
    
          #include "UnityCG.cginc"  
          // 頂点シェーダーに送られるデータの型  
          struct appdata  
          {
            float4 vertex : POSITION;  
            fixed3 color : COLOR0;  
          };  
    
          // 頂点シェーダーからフラグメントシェーダーに送るデータの型  
          struct v2f  
          {
            float4 vertex : SV_POSITION;  
            fixed3 color : COLOR0;  
          };  
    
            v2f vert (appdata v)  
            {
                v2f o;  
                o.vertex = UnityObjectToClipPos(v.vertex);  
                o.color = v.color;  
                return o;  
            }
    
          fixed4 frag (v2f i) : SV_Target  
          {
            return fixed4(i.color, 1);  
          }
          ENDCG  
        }
      }
    }
    

    ソースコード内に適宜コメントを残しているので、参考にしてみてください。また、記事内の解説と照らし合わせながら読むと、より理解が深まります。

    「Unity初心者大学」というUnity初心者向けのYouTube始めました!!
    ぜひチャンネル登録をお願いします!

    最後まで読んでいただきありがとうございました!
    すばらしい3Dプログラミングライフをお過ごしください。

    オススメ記事
    検証環境