今回は前回説明できなかった部分を細かく説明して行こうと思います。
射影座標で考えよう
前回、三角形を画面に描画しました。その時に以下のように3つの座標を作りましたね。
1 2 3 4 5 6 |
// 三角形を作るため、頂点を3つ作る VertexType v[3] = { {{ 0, 0, 0}}, // 頂点座標0 {{ 1,-1, 0}}, // 頂点座標1 {{-1,-1, 0}}, // 頂点座用2 }; |
1つ目が(0, 0, 0)、2つ目が(1, -1, 0)、3つ目が(-1, -1, 0)です。順番に(x, y, z)座標です。
この座標はみなさんがよく知っているピクセル座標ではなく、射影座標で指定する必要があります。
座標とは皆さん知っての通り、数学のグラフと時と同じで右がXで上がYです。それをそのまま画面に当てはめるの上図のようになります。
- 画面の真ん中が(0, 0, 0)。
- X座標やY座標は-1~1の範囲。それ以外は画面外になる。
一般的なパソコンのペイントソフトなどはこの座標系ではなく、ピクセルで表現されることが多いのでちょっと違和感があるかもですね。
今回の画面は1280x720の解像度でDirectXを動作させていますが、射影座標は画面の解像度関係なく、いつでも-1~1です。
頂点の順番にも意味があります。上図のように時計周りになるようにしましょう(左手座標系)。反時計回りにすると裏面になってしまいます。そして特別な設定をしない限り、裏面を向いている面は透明になり見えなくなってしまいます。
シェーダーでの描画について
前回は以下のようなシェーダーを書きましたね。この頂点シェーダーとピクセルシェーダーはいったい何をやっているのでしょうか?ちょっと見てみましょう。
前回書いたシェーダープログラム
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
// 頂点シェーダーから出力するデータ struct VSOutput { float4 Pos : SV_Position; // 頂点の座標(射影座標系) }; //======================================== // 頂点シェーダー //======================================== VSOutput VS(float4 pos : POSITION) { VSOutput Out; // 頂点座標を、何も加工せずそのまま出力 Out.Pos = pos; return Out; } //======================================== // ピクセルシェーダー //======================================== float4 PS(VSOutput In) : SV_Target0 { // 緑色にする return float4(0, 1, 0, 1); } |
今回は三角形を描画しました。つまり頂点は3つ使用してますよね。この場合頂点シェーダーは3回実行されます。
頂点シェーダーを別に例えるなら、頂点データ加工工場だと思ってください。この3つの頂点が1つずつベルトコンベアで流れてくるのです。でも今回は特になにも加工をせず次の工場へ出荷しています。それがこの部分です。
Out.Pos = pos; // ←posに座標が来ます。それをそのまま出荷します。
もちこん、ここで何かしら座標を変更するようなプログラムを書けば、自由自在に頂点の座標を変えることができます。3Dゲームにしたい場合は立体的になるように変化させば良いのです。
そして、ピクセルシェーダーはすごく単純で、ただ緑色を出荷しているだけなんです。ピクセルシェーダーも別に例えるとピクセル着色工場ですね。プログラムではこの部分ですね。
return float4(0, 1, 0, 1); // ← 緑色を作ってそれを出荷します。するとこのピクセルは緑色になります。
頂点は3つだけでしたが、ピクセルは下図のようにいっぱいできあがります。この数ぶんピクセルシェーダーが実行されているのですよ。グラフィックスの処理を行ってくれるGPUはものすごく高速なのでこの程度なら一瞬で完了しちゃいます。
頂点1つ1つを加工し次の工程へ。それが面となって大量のピクセルが生まれる。それら全てに色を塗る。といった手順がパソコン内で動いてるんです。パソコンすごいですよね。
今回はものすごく単純なプログラムにしたので緑一色だけでしたが、ここをもっとプログラムすれば美麗な表現ができるようになります。この記事のトップに掲載しているキャラクター(ハッカドール1号)や背景が映っている画像も、このシェーダープログラムで表現しています。
四角形にしてみよう
2Dゲームを作るとしたら、三角形はちょっと扱いにくいです。通常は四角形がいいですよね。四角形の面に画像を乗っければ、キャラクターなど何かしら画像を表示できますからね。
パソコンの画像って、通常は長方形ですよね。
四角形のやり方は簡単です。頂点を1つ増やして4つにするだけです。下図の結果になるようにプログラムを変更してみましょう。
まずは頂点を作成している部分を変更
1 2 3 4 5 6 7 |
// 四角形を作るため、頂点を4つ作る VertexType v[4] = { {{-0.5f, -0.5f, 0}}, {{-0.5f, 0.5f, 0}}, {{ 0.5f, -0.5f, 0}}, {{ 0.5f, 0.5f, 0}}, }; |
描画命令では頂点を4つ描画するようにしよう!
1 2 3 4 5 |
//----------------------------- // 描画実行 //----------------------------- // デバイスコンテキストくん、上記のセットした内容で描画してください D3D.m_deviceContext->Draw(4, 0); |
D3D.m_deviceContext->Draw( 4 , 0 );
デバイスコンテキストのDraw関数の第一引数には、描画に使用する頂点数を指定するのです。
プリミティブ・トポロジについて
頂点をたくさん作って面を複数描画する時は、ちょっと注意が必要になります。射影座標の説明の時にこんなこと言ってましたよね。
「頂点の順番は時計周りになるようにしましょう」
確かに1枚目の三角形の0,1,2番目の頂点は時計回りになっていますが、2枚目の三角形の1,2,3番目の頂点は反時計周りになってますよね。なんで?と疑問に思うでしょう。
こういった「頂点をどのように使用するか?」といった設定をプリミティブ・トポロジと言い、今回はトライアングルストリップという設定にしてます。プログラムではこの部分ですね。
1 2 |
// プロミティブ・トポロジーをセット D3D.m_deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); |
プリミティブ・トポロジにはいろいろ種類があるのですが、今回は以下の4種類を紹介しましょう。
D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP | トライアングルストリップ ・(0,1,2)番目の頂点で三角形1枚(時計回りが表面になる) ・(1,2,3)番目の頂点で三角形1枚(反時計回りが表面になる) ・(2,3,4)番目の頂点で三角形1枚(時計回りが表面になる) ・(3,4,5)番目の頂点で三角形1枚(反時計回りが表面になる) といったように、頂点を1つずつずらして三角形として描画していく方法。 特徴としては、偶数面が時計回りで表面となり、奇数面が反時計回りで表面になる。 ちょっとややこしいですが、レーザービームなどの連続して連なったものを少ない頂点数で描画したい場合はこれが便利。逆にいうと連続したものでしか使えない。 |
D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST | トライアングルリスト ・(0,1,2)番目の頂点で三角形1枚(時計回りが表面になる) ・(3,4,5)番目の頂点で三角形1枚(時計回りが表面になる) ・(6,7,8)番目の頂点で三角形1枚(時計回りが表面になる) といったように、単純に頂点3つずつ使用し、時計回りで表面の三角形として描画していく方法。 四角形を描画するには頂点が6つ必要になってしまいますが、三角形が分離したような非連続の表現もできる。キャラクターのような複雑なものはこれを使用する(メッシュという)。 |
D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP | ラインストリップ ・(0,1)番目の頂点で線1本 ・(1,2)番目の頂点で線1本 ・(2,3)番目の頂点で線1本 ライン系は線を描くモードです。デバッグ表示などで活躍します。 連続した線を少ない頂点で描画したい場合はこれが便利 |
D3D11_PRIMITIVE_TOPOLOGY_LINELIST | ラインリスト ・(0,1)番目の頂点で線1本 ・(2,3)番目の頂点で線1本 ・(4,5)番目の頂点で線1本 ライン系は線を描くモードです。デバッグ表示などで活躍します。 非連続の線も描画したい場合はこっち。 |
この4種類を覚えておこう!
あと文章だけじゃあわかりにくいので、図も用意しましたよ。
頂点を描画するにはバッファリソースとして作成する必要がある
VertexType v[4] = …のように頂点用の配列データを作っても、実は描画には使用できません。描画で使える頂点を作成するにはグラフィックス専用のメモリに作る必要があるのです。DirectXではバッファリソースでこれを実現できます。特に頂点用のバッファリソースのことを頂点バッファと呼びます。
バッファリソースを作成している場所(作成時にvの内容も書き込んでいます)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
//----------------------------- // 頂点バッファ作成 // ・上記で作った3つの頂点はそのままでは描画に使用できないんす… // ・ビデオメモリ側に「頂点バッファ」という形で作る必要があります! // ・今回は効率無視して、その場で作って、使って、すぐ捨てます。 //----------------------------- // 作成するバッファの仕様を決める // ・今回は頂点バッファにするぞ!って感じの設定 D3D11_BUFFER_DESC vbDesc = {}; vbDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; // デバイスにバインドするときの種類(頂点バッファ、インデックスバッファ、定数バッファなど) vbDesc.ByteWidth = sizeof(v); // 作成するバッファのバイトサイズ vbDesc.MiscFlags = 0; // その他のフラグ vbDesc.StructureByteStride = 0; // 構造化バッファの場合、その構造体のサイズ vbDesc.Usage = D3D11_USAGE_DEFAULT; // 作成するバッファの使用法 vbDesc.CPUAccessFlags = 0; // 上の仕様を渡して頂点バッファを作ってもらう // もちろんデバイスさんにお願いする ComPtr<ID3D11Buffer> vb; D3D11_SUBRESOURCE_DATA initData = { &v[0], sizeof(v), 0}; // 書き込むデータ // 頂点バッファの作成 D3D.m_device->CreateBuffer(&vbDesc, &initData, &vb); |
今回は簡単にするため、毎回作って、セットして描画、その後破棄 を繰り返しています。これはかなり非効率な方法なので推奨されません。
頂点バッファに限らず、描画で使用するデータはほとんどこのバッファリソースとして作成する必要があります。
そして、作った頂点バッファを描画で使用するためには、デバイスコンテキストさんへ渡してあげればよいです。
頂点バッファをセットする方法
// 頂点バッファを描画で使えるようにセットする
UINT stride = sizeof(VertexType); // ← 1頂点のバイトサイズを教えてあげる必要がある。
UINT offset = 0;
D3D.m_deviceContext->IASetVertexBuffers(0, 1, vb.GetAddressOf(), &stride, &offset); // デバコンへ頂点バッファをセット!
デバイスコンテキストに描画をお願いするまでの道のり
頂点バッファも作ったし、シェーダーも作った。あとはこれらを描画の職人であるデバイスコンテキストに渡してあげて、「描画してね!」ってお願いすれば描画完了です。
- 頂点バッファはこれを使ってね!(IASetVertexBuffers)
- ちなみに頂点はこんな感じで使ってほしいな!(IASetPrimitiveTopology)
- あと、これらの頂点は、こんな計算で変換してください!(VSSetShader)
- あ、頂点はこんな構造になってるから。(IASetInputLayout)
- 面になったときは、こんな計算で色を付けて。(PSSetShader)
- よし、頂点をn個ぶん描画しろ!(Draw)
これが全てではありません。必要に応じてセットする内容がもっと増えます。