カウンタ

  インデックスバッファを使用したボックスの描画

Google
▲探し物はこちら

 今までは三角形や四角形など少ないポリゴンを使っていましたが、今回は少し多めに使用してボックスを作成します。また、効率よく頂点データを作成するために「インデックスバッファ」というものを使用してみたいと思います。

インデックスバッファを使用したボックスの描画

 下のリンクから今回のプロジェクトをダウンロードできます。

ファイル名 言語 サイズ バージョン
box_cs_1_1.zip C# 24KB 1.1
box_vb_1_1.zip VB.NET 30KB 1.1
box_cpp_1_1.zip C++/CLI 14KB 1.1

 今回のメインコードファイルを載せます。重要なコードを赤色で表示させています

MainClass.cs

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

namespace MDXSample
{
    /// <summary>
    /// メインサンプルクラス
    /// </summary>
    public partial class MainSample : IDisposable
    {
        /// <summary>
        /// 頂点バッファ
        /// </summary>
        private VertexBuffer _vertexBuffer = null;

        /// <summary>
        /// インデックスバッファ
        /// </summary>
        private IndexBuffer _indexBuffer = null;

        /// <summary>
        /// インデックスバッファの各頂点番号配列
        /// </summary>
        private static Int16[] _vertexIndices = new Int16[] { 2, 0, 1, 1, 3, 2, 4, 0, 2, 2, 6, 4,
            5, 1, 0, 0, 4, 5, 7, 3, 1, 1, 5, 7, 6, 2, 3, 3, 7, 6, 4, 6, 7, 7, 5, 4 };


        /// <summary>
        /// アプリケーションの初期化
        /// </summary>
        /// <param name="topLevelForm">トップレベルウインドウ</param>
        /// <returns>全ての初期化がOKなら true, ひとつでも失敗したら false を返すようにする</returns>
        /// <remarks>
        /// false を返した場合は、自動的にアプリケーションが終了するようになっている
        /// </remarks>
        public bool InitializeApplication(MainForm topLevelForm)
        {
            // フォームの参照を保持
            this._form = topLevelForm;

            // 入力イベント作成
            this.CreateInputEvent(topLevelForm);

            try
            {
                // Direct3D デバイス作成
                this.CreateDevice(topLevelForm);

                // フォントの作成
                this.CreateFont();
            }
            catch (DirectXException ex)
            {
                // 例外発生
                MessageBox.Show(ex.ToString(), "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }

            // 箱を作成するための頂点バッファを作成
            // 箱の頂点は8個
            this._vertexBuffer = new VertexBuffer(typeof(CustomVertex.PositionColored),
                8, this._device, Usage.None, CustomVertex.PositionColored.Format, Pool.Managed);

            // 8点の情報を格納するためのメモリを確保
            CustomVertex.PositionColored[] vertices = new CustomVertex.PositionColored[8];

            // 各頂点を設定
            vertices[0] = new CustomVertex.PositionColored(-2.0f, 2.0f, 2.0f, Color.Yellow.ToArgb());
            vertices[1] = new CustomVertex.PositionColored(2.0f, 2.0f, 2.0f, Color.Gray.ToArgb());
            vertices[2] = new CustomVertex.PositionColored(-2.0f, 2.0f, -2.0f, Color.Purple.ToArgb());
            vertices[3] = new CustomVertex.PositionColored(2.0f, 2.0f, -2.0f, Color.Red.ToArgb());
            vertices[4] = new CustomVertex.PositionColored(-2.0f, -2.0f, 2.0f, Color.SkyBlue.ToArgb());
            vertices[5] = new CustomVertex.PositionColored(2.0f, -2.0f, 2.0f, Color.Orange.ToArgb());
            vertices[6] = new CustomVertex.PositionColored(-2.0f, -2.0f, -2.0f, Color.Green.ToArgb());
            vertices[7] = new CustomVertex.PositionColored(2.0f, -2.0f, -2.0f, Color.Blue.ToArgb());

            // 頂点バッファをロックする
            using (GraphicsStream data = this._vertexBuffer.Lock(0, 0, LockFlags.None))
            {
                // 頂点データを頂点バッファにコピーします
                data.Write(vertices);

                // 頂点バッファのロックを解除します
                this._vertexBuffer.Unlock();
            }

            // インデックスバッファの作成
            // 第2引数の数値は(三角ポリゴンの数)*(ひとつの三角ポリゴンの頂点数)*
            // (16 ビットのインデックスサイズ(2byte))
            this._indexBuffer = new IndexBuffer(this._device, 12 * 3 * 2, Usage.WriteOnly,
                Pool.Managed, true);

            // インデックスバッファをロックする
            using (GraphicsStream data = this._indexBuffer.Lock(0, 0, LockFlags.None))
            {
                // インデックスデータをインデックスバッファにコピーします
                data.Write(_vertexIndices);

                // インデックスバッファのロックを解除します
                this._indexBuffer.Unlock();
            }

            // ライトを無効
            this._device.RenderState.Lighting = false;

            return true;
        }

        /// <summary>
        /// メインループ処理
        /// </summary>
        public void MainLoop()
        {
            // カメラの設定
            this.SettingCamera();


            // 描画内容を単色でクリアし、Zバッファもクリア
            this._device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.DarkBlue, 1.0f, 0);

            // 「BeginScene」と「EndScene」の間に描画内容を記述する
            this._device.BeginScene();


            // 頂点バッファをデバイスのデータストリームにバインド
            this._device.SetStreamSource(0, this._vertexBuffer, 0);

            // 描画する頂点のフォーマットをセット
            this._device.VertexFormat = CustomVertex.PositionColored.Format;

            // インデックスバッファをセット
            this._device.Indices = this._indexBuffer;

            // レンダリング(描画)
            this._device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 8, 0, 12);


            // 文字列の描画
            this._font.DrawText(null, "θ:" + this._lensPosTheta, 0, 0, Color.White);
            this._font.DrawText(null, "φ:" + this._lensPosPhi, 0, 12, Color.White);
            this._font.DrawText(null, "マウス位置:" + this._oldMousePoint, 0, 24, Color.White);

            // 描画はここまで
            this._device.EndScene();

            // 実際のディスプレイに描画
            this._device.Present();
        }

