C#のコードからメッシュを生成するチュートリアル

Polygon from s2kw on Vimeo.

  • コードからポリゴンを生成するチュートリアル。
  • 大まかな流れは以下のとおり
    • Mesh オブジェクトの生成
    • Mesh オブジェクトに頂点情報を渡す
    • Mesh オブジェクトの頂点情報に合わせた三角ポリゴン情報を渡す (この時点で画面には描画される)
    • Mesh オブジェクトにUV情報を渡す
    • Mesh オブジェクトにtangent情報を渡す
  • 書いたコード

  • チュートリアル
  • Unity::Reference::Mesh
Ad

Mesh オブジェクト

    var mesh = new Mesh();

メッシュオブジェクトを生成する。
このMeshオブジェクトにパラメータを流し込むとmeshができる

    Mesh mesh; // 色々操作するためのインスタンス変数
    GetComponent<MeshFilter>().mesh = mesh = new Mesh(); // コンポーネント側のmeshと表示用の両方にObjectの参照を代入
    mesh.name = "Procedural Grid"; // MeshFilterコンポーネントが参照するメッシュに表示される文字列

頂点情報

頂点は vertices と呼ばれる vertex の複数形で命名されたメンバがMeshオブジェクトにあるので、ここにVector3[]形式で渡してあげる必要がある。

vertex shader で書く vert.position に該当する。
座標はローカル座標扱いになる。上記のMeshfilterをアタッチしたGameObjectの座標からオフセットがかかる。

    this.vertices = new Vector3[ (this.xSize + 1 ) * ( this.ySize + 1 )];

    for ( int i = 0, y = 0; y <= ySize; y++ ) {
        for ( int x = 0; x <= xSize; x++, i++ ) {
            vertices[i] = new Vector3( x, y );
        }
    }

    mesh.vertices = vertices;

ここまでは頂点が整然と並ぶだけなので画面上には影響がでない。

三角ポリゴン

  • 三角ポリゴンは3つの頂点を使って生成される
  • どの順番で頂点を渡したかによって表と裏ができ、表しか描画されない
  • 隣接するポリゴンは同じ頂点を使って三角形を描く
    int[] triangles = new int[3];
    triangles[0] = 0;
    triangles[1] = 1;
    triangles[2] = 2;
    mesh.triangles = triangles;

生成されたポリゴンは直線的で視認できない。
頂点は一直線にならばせるのではなくて、隣わせにしたい。
ここでは、 triangles[0]triangles[2] は隣り合わせにする。

    int[] triangles = new int[3];
    triangles[0] = 0;
    triangles[1] = 1;
    triangles[2] = xSize + 1;
    mesh.triangles = triangles;

xSizeは横の頂点の数なので、その次の番号は一周してひとつ上にあるということ。

が、実際に画面に出してみると裏側にカメラを回さないと確認できない。

    int[] triangles = new int[3];
    triangles[0] = 0;
    triangles[1] = xSize + 1;
    triangles[2] = 1;
    mesh.triangles = triangles;

2番めと3番目を入れ替えることで正面(Zマイナスからプラスに向けて)方向からの描画がなされた。
Unityは左手系だからなのか、時計回りに渡された方向からでしか視認できない、ということを覚えておく。
参考:頂点番号
参考:法線ベクトルとスムージング角

2枚目のポリゴンは反対側に位置する。
これまでの頂点番号(代入している値)を 線分 triangle[1] – triangle[2] の線対称として描く。

    int[] triangles = new int[6]; // 6に変更!

    ...

    triangles[3] = 1;
    triangles[4] = xSize + 1;
    triangles[5] = xSize + 2;

    mesh.triangles = triangles;

対象となっている4,5番目は1段上なので1周回ってるためxSize分追加。

ここでチュートリアル側だと2つの三角ポリゴンを4行で表現しているが、僕にとっては関数にしたほうが分かりやすかったので別の記述をする。

    void DrawTriCorrectLocation(ref int[] triangles, int count, int total  ){
        triangles[ count + 0] = total;
        triangles[ count + 1] = total + xSize + 1;
        triangles[ count + 2] = total + 1;
    }
    void DrawTriInversion( ref int[] triangles, int count, int total ){
        triangles[ count + 3 ] = total + 1;
        triangles[ count + 4 ] = total + xSize + 1;
        triangles[ count + 5 ] = total + xSize + 2;
    }

正位のポリゴンを生成する DrawTriCorrectLocation() と逆位のポリゴンを生成する DrawTriInversion() を記述。
ポリゴンの通し番号 count と頂点番号のオフセット total を引数に受け取る。
ref でtriangles変数は返り値で代入(値肩なのでコピー)が大量発生しなくなる。
また、ポリゴンの生成を2枚(正方形)単位で表示していたチュートリアルと違い、三角ポリゴン単位で表示されるようにしてある。

    var wait = new WaitForSeconds(0.05f);

    ...

    int[] triangles = new int[ xSize * ySize * 6 ];
    for( int y = 0,count = 0, total = 0; y < this.ySize; y ++, total ++ ){
        for(int x = 0; x < this.xSize; x++, count += 6, total ++ ){
            this.DrawTriCorrectLocation (ref triangles, count, total);
            mesh.triangles = triangles; // 代入すると描画される
            yield return wait;
            this.DrawTriInversion (ref triangles, count, total);
            mesh.triangles = triangles;
            yield return wait;
        }
    }

これで全部のポリゴンを生成できる。

UV情報

UV情報は各頂点がテクスチャ等の平面上のどこに位置するかを定義する。

    Vector2[] uv = new Vector2[vertices.Length];

    for (int i = 0, y = 0; y <= this.ySize; y++) {
        for (int x = 0; x <= this.xSize; x++, i++) {
            this.vertices[i] = new Vector3(x, y);

            // UV情報を作成
            uv[i] = new Vector2( (float)x / xSize, (float)y / ySize);
        }
    }

    ...

    mesh.uv = uv;

shader で uv:TEXCORRD0 として登場したりする。
shader でも float2 なのでイメージが湧きやすい。

    uv[i] = new Vector2( (float)x / xSize, (float)y / ySize);

右をU方向、下をV方向とし、左上を原点(0,0)、で右下を(1,1)という正規化された二次元における、位置情報を入れている。

uvはそれぞれ1を超えたところで1周回る。

uvスクロールは個々の値を毎フレーム更新してあげれば一応可能だけど値が増えるに連れて処理は若干重くなる。
不要に座標をスクロールさせた分があるからだ。
Shaderで直接uvスクロールしてあげるか、1以上になったら1減算してあげる処理にしてあげると重さは会費される。

法線と接線

Material側にNormalマップも割り当てているので最後の

    mesh.RecalculateNormals();

これが走った時点で見た目がパッとかわり、法線が割り当てられる瞬間がわかる。

続きはまたいつか

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です