MonoGame で 3D 描画

この記事は [Xamarin Student Advent Calendar 2016](http://qiita.com/advent-calendar/2016/xamarin-student) 20 日目の記事です。

# MonoGame とは

MonoGame は C# で記述されたゲームフレームワークです。
XNA との完全互換を目指した設計でクロスプラットフォームが売り。
Xamarin を用いることで iOS, Android へのデプロイも可能です。
[詳しくは本アドベントカレンダーの14日目を参照してください](http://qiita.com/blonde_namazu/items/fbea7fb9be57651bb9f9)

私が触り始めたころは扱いづらいところも多かった MonoGame ですが、
ここ1, 2年のアップデートで Content Pipeline がそこそこまともに動くようになるなど、
フレームワークとして洗練されてきています。

しかしながら、ドキュメンテーション・情報量はいまだ乏しく、
MonoGame で 3D を扱うサンプルコードや入門記事はほとんどありません。
そこで本記事では、 MonoGame で 3D Primitive を描画する方法についてまとめます。:point_up:

# 3D Primitive とは

> 情報工学において、プリミティブ (primitive) とは、ある大きな構造を表現するのに使われる、もっとも基本的な構成要素のことを指す。
> [wikipedia](https://ja.wikipedia.org/wiki/%E3%83%97%E3%83%AA%E3%83%9F%E3%83%86%E3%82%A3%E3%83%96)

3D 表現における基本的な構成要素といえば三角形ですね！
三角形を組み合わせることで、いかなる 3D オブジェクトをも描画することができます。

# コード解説

完全なソースコードは [MonoGame3DExamples](https://github.com/haripo/MonoGame3DExamples/tree/master/MonoGame3DExamples) を参照してください。
今回解説するのは [MonoGame3DExamples/DrawBasicPrimitiveExample.cs](https://github.com/haripo/MonoGame3DExamples/blob/master/MonoGame3DExamples/DrawBasicPrimitiveExample.cs) です。

三角形描画の手順は

 1. エフェクトを作成
 2. 頂点データを作成
 3. 描画

これだけです。


## エフェクトの作成

effect というのは頂点データをどのように画面に投影するかを指定するものです。
頂点データは 3 次元空間上の座標のリストに過ぎないので、
それらをどのように画面に描画するのかを設定する必要があります。

```C#:エフェクト作成
var effect = new BasicEffect(graphicsDevice);
effect.VertexColorEnabled = true;
effect.View = Matrix.CreateLookAt(
        new Vector3(0.0f, 2.0f, 2.0f),
        Vector3.Zero,
        Vector3.Up);
effect.Projection = Matrix.CreatePerspectiveFieldOfView(
        MathHelper.PiOver4,
        graphicsDevice.Viewport.AspectRatio,
        1.0f,
        100.0f);
```


1 行目から順に追います。

```C#
var effect = new BasicEffect(graphicsDevice);
```

今回は BasicEffect という effect を使います。
独自にシェーダーを記述し effect として用いることも可能です。

```C#
effect.VertexColorEnabled = true;
```

頂点に色を設定できるようにします。
普通は面に色、もしくはテクスチャを設定するのが自然ですが、簡単のため頂点に色を置きます。
よくわからないかもしれませんが、最終的な画面出力を見れば「頂点に色がついている」のが一目瞭然です。

```C#
effect.View = Matrix.CreateLookAt(
        new Vector3(0.0f, 2.0f, 2.0f),
        Vector3.Zero,
        Vector3.Up);
```

View Matrix （ビュー行列）を設定します。
View Matrix は頂点データを「どの位置から見た」画面を描画するかを決めています。
要するに画面を映し出すカメラの位置と向きですね。
ここでは (0, 2, 2) の位置から (0, 0, 0) の方向を映すカメラを指定しています。
第 3 引数の ```Vector3.Up``` はカメラの回転（首をかしげる方向の回転）を指定しており、
カメラの天面（映像の上方向）が (0, 1, 0) の方向と一致することを示しています。

``` C#
effect.Projection = Matrix.CreatePerspectiveFieldOfView(
        MathHelper.PiOver4,
        graphicsDevice.Viewport.AspectRatio,
        1.0f,
        100.0f);
```

Projection Matrix を設定します。
第 1, 2 引数はそれぞれ視野角と視野の縦横比、第 3, 4 引数は遠近方向の見える範囲です。
この場合、カメラは 45 度の範囲を撮影しており、カメラから 1~100 の距離にあるものが投影されています。

```CreatePerspectiveFieldOfView``` は遠くのものが小さく、近くのものが大きくなるような変換を行う射影です。
別の斜影に ```CreateOrthographic``` というのもありまして、これは遠近で大きさが変わらないような斜影を行います。
threejs の解説記事にわかりやすい例がありました（[Three.js – Working with Cameras](https://www.script-tutorials.com/webgl-with-three-js-lesson-9/)）。

## 頂点データの作成

``` C#:頂点配列の作成
var vertices = new VertexPositionColor[3];
vertices[0] = new VertexPositionColor(new Vector3(0, 0, 1), Color.Red);
vertices[1] = new VertexPositionColor(new Vector3(0, 1, 0), Color.Blue);
vertices[2] = new VertexPositionColor(new Vector3(1, 0, 0), Color.Green);
```

三角形を作るためには頂点が 3 つ必要ですね。

```VertexPositionColor``` は 位置と色を保持する頂点です。
ほかにはテクスチャを保持できる ```VertexPositionTexture```　や 法線ベクトルを持つ```VertexPositionNormalTexture```があります。
今回はテクスチャを張らないので ```VertexPositionColor``` を使います。

2-4 行目で頂点を作成しており、座標 (0, 0, 1), (0, 1, 0), (1, 0, 0) にそれぞれ Red, Blue, Green の頂点を設定しています。

## 描画

作成した頂点データを ```Game.Draw``` にて描画します。

```C#:描画
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
  pass.Apply();
  graphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 0, 1);
}
```

effect は Technique と Pass から構成されており、Pass ごとに頂点データを描画します。

```PrimitiveType.TriangleList``` は ```vertices``` が三角形の頂点を保持した配列であることを表します。
今回描画する三角形は 1 つですが、複数の三角形を描画する場合、
vertices[0], vertices[1], vertices[2] が 1 つめの三角形の頂点、 vertices[3], vertices[4], vertices[5] が 2 つ目の三角形の頂点という具合になります。

vertices で記述された primitive のうち、描画する範囲を指定するのが 3, 4 番目の引数です。
ここでは 0 番目から 1 つの primitive を描画することを指定します。

# 実行結果

![image](https://qiita-image-store.s3.amazonaws.com/0/302/ce2fc679-372d-f99c-0f11-c7bb1dbcb20a.png)

はい、三角形ですね。 :ok_woman_tone1: 

座標 (0, 0, 1), (0, 1, 0), (1, 0, 0) を頂点とする三角形です。

> 描画した三角形のイメージ
> ![image](https://qiita-image-store.s3.amazonaws.com/0/302/119581cd-0746-153a-2737-c25476e903f5.png)

これを (0, 2, 2) の位置から (0, 0, 0) を向いたカメラで撮影したら、
実行結果のような三角形になりますね！

# まとめ

MonoGame で一番基本的な 3D 描画を紹介しました。
[MonoGame3DExamples](https://github.com/haripo/MonoGame3DExamples/tree/master/MonoGame3DExamples) はカスタムシェーダーなど色々なサンプルを拡充していく予定ですので、ぜひ参考にしてください。 :sparkles: 

# FAQ

 - Unity つかわないの？
   - 複雑なことをしなければ MonoGame のほうが楽なこともあるかもしれない（ないかもしれない）
   - MonoGame はアセット管理（.mgcb）もテキストファイルなので Git フレンドリー
   - MonoGame は OSS （ただし Xamarin が OSS ではない）