忍者ブログ

Memeplexes

プログラミング、3DCGとその他いろいろについて

DirectX10 + C++/CLI 三角形の描画

[注意:まだ書きかけです!]

Direct3D10でのポリゴン(三角形)の描画をやってみました。
3DCGではポリゴンの表示は基本ですから。

解説はグダグダになるでしょうから、まずはコードと実行結果をのせましょう。


DX10Test.cpp

#using<System.Windows.Forms.dll>
#using<System.dll>

#include<d3d10.h>
#include<d3dx10.h>

using namespace System;
using namespace System::Windows::Forms;


ref class Game : Form
{
    ID3D10Device* graphicsDevice;
    IDXGISwapChain* swapChain;
    ID3D10RenderTargetView* renderTargetView;


    ID3D10Effect *effect;
    ID3D10EffectTechnique *effectTechnique;
    ID3D10InputLayout* vertexLayout;
    ID3D10Buffer* vertexBuffer;

public:
    void Run()
    {
        InitDevice();
        Show();

        while(Created)
        {
            Draw();
            Application::DoEvents();
        }
    }

private:

    void InitDevice()
    {
        createGraphicsDevice();

        createRenderTargetView();

        setupViewport();

        LoadGraphicsContent();
    }

    void LoadGraphicsContent()
    {
        //シェーダーの設定
        //「グラフィックスカードへおくる頂点バッファが
        //どのように処理されて画面に表示されるか」
        //を表すエフェクト・オブジェクトを
        //(外部のHLSLで書かれたファイルMyShader.fxから)作り出します。
        ID3D10Effect* effect;
        HRESULT result = D3DX10CreateEffectFromFile(
            "MyShader.fx",
            NULL, 
            NULL,
            "fx_4_0",
            D3D10_SHADER_ENABLE_STRICTNESS,
            0,
            graphicsDevice,
            NULL,
            NULL,
            &effect,
            NULL,
            NULL
            );

        if(FAILED(result))
            throw gcnew Exception("couldn't create effect object");

        this->effect = effect;
        this->effectTechnique = effect->GetTechniqueByName("MyTechnique");



        //描画のときデバイスに入力する頂点データのレイアウトの設定
        //普通なら頂点データは座標のほかに、色やテクスチャ座標を持っていますが、
        //ここでは話を単純にするために座標データのみです。
        D3D10_INPUT_ELEMENT_DESC vertexElements[] = 
        {
            {"SV_POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0}
        };

        D3D10_PASS_DESC passDesc;
        effectTechnique->GetPassByIndex(0)->GetDesc(&passDesc);

        ID3D10InputLayout *vertexLayout;
        result = graphicsDevice->CreateInputLayout(
            vertexElements,
            1,
            passDesc.pIAInputSignature,
            passDesc.IAInputSignatureSize,
            &vertexLayout
            );

        if(FAILED(result))
            throw gcnew Exception("couldn't create InputLayout");

        this->vertexLayout = vertexLayout;

        graphicsDevice->IASetInputLayout(vertexLayout);



        //頂点バッファの設定
        //3Dモデルの頂点データを格納する頂点バッファを
        //グラフィックスカード内に作り、
        //それをDrawのとき使われるようにセットします。
        D3DXVECTOR3 vertices[] = 
        {
            D3DXVECTOR3(0, 0.5f, 0),
            D3DXVECTOR3(0.5f, 0, 0),
            D3DXVECTOR3(-0.5f, 0, 0)
        };

        D3D10_SUBRESOURCE_DATA initData;
        initData.pSysMem = vertices;

        D3D10_BUFFER_DESC bufferDesc;
        ZeroMemory(&bufferDesc, sizeof(bufferDesc));
        bufferDesc.Usage = D3D10_USAGE_DEFAULT;
        bufferDesc.ByteWidth = sizeof(D3DXVECTOR3) * 3;
        bufferDesc.BindFlags = D3D10_BIND_VERTEX_BUFFER;

        ID3D10Buffer *vertexBuffer;

        result = graphicsDevice->CreateBuffer(
            &bufferDesc,
            &initData,
            &vertexBuffer
            );

        if(FAILED(result))
            throw gcnew Exception("couldn't create Vertex Buffer");

        this->vertexBuffer = vertexBuffer;

        unsigned int stride = sizeof(D3DXVECTOR3);
        unsigned int offset = 0;
        graphicsDevice->IASetVertexBuffers(0, 1, &vertexBuffer, &stride, &offset);



        //頂点バッファが三角形のリストとして描画されるようにセットします。
        //ここでは三角形のリストですが、ほかにも
        //点や線のリストとして描画されるようにセットすることもできます。
        graphicsDevice->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    }

    ~Game(){ this->!Game(); }

    !Game()
    {
        if(graphicsDevice) graphicsDevice->ClearState();

        if(vertexBuffer) vertexBuffer->Release();
        if(vertexLayout) vertexLayout->Release();
        if(effect) effect->Release();

        if(renderTargetView) renderTargetView->Release();
        if(swapChain) swapChain->Release();
        if(graphicsDevice) graphicsDevice->Release();
    }

    void Draw()
    {
        float blue[] = {0, 0, 1, 1};
        graphicsDevice->ClearRenderTargetView(renderTargetView, blue);

        D3D10_TECHNIQUE_DESC techniqueDesc;
        effectTechnique->GetDesc(&techniqueDesc);

        for(int i = 0; i < techniqueDesc.Passes; i++)
        {
            effectTechnique->GetPassByIndex(i)->Apply(0);
            graphicsDevice->Draw( 3, 0);
        }

        swapChain->Present(0, 0);
    }

    void createGraphicsDevice()
    {
        DXGI_SWAP_CHAIN_DESC swapChainDescription;
        ZeroMemory( &swapChainDescription, sizeof(swapChainDescription) );
        swapChainDescription.BufferCount = 1;
        swapChainDescription.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
        swapChainDescription.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
        swapChainDescription.OutputWindow = (HWND)(this->Handle.ToInt32());
        swapChainDescription.SampleDesc.Count = 1;
        swapChainDescription.Windowed = TRUE;



        ID3D10Device *graphicsDevice;
        IDXGISwapChain *swapChain;

        HRESULT result = D3D10CreateDeviceAndSwapChain(
            NULL,
            D3D10_DRIVER_TYPE_REFERENCE,
            NULL, 
            0, 
            D3D10_SDK_VERSION,
            &swapChainDescription,
            &swapChain,
            &graphicsDevice
            );

        if( FAILED(result) )
            throw gcnew Exception("Couldn't create Graphics Device");

        this->graphicsDevice = graphicsDevice;
        this->swapChain = swapChain;
    }

    void createRenderTargetView()
    {
        ID3D10Texture2D *backBuffer;
        HRESULT result = swapChain->GetBuffer(0, __uuidof(ID3D10Texture2D), (LPVOID*)&backBuffer);

        if( FAILED(result) )
            throw gcnew Exception("Couldn't get BackBuffer from swap chain.");

        ID3D10RenderTargetView* renderTargetView;
        result = graphicsDevice->CreateRenderTargetView(backBuffer, NULL, &renderTargetView);
        backBuffer->Release();

        if( FAILED(result) )
            throw gcnew Exception("Couldn't create RenderTargetView.");

        this->renderTargetView = renderTargetView;

        graphicsDevice->OMSetRenderTargets(1, &renderTargetView, NULL);
    }

    void setupViewport()
    {
        D3D10_VIEWPORT viewport;
        viewport.Width = Width;
        viewport.Height = Height;
        viewport.MinDepth = 0.0f;
        viewport.MaxDepth = 1.0f;
        viewport.TopLeftX = 0;
        viewport.TopLeftY = 0;
        graphicsDevice->RSSetViewports( 1, &viewport );
    }
};

int main()
{
    Game ^game = gcnew Game();
    game->Run();
}

MyShader.fx (シェーダーのファイルです。DX10Test.exeと同じフォルダに入れておきます。このシェーダは、入力された頂点はそのまま処理し(3Dの遠近法っぽい効果や陰をつけたりせずに)、全てのポリゴンの全てのピクセルを白く描画することを意味しています)
float4 MyVertexShader(float4 position : SV_Position) : SV_Position
{
    return position;
}

float4 MyPixelShader() : SV_Target
{
    return float4(1, 1, 1, 1);
}

technique10 MyTechnique
{
    pass
    {
        SetVertexShader(CompileShader(vs_4_0, MyVertexShader()));
        SetPixelShader(CompileShader(ps_4_0, MyPixelShader()));
    }
}


DrawTriangle.jpg
このプログラムでは白い三角形(座標は(0, 0.5f, 0), (0.5f, 0, 0), (-0.5f, 0, 0))を、ウィンドウに表示しています。
三角形を表す3次元ベクトルの配列を、グラフィックス・デバイスに送り込み、Game::Drawの呼び出しのたびにシェーダーを使ってデータの変換処理をさせて(といってもここではたいしたことはしていなくて、ポリゴンの全部のピクセルの色を白にするだけ)、ウィンドウ(正確にはスワップチェーンのバック・バッファ)に出力します。
結果、上の画像のように白い三角形が1つ表示されるというわけです。

Direct3D9ではシェーダーを書かなくても三角形は表示できたのですが、Direct3D10ではその機能が削除されているため、三角形1つ表示するだけなのにかなり難しくなっているように思えます。
XNAのBasicEffectのように、シェーダーがクラスライブラリになっていたらずいぶん楽だったんでしょうけどね……。

コンパイルには前回のd3d10.libに付け加えてd3dx10.libが必要です。

cl DX10Test.cpp /clr d3d10.lib d3dx10.lib



大まかな流れ

前回のに新しく付け加わったところだけ…

1.ID3D10RenderTargetViewをデバイスにセットします(ID3D10Device::OMSetRenderTargetsを使う)。これによって、デバイスが描画した結果がウィンドウに反映されるようになります。(もしこれを忘れていたら、たとえ描画するメソッドを呼んでいたとしても、三角形がウィンドウに表示されません!)

2.シェーダーをHLSLで記述したファイルMyShader.fxから、ID3D10Effectオブジェクトを作り出します。このオブジェクト(シェーダー)によって、頂点バッファがどのようにデバイスの中で処理されて、最終的に画面に表示されるのかが決定されます。ここでは話をシンプルにするため「ポリゴンのピクセルの色を全て白にする」というシェーダーを作りましたが、実際にシェーダーを使う時にはもっと複雑なことをします。たとえば、ポリゴンを遠近法で大きさを前後でゆがめたり、光の当たらないポリゴンを暗くして、影を作り出したりといったことです。

3.ID3D10InputLayoutオブジェクトを作り、デバイスにセットします。このオブジェクトは頂点バッファに使う頂点データのレイアウトを表します。どういうことかというと、まず頂点バッファの頂点データと言うのは座標(3次元ベクトル)だけだとは限りません。他にも、その点の「色」や「テクスチャ座標(テクスチャのどの部分を貼り付けるかを表す)」など、いろいろな情報を持つのが普通です。つまり、頂点データに何が入っているかはプログラマが自由に出来るので、プログラマがデバイスに対して「頂点に何が入っているか」の説明をする責任があるのです(説教臭い話ですが、「自由には責任が~」というやつです)。その、「頂点データに何が入っているか」をデバイスに教えるオブジェクトが、これです。 ちなみに、XNAでは同じ働きをするオブジェクトを、VertexDeclarationといいました。D3D10のほうが抽象的な名前をしていますが、それはたぶんD3D10のほうがより柔軟なことが出来るからでしょう。そのぶん初学者がますます学びにくくなっているような気もしますが…。

4.ID3D10Bufferを使って、頂点バッファを作り、デバイスにセットします。おそらくこれが多くのプログラマにとって興味のあるオブジェクトではないでしょうか。なんといっても、ウィンドウに表示する3Dモデルの頂点データを格納するのがこのオブジェクトだからです。実際に目に見える形になるデータを持っているオブジェクトというのは理解しやすいものです。ここでは、表示する三角形の座標をセットしています。つまり頂点データのメモリ(といってもグラフィックスカードのメモリですが)の中には、{{0, 0.5f, 0}, {0.5f, 0, 0}, {-0.5f, 0, 0}}が入っています。頂点バッファを作った後は、それをデバイスにセットしています。そうすると、ID3D10Device::Drawメソッドを呼んだ時、ポリゴンを描画するのに使われるようになるのです。もし別の頂点バッファの3Dモデルも表示したいと思ったのなら、Game::Drawメソッドの中で頂点バッファを別のものにセットしなおし、またID3D10Device::Drawメソッドを呼びます。頂点バッファやInputLayoutをデバイスにセットするのは、必ずしもデバイス初期化のときだけでなくてもいいのです(これはシンプルなサンプルからはわかりにくいですよね)

5.D3D10_PRIMITIVE_TOPOLOGY列挙型を使って、デバイスに「頂点バッファがどのようなプリミティブ(三角形、点、線とか)として表示されるか」をセットします。ここでは頂点バッファが三角形の頂点のリストであることを表す"D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST"をセットしています。

6.エフェクトのパスを適用し、デバイスのDrawメソッドを呼びます。これによってデバイスにセットされたInputLayoutや頂点バッファの情報から、3Dモデルが描画されます。ここでは三角形1つだけですね。このプロセスは1から5までと違って、何度も行います。Game::Drawはwhileループの中で何度も呼ばれるからです。理想的にはこれはだいたい1秒間に60回ほど呼ばれるべきですが、ここではリファレンス・デバイスを使っているのでずっと遅いでしょうね。



デバイスの初期化のときに

デバイスの初期化のときに上の1~5までを行っています。そこを詳しく見てみましょう。以下MSDNを大きく変更した意訳です。



レンダーターゲットのセット

ID3D10Device::OMSetRenderTargets

まずID3D10Device::OMSetRenderTargetsでレンダーターゲットビューをデバイスにセットしています。これによって、デバイスがなんに対して描画を行うかが決定されます。普通はウィンドウ(に表示されることになるスワップチェーンのバックバッファ)ですが、単なるテクスチャに描画して何かのポリゴンに貼り付けたり・・・してもかまいません。このメソッドはセットされたインターフェースへの参照を追加しないので、デバイスにセットされたままのインターフェースをリリースしないように気をつけなければなりません。おそらくそのためにデバイスのすべてをNULLにセットするID3D10Device::ClearStateがあるのでしょうね。
void OMSetRenderTargets(
    UINT numViews,
    ID3D10RenderTargetView *const *renderTargetViews,
    ID3D10DepthStencilView *depthStencilView
);

numViewsはセットするレンダーターゲットの数です。上限は定数D3D10_SIMULTANEOUS_RENDER_TARGET_COUNTで定められています。
renderTargetViewsはレンダーターゲットの配列です。
depthStencilViewは深度ステンシルのビューです。これがNULLだと深度ステンシルは使われません。(深度ステンシルとは、ポリゴンを描画する順番にかかわり無く、正しい前後関係で描画するための情報を格納する、テクスチャのようなものです(でも持っているのは色ではなくて深度とステンシル)。これがないと、たとえば近くにいる敵を描画した後その後ろにある壁を描画すると、遠くの壁だけが見えてしまい、近くの敵が見えなくなってしまいます。そうすると、いないはずの敵に攻撃されてわけのわからないまま死んでしまうことになるでしょう。ここではプログラムを簡単にするため、NULLにしておきます)



シェーダーのセット

D3DX10CreateEffectFromFile 関数

次にD3DX10CreateEffectFromFile関数を呼んでいます。
この関数はシェーダー記述用の言語HLSL(Hight Level Shader Language)で書いたシェーダーのファイルから、ID3D10Effectオブジェクトを作り出します。
このオブジェクトは、「デバイスが頂点バッファを変換してポリゴンを表示する時の具体的な処理を表す」、シェーダーを管理します。(このオブジェクトはDrawの時に使います。)


HRESULT D3DX10CreateEffectFromFile(
    LPCTSTR fileName,
    CONST D3D10_SHADER_MACRO *defines,
    ID3D10Include *shaderIncludeFile,
    LPCSTR profile,
    UINT hlslFlags,
    UINT fxFlags,
    ID3D10Device *graphicsDevice,
    ID3D10EffectPool *effectPool,
    ID3DX10ThreadPump *pump,
    ID3D10Effect **outEffect,
    ID3D10Blob **outErrors
);

fileNameはHLSLで書いたシェーダーのファイル名です。

definesは,NULLでも構わないのですが、シェーダーのコード内で使うマクロ(#defineを使うアレ)を表すD3D10_SHADER_MACRO構造体の、NULLで終わる配列です。これを使うことによって、シェーダー内の#defineを実行時に行うことが出来ます(そんなことをして何の得があるのかはよくわかりませんが)。予想できるかもしれませんが、D3D10_SHADER_MACRO構造体は、マクロ名(Name)とその定義(Definition)の2つのLPCSTR型のメンバを持っています。

shaderIncludeFileは、シェーダーでインクルードされるファイルを操作するID3D10Includeです。実際に使う時にはID3D10Includeを継承して、2つのメソッド(CloseとOpen)をを自前で実装して使わなければいけません。この引数はNULLでもかまいません。たぶん普通はNULLでしょう。

profileは、エフェクトのプロファイル(バージョンを表す文字列)です。ここでは、D3D10のシェーダーモデル4.0を使うため、"fx_4_0"を使っています。(D3D10では"fx_2_0"か"fx_4_0"を指定するようです。)

hlslFlagsは、HLSLをコンパイルする時のオプションで、D3D10_SHADER定数を使います(何で列挙型にしないんだろ……)

変数名 説明
D3D10_SHADER_DEBUG デバッグ情報を挿入します。
D3D10_SHADER_SKIP_VALIDATION 過去にコンパイルできたコードをまたコンパイルする時、VALIDATIONをスキップする目的で使います。
D3D10_SHADER_SKIP_OPTIMIZATION デバッグしたい時に、最適化をスキップします。
D3D10_SHADER_PACK_MATRIX_ROW_MAJOR 例外が明示されない限り、マトリックスは行が優先的にレジスタに入ります。
D3D10_SHADER_PACK_MATRIX_COLUMN_MAJOR 例外が明示されない限り、マトリックスは列が優先的にレジスタに入ります。内積の計算方法の関係で、たいていこっちの方が効率的です。
D3D10_SHADER_PARTIAL_PRECISION 計算の正確性を削って速さを優先します。(速くなるかどうかは実際には保障されていませんが・・・。速くなる「かもしれない」レベルです)
D3D10_SHADER_FORCE_VS_SOFTWARE_NO_OPT 頂点シェーダを次に高いシェーダープロファイルでコンパイルします。これは最適化をoffにしてデバッグをonにします。
D3D10_SHADER_FORCE_PS_SOFTWARE_NO_OPT ピクセルシェーダを次に高いシェーダープロファイルでコンパイルします。これは最適化をoffにしてデバッグをonにします。
D3D10_SHADER_NO_PRESHADER Preshader(CPUで出来る計算は1回だけCPUでやって残りの計算をGPUで毎回行う)を無効にします。
D3D10_SHADER_AVOID_FLOW_CONTROL 可能ならば、フロー制御を使わないようにします。
D3D10_SHADER_PREFER_FLOW_CONTROL 可能ならば、フロー制御を使うようにします。
D3D10_SHADER_ENABLE_STRICTNESS 古いシンタックスを使えなくします。(デフォルトでは、HLSLのコンパイラは古いシンタックスがあってもコンパイルしてくれます)
D3D10_SHADER_ENABLE_BACKWARDS_COMPATIBILITY 後方互換を有効にして、古いシェーダーを4_0でコンパイルできるようにします。
D3D10_SHADER_IEEE_STRICTNESS IEEEに厳密にします。
D3D10_SHADER_OPTIMIZATION_LEVEL0 1番低い最適化のレベルです。
D3D10_SHADER_OPTIMIZATION_LEVEL1 2番目に低い最適化のレベルです。
D3D10_SHADER_OPTIMIZATION_LEVEL2 2番目に高い最適化のレベルです。
D3D10_SHADER_OPTIMIZATION_LEVEL3 1番高い最適化のレベルです。パフォーマンスがとっても重要な時にだけ使います。


fxFlagsは、エフェクトのコンパイルのオプションです。D3D10_EFFECT定数を使います。この定数群はエフェクトのコンパイル時のふるまいや実行時の振る舞いを表します。


定数名 説明
D3D10_EFFECT_COMPILE_CHILD_EFFECT 1 << 0 .fxファイルを子エフェクトにコンパイルします。子エフェクトは共有された値に対する初期化を行いません。それらは、エフェクトプールで初期化されるからです。
D3D10_EFFECT_COMPILE_ALLOW_SLOW_OPS 1 << 1 ミュータブルなステート・オブジェクトを作ることが出来ます。
どういうことかというと、デフォルトではパフォーマンスのため、ステートオブジェクトはリテラルを使ってしか定義できないのですが、このフラグを使えばそれを無効に出来ます。
D3D10_EFFECT_SINGLE_THREADED 1 << 3 同じプールにエフェクトをロードしたほかのスレッドと同期しようとしません。

graphicsDeviceは、このエフェクトを使うデバイスです。

effectPoolは複数のエフェクト間で変数を共有し、変数を書き換えるAPIコールを少なくしてパフォーマンスを上げるための、エフェクト・プールです。この型はID3D10EffectPoolで、D3DX10CreateEffectPoolFromFile関数などを使って作ります。この引数はNULLでもかまいません。

pumpはやや特殊な引数です。これに入れる値によってこの関数は同期になったり非同期になったりします。具体的にいうと、この引数にID3D10ThreadPumpのインスタンス(D3DX10CreateThreadPumpを使ってつくる)を入れると、別のスレッドを使って、非同期に動きます。そうすればメインのスレッドはほとんど時間を食わないので、ユーザーを待たせる時間が減るかもしれません。逆に、この引数にNULLを入れると、同期に動きます。つまり、エフェクトオブジェクトのロードが完了するまではこの関数は終了しません。

outEffectは結果(ID3D10Effect*)を格納する変数へのポインタです。この中にエフェクトオブジェクトが入ります。

outErrorsはこの関数のエラー情報をを格納する変数のポインタです。この引数が使っているID3D10Blogというのは、好きなサイズのデータを格納することの出来るCOMインターフェースです。つまり、メモリバッファへのポインタと、そのバッファのサイズを持っています。ちょうど.netの配列みたいなものでしょうか……。このインスタンスを得るには、D3D10CreateBlog関数を使います。
この引数はNULLでもかまいません。



入力レイアウトのセット

ID3D10Device::CreateInputLayout

次にID3D10Device::CreateInputLayoutを使って、頂点バッファがどのようなレイアウトの頂点を格納しているかを決めています(そしてそれをデバイスにセットし、デバイスがポリゴン描画の際に頂点バッファを理解できるようにしています)。D3D9の時の同様のメソッド(引数2つ)と比べて一気に複雑化していますね・・・。シェーダーの入力シグネイチャーがあたらしく引数に必要だからです。
HRESULT CreateInputLayout(
    const D3D10_INPUT_ELEMENT_DESC *inputElementDescs,
    UINT numElements,
    const void *shaderBytecodeWithInputSignature,
    SIZE_T bytecodeLength,
    ID3D10InputLayout **outInputLayout
);


inputElementDescsは、頂点バッファに格納される頂点のレイアウトをあらわすD3D10_INPUT_ELEMENT_DESC構造体の配列です。基本的には、頂点の構造体を構成するメンバ変数1つが1つのD3D10_INPUT_ELEMENT_DESCに対応します。例えば、頂点が3次元ベクトル1つだけならこの配列の長さも1で、もし頂点が3次元ベクトルと色を表すデータの2つを持っているならこの配列の長さは2です(そして、座標と色と、テクスチャ座標も持っていたりして3つなら・・・もちろん3です)

D3D10_INPUT_ELEMENT_DESC 構造体

 

typedef struct D3D10_INPUT_ELEMENT_DESC {
    LPCSTR SemanticName;
    UINT SemanticIndex;
    DXGI_FORMAT Format;
    UINT InputSlot;
    UINT AlignedByteOffset;
    D3D10_INPUT_CLASSIFICATION InputSlotClass;
    UINT InstanceDataStepRate;
} D3D10_INPUT_ELEMENT_DESC;

SemanticNameはHLSLでシェーダーの入力や出力の変数に付けるセマンティック、つまりこの要素がどのような役割を果たすかをあらわす文字列です。たとえば、座標を表すのなら"SV_Position"、色を表すのなら"Color"、テクスチャ座標を表すのなら"TexCoord"といった具合です。これは、大文字小文字の区別が結構ゆるいようです。MSDNにはCOLORやTEXCOORDと全部大文字で書いていますが、そうでなくてもいけます。

SemanticIndexは、セマンティックに付ける数です。この数によって、同じ名前のセマンティックでも別々に扱うことができます(Color0とColor1は違う)。セマンティックとは要素の役割をあらわすわけで、同じ役割を持つものが複数あるのは変に思えるかもしれませんが、たとえばColor0は乱反射の色、Color1は鏡面反射の色という風に分けたい時だってあるでしょう。あるいは、4x4のマトリックスを、4つの4次元ベクトルとしてシェーダーに流し込みたいとき、セマンティクスは全部同じにしてこのインデックスをそれぞれ変えて入力する、なんてのもありです(たしかXNAのサンプルにそんなのがありました)。

Formatは、この要素のフォーマットです。このメンバの型は、スワップチェーンを作るときにテクスチャを構成するピクセル(テクセル)のフォーマットを決めるのに使った、あのDXGI_FORMATです。つまり、どのくらいのサイズの要素なのかといったことを決めるんですね。

InputSlotは、頂点バッファのインデックスのようなものです(0から15まで。たいてい0)。GPUがポリゴンを描画するとき、(普通は1つだけですが)複数の頂点バッファをセットすることができます。これによって、3DのモデルをGPUの中で複製したりといったことができる(0番目の頂点バッファはモデルの頂点、1番目の頂点バッファは各モデルのインスタンスの情報)のですが、このメンバがあらわしているのはその使う頂点バッファのインデックスです。そういうわけで、普通に3Dモデルを描画したいときにはここは0です。テクニカルなことをしたいときに、ここは0以外になります。

AlignedByteOffsetは、要素間のオフセットだそうです。これはあくまでもオプションで、必ず指定しなければいけないということはありません。自分で値を指定したくない場合は、D3D10_APPEND_ALIGNED_ELEMENTをセットすると、この要素が前の要素のすぐ後にくるようになります。

InputSlotClassには、D3D10_INPUT_CLASSIFICATION列挙型、つまりD3D10_INPUT_PER_VERTEX_DATA(=0)かD3D10_INPUT_PER_INSTANCE_DATA(=1)を指定します。それぞれ、インプットデータが、頂点ごとのデータ、インスタンスごとのデータであることをあらわします。たぶんこれは3DモデルのGPUによる複製、つまりハードウェア・インスタンシングをするときに使うのではないでしょうか(たぶん)。普通に3Dモデルをあらわす頂点バッファから描画するときにはD3D10_INPUT_PER_VERTEX_DATAを指定していいでしょう。

InstanceDataStepRateは、上のメンバInputSlotClassがD3D10_INPUT_PER_INSTANCE_DATAにセットされているときだけ意味を持ちます。D3D10_INPUT_PER_VERTEX_DATAがセットされているときには、このメンバは0でなくてはいけません。このメンバが何を意味するのかというと、MSDNによると、「同じインスタンス毎のデータを使って、バッファをひとつの要素分進む前に、描画するインスタンスの数」だそうです。なんのこっちゃ……。とりあえずハードウェア・インスタンシングをしないのならあんまり意味がなさそうです。そんなに心配する必要はないでしょう。たぶん

numElementsは、頂点レイアウト配列の長さです。つまり、一番目の引数inputElementDescsの長さです。

shaderBytecodeWithInputSignatureは、「使うシェーダーにどんな入力ができるのか」です。これは自前で作る必要はなく、エフェクトオブジェクト->テクニック->パス->パスのDESC構造体->pIAInputSignatureメンバからゲットできます。シェーダーとして使える最小単位(?)はパス(使う頂点シェーダーとピクセルシェーダーを定義している)なので、パスのDESC構造体を使うのでしょうね。

msdnによると、「一度インプット・レイアウト・オブジェクトがシェーダー・シグネイチャから作られると、そのインプットレイアウトオブジェクトは、同じインプットシグネイチャ(セマンティクスも含まれる)をもつ、他のどんなシェーダーからも再利用できる」そうです。 ようするに「いちおうシェーダーのメンバを引数にとってインプットレイアウトを作るけど、そのシェーダーだけでしか使えないなんてことはないよ」ということなのでしょう。

bytecodeLengthは、上の引数、shaderBytecodeWithInputSignatureのサイズです。これもやはりパスのDESC構造体からゲットできます。D3D10_PASS_DESC::IAInputSignatureSizeを使います。

outInputLayoutは、このメソッドによって作られるインプット・レイアウト・オブジェクトのインスタンスを格納することになります。ここにはこのメソッドの結果が入るのです。


ID3D10Device::IASetInputLayout

CreateInputLayoutで作成したGPUへの入力レイアウト・オブジェクトは、ID3D10Device::IASetInputLayoutメソッドでセットすることによってはじめて、頂点バッファの解釈に利用されるようになります(ですからセットするのを忘れないでください。頂点データはプログラマが自由に決められるので、このメソッドを呼ばないとGPUが頂点バッファをどう解釈してポリゴンを表示すればいいのかわからなくなってしまいます)

一番最初についてる"IA"というのはInput-Assembler ステージのことを意味しています。入力レイアウトはinput-assemblerステージで、頂点の解釈に使われるわけですからね。

void IASetInputLayout (
    ID3D10InputLayout *inputLayout
);

引数は1つだけ。
インプットレイアウトのインスタンスです。



頂点バッファのセット

ID3D10Device::CreateBuffer


まずはID3D10Device::CreateBufferで頂点バッファを作ります。なお、このメソッドは頂点バッファだけではなく、インデックス・バッファや、シェーダー定数バッファを作ることもできます。この柔軟性のおかげでややこしくてたまりません。
HRESULT CreateBuffer(
    const D3D10_BUFFER_DESC *desc,
    const D3D10_SUBRESOURCE_DATA *initialData,
    ID3D10Buffer **outBuffer
);

descは、このメソッドがどんなバッファを作るかをあらわす、D3D10_BUFFER_DESC構造体です。

D3D10_BUFFER_DESC構造体
typedef struct D3D10_BUFFER_DESC
{
    UINT ByteWidth;
    D3D10_USAGE Usage;
    UINT BindFlags;
    UINT CPUAccessFlags;
    UINT MiscFlags;
} D3D10_BUFFER_DESC;

ByteWidthは、バッファのサイズ(単位はbyte)です。

Usageは、このバッファをどのようにつかわれるかです。
これには4つ選択肢があって、それぞれCPUやGPUからのアクセスできたりできなかったりが違います。

  GPUから読む GPUから書く CPUから読む CPUから書く ステージの入力として使える(ポリゴンに貼るテクスチャとか?) ステージの出力として使える(レンダーターゲットのことかな?)
D3D10_USAGE_DEFAULT ○(制限あり)
D3D10_USAGE_IMMUTABLE
D3D10_USAGE_DYNAMIC ○(制限あり) ○(サブリソースは1つだけでなければいけません。テクスチャの配列やmipmapチェーンにはできません)
D3D10_USAGE_STAGING ○(制限あり) ○(制限あり) ○(制限あり) ○(制限あり)  

CPUからのアクセスは、ID3D10Buffer::Map、ID3D10Texture1D::Map、ID3D10Texture2D::Map、ID3D10Texture3D::Mapメソッドで行います。

GPUからのアクセスは、ID3D10Device::CopySubresourceRegion, ID3D10Device::CopyResource、ID3D10Device::UpdateSubresourceで行えます。
ただし!Defaultの書き込みとDynamic、そしてStagingでは、最初の2つだけに制限されます。

なお、Stagingのバッファは、深度ステンシルバッファにはできませんし、マルチサンプルのレンダーターゲットにもできません。

BindFlagsは、どんなふうにバインドできるかをあらわします。
たとえば、頂点バッファとして使えるか、インデックスバッファとして使えるか、定数バッファとして使えるか…といったぐあいです。
これには、D3D10_BIND_FLAGを使います。
typedef enum D3D10_BIND_FLAG
{
    D3D10_BIND_VERTEX_BUFFER = 0x1L,
    D3D10_BIND_INDEX_BUFFER = 0x2L,
    D3D10_BIND_CONSTANT_BUFFER = 0x4L,
    D3D10_BIND_SHADER_RESOURCE = 0x8L,
    D3D10_BIND_STREAM_OUTPUT = 0x10L,
    D3D10_BIND_RENDER_TARGET = 0x20L,
    D3D10_BIND_DEPTH_STENCIL = 0x40L
} D3D10_BIND_FLAG;

CPUAccessFlagsは、CPUからの書き込みや読み込みを許可するかを指定します(D3D10_CPU_ACCESS_FLAG列挙型を使います)。
もし、CPUからまったくアクセスしないのであれば、0を指定します。
書き込みを許可するのはD3D10_CPU_ACCESS_WRITEで、これを使うとパイプラインの出力としては使えなくなり、またUsageメンバがDynamicかStagingになってなければいけません。
読み込みを許可するのはD3D10_CPU_ACCESS_READで、これを使うとパイプラインの入力としても出力としても使えなくなり、またUsageメンバがStagingになっていなければいけません。

MiscFlagsは、あんまり使われない、「その他」な機能を表すフラグです。
使わないのなら0です。
このメンバにはD3D10_RESOURCE_MISC_FLAG列挙型を使います。
typedef enum D3D10_RESOURCE_MISC_FLAG
{
    D3D10_RESOURCE_MISC_GENERATE_MIPS = 0x1L,
    D3D10_RESOURCE_MISC_SHARED = 0x2L,
    D3D10_RESOURCE_MISC_TEXTURECUBE = 0x4L
} D3D10_RESOURCE_MISC_FLAG;









initialDataは、これから作るバッファの初期値です。この引数の型は、サブリソースを初期化するときのデータをあらわすD3D10_SUBRESOURCE_DATA構造体で、メンバに初期値を現す領域へのポインタを持っています。たとえば、頂点バッファを作るときには、この引数は頂点の配列(ポインタ)を持ちます。NULLを指定すると、バッファを初期化せず、領域を確保するだけです。

D3D10_SUBRESOURCE_DATA 構造体
typedef struct D3D10_SUBRESOURCE_DATA
{
    const void *pSysMem;
    UINT SysMemPitch;
    UINT SysMemSlicePitch;
} D3D10_SUBRESOURCE_DATA;


pSysMemは、これから作るサブリソースを初期化する値です。たとえば、作るサブリソースがインデックスバッファならインデックスの配列、テクスチャなら色の配列です。

SysMemPitchは、2Dや3Dのテクスチャを作るときのみ使われます。頂点バッファやインデックスバッファでは使いません。このメンバが何を意味するのかというと、テクスチャの横幅です(ただし単位はbyteらしいです。そのままwidthとかを突っ込んではいけません。つまり、テクスチャのピクセル1つのデータのサイズを×しなきゃいけないとうことです。)

SysMemSlicePitchは、3Dのテクスチャを作るときのみ使われます。これはテクスチャの深さ(単位はbyteで)ですね。



outBufferには結果が入ります。なお、ここにNULLを指定すると、他の引数が適正かどうかをチェックできるそうです。その場合はS_FALSEが上手くいっていることをあらわします。

ID3D10Device::IASetVertexBuffers

CreateBufferで作った頂点バッファは描画の前にID3D10Device::IASetVertexBufferでデバイスにセットしてやる必要があります。
このメソッドは、複数の(普通は1個だけで十分でしょうが、ハードウェア・インスタンシングなことをしたいのならもっと使う場合もあるでしょう。その場合はおそらく16個までです(たぶん。入力スロットは16個しかありませんからね)。)頂点バッファをデバイスにセットすることができます。
void IASetVertexBuffers(
    UINT startSlot,
    UINT numBuffers,
    ID3D10Buffer *const *vertexBuffers,
    const UINT *strides,
    const UINT *offsets
);

startSlotは、頂点バッファをセットする最初のスロットです。グラフィックス・デバイスに頂点バッファのスロットは全部で16あり、それに頂点バッファをセットしていくわけですが、これは一番最初に頂点バッファがセットされるスロットのインデックスです。普通は0でいいでしょう。つまり一番最初のスロットからセットしていきます。普通に使う分にはずらしても意味ないでしょうし。

numBuffersは、セットする頂点バッファの数です。セットする頂点バッファがひとつだけなら、この値は1になります(多くの場合そうでしょう)。この引数に指定した数は、次の3つの配列の引数、vertexBuffers、strides、offsetsの長さでもあります。

vertexBuffersは、セットする頂点バッファの配列です。この配列の長さは上の引数numBuffersと同じです。

stridesは、頂点バッファに使っている頂点一つのサイズ(単位はbyte)の配列です。頂点に使う構造体のsizeofを使えばいいでしょう。

offsetsは、各頂点バッファが使われるオフセットへの配列です。オフセットとはつまり、頂点バッファの中の、一番最初に使われる位置です(単位はbyte)。普通は頂点バッファは最初から読むでしょうから、0(の配列)でいいでしょう。




プリミティブ・トポロジーのセット

ID3D10Device::IASetPrimitiveTopology

最後に、デバイスに、頂点バッファをどのような図形として描くのかを指定します。
たとえば、頂点バッファの各頂点を独立した点として描画したり、あるいは頂点バッファに入っている頂点を線のリストとして描画したり、はたまた三角形(ポリゴン)のリストとして描画したりといった具合です(一番最後のが一般的でしょう)。

void IASetPrimitiveTopology(
    D3D10_PRIMITIVE_TOPOLOGY topology
);

topologyは、描画する図形の種類のようなもので、D3D10_PRIMITIVE_TOPOLOGY列挙型です。

typedef enum D3D10_PRIMITIVE_TOPOLOGY
{
    D3D10_PRIMITIVE_TOPOLOGY_UNDEFINED = 0,
    D3D10_PRIMITIVE_TOPOLOGY_POINTLIST = 1,
    D3D10_PRIMITIVE_TOPOLOGY_LINELIST = 2,
    D3D10_PRIMITIVE_TOPOLOGY_LINESTRIP = 3,
    D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST = 4,
    D3D10_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP = 5,
    D3D10_PRIMITIVE_TOPOLOGY_LINELIST_ADJ = 10,
    D3D10_PRIMITIVE_TOPOLOGY_LINESTRIP_ADJ = 11,
    D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ = 12,
    D3D10_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP_ADJ = 13
} D3D10_PRIMITIVE_TOPOLOGY;


[TODO:ここに各定数の説明を書くべきですね]


描画

ID3D10EffectPass::Apply

描画にはデバイスのDrawメソッドを呼びます。
しかしその前に、エフェクト・パス(シェーダー)をデバイスに適用して、Drawを呼んだときにその効果(シェーダー)が反映されるようにしなければいけません。
それを行うには、ID3D10EffectPass::Applyメソッドを呼びます。
HRESULT Apply(UINT flags);

ケッサクなことに、唯一の引数であるflagsは、MSDNによると”Unused(使用されません)”です。意味不明ですが、そんなもんだと割り切ることにしましょう。ちなみにサンプルでは0が使われています。

ID3D10EffectTechnique::GetPassByIndex

パスを得るには、エフェクト・テクニック(エフェクト・パス(シェーダー)をまとめたもの)の、GetPassByIndexメソッドを使います(実はもう1つ方法があって、GetPassByName(LPCSTR name)を使うこともできます)。
(ですから形としては、HLSLで書いたシェーダーのファイルから、エフェクトオブジェクトを作って、そこからエフェクト・テクニックを取り出して、さらにそこからエフェクト・パスを得ることになります。ああ、ややこしい!)
ID3D10EffectPass* GetPassByIndex( UINT index );

indexはこのメソッドを使って得るエフェクトパスのインデックスです。HLSLで書いたシェーダーコードがパスを1つしか持っていないのから(今回のケース)、ここはもちろん0です。

このindexは、とりあえず0を指定しておけば動くのですが、HLSLでパスを複数書いておいたときにはそれだけでは最初のパス以外適用されません。
パスの数をどこからかゲットして、forループでまわしてやる必要があるでしょう。
そのパスの数をどこから得るのかというと、それはD3D10_TECHNIQUE_DESC構造体のメンバ、Passesです。
で、D3D10_TECHNIQUE_DESC構造体(のデータ)を得るには、エフェクトテクニックのGetDescメソッドを呼べばいいわけです。
ここら辺がめんどくさいばあいはとりあえずindexを0にしていすればOKです。(そのかわり柔軟性はなくなりますが)


ID3D10Effect::GetTechniqueByName

エフェクトテクニックからエフェクトパスを得て、それをデバイスに適用する必要があるのですが、ではそのエフェクトテクニックを得るにはどうすればいいのでしょうか?
それにはエフェクトオブジェクトの、GetTechniqueByName(またはGetTechniqueByIndex)メソッドを使います。

ID3D10EffectTechnique* GetTechniqueByName(
    LPCSTR name
);

nameは、テクニックの名前です。つまり、HLSLコードの、"technique10 "に続く文字列です。このサンプルの例では"MyTechnique"ですね。


ID3D10Device::Draw

かんじんのポリゴンを描画するメソッドです。
描画に使う情報、つまり頂点バッファやシェーダーは、別のところでセットされているため、このメソッド自体の引数は少なく、2つだけです。
void Draw(
    UINT vertexCount,
    UINT startVertexLocation
);

vertexCountは、描画に使用する頂点の数です。

startVertexLocationは、描画に使用する最初の頂点です。ここからvertexCountの数だけ描画されます。








拍手[1回]

PR