読者です 読者をやめる 読者になる 読者になる

渋谷ほととぎす通信

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

Unity 動的にメッシュを生成してゴニョゴニョする : 超基本編その2


f:id:esakun:20150730215258g:plain

esakun.hateblo.jp

前回スクリプト上から三角形ポリゴンを生成し、テクスチャを貼るところまで来ました。

順番が逆かもしれませんが今回は頂点カラーを設定して描画してみます。

頂点カラーとは

前回のおさらいですが、メッシュを形成する頂点には、

  • 頂点座標 : Vector3型
  • 頂点を結ぶ順番 : int型
  • UV座標 : Vector2型

がありましたが、今回はUV座標を使わずに色情報が追加されます。

  • 色情報 : Color型

まずはソースはコチラ。

using UnityEngine;
using System.Collections;

namespace Esakun.Sample
{
    /// <summary>
    /// 頂点カラーで三角形を描画します
    /// </summary>
    [RequireComponent (typeof(MeshRenderer))]
    [RequireComponent (typeof(MeshFilter))]
    public class DynamicCreateMesh : MonoBehaviour
    {
        private void Start ()
        {
            var mesh = new Mesh ();
            mesh.vertices = new Vector3[] {
                new Vector3 (0, 1f),
                new Vector3 (1f, -1f),
                new Vector3 (-1f, -1f),
            };
            mesh.triangles = new int[] {
                0, 1, 2  
            };

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

            var filter = GetComponent<MeshFilter> ();
            filter.sharedMesh = mesh;
        }
    }
}

f:id:esakun:20151129184116p:plain

このように、各頂点に白色、赤色、緑色を設定します。

Unityは頂点カラーをデフォルトで描画できません

ただ、Unityにはデフォルトで頂点カラーを表示させるシェーダがありません。ということでシェーダを書きます。

f:id:esakun:20151129184431p:plain

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

シェーダのソースはコチラ。

Shader "Esakun/Unlit/SimpleVertexColorShader"
{
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        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 = mul(UNITY_MATRIX_MVP, v.vertex);
                o.color = v.color;
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                return fixed4(i.color, 1);
            }
            ENDCG
        }
    }
}

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

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

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

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

Vertexシェーダとは

メッシュの頂点情報を加工するのが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とはなんぞやという話になりますが、これはUnity側から送られる頂点の情報です。

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

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

POSITION COLOR0
頂点座標 頂点カラー

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

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

  • 三角形メッシュ頂点の座標変換
  • 設定された頂点カラーの取得

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

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

この1行が何を意味しているか、パット見わからないかもしれませんが、とてつもなく凄いことをしています。

メッシュがどうやってモニタに表示されているのか、という事を真面目に考えてみます。
※そもそも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

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

まとめ

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

「動的メッシュ生成」シリーズ記事

esakun.hateblo.jp esakun.hateblo.jp esakun.hateblo.jp esakun.hateblo.jp