SharpDX

ゲーム製作日誌 プログラム編 No.10-4 「GPU専用の頂点置き場VRAM」


 前々回は三角形の表現の方法、そして前回はそれをどのようにGPUに解釈させるかという方法について考えてきました。今回は、三角形の頂点データをGPUに送るためのGPU専用の領域を確保したいと思います。


1,頂点データはいつ送るか?

 実際に三角形を描画するために、前々回に用意した頂点配列をGPUに伝える必要があります。GPUには専用のビデオメモリが存在し、通常そこに頂点データやテクスチャを配置しておきます。

 C#で書いたプログラムを実行するのはCPUですが、三角形を描画するのはGPUという全く別の装置です。一般的にCPU・GPU間のデータのやりとりの速度は、それぞれが単独で動くよりも速くはないです。そのため、書きたいと思った時に毎回、C#のプログラムから描画させたい三角形を送信していると大きなタイムロスになってしまいます。実際、ゲームは1秒間に60回のペースで画面に描画する必要が有るため頻繁な送信はタイムロスになります。

 そこで、三角形の描画が必要だとわかり三角系の形が確定した段階でGPUの専用メモリにその三角形の頂点データを送っておきます。こうしておけば、C#のプログラムからはGPUに対して専用メモリにある三角形を描画するよう指令を出すだけで、遅い転送時間をかけることもなく効率的に描画が行えるようになります。しかもこの三角形の頂点データは常に置きっぱなしになるため、次に三角形を描画したい時には使い回すこともできるわけです。つまり次からは転送しなくてもいいわけです。

 もちろん描画したい三角形の形が変わってしまった場合には、頂点データの使い回しが効かないので頂点データをVRAMに転送し直す必要があります。

 同じ理屈で言うと、人間の体のように瞬間瞬間で形が変わっていくようなものだったら、描画する前に毎回三角形の頂点を送り直さなければいけないことになります。しかし、そういうような一部は変形しないような変形は変形の仕方を転送することで三角形を送り直す必要がなくなるのですがそれはまた今度扱います。


2,頂点バッファの確保 

 効率よく描画をするためには、GPUのメモリ(VRAM)に三角形の頂点データを予め置いておきたいのです。その置き場所を表すものが「頂点バッファ(VertexBuffer)」です。頂点バッファは、使いたいサイズや目的を指定して作成します。

10-4-1
  • 2行目には、使いたいサイズ(バイト数)を書く

    前回作成した頂点構造体PositionColorは、1つあたり28バイトを要していました。その点を3つつなげて三角形を表現するので28×3 = 84バイト必要になります。
    このように毎回計算するのは大変なので、頂点配列の要素数vertex.Lengthと構造体に要するバイトサイズをMarshal.SizeOf()を利用して求めたものを掛け算して、全体的に必要なバイトサイズを求めています。


  • 3行目には、頂点バッファに読み書きするかを書く

    頂点バッファには、頂点バッファに書かれた情報を読み取るReadと、頂点バッファに情報を書き込むWriteができます。今回は描画したい三角形の頂点データを予め送っておくだけでよく、その三角形を改めて読み返す必要もないためUsage.WriteOnly(書き込みのみ)を指定します。


  • 4行目には、頂点の各要素の使用目的を|(OR演算)でつなげて並べて書く

    例えば、前回作成した頂点構造体PositionColorは、座標(Position)と色(Color)を要素として持っていたので、頂点バッファ作成時にはVertexFormat.Position | VertexFormat.Colorと指定してあげます。
    他にも法線(VertexFormat.Normal)やテクスチャ座標(VertexFormat.Texture0~8)があります。


  • 5行目には、転送する頂点の保管方法を書く

    transfer

    保管方法は、Direct3D9で発生してしまうロストデバイスという現象に大きく関係しています。
    ロストデバイスは、「Ctrl+Alt+Delete」を押した時やディスプレイを切り替えた時に、VRAMに送ったデータが消えてしまう現象です。ようは今から送ろうとしている頂点もその際に消えてしまうということです。
    Pool.Defaultを指定するとCPUからVRAMに頂点を転送したっきり、ロストデバイスが起きてしまっても送った頂点を元に戻そうという機能が働きません。
    default

    Pool.Managedを指定するとCPUからVRAMに頂点を転送した後も、CPUは送ったデータを持ち続けます。それを使って、ロストデバイスが発生してVRAMからデータが消えてしまった時も再転送をして対処してくれます。通常はこちらを使っておいたほうが無難だと思います。
    managed