        /// <summary>
        /// リソースの破棄をするために呼ばれる
        /// </summary>
        public void Dispose()
        {
            // 頂点バッファを解放
            if (this._vertexBuffer != null)
            {
                this._vertexBuffer.Dispose();
            }

            // インデックスバッファを解放
            if (this._indexBuffer != null)
            {
                this._indexBuffer.Dispose();
            }

            // フォントのリソースを解放
            if (this._font != null)
            {
                this._font.Dispose();
            }

            // Direct3D デバイスのリソース解放
            if (this._device != null)
            {
                this._device.Dispose();
            }
        }
    }
}

 では、赤文字の部分を説明していきます。MainSamplePartial.cs ファイルのコードはこちらです。


 ボックスの構成について少し考えてみたいと思います。

 まずボックスは6つの面から成り立っており、ひとつの四角形の面は今までやってきたとおり、三角ポリゴン2つで構成されています。ということは三角ポリゴンは合計で「2×6=12」枚となります。さらに三角ポリゴンの頂点は3つなので頂点の合計は「12×3=36」個となります。なので、「VertexBuffer」のみで作成する場合は、36個のデータを箱の形になるように位置情報を決めて書き込めば、箱として表示することは可能です。(TriangleStrip でも24個必要)

 しかし、箱をイメージしてください。箱の角は8個です。位置情報としては8個で十分なはずです。頂点データというのは、数が増えてくるとメモリを圧迫します。これを何とか減らすために使用するのが「IndexBuffer」です。

 位置情報は8個でいいのですが、ポリゴンの頂点は必ず36個必要になります。そこで、8個の頂点データを共有してしまいましょうというのが「IndexBuffer」の使用目的になります。

ボックスインデックス


/// <summary>
/// 頂点バッファ
/// </summary>
private VertexBuffer _vertexBuffer = null;

/// <summary>
/// インデックスバッファ
/// </summary>
private IndexBuffer _indexBuffer = null;

/// <summary>
/// インデックスバッファの各頂点番号配列
/// </summary>
private static Int16[] _vertexIndices = new Int16[] { 2, 0, 1, 1, 3, 2, 4, 0, 2, 2, 6, 4,
    5, 1, 0, 0, 4, 5, 7, 3, 1, 1, 5, 7, 6, 2, 3, 3, 7, 6, 4, 6, 7, 7, 5, 4 };

 コードでは「IndexBuffer」が宣言されていますが、その下に「頂点番号配列」をあらかじめ作成しています。これは36個の頂点分配列を確保していますが、各パラメータの意味は、各三角ポリゴンの頂点が8個の頂点データのうちの、何番目の頂点データを使用するかという意味になります。よく見るとわかりますが、中身のデータは「0~7」の間のインデックスが書き込まれています。各数字を下のテーブルのように並び替えるとわかりやすいでしょう。

2, 0, 1 // 1枚目のポリゴン
1, 3, 2 // 2枚目のポリゴン
4, 0, 2 // 3枚目のポリゴン
2, 6, 4 // 4枚目のポリゴン
5, 1, 0 // 5枚目のポリゴン
0, 4, 5 // 6枚目のポリゴン
7, 3, 1 // 7枚目のポリゴン
1, 5, 7 // 8枚目のポリゴン
6, 2, 3 // 9枚目のポリゴン
3, 7, 6 // 10枚目のポリゴン
4, 6, 7 // 11枚目のポリゴン
7, 5, 4 // 12枚目のポリゴン

 ちなみに配列の型を「Int16[]」としていますが「short[]」でもかまいません(2バイト)。「int」(4バイト)で配列を作る場合もありますが、これは頂点数が「65535」を超える場合に使用します。そうでない場合は2バイトで配列を作成してください。4バイトだとメモリの無駄遣いになります。


