渋谷ほととぎす通信

完全趣味でやってるUnityメモ。説明できないところを説明できるようにするための個人ブログ。昨日の自分より少しでも大きくなれるように。。。 ※所属団体とは一切関係がありません

Unity 動的に生成したメッシュに対して頂点カラーを反映する


f:id:esakun:20150730215258g:plain:w450

前回記事の続きにあたります。
※本記事は動的にメッシュを生成するシリーズの一貫です。

スクリプト上から三角形ポリゴンを動的に生成し、テクスチャを貼るところまで来ました。順番が逆かもしれませんが今回は頂点カラーを反映して三角形を描画してみます。

動的にメッシュを生成するシリーズの一貫です。

頂点カラーとは

そもそも頂点には以下のように複数のデータを格納することができ、このデータのことを頂点バッファと呼ぶこともあります。

  • 頂点座標
  • 法線ベクトル
  • UV座標
  • 頂点カラー など

頂点カラーとは、一つの頂点にカラー情報をもたせたカラーの事を言います。

ちなみに今回使用する頂点バッファには以下の型を含みます。

  • 頂点座標 : Vector3型
  • 頂点カラー : Color型

ちなみに頂点を結ぶ順番は頂点バッファではなくインデックスバッファと呼びます。
頂点バッファには含まれません。

では頂点カラーを反映させるコードを書いてみます。前回作成したDynamicCreateMeshクラスを変更していきます。

◆26行目辺り

// 変更箇所 : 各頂点に色情報を設定
mesh.colors = new Color[] {
  Color.white,
  Color.red,
  Color.green
};

meshインスタンスのcolorsプロパティに各頂点のカラーを配列でセットします。

f:id:esakun:20151129184116p:plain:w240

頂点カラー未反映の三角形ですが、各頂点に白色、赤色、緑色を設定したイメージです。


頂点カラーを反映するシェーダを書く

Unityにはデフォルトで頂点カラーを表示させるビルトインシェーダが無いため書いていきます。

f:id:esakun:20151129184431p:plain

ProjectブラウザのCreateボタンからShader -> Unlit Shaderを選んで作成します。

少し長くなりそうですが、このシェーダの解説をしていきます。

1つのシェーダファイル(SimpleVertexColor.shader)の中には2種類のシェーダが内包されています。

  • Vertexシェーダ
  • Fragmentシェーダ

この2つのシェーダの大まかな流れを説明すると、Vertexシェーダで作られたデータは、Fragmentシェーダに渡され描画されます。

シェーダとはなんぞや?という方はこちらの記事をどうぞ



Vertexシェーダとは

CPUから渡ってきた頂点情報を加工するのがVertexシェーダの仕事です。

v2f vert (appdata v)
{
    v2f o;
    o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
    o.color = v.color;
    return o;
}

vert関数が毎フレームこのシェーダに紐づくメッシュの頂点数ごとに実行されます。今回の場合は、3頂点あるため、毎フレーム3回実行されます。

そもそもvert関数引数のappdataとはなんぞやという話になりますが、これはCPU側から送られる頂点バッファです。

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

『型 変数名 : セマンティクス』という文法で構造体で定義されます。 セマンティクス とは、シェーダーの入出力時に使用し、どのような目的を持っているかということを示す文字列です。

POSITION COLOR0
頂点座標 頂点カラー

※この他にも様々なセマンティクスが存在します。興味ある方はコチラをどうぞ。

結局今回のVertexシェーダで何をしているのかというと以下の3つです。

  • 三角形メッシュ頂点の座標変換
    ※ここでいう座標変換とはローカル座標の頂点をモニタ上に写すための座標に変換することを指します
  • 設定された頂点カラーの取得
  • 変換後の座標と頂点カラーをFragmentシェーダに渡す


三角形メッシュ頂点の座標変換

メッシュがどうやってモニタに表示されているのか、という事を真面目に考えてみます。
※そもそもVertexシェーダに渡されるのは、三角形を中心としたローカル座標です。

  1. ローカル座標をグローバル座標に変換する(モデル座標変換)
  2. カメラを原点としてカメラの移動分の値をメッシュのグローバル座標にオフセットする(ビュー座標変換)
  3. カメラの画角、クリッピングなどの設定から映す範囲を指定する(投影変換)
  4. 描画範囲に合わせた変換(ビューポート変換)
  5. ディプレイの解像度に合わせてピクセルを割り当てる(ラスタライズ)
  6. テクスチャや色を合成
  7. モニタに表示される

このような長い道のりを経てモニタにメッシュが映しだされるわけですが、この道程の中でVertexシェーダが担う部分は、1〜4(確か)の部分です。

本来ならたくさんの行列計算をしないといけないのですが、1〜4の部分を先の1行で書き終わることが出来ます。Unity恐ろしい....。

o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);

このo.vertexの中身は全ての座標変換が終わった頂点座標が格納されています。
(この頂点座標の型がfloat3ではなく、float4なのかは別記事で。)

最初に説明したようにVertexシェーダで作られたデータ(v2f構造体)はFragmentシェーダに渡されます。

v2fもappdataと同じようなものでVertexシェーダからFragmentシェーダに渡されるデータの集合体で、予め何を送るか定義するものです。中身を確認すると、

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

Fragmentシェーダに渡されるものは、頂点座標頂点カラーということが見て取れます。
SV_POSITIONセマンティクスが、POSITIONではないのはDX11への互換性確保とのことです。

参考
tips.hecomi.com

ちなみにFragmentシェーダは各頂点情報からどういうふうに描画するかということを担います。Vertexシェーダから送られるv2fを元に描画処理をしていきます。



Fragmentシェーダの処理

FragmentシェーダはVertexシェーダと違い、頂点数分だけ実行されるわけではありません。1フレーム辺り、頂点が結ぶ描画エリアのピクセル数分frag関数が実行されます。今回の記事ではあまり関係ありませんが、複雑な計算処理はFragmentシェーダではなくVertexシェーダで行う方がパフォーマンスは良くなります。

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

フラグメントシェーダの処理部分です。 頂点カラーを描画するだけなので非常に簡潔です。

v2fのカラー情報を取得して、アウトプットしています。
※フラグメントシェーダで、v2fのvertex使ってないから省略しても良いのでは?という疑問も湧くのですが、vertexは必須のプロパティなので省略できません。(省略するとエラー)

f:id:esakun:20151129191629p:plain

シェーダー: 頂点とフラグメントプログラム - Unity マニュアル

結果色のついた三角形メッシュが描画されます。

f:id:esakun:20151129185818p:plain

各頂点間はシェーダ側で勝手にリニア補間が入るため、頂点間はグラデーションになります。

まとめ

終盤かなり駆け足になりましたが、頂点カラーを描画することが出来ました。 次回は頂点カラー + テクスチャ描画をやってみます。

その他の動的にメッシュを生成するシリーズ

Unity動的にメッシュを生成するシリーズ - 渋谷ほととぎす通信