ゲームエフェクトデザイナーのブログ | A Real-Time VFX Artist's Blog

About Making Materials on UE, Making Tools with C#, etc

C# 多角形の3Dモデルの描画

MonoGame(XNA)を使ってC#で多角形ポリゴンを描画してみました。
MonoGame 自体に関してはこちらで紹介しています。
f:id:moko_03_25:20190114124710g:plain  
かんたんXNA4.0 (XNA入門)|Memeplexes
ポリゴン生成などの基本的な記述に関してはこちらのサイトを参考にさせていただいています。
 
「多角形の分割数」を変えれば、四角形>五角形>六角形‥と増やしていけます。
頂点インデックスを生成するのが大変ですね。。

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace MonoGame3DTest2
{
    public class MyGame : Game
    {
        //////////////////////////////
        //                         //
        // 円ポリゴンを描くサンプル //
        //                         //
        //////////////////////////////

        GraphicsDeviceManager graphics;
        
        // データがどのように描画されるかを制御するクラス
        BasicEffect effect;

        // 円の分割数
        static int polyCircleCount = 12;

        // 座標と色の2つのデータを持った頂点情報の構造体
        VertexPositionColor[] vertices = new VertexPositionColor[polyCircleCount + 1];

        // 頂点インデックス指定用
        int[] indices = new int[polyCircleCount * 3];
        
        // カメラの制御用
        float angle = 0;
        float height = 0;

        // コンストラクタ
        public MyGame()
        {
            graphics = new GraphicsDeviceManager(this);
        }

        // 表示物の準備
        protected override void LoadContent()
        {
            // 中心点
            vertices[0] = new VertexPositionColor(new Vector3(0, 0, 0), Color.White);

            // 円の外周
            for (int i = 1; i <= polyCircleCount; i++)
            {
                // 円を分割した1つ分の角度
                double cut = (Math.PI * 2) / polyCircleCount;
                // 角度
                double theta = cut * (i - 1);
                // X, Y の算出
                float x = (float)Math.Cos(theta);
                float y = (float)Math.Sin(theta);

                vertices[i] = new VertexPositionColor
                    (
                        new Vector3(x, y, 0),
                        Color.White
                    );

                // デバッグ用
                //theta = (theta / Math.PI) *180;
                //float dX = (float)(((double)x / Math.PI) * 180);
                //float dY = (float)(((double)y / Math.PI) * 180);
                //Console.WriteLine("cut: " + cut + " d; " + theta + " X: " + dX + " Y: " + dY);
            }

            // 円ポリゴンのインデックス生成
            int vertexId = 0;
            for (int i = 0; i <= (polyCircleCount - 1) * 3; i += 3)
            {
                // 中心
                indices[i]      = 0;

                // 外側1
                indices[i + 1] = vertexId + 1;

                // 外側2
                if (vertexId != polyCircleCount - 1)
                {
                    indices[i + 2] = vertexId + 2;
                }
                else
                {
                    indices[i + 2] = 1;
                }
                vertexId++;

                // デバッグ用
                //Console.WriteLine("i: " + i + " c: " + vertexId);
            }

            effect = new BasicEffect(GraphicsDevice)
            {
                Projection = Matrix.CreatePerspectiveFieldOfView
                (
                    MathHelper.ToRadians(45),              // 視野角
                    GraphicsDevice.Viewport.AspectRatio,    // アスペクト比(横/縦)
                    1,     // ニアクリップ
                    100        // ファークリップ
                )
            };
        }

        // メモリからの解放
        protected override void UnloadContent()
        {
            effect.Dispose();
        }

        // 計算の更新
        protected override void Update(GameTime gameTime)
        {
            // キーボード制御
            KeyboardState keyboardState = Keyboard.GetState();

            if (keyboardState.IsKeyDown(Keys.Left))
            {
                angle -= MathHelper.ToRadians(2.0f); // 1/60秒に0.5°回転
            }
            if (keyboardState.IsKeyDown(Keys.Right))
            {
                angle += MathHelper.ToRadians(2.0f); // 1/60秒に0.5°回転
            }
            if (keyboardState.IsKeyDown(Keys.Up))
            {
                height += 0.1f; // 1/60秒に0.1移動
            }
            if (keyboardState.IsKeyDown(Keys.Down))
            {
                height -= 0.1f; // 1/60秒に0.1移動
            }
        }

        // 描画
        protected override void Draw(GameTime gameTime)
        {
            // 背景のクリア
            Color c = new Color(100, 110, 60);
            GraphicsDevice.Clear(c);

            // 両面描画
            GraphicsDevice.RasterizerState = RasterizerState.CullNone;

            // カメラの位置
            effect.View = Matrix.CreateLookAt
            (
                new Vector3
                (
                    3 * (float)Math.Sin(angle),
                    height,
                    3 * (float)Math.Cos(angle)
                ),
                new Vector3(0, 0, 0),   //カメラの注視点
                new Vector3(0, 1, 0)    //カメラのUPベクトル
            );

            // 三角形の描画
            foreach (var pass in effect.CurrentTechnique.Passes)
            {
                pass.Apply();

                // 3角形を実際に描画する
                GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionColor>(
                    PrimitiveType.TriangleList,
                    vertices,
                    0,
                    vertices.Length,
                    indices,
                    0,
                    indices.Length / 3
                    );
            }
        }
    }
}

 
中心の頂点だけ座標が(0, 0, 0)で、外周の頂点は Cos(角度) と Sin(角度) でXとY座標を出しています。
 
ちなみにこの記述では裏を向いて生成されてしまっていると思います。
(両面描画にしているので表示されていますが)