以上を踏まえたコードが以下です。
//頂点バッファ
VertexBuffer vertexBuffer = new VertexBuffer(D3Device,
vertex.Length * Marshal.SizeOf(typeof(VertexType)),
Usage.WriteOnly,
VertexFormat.Position,
Pool.Managed);
 
3,まとめ

 CPU側で作成した頂点をGPUに伝えるためには、まずGPUの専用メモリであるVRAMに頂点データを転送する必要があります。しかし、その転送にかかるコストは大きいためできるだけ一回転送した頂点データを三角形の形が変わらないかぎり使いまわすことが必要です。
 そこでVRAM上で自由に使える場所である頂点バッファを確保して、そこに頂点データを転送することにしました。頂点データ作成時で特に大切だった引数はPoolで、Managedに設定するとロストデバイスが発生しても転送しなおしを考慮しなくて良くなるという設定でした。


 今回で頂点データの置き場所(頂点バッファ)が確保できたので、次回は実際にそこに頂点データを移動する方法を扱います。

 

ゲーム製作日誌 プログラム編 No.10-3 「GPUにヘルプを渡す」


 前回は画面に表示させる頂点をデータとして用意したので、今回はそれをGPUにも読んで解釈できるように、読み方(ヘルプ)を伝える処理を扱っていきます。


1,頂点の構成情報をまとめる意味とは


 前回、頂点一つ一つを構造体としてまとめてデータを作成しました。それを実際に三角形の描画を行うGPUに転送する必要があります。

vertexwhat
 しかし、転送するときは頂点一つ一つを区切ってわかるように書き込まれるのではなく、すべての頂点が連続してメモリに書き込まれていきます。また座標や色等のデータも続けて書き込まれるために、もはやデータが送られてきたGPUにはどこをどう読めばいいのかわからない状態になってしまいます。


vertexgive
 それでもGPUに、読み方の指南書を渡してあげれば話は別です。上図のような情報を与えておけば「0~12の位置に座標が書いてあって、12~28には色が書いてあるんだな」と解釈できるわけです。
 そのため、頂点の座標や色がどの部分に書き込まれているのかを事前にGPUに伝えることが必要となるのです。今後はその情報のことを「頂点の構成情報」と呼びますが、まずはその情報の作り方から見て行きましょう。



2,頂点の構成情報を作る

 頂点の構成情報を作るためには、まず頂点の構成要素(座標や色)一つ一つがどこに書き込まれていて、どんな役割を持つのかをVertexElementを作成して決めていきます。


VertexEle
 これがVertexElementのコンストラクタです。いま気にするべきは2,3,5行目です。
 
  • 2行目には、データが始まる番地を書く
    vertexgive
    例えば、座標はこの表でいうと「0」番目から始まっています。ですので座標用のVertexElementを作成するときは、2行目の引数を「0」に設定するわけです。色でしたら「12」になりますね

  • 3行目には、データの型を書く

    データの型は、C#でいうint,float,string等と同じものです。3つ要素(xyz)があるものなら「.Float3」と、4つ要素(rgba,xyzw)があるものなら「.Float4」と設定します。つまり座標なら3の方で、色なら4ですね。

  • 4行目には、使用目的を書く

    座標だったら「Position」とか、色だったら「Color」と書いておきます。まんまです。


 まとめると以下の様な定義をしてあげれば良いわけです。
//座標用
new VertexElement(0, 0, DeclarationType.Float3, DeclarationMethod.Default, DeclarationUsage.Position, 0)

//色用 new VertexElement(0, 12, DeclarationType.Float4, DeclarationMethod.Default, DeclarationUsage.Color, 0)


 次に、作った情報をすべて配列にまとめます。その際、配列の最後の要素には終わりの合図としてVertexElement.VertexDeclarationEndをいれておきます。作った配列は、前回作った頂点構造体の中に入れておくと扱いが楽です。