// 箱を作成するための頂点バッファを作成
// 箱の頂点は8個
this._vertexBuffer = new VertexBuffer(typeof(CustomVertex.PositionColored),
    8, this._device, 0, CustomVertex.PositionColored.Format, Pool.Managed);

// 8点の情報を格納するためのメモリを確保
CustomVertex.PositionColored[] vertices = new CustomVertex.PositionColored[8];

// 各頂点を設定
vertices[0] = new CustomVertex.PositionColored(-2.0f, 2.0f, 2.0f, Color.Yellow.ToArgb());
vertices[1] = new CustomVertex.PositionColored(2.0f, 2.0f, 2.0f, Color.Gray.ToArgb());
vertices[2] = new CustomVertex.PositionColored(-2.0f, 2.0f, -2.0f, Color.Purple.ToArgb());
vertices[3] = new CustomVertex.PositionColored(2.0f, 2.0f, -2.0f, Color.Red.ToArgb());
vertices[4] = new CustomVertex.PositionColored(-2.0f, -2.0f, 2.0f, Color.SkyBlue.ToArgb());
vertices[5] = new CustomVertex.PositionColored(2.0f, -2.0f, 2.0f, Color.Orange.ToArgb());
vertices[6] = new CustomVertex.PositionColored(-2.0f, -2.0f, -2.0f, Color.Green.ToArgb());
vertices[7] = new CustomVertex.PositionColored(2.0f, -2.0f, -2.0f, Color.Blue.ToArgb());

 頂点バッファの作成は基本的に変わりはありません。ただし、頂点の数は36個ではなく8個だけですみます。


// インデックスバッファの作成
// 第2引数の数値は(三角ポリゴンの数)*(ひとつの三角ポリゴンの頂点数)*
// (16 ビットのインデックスサイズ(2byte))
this._indexBuffer = new IndexBuffer(this._device, 12 * 3 * 2, Usage.WriteOnly,
    Pool.Managed, true);

 「IndexBuffer」を使うので、IndexBuffer を作成しなければなりません。コンストラクタに渡すデータは下の通りです。

IndexBuffer コンストラクタ

インデックスバッファの作成
device Direct3D デバイスを渡します。
sizeOfBufferInBytes インデックスデータのバイト数を渡します。上記のコメントどおりの計算で、合計で「72」を渡します。
usage 使用法。今回は「Usage.WriteOnly」を指定
pool リソースを配置する有効なメモリ クラスの指定。特に無ければ「Pool.Managed」でよい。
sixteenBitIndices インデックス バッファが 16 ビットのインデックスを格納する場合は true を指定

// インデックスバッファをロックする
using (GraphicsStream data = this._indexBuffer.Lock(0, 0, LockFlags.None))
{
    // インデックスデータをインデックスバッファにコピーします
    data.Write(_vertexIndices);

    // インデックスバッファのロックを解除します
    this._indexBuffer.Unlock();
}

 「IndexBuffer」も「VertexBuffer」と同じように書き込むだけです。書き込むデータは、あらかじめフィールドで作成した配列データをそのまま渡します。ちなみに今回作成したインデックスの配列は、このプロジェクトで使用するボックス専用なので、形状が違うポリゴンになる場合は配列数やインデックスが変わりますので注意してください。


// カメラの設定
this.SettingCamera();

 カメラの設定をまとめました。今後カメラはマウスで操作することにします。


// 頂点バッファをデバイスのデータストリームにバインド
this._device.SetStreamSource(0, this._vertexBuffer, 0);

// 描画する頂点のフォーマットをセット
this._device.VertexFormat = CustomVertex.PositionColored.Format;

// インデックスバッファをセット
this._device.Indices = this._indexBuffer;

// レンダリング(描画)
this._device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 8, 0, 12);

 最初の2行については今までと同じですが、インデックスバッファ用のコードが追加されます。

 Device.Indices には使用するインデックスバッファをセットします。

 また描画メソッドには「Device.DrawIndexedPrimitives」というメソッドを使用します。

Device.DrawIndexedPrimitives メソッド

頂点データとインデックスからポリゴンを描画します。
primitiveType 描画するプリミティブの種類。今回は「PrimitiveType.TriangleList」を使用
baseVertex インデックス バッファの先頭から最初の頂点インデックスまでのオフセット。特に無ければ 0
minVertexIndex 呼び出しで使われる頂点の最小頂点インデックス。これも特に無ければ 0
numVertices 頂点データの数。基本的に頂点バッファに書き込んだ頂点数
startIndex インデックス配列における頂点の読み出し開始位置。特に無ければ 0
primCount レンダリングするプリミティブの数。今回三角形ポリゴン12枚なので「12

// 頂点バッファを解放
if (this._vertexBuffer != null)
{
    this._vertexBuffer.Dispose();
}

// インデックスバッファを解放
if (this._indexBuffer != null)
{
    this._indexBuffer.Dispose();
}

 最後に頂点バッファとインデックスバッファを破棄しています。

その他の関連情報です▼