[StructLayout(LayoutKind.Sequential)]
public struct PositionColor
{
    public Vector3 Position;
    public Vector4 Color;
    public static readonly VertexElement[] Elements =
{
new VertexElement(0, 0, DeclarationType.Float3, DeclarationMethod.Default, DeclarationUsage.Position, 0), new VertexElement(0, 12, DeclarationType.Float4, DeclarationMethod.Default, DeclarationUsage.Color, 0), VertexElement.VertexDeclarationEnd
}; }


 ここまでで作ったVertexElement配列を使って、VertexDeclarationというものを作成します。これがGPUに与える指南書になります。VertexDeclarationは、Direct3D9デバイスを初期化してからでなければ作れません。
VertexDeclaration vertexDec = new VertexDeclaration(D3Device, PositionColor.Elements);


4,まとめ

 GPUには、頂点データを連続的に書き並べたバイト列のみが転送されてくるため各要素の位置や意味をそれだけでは解釈することができません。
 そこで、あらかじめ解釈するためのヘルプのようなものを渡しておくことで、GPUにも頂点の意味を理解することが可能になります。今回は、そのヘルプの作成の仕方を扱いました。
 次回は、解釈の仕方が分かったGPUに頂点データを送ってみようと思います。

P.S.
 今回が一番の山場だったので次回は今回苦労した分楽になる予定?
 

ゲーム製作日誌 プログラム編 No.10-2 「頂点配列の用意」


 Direct3Dの複雑さが見えた前回に引き続き、今回は画面に表示させる頂点の用意をするところまで解説していきます。


1,頂点の構造体を用意する


 画面に三角形を描くことを目的としているため、まずはどのような三角形を描くかを定義する必要があります。

 Direct3Dでは、三角形自体に情報を与えずにそれを構成する3つの頂点1つ1つに情報を与えてそのような三角形にするのか定義します。

tri_vertex


 3つの頂点に別々に情報を与えて描画させると、Direct3Dは点の間の情報を補間して三角形内部の様子がしだいに移り変わっていくように描画してくれます。図の場合は色がグラデーションがかかったかのように描画されています。点の色を変えるだけでグラデーションできるなんて簡単でいいですね。

 この情報の補間機能ですが、色だけではなくほかにもテクスチャ座標や法線ベクトル等にも適応されます。そのことは今後扱っていきましょう。




 さて、三角形を作るには頂点を決めればいいということなので、まずは頂点1つをあらわす構造体を作成します。


[StructLayout(LayoutKind.Sequential)] public struct PositionColor { public Vector3 Position; public Vector4 Color; }

 Vector3やVector4型は、実数型floatを数個そなえているもので座標や色をあらわすときに使います。座標であれば順にx,y,zと割り当て、色であればr,g,b,aというように割り当てます。

 つまり、この構造体は頂点に座標と色を与えることができるそういった定義なわけです。


 構造体の前につけた
[StructLayout(LayoutKind.Sequential)]
は構造体の中身をメモリに記録する時に、定義した順に配置することを約束する属性です。おまじないなのでつけておきます。




2,頂点配列の作成

 構造体を定義し、頂点を表すことができるようになったため描画したい三角形の3つの頂点を表現したいと思います。

PositionColor[] tri_vertices = new PositionColor[3];

tri_vertices[0] = new PositionColor();
tri_vertices[0].Position = new Vector3(0f, 0.5f, 0f); //上
tri_vertices[0].Color = new Vector4(255f / 255f, 201f / 255f, 14f / 255f, 1f); //オレンジ

tri_vertices[1] = new PositionColor();
tri_vertices[1].Position = new Vector3(-0.5f, -0.5f, 0f); //左下
tri_vertices[1].Color = new Vector4(255f / 255f, 201f / 255f, 14f / 255f, 1f); //オレンジ

tri_vertices[2] = new PositionColor();
tri_vertices[2].Position = new Vector3(0.5f, -0.5f, 0f); //右下
tri_vertices[2].Color = new Vector4(255f / 255f, 0f / 255f, 0 / 255f, 1f); //赤

 先ほど定義した構造体の要素数3の配列を作成し、3つの頂点を決めました。座標や色は以下の形式に従って入力していきます。
vertex.Position = new Vector3(X座標, Y座標, Z座標);
vertex.Color = new Vector4(赤成分, 緑成分, 青成分, 透明度);
 ここで注意しなくてはならないことは、色成分は0~1の範囲に直さなければいけないことと、XYZ座標空間が多少変わっていることの2点です。
proj_space
 黒枠は、画面の枠です。そうしたとき画面の左上が(-1,1)、右下が(1,-1)となるような空間が設定されていてそこに三角形を配置していくことになります。間違っても画面のピクセル座標にあわせないようにしてください。

 この変わった座標配分の話は、今後変換行列のところですることになると思います。




3、まとめ

 今回で表示する三角形の頂点を定義することができました。次回はこの配列のデータをグラフィックボードに転送する処理を扱っていきます。

ゲーム製作日誌 プログラム編 No.10-1 「ポリゴン描画の全体の流れ」


 今回と次回では、Direct3Dを使って画面に三角形を描画するまでを扱っていきたいと思います。やることにしては意外とやることが多く、また後々の基本になることになるので何回かに分割して説明していきます。


1,ポリゴン描画の流れ

 「ポリゴン」とは、複数の点を結んだ線で囲まれた多角形のことを言います。

 例えば「三角ポリゴン」といえば、3つの頂点を結んだ三角形のことでして、今後はその三角形のみを扱っていきます。なぜ三角ポリゴンしか使わないかといえば、自分たちが使おうとしているDirect3Dが三角形の表示しか扱っていないからです。基本的にどんな図形でも三角形を何個も並べれば形作れるのでそういう仕様になっているのでしょうね。

polygon


 そんな三角形をDirect3D9で描画するには以下の手順を踏まなければいけません。

  1. 頂点自体のデータを用意する

    ・頂点1要素を表す構造体を定義する

    ・構造体の配列を作って全頂点のデータを作成する


  2. 頂点データが記録された位置を定義する

    ・頂点の各項目がどの位置に記録されているかをVertexElementで指定する

    ・指定したVertexElementでVertexDeclarationを作成する


  3. 頂点をグラフィックボードに送るための準備

    ・専用の格納場所VertexBufferを作成する

    ・頂点の配列をVertexBufferに書き込む


  4. 頂点を画面に表示するためのシェーダを用意する

    ・シェーダをコンパイルする

    ・シェーダの実体であるEffectを作成する


  5. 画面に描画する

    ・描画対象の空間(世界)の状況を設定する

    ・描画する


  6. 不必要なものを破棄する

    ・VertexBufferとVertexDeclarationとEffectを破棄する



 全体的に見ても、たかが三角形の描画でいろいろな準備が必要となっています。はっきりいってめんどうくさそうですね。
 でも拡張性つまり多種なシーンを作り出すためにここまで複雑な作りになっているわけですから、基本を大切にすべく次回からは一つ一つじっくり解説していきます。 

ゲーム製作日誌 プログラム編 No.9 「Direct3D9デバイスの作成」

今回は、前回解説して出現させた「ウィンドウフォーム」からDirect3D9デバイスを作成する方法を解説します。


1、デバイスの作成方法

 Direct3D9デバイスの作成手順は以下のとおりになっています。

//Direct3D9デバイスを作成する
Device device; //代入用変数を用意
Direct3D direct3D = new Direct3D(); //Direct3Dインターフェースを作る(おまじない)
PresentParameters pp = new PresentParameters(); //デバイス作成にあたっての設定を容易

...

//上記で設定したパラメータでデバイスを作成
device = new Device(direct3D, 0, DeviceType.Hardware, frm.Handle, CreateFlags.HardwareVertexProcessing, pp);

 ちなみに、このコードは前回配布したソースコードを抜粋したものです。各行について細かく説明をしていきます。

  1.  Direct3Dインターフェースを作成する(おまじない)
    Direct3D direct3D = new Direct3D(); //Direct3Dインターフェースを作る(おまじない)
     SharpDX.Direct3D9名前空間内Direct3Dクラスのインスタンスを作成しておきます。このインスタンスがないと、Direct3D9デバイスが作成できないので必ず作成しておく必要があります。


  2. 描画設定リストを作る
    PresentParameters pp = new PresentParameters(); //デバイス作成にあたっての設定を容易
     Direct3D9デバイスを作成するには、描画設定を予め伝えておかなくてはなりません。そのため、描画設定を格納する変数を用意します。型はそれ専用のPresentParameters(名前空間:SharpDX.Direct3D9)です。

  3. 描画設定(バックバッファ)を設定する
    //バックバッファ関連
    pp.BackBufferCount = 1; //バックバッファを一枚用意
    pp.BackBufferFormat = Format.A8R8G8B8; //バックバッファに保管する色要素はアルファ抜きRGB
    pp.BackBufferWidth = frm.ClientSize.Width; //バックバッファの幅
    pp.BackBufferHeight = frm.ClientSize.Height; //バックバッファの高さ
     バックバッファとは、画面に直接描画するのではなく、代わりにメモリに書き込む機能です。この機能を利用することでCPUの処理とGPUの処理を分離して、全体的な流れの高速化が見込めます。また、メモリに完全に書き込んでから一気に画面に表示するので”ちらつき防止”の意味もあります。
     設定はソースコードを参照してください。バックバッファの大きさは、フォームの大きさを指定してください。

  4. 描画設定(Zバッファ)を設定する
    //Zバッファとステンシルバッファの設定
    pp.AutoDepthStencilFormat = Format.D24X8; //Zバッファに24ビット,ステンシルバッファに8ビットの精度を与える
    pp.EnableAutoDepthStencil = true; //Zバッファとステンシルバッファを有効にする
     Zバッファとは、一度書き込んだポリゴンのピクセルの遠さを記録しておくメモリです。これを有効にしておくと、遠くのものから近くのものにと順序に沿って物体を描画する必要がなくなります。Format.D16もありますが、精度不足に陥ることがよくあるのでD24X8を指定してください。

  5. 描画設定(ウィンドウかフルスクリーンか)を設定する
    //描画ウィンドウの設定
    pp.Windowed = true; //ウィンドウモードで設定
     画面にどのように絵を描画するか設定します。この設定をfalseにするとフルスクリーンモードで描画ができるのですが、今回はウィンドウフォームを用いた描画なので解説しません。

  6. 描画設定(フレーム更新タイミング)を設定する
    //フレーム更新タイミング関連
    pp.PresentationInterval = PresentInterval.Default; //ディスプレイの垂直同期に合わせて更新
     描画した絵をどのタイミングで画面に表示させるか設定します。Defaultでは、ディスプレイの表示更新タイミングに合わせて表示させます。Immediateでは、ディスプレイを待たずに直ちに画面に反映させます。
     このような性質上、ディスプレイの表示周波数によって設定をDefaultにした場合は、FPSがそれを超えることはありません。

  7. 描画設定(アンチエイリアス)を設定する
    //アンチエイリアス設定
    pp.SwapEffect = SwapEffect.Discard;
    pp.MultiSampleType = MultisampleType.None; //アンチエイリアス無効
     描画したポリゴンの縁がピクセル単位にまとめられてギザギザする減少を回避する機能がアンチエイリアスです。サンプルでは無効にしましたが、有効にしたい場合は、
    pp.MultiSampleType = MultisampleType.FourSamples; //アンチエイリアス有効
    などと指定してください。

  8. デバイスを作成する
    //上記で設定したパラメータでデバイスを作成
    device = new Device(direct3D, 0, DeviceType.Hardware, frm.Handle, CreateFlags.HardwareVertexProcessing, pp);
    Direct3Dインターフェースと、フォームのハンドル、描画設定リストを使ってデバイスのコンストラクタを呼び出します。これが完了すれば、Direct3D9デバイスを取得することができ、いよいよ画面への描画が可能になってくるのです。 

2、総括

 今回はDirect3D9デバイスの作成方法と、そのための描画設定リストの設定項目について確認しました。設定項目の中には、解説不足なものも多くあったと思うので、今後それぞれ詳しく解説していきたいと思います。
 次回からはこのDirect3D9デバイスを利用して画面への描画へと進んでいきます。
twitterはこちら
ニコ生放送します
ギャラリー
  • ウマモサク
  • ウマモサク
  • ウマモサク
  • 3/17 制作記録
  • 3/17 制作記録
  • C91告知
  • C91告知
  • C91告知
  • C91告知
アクセスカウンター
  • 今日:
  • 昨日:
  • 累計:

  • ライブドアブログ