忍者ブログ

Memeplexes

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

DirectX10 + C++/CLI 3D三角形

Direct3D10で3Dの三角形を1つ表示して、近づけたり遠ざけたりしてみました。

moving3DTriangle1.jpg
moving3DTriangle2.jpg
moving3DTriangle3.jpg

近づいた後は、遠ざかって、また近づいて・・・を繰り返します。
System.TickCountプロパティ(分解能十分あったっけ?と思いましたが、どうせリファレンスデバイスで遅いので問題ありません。)をMath.Sineの中に放り込み、距離を決めます。

HLSLで書いたシェーダープログラムに、C++/CLI側から、「三角形がどのくらい離れているか」、「カメラの視野の角度はどのくらいか(3Dやるときは必須)」といったことをセットするんですね。
(厳密には、それらによって三角形の座標がどのように変換されて(画面から離れたり、遠近法の効果が与えられたり)最終的に画面に表示されるかを表すマトリックスをセットしているのですが、意味的にはこうです)

しかし3D空間中に三角形表示するだけなのに結構大変ですね。
大変になったときにはどうするのかというと、隠蔽とカプセル化が定石ですが、サンプルプログラムでそれをやるとかえってわかりにくくなります。
サンプルというのは、ここでいうと大変な部分が主役で、それを隠してしまったら元も子もないからです。

まったく、XNAのありがたみが身にしみますね。
さっさとD3D10にも対応してくれれば良いのに!


コード

DX10Test.cpp
#using<System.Windows.Forms.dll>
#using<System.Drawing.dll>//ClientSizeを使うのに必要
#using<System.dll>

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

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

struct VertexPositionColor
{
    D3DXVECTOR3 Position;
    D3DXVECTOR4 Color;
};

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


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

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

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

private:

    void InitDevice()
    {
        createGraphicsDevice();

        createRenderTargetView();

        setupViewport();

        LoadGraphicsContent();
    }


    void LoadGraphicsContent()
    {
        //シェーダーの設定
        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;



       //頂点データのレイアウトの設定
        D3D10_INPUT_ELEMENT_DESC vertexElements[] = 
        {
            {"SV_Position", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D10_APPEND_ALIGNED_ELEMENT, D3D10_INPUT_PER_VERTEX_DATA, 0},
            {"Color", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D10_APPEND_ALIGNED_ELEMENT, D3D10_INPUT_PER_VERTEX_DATA, 0}
        };

        D3D10_PASS_DESC passDesc;
        effect->GetTechniqueByName("MyTechnique")->GetPassByIndex(0)->GetDesc(&passDesc);

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

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

        this->vertexLayout = vertexLayout;

        graphicsDevice->IASetInputLayout(vertexLayout);



        //頂点バッファの設定
        VertexPositionColor vertices[] = 
        {
            {D3DXVECTOR3(0, 0.5f, 0), D3DXVECTOR4(1, 1, 1, 1)},
            {D3DXVECTOR3(0.5f, 0, 0), D3DXVECTOR4(0, 0, 1, 1)},
            {D3DXVECTOR3(-0.5f, 0, 0), D3DXVECTOR4(1, 0, 0, 1)}
        };

        D3D10_SUBRESOURCE_DATA initData;
        initData.pSysMem = vertices;

        D3D10_BUFFER_DESC bufferDesc;
        ZeroMemory(&bufferDesc, sizeof(bufferDesc));
        bufferDesc.Usage = D3D10_USAGE_DEFAULT;
        bufferDesc.ByteWidth = sizeof(VertexPositionColor) * 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(VertexPositionColor);
        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 cornflowerBlue[] = {0.39, 0.58, 0.93, 1};
        graphicsDevice->ClearRenderTargetView(renderTargetView, cornflowerBlue);


        //三角形の3D空間の中での動きをあらわすマトリックスです。
//ここでは、時間によってz座標が[-1 ~ -3]の間を動きまわります。
//(xとyは動かない) D3DXMATRIX worldTransform; D3DXMatrixTranslation( &worldTransform, 0, 0, (float)Math::Sin(D3DXToRadian(Environment::TickCount / 20.0)) - 2 ); //三角形に遠近法の効果を与えるマトリックスです。
//視野の角度は90°です。
//また、あまり見た目に影響を与えませんが、
//描画される領域は画面からの距離が[0.1f ~ 1000]に設定されていて、
//この領域を外れると、三角形は描画されなくなります。
D3DXMATRIX projectionTransform; D3DXMatrixPerspectiveFovRH( &projectionTransform, D3DXToRadian(90), (float)ClientSize.Width / ClientSize.Height, 0.1f, 1000 ); //上の2つのマトリックスを組み合わせて、シェーダーに送り込みます。
//つまり、「三角形のz座標を動かしてから、遠近法っぽい効果を適用する」
//ことを意味するマトリックスを新たに作り、
//それをエフェクトオブジェクトにセットしています。
//(実際の変換はDrawの時、HLSLのシェーダープログラムの中で) effect->GetVariableByName("Transform")->AsMatrix()->SetMatrix( (float*)&(worldTransform * projectionTransform) );
effect->GetTechniqueByName("MyTechnique")->GetPassByIndex(0)->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(); }

ここでやっている新しいことは、2つのマトリックス(ベクトルの変換を表す魔法のようなオブジェクト!3Dモデルの座標を表すベクトルを平行移動したり、回転したり、拡大したり、はたまた遠近法の効果(近いものは大きく、遠いものは小さく)を出すように変換してくれたりもします)を掛け合わせて(2つのマトリックスを * で掛け合わせると、2つの変換を両方行う新たなマトリックスが出来上がります。たとえば、「90°回転する」マトリックスと「2倍に拡大する」マトリックスを掛け合わせると、「90°回転してから2倍する」新たなマトリックスが1つ出来上がります。おもしろいことに、マトリックスを掛け合わせてできるマトリックスの使うメモリのサイズは同じです(ヒープとか使ってません。スタックだけです)。マトリックスの実体は、16個のfloat型の変数なのです。)、新たなマトリックスを作り、それをシェーダーにセットしていることです。
ようするに、シェーダーに対して、「ポリゴンのz座標を動かしてから、遠近法の効果を適用せよ!」と命令しているようなものです。


MyShader.fx
matrix Transform;



struct VertexPositionColor
{
    float4 Position : SV_Position;
    float4 Color : COLOR;
};

VertexPositionColor MyVertexShader(VertexPositionColor input)
{
    input.Position = mul(input.Position, Transform);
    return input;
}

float4 MyPixelShader(VertexPositionColor input) : SV_Target
{
    return input.Color;
}

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

シェーダーのほうでは、新たにTransformというグローバル変数を宣言しています。
これはポリゴンの頂点をどのように変換するかを表すmatrixオブジェクトで、C++/CLI側から値をセットします(HLSLのグローバル変数は、デフォルトでC++側からアクセスできます)
実際の変換は、頂点シェーダの中で、mul関数を呼ぶことによって行っています。
ベクトルにマトリックスをかければ、そのベクトルはマトリックスによって変換されるのです。
たとえば、「2倍に拡大せよ」というマトリックスをベクトルにかけると、そのベクトルは2倍されます。
ここでは、Transformというマトリックスを使ってベクトルを変換しています。
TransformはC++/CLI側から、「ポリゴンのz座標を動かしてから、遠近法の効果を適用せよ」という風にセットされているので、つまりはそのように位置を表すベクトルは変換されます。


マトリックス

マトリックスにはD3DXMATRIX構造体を使います(マトリックスの説明は「かんたんXNA」しましたし、上のほうでもそれなりに説明したので、ここでは省きます)
D3DXMATRIX構造体は、D3DMATRIX構造体に、C++から使いやすくなるよう、演算子を追加したものです(ですから、D3DMATRIXはC言語でも使えますが、D3DXMATRIXはCでは使えません)
個の構造体のメンバ変数はたぶんあまり使いません。
よく使うかもしれないのは * 演算子でしょう。
別のマトリックスとかけると、新たな、2つを融合させたマトリックスを返します。

D3DXMATRIX operator * ( CONST D3DXMATRIX& ) const;
D3DXMATRIX& operator *= ( CONST D3DXMATRIX& );



で、このマトリックスというのは、ベクトルの変換を表すオブジェクトなのですが、どういう変換を表すのか決めるのには関数を使います。
このマトリックスを作る(というかマトリックスへのポインタを受け取って値をセットする)関数はいろいろありますが、ここでは平行移動を表すD3DXMatrixTranslation関数と遠近法な感じにするD3DXMatrixPerspectiveFovRH関数を使っています。
どうやら関数名は D3DXMatrix変換の名前 となっているようですね。
D3DXMATRIX* D3DXMatrixTranslation(
    D3DXMATRIX *outMatrix,
    float x,
    float y,
    float z
);

D3DXMATRIX * D3DXMatrixPerspectiveFovRH(
    D3DXMATRIX *outMatrix,
    float fieldOfViewY,
    float aspectRatio,
    float nearPlaneDistance,
    float farPlaneDistance
);

この関数は説明が必要でしょう。

まず、fieldOfViewYは、Y方向の視野の角度です。これが大きければ大きいほど、広い範囲を見ることができます。3Dの一人称シューティングゲームなんかで、この値を大きくなるよう改造して、より敵に対応しやすくすることがあるという話を聞いたことがあります。
この引数の単位はラジアンです。D3DXToRadianマクロを使うと、小学生にも親しみ深い ° からラジアンに変換できるので使うといいかもしれません(バカにしているわけではありませんよ。可読性が一番です)。

aspectRatioは、表示する画面のアスペクト比ですね。つまり、横幅 / 縦幅です。これが狂っていると、縦に延びたり横に伸びたりしてしまいます。

nearPlaneDistancefarPlaneDistanceは一緒に説明するべきでしょう。この2つの引数は、ポリゴンの描画される領域を表します。ポリゴンは画面に近すぎても描画されませんし、遠すぎても描画されません。その境目を、この2つは表します。nearPlaneDistanceは手前にある境界で、farPlaneDistanceは遠くにある境界です。


シェーダーの変数のセット

シェーダーのグローバル変数に値をセットするには、まずID3D10Effect::GetVariableByNameを使って、ID3D10EffectVariableオブジェクトを得ます。

ID3D10EffectVariable* GetVariableByName(
    LPCSTR name
);

そしてこれだけではまだダメです。
これからアクセスする変数の型を定めなければいけません。
そこで、 ID3D10EffectVariable::As変数の型 というメンバ関数を呼んで、型に特有なインターフェースをゲットします。
このサンプルではマトリックスを使っているので、AsMatrixです。

ID3D10EffectMatrixVariable* AsMatrix();

これを呼ぶと、ID3D10EffectMatrixVariableという、マトリックスのセットやゲットをできるインターフェースが返ります。
このインターフェースは、ID3D10EffectVariableを継承していて、マトリックス専用のセッターやゲッター(?)を持っています。
ここではID3D10EffectMatrixVariable::SetMatrixを使っています。
HRESULT SetMatrix(
    float *data
);

引数dataはセットするマトリックスへのポインタです。








拍手[1回]

PR

DirectX10 + C++/CLI 色つき三角形を表示

前回は真っ白なポリゴンを表示しただけだったので、今回はそれに色をつけてみました。

前回とどこが違うのかというと、頂点に使う構造体です。
前回は3D座標だけが頂点のメンバでしたが、今回はそれに付け加え、色もメンバになっています。

そしてその影響で、インプット・レイアウトオブジェクトやHLSLで書くシェーダーが変更されます。

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

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

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

struct VertexPositionColor
{
    D3DXVECTOR3 Position;
    D3DXVECTOR4 Color;
};

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


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

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

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

private:

    void InitDevice()
    {
        createGraphicsDevice();

        createRenderTargetView();

        setupViewport();

        LoadGraphicsContent();
    }


    void LoadGraphicsContent()
    {
        //シェーダーの設定
        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;



        //描画のときデバイスに入力する頂点データのレイアウトの設定
        D3D10_INPUT_ELEMENT_DESC vertexElements[] = 
        {
            {"SV_Position", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D10_APPEND_ALIGNED_ELEMENT, D3D10_INPUT_PER_VERTEX_DATA, 0},
            {"Color", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D10_APPEND_ALIGNED_ELEMENT, D3D10_INPUT_PER_VERTEX_DATA, 0}
        };

        D3D10_PASS_DESC passDesc;
        effect->GetTechniqueByName("MyTechnique")->GetPassByIndex(0)->GetDesc(&passDesc);

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

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

        this->vertexLayout = vertexLayout;

        graphicsDevice->IASetInputLayout(vertexLayout);



        //頂点バッファの設定
        VertexPositionColor vertices[] = 
        {
            {D3DXVECTOR3(0, 0.5f, 0), D3DXVECTOR4(1, 1, 1, 1)},
            {D3DXVECTOR3(0.5f, 0, 0), D3DXVECTOR4(0, 0, 1, 1)},
            {D3DXVECTOR3(-0.5f, 0, 0), D3DXVECTOR4(1, 0, 0, 1)}
        };

        D3D10_SUBRESOURCE_DATA initData;
        initData.pSysMem = vertices;

        D3D10_BUFFER_DESC bufferDesc;
        ZeroMemory(&bufferDesc, sizeof(bufferDesc));
        bufferDesc.Usage = D3D10_USAGE_DEFAULT;
        bufferDesc.ByteWidth = sizeof(VertexPositionColor) * 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(VertexPositionColor);
        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 cornflowerBlue[] = {0.39, 0.58, 0.93, 1};
        graphicsDevice->ClearRenderTargetView(renderTargetView, cornflowerBlue);


        effect->GetTechniqueByName("MyTechnique")->GetPassByIndex(0)->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
struct VertexPositionColor
{
    float4 Position : SV_Position;
    float4 Color : COLOR;
};

VertexPositionColor MyVertexShader(VertexPositionColor input)
{
    return input;
}

float4 MyPixelShader(VertexPositionColor input) : SV_Target
{
    return input.Color;
}

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


coloredTriangle.jpg
色が付いてカラフルになりました。
ついでに背景を、XNAのサンプルでしょっちゅう使われているコーンフラワーブルーにしました。(三角形の3つの色とかぶらないようにしないと見にくくなってしまうからです。)

拍手[0回]


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回]


DirectX10 + C++/CLI デバイスの作成と背景のクリア

さっそくDirect3D10のデバイスを作って、ウィンドウのクライアントエリアを(青く)クリアしてみました。

デバイスを作るといっても、残念ながらぼくはDirectX10対応のグラフィックスカードを持っていないので、(リファレンス・デバイスを使って)ソフトウェアで動作をエミュレートすることにします。

解説はややこしくなりそうなので、まずはコードと結果ををのせます。

Dx10Test.cpp


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

#include<d3d10.h>

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


ref class Game : Form
{
	//描画を行うデバイスです。
	//これを使ってポリゴンとかいろいろなものを描画します。
	ID3D10Device* graphicsDevice;

	//スワップ・チェーン
	//これはダブルバッファリングを行うための2つのバッファを持っています。
	//(片方が描画対象、もう片方はディスプレイに表示されるバッファ)
	//この2つのバッファは、描画が終わって実際にディスプレイに表示するときに
	//役割が交代(スワップ)します。
	//また、実際には2より多くのバッファが鎖(チェーン)のように
	//続くように設定することもできます。
	IDXGISwapChain* swapChain;

	//描画する対象です。
	//画面をある一色で塗りつぶ(クリア)したいときには
	//これに対してクリアします。
	ID3D10RenderTargetView* renderTargetView;

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

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

		CleanupDevice();
	}

private:

	void InitDevice()
	{
		createGraphicsDevice();

		createRenderTargetView();

		setupViewport();
	}

	void CleanupDevice()
	{
		if(graphicsDevice) graphicsDevice->ClearState();
		if(renderTargetView) renderTargetView->Release();
		if(swapChain) swapChain->Release();
		if(graphicsDevice) graphicsDevice->Release();
	}

	void Draw()
	{
		float blue[] = {0, 0, 1, 1};
		graphicsDevice->ClearRenderTargetView(renderTargetView, blue);
		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;


		//C++/CLIでは、クラスのメンバー変数のポインタを
		//直接&graphicsDeviceとか&swapChain
		//というふうにして関数に渡すことができません。
		//一方、ローカルな変数ならそれができます。
		//そこでここでは間接的に、このクラスのメンバー、
		//graphicsDeviceとswapChainに値をセットすることにします。
		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;
	}

	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();
}

 CreatedDevice.jpg

青くウィンドウのクライアント領域が塗りつぶされました。
成功です。
次回にでもこの上にポリゴンを描いてみましょうかね。

ここからDirectX10を使っているのでコンパイルにはd3d10.libが必要です。

cl /clr dx10Test.cpp d3d10.lib


大まかな説明
 
大まかな流れは次のようになります。

1.ID3D10DeviceIDXGISwapChainID3D10RenderTargetViewオブジェクトをつくっていろいろ設定を行います。

2.(メッセージループ中に)ウィンドウのクライアント領域を青でクリア。この作業には1で作った3つのオブジェクト全部を使います。

3.クリーンナップ。 「1.」の後始末。


ID3D10DeviceとはDirect3D10で描画に使うハードウェア(ビデオカード)の描画機能をあらわすインターフェースです(ただし!ハードウェアを使わずにソフトウェアで機能をエミュレートすることもできます。そうした場合実行速度が遅くなりますが、DirectX10対応のビデオカードが無くても動きます)
描画を行うメソッドはこのインターフェースが持っています。
今回はやりませんが、ポリゴンとかを描画するのに使うのはこのインターフェースです。

IDXGISwapChainは、乱暴に説明するなら、Direct3D10でダブルバッファリングを行うためのインターフェースです。
ダブルバッファリング――つまりプログラムで描画するバッファ(バック・バッファ)と、実際にディスプレイ画面に描画されるバッファ(フロント・バッファ)の2つに分かれています。
このインターフェースは多くの場合この2つのバッファを持つことになります(しかし、3つ以上にすることも可能です。そうした場合、バッファが"鎖"(チェーン)のようにつながっているように見えることがわかると思います)

プログラムはバック・バッファに対して描画を行います。
そして、描画が完了するとそのバック・バッファを今度はフロント・バッファに変えます(そうすると描画したものがディスプレイ画面に表示されるようになりますからね)。
同時に、それまでフロント・バッファだったものは今度はバック・バッファに変えます。
つまり、バック・バッファとフロント・バッファの役割を交代(スワップ)することによって、描画結果を画面に反映させているのです。(まるではたおり機で布をバッタンバッタンと織っていくような感じですね。あれは縦糸を上下の2つのグループに分けて、横糸を通したらこの2つのグループを入れ替えて…というふうにして布を織っていくのです。)

  バッファ1 バッファ2
1回目のDraw バック・バッファ(プログラムが描画を行う) フロント・バッファ(ディスプレイに表示される)
2回目のDraw フロント・バッファ バック・バッファ
3回目のDraw バック・バッファ フロント・バッファ

ちなみに、Iのあとの最初の4文字、"DXGI"というのは"DirectX Graphics Infrastructure"の略です。
DXGIの主な目的はDirect3Dより低レベル(カーネルモードやシステムハードウェアと通信したり)で、あんまり変化しない部分の仕事をすることだそうです(MSDNいわく)。


ID3D10RenderTargetViewはID3D10Deviceで描画を行う対象(レンダーターゲット)です(実体はテクスチャ)。ここではこれは画面(正確には画面に描画される前のバック・バッファ)ですが、単なるテクスチャをレンダーターゲットにして、そのテクスチャを何かのポリゴンに貼り付けて鏡みたいな効果を出すっていうのもアリでしょう。

さて、このインターフェースの名前は単なる「レンダーターゲット」ではなく、「レンダーターゲットビュー」となっています。
この「ビュー」というのは何かというと、これはDirect3D10で使うリソース(テクスチャとか頂点バッファとか…)がどのようにデバイスで解釈されるかを表します(C言語系でいう「型」みたいなものですね。ただし、「ビュー」は動的に変更することが出来ますし、複数のビューを1つのリソースが持つことが出来ます)。
ここでは、テクスチャをレンダーターゲットとして扱うため、「RenderTargetView」です。
(他にも、リソースを深度ステンシルバッファとして扱うインターフェース「ID3D10DepthStencilView」や、シェーダで使うリソースとして扱う「ID3D10ShaderResourceView」があります)

Direct3D10ではリソースは単なるビットのかたまりで、型が無いように設定できます。
1つのリソースを、複数の種類のものとして扱うことが出来ます(1つのリソースは複数のビューを持つことが出来ます)。
テクスチャとしても扱えるし、頂点バッファとしても扱えるようにリソースを作ることが出来るのです。
そうすれば、「ピクセルシェーダを使って頂点バッファに何かのモデルの頂点を描き出す」、なんてことが事が出来ます。
より柔軟なことが出来るようになるとい言うわけです。




デバイスとスワップチェーンを作る

ID3D10DeviceとIDXGISwapChainのオブジェクトを作るにはD3D10CreateDeviceAndSwapChain関数を使います。(いちおう、D3D10CreateDeviceとIDXGIFactory::CreateSwapChainを使って別々に作ることも可能です。しかし、両方とも使うのなら同時に作った方がいいでしょう。MSDNいわく、別々に作るのはもう一方を必要としない時だとかなんとか・・・)

 

HRESULT D3D10CreateDeviceAndSwapChain(
    IDXGIAdapter *adapter,
    D3D10_DRIVER_TYPE driverType,
    HMODULE softwareRasterizerDll,
    UINT deviceCreationFlags,
    UINT sdkVersion,
    DXGI_SWAP_CHAIN_DESC *swapChainDescription,
    IDXGISwapChain **outSwapChain,
    ID3D10Device **outDevice
);


adapterはビデオカード(あるいはマザーボードの描画する機能)をあらわします。
ふつうはビデオカードは1枚だけですが、複数枚ある場合もありますからね。
このadapterに対応したID3D10Deviceが作られるのです。
ビデオカードが一枚だけの場合(たいていそうです)はNULLを渡すことが出来ます。

driverTypeはデバイスのタイプです。
ID3D10Deviceがビデオカードを使うか、あるいはデバッグやデモ用にソフトウェアでビデオカードをエミュレートするかといったことを決めます。

この引数の型はD3D10_DRIVER_TYPE列挙型で、4つのメンバ(定数?)があります。
typedef enum D3D10_DRIVER_TYPE
{
    D3D10_DRIVER_TYPE_HARDWARE = 0,
    D3D10_DRIVER_TYPE_REFERENCE = 1,
    D3D10_DRIVER_TYPE_NULL = 2,
    D3D10_DRIVER_TYPE_SOFTWARE = 3
} D3D10_DRIVER_TYPE;

  説明
HARDWARE ハードウェア(ビデオカード)を使ってID3D10Deviceを作ります。実用向け。
REFERENCE ソフトウェアでDirect3D10対応ビデオカードをエミュレートします。そのためDirect3D10対応ビデオカードが無くってもデモを動かすことが出来て便利です(でも遅い)。
NULL 実際の描画能力のないデバイスを作ります。
SOFTWARE 将来のために予約されています。


softwareRasterizerDllは、ソフトウェアラスタライザを実装したDllへのハンドルです。
ソフトウェアラスタライザを使わない時(たいていそうでしょうが・・・)はNULLです。


deviceCreationFlagsは、デバイスに与える追加の機能(デバッグしやすくしたりとか…)を表します。

この引数にはD3D10_CREATE_DEVICE_FLAG列挙型を使います。
typedef enum D3D10_CREATE_DEVICE_FLAG
{
    D3D10_CREATE_DEVICE_FLAG_SINGLETHREADED = 0x1,
    D3D10_CREATE_DEVICE_FLAG_DEBUG = 0x2,
    D3D10_CREATE_DEVICE_FLAG_SWITCH_TO_REF = 0x4,
    D3D10_CREATE_DEVICE_FLAG_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS = 0x8
} D3D10_CREATE_DEVICE_FLAG;


  説明
SINGLETHREADED デバイスのスレッドセーフティを無効にします。(Direct3D10ではデフォルトでスレッドセーフ)
DEBUG デバッグレイヤーをデバイスに追加します。
それによってエラーレポートの文字列を出力(ID3D10InfoQueueを使う)できるようになったりします。
SWITCH_TO_REF デバイスがハードウェアかリファレンスかを変えることができるようになります(ID3D10SwitchToRef::SetUseRefを使う)。
PREVENT_INTERNAL_THREADING_OPTIMIZATIONS 予約されています。

sdkVersionはSDKのバージョンです(そのまんま……)。
これはd3d10.hのなかで定義されている定数、D3D_SDK_VERSIONを使います。


swapChainDescriptionはどのようなスワップチェーンを作るかを表します。

outSwapChainはこの関数の結果を格納するためのポインタです。この引数の表すアドレスに、スワップチェーンへのポインタが書き込まれます。

outDeviceはこの関数の結果を格納するためのポインタです。この引数の表すアドレスに、デバイスへのポインタが書き込まれます。


DXGI_SWAP_CHAIN_DESC構造体

6番目の引数、swapChainDescriptionについてもう少しくわしく見てみます。
これはどのようなスワップチェーンを作るかを表すDXGI_SWAP_CHAIN_DESC構造体へのポインタなので、まずはこの構造体を見てみます。
typedef struct DXGI_SWAP_CHAIN_DESC
{
    DXGI_MODE_DESC BufferDesc;
    DXGI_SAMPLE_DESC SampleDesc;
    DXGI_USAGE BufferUsage;
    UINT BufferCount;
    HWND OutputWindow;
    BOOL Windowed;
    DXGI_SWAP_EFFECT SwapEffect;
    UINT Flags;
} DXGI_SWAP_CHAIN_DESC;



BufferDescはスワップチェーンがどのようなバッファを持つかを表します。
バッファの大きさやフォーマット、拡大する時にはどのように拡大するかといったことを表します。

BufferDescの型はディスプレイのモードを表すDXGI_MODE_DESC構造体です。

typedef struct DXGI_MODE_DESC {
    UINT Width;
    UINT Height;
    DXGI_RATIONAL RefreshRate;
    DXGI_FORMAT Format;
    DXGI_MODE_SCANLINE_ORDER ScanlineOrdering;
    DXGI_MODE_SCALING Scaling;
} DXGI_MODE_DESC, *LPDXGI_MODE_DESC;

WidthHeightは解像度の横幅と縦幅です。

RefreshRateは一秒間にリフレッシュする回数を表します。この型は分数(Rational)を表すDXGI_RATIONAL構造体で、2つのUINT型のメンバー、Numerator(分子)とDenominator(分母)を持っています。MSDNのチュートリアルを見る限りでは分子を60、分母を1にセットするのが普通なようです(つまり1秒間に60回リフレッシュ)。でもそれじゃあ構造体なんて使わなくって、ただのUINTでいいような気も……。
typedef struct DXGI_RATIONAL {
    UINT Numerator;
    UINT Denominator;
} DXGI_RATIONAL, *LPDXGI_RATIONAL;


Formatは、データのフォーマットを表します。
この型はDXGI_FORMAT列挙型です。
メンバーを紹介したいのですが、死ぬほど多い(90個)のでリンクだけ。
メンバー名を見たところ、「DXGI_FORMAT_[データのレイアウト]_[型]」という法則があるようですね(例えば、DXGI_FORMAT_R32G32B32_FLOATと言った具合です)

ScanlineOrderingはラスターがイメージを作り出す方法です。この型はDXGI_MODE_SCANLINE_ORDER列挙型です。

定数名 説明
DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED 順番を指定しません。 0
DXGI_MODE_SCANLINE_ORDER_PROGRESSIVE 最初から最後まで順番に、スキップすることなく。 1
DXGI_MODE_SCANLINE_ORDER_UPPER_FIELD_FIRST 上のほうのフィールドから始めます。 2
DXGI_MODE_SCANLINE_ORDER_LOWER_FIELD_FIRST 下の方のフィールドから始めます。 3


Scalingはどのようにイメージを拡大するかを表します(指定されたモニターの解像度に合わせなければいけませんからね)。
この型はDXGI_MODE_SCALING列挙型です。

定数名 説明
DXGI_MODE_SCALING_UNSPECIFIED 拡大の方法を指定しません 0
DXGI_MODE_SCALING_CENTERED 拡大しません。イメージはディスプレイの中央に表示されます。 1
DXGI_MODE_SCALING_STRETCHED 引き伸ばされて拡大されます。 2



さて、DXGI_SWAP_CHAIN_DESC構造体のメンバーの説明に戻ります。(こういうネストっていやですね!)
次はSampleDescです。

SampleDescは、このスワップチェーンでアンチ・エイリアシング(画像のギザギザを無くして滑らかに)する方法をあらわします。
アンチエイリアシングするにはいくつかの点の色を採ってきて(サンプルする)、その平均値の色を使います。

このメンバの型はそのマルチ・サンプリングを表すDXGI_SAMPLE_DESC構造体です。

typedef struct DXGI_SAMPLE_DESC {
    UINT Count;
    UINT Quality;
} DXGI_SAMPLE_DESC, *LPDXGI_SAMPLE_DESC;

Countはピクセル1つあたりのマルチサンプルの数です。デフォルトでは1で、アンチエイリアシングを行いません。

Qualityはイメージのクオリティレベルです。
値の範囲は0 ~ (ID3D10Device::CheckMultisampleQualityLevelsの戻り値 - 1)までです。
デフォルトでは0です。


BufferUsageはこのスワップチェーンのバックバッファの使用目的と、CPUアクセスオプションを表します。
この引数の型はD3D10のリソースの使い方を表すDXGI_USAGEですが、これは意外なことに列挙型ではなく、typedefでUINTから作られた型です(といっても注意しなきゃいけないようなことは無いようですが・・・)

  説明
DXGI_USAGE_SHADER_INPUT シェーダーへの入力として使います。
DXGI_USAGE_RENDER_TARGET_OUTPUT レンダーターゲット、つまりディスプレイへの出力として使います。
DXGI_USAGE_BACK_BUFFER  バックバッファとして使います。
DXGI_USAGE_SHARED  共有します。
DXGI_USAGE_READ_ONLY 読み込み専用です。


BufferCountは・・・これは2つの情報があってどっちが正しいのかよくわかりません。
msdnには、これはフロントバッファも含めた、全てのバッファの数だと書いてあります。
でもmsdnのチュートリアルではこのメンバに1を代入しているわけですから、これではバックバッファが作られないことになってしまいます。
これではダブルバッファリングできません。(いや、でももしかするとバックバッファの数を0にしていすると自動的に1つ作ってくれる仕様なのかもしれません。うーん…)

一方、つい最近読んだ『Beginning DirectX 10 Game Programming』や、DXTen.comのwikiには、これはバックバッファの数だと書いてあります。
msdnのチュートリアルから考えて、こっちの方が筋が通っているように思えます。
こっちの解釈ならこのメンバに1を代入してもバックバッファとフロントバッファの両方が作られますからね。
それにDirectXの古いバージョンではこっちでしたし・・・。

OutputWindowは、D3D10で使うウィンドウへのハンドルです。
ここではWindows.Formsを使っているのでControl.Handleプロパティの値を使えばいいでしょう。

Windowedは、trueならウィンドウモードになって、falseならフルスクリーンモードになります。

SwapEffectは、どのようにバックバッファとフロントバッファをスワップ(入れ替え。ダブルバッファリングで、描画を完了した時にその描画を実際の画面に反映させる時に行う)するかを表します。
typedef enum DXGI_SWAP_EFFECT
{
    DXGI_SWAP_EFFECT_DISCARD = 0,
    DXGI_SWAP_EFFECT_SEQUENTIAL = 1
} DXGI_SWAP_EFFECT, *LPDXGI_SWAP_EFFECT;

DXGI_SWAP_EFFECT_DISCARDは、バックバッファ(だったもの)がディスプレイに表示したあとは、その内容は捨てられる、保障されないことを表します。(「ディスプレイに表示」とは、ようするにスワップのことです。)
まあ、バックバッファと言うのは描画結果をディスプレイに表示するまでの一時的なバッファですから、表示された後どうなっても問題ないでしょう。
実行速度は速いですし、普通はこれをセットします。

DXGI_SWAP_EFFECT_SEQUENTIALは、DISCARDの逆(?)で、ディスプレイに表示した後も、バックバッファ(だったもの)の内容が変わらないことを意味します。

Flagsは、このスワップチェーンがどんな風に働くかを表す追加の情報です。
typedef enum DXGI_SWAP_CHAIN_FLAG
{
    DXGI_SWAP_CHAIN_FLAG_NONPREROTATED = 1,
    DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH = 2
} DXGI_SWAP_CHAIN_FLAG, *LPDXGI_SWAP_CHAIN_FLAG;

  説明
DXGI_SWAP_CHAIN_FLAG_NONPREROTATED これはフルスクリーンモードでしか意味を成さないのですが、自動的なイメージの回転をオフにします。つまり、ゲームが回転して表示しなきゃいけないような時に、フロントバッファからモニターへ表示する時の回転をしません。
DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH ディスプレイモード(DXGI_MODE_DESC)を途中で変えることができることを表します(変えるにはIDXGISwapChain::ResizeTargetを使います)。



メソッド IDXGISwapChain::GetBuffer

このプログラムで一番最初に使っているD3Dのメソッドは、IDXGISwapChain::GetBufferです。
このメソッドは、スワップチェーンのバックバッファの1つを取得します。
HRESULT GetBuffer(
    UINT bufferIndex,
    FEFIID bufferInterfaceType,
    void **outBackBuffer
);

bufferIndexは、取得するバックバッファのインデックスです。

bufferInterfaceTypeは、バッファのインターフェースの型です。

outBackBufferは、このメソッドで取得するバックバッファを入れるための変数へのポインタです。
ここに結果が入ります。


メソッド ID3D10Device::CreateRenderTargetView

次に使っているメソッドはID3D10Device::CreateRenderTargetViewです。
これは「あるリソース(普通はテクスチャでしょうが、トリッキーな方法を使いたいのなら、たとえば頂点バッファでもOK)をレンダーターゲットとして扱う」ことを表すビューを作り出します。

このサンプルでは、上記のIDXGISwapChain::GetBufferで取り出したバックバッファから、レンダーターゲットビューを作り出しています。

HRESULT CreateRenderTargetView(
    ID3D10Resource *resource,
    const D3D10_RENDER_TARGET_VIEW_DESC *description,
    ID3D10RenderTargetView **outRenderTargetView
);

resourceは、このメソッドでビューを作るリソースです。
このリソースは、レンダーターゲットとして扱うことができるようになります。
ただし、そのリソースは、作られるときにD3D10_BIND_RENDER_TARGETフラグを指定されていなければいけません。

descriptionは、リソースがどのようなレンダーターゲットとして扱われるようになるかを表します。
リソースの作成時にフォーマットの型を指定していない時(TYPELESSのフォーマットを指定した時)には、ここで指定できます。
このパラメーターにNULLを指定すると、mipmapレベル0(つまり一番大きい、縮小されていないテクスチャ)にアクセスするようになります。
普通の用途ならNULLでいいでしょう。

outRenderTargetViewは、このメソッドで作ったレンダーターゲットビューを格納するための変数へのポインタです。
ここに結果が入ります。


メソッド ID3D10Device::RSSetViewports

グラフィックスデバイスにビューポート(ウィンドウ中のどの領域に描画されるかを表す構造体。二人対戦型ゲームで、ウィンドウの画面を真ん中で半分に分割したい時に使います。ビューポートをウィンドウの半分のサイズにして、それぞれのプレイヤー用に位置は変えつつ、2回描画すればいいのです。)をセットします。
DirectX9まではこれはデフォルトの値でよかったはずですが、DirectX10ではどうやら必ずプログラマがセットしてやらなければいけないようです。

なお、このメソッドは、頭に「RS」というのをくっつけていますが、これはラスタライザー・ステージ(rasterizer stage : 頂点データからピクセルデータを作り出すステージ(まだ色は決まってない。ピクセルのテクスチャ座標とかはこれで決まる)。)の略です。
ラスタライザー・ステージで使うパラメーターをセットしているので、こういう名前になっているのでしょう。
実際、ID3D10Deviceには、ステージの略称が頭にくっついたメソッドが他にもまだまだたくさんあります。

void RSSetViewports(
    UINT numViewports,
    const D3D10_VIEWPORT *viewports
);

numViewportsはセットするビューポートの数です。
普通は2番目の引数viewports配列の長さになるでしょうね。

viewportsはセットするビューポートを格納する配列です。

ビューポートはD3D10_VIEWPORT構造体によって定義されています。

typedef struct D3D10_VIEWPORT {
    INT TopLeftX;
    INT TopLeftY;
    UINT Width;
    UINT Height;
    FLOAT MinDepth;
    FLOAT MaxDepth;
} D3D10_VIEWPORT;


TopLeftXTopLeftYは、描画する領域の左上の点を表します。(こんな変数名にしないで単にX, Yでいいような気もするのですが・・・)

WidthHeightは描画する領域のサイズを表します。

MinDepthMaxDepthは深度バッファのとる値の範囲です。(深度バッファとはピクセルごとに用意される値で、そのピクセルのカメラからの距離を表します(ただし0から1まで)。これの値を使って、デバイスは近くのポリゴンを、遠くのポリゴンより優先して描画します。これがないと近くの壁が遠くのキャラクターの後ろに描画されると言うめちゃくちゃなことになります。)
これは両方とも0と1の間でなければなりません。
普通は単純に、MinDepth = 0、MaxDepth = 1としていいでしょう。


メソッド ID3D10Device::ClearRenderTargetView

ようやく描画関連のメソッドです。
このメソッド、ID3D10Device::ClearRenderTargetViewは、レンダーターゲットビューを指定した一色で塗りつぶ(クリア)します。
普通このメソッドは、描画の一番初めに呼び出します。

void ClearRenderTargetView(
    ID3D10RenderTargetView *renderTargetView,
    const FLOAT colorRGBA[4]
);

renderTargetViewはクリアするレンダーターゲットです。
ここに指定したレンダーターゲットが、colorRGBAで塗りつぶされます。

colorRGBAは塗りつぶす色を表します。これはfloatの配列(長さ4)ですが、内訳は、{赤、緑、青、アルファ}です。

メソッド IDXGISwapChain::Present

このメソッドは描画結果をユーザーに見えるようにします。
それまではバックバッファに対して描画していたので、ユーザーには見えないというわけです。

このメソッドはバックバッファとフロントバッファを入れ替え、新しい描画結果を、ユーザーが見えるようにするのです。

HRESULT Present(
    UINT syncInterval,
    UINT flags
);

syncIntervalは、垂直ブランクとどのようにシンクロするかを表します(垂直帰線区間)。

これに0を指定すると、シンクロは行われずに、すぐにpresentします。
1,2,3,4だとその分の垂直ブランクの後に、描画結果を見えるようにする作業をシンクロします。

flagsはオプションです。0でいいでしょう。(msdnのDXGI_PRESENTの書き方が悪いんです!これじゃわかりませんって!)

メソッド ID3D10Device::ClearState

このメソッドはグラフィックスデバイスを最初の、作った直後の状態に戻します。
セットしたリソースやビューポート、さらに他のもろもろの設定をNULLにします。
サンプルを見るに、後始末をする前に呼ぶようですね。
void ClearState();


拍手[1回]


DirectX10 + C++/CLI ウィンドウの表示

最近忙しかったので久々の投稿です。
こう時間が空くとどうも精神的にやりにくくなりますね・・・。

さて、前々から思っていたのですが、そろそろDirectX10をやってみたいと思います。
言語はC#・・・を使いたいところですが、ManagedDirectXは消滅してしまったので、泣く泣くC++/CLIを使います。
OSはVistaオンリーです。DirectX10はXPでは使えません。

まずはSDKのダウンロードです。
現在最新のDirectX のSDKは(November 2007)です。
Novemberって何月だっけ・・・と思って辞書で引くとなんと11月、つまり今月です!
良いタイミングでDirectX10をはじめたことになります。
幸先良いスタートです。

また、Windows Platform SDKをインストールする必要もあるようです。(もちろんWindows Vista用のです)
なんせVisual C++ ExpressにはWindows.hが付いてきません
DirectX10のサンプルを動かすにはWindows SDKのIncludeファイルとLibファイルが必要です。

で、Windows SDKのIncludeファイルととLibファイルをC++/CLIのコンパイラから使えるようにします。(単にコピー&ペーストでいいでしょう。(いいわけない。環境変数LIBとINCLUDEでそれぞれのディレクトリのパスをセットしてやりましょう。)いちおうWindows SDKには"Integrate Windows SDK with Visual Studio 2005"というのが付いていますが、cl.exeには効果が無いように見えます。)

これで準備は整いました(たぶん)。
さっそくやってみましょう!



C++はほんと久しぶりですし、C++/CLIにいたっては初めてなので、少しずつ進んでいくことにします。

まずはウィンドウを作ってみます。


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

using namespace System::Windows::Forms;

int main()
{
	Form ^form = gcnew Form();
	form->Show();
	
	while(form->Created)
	{
		Application::DoEvents();
	}
}

で、Visual Studio 2005 Command Promptからコンパイルです。

cl /clr dx10Test.cpp

CreatedWindow.jpg
よし!
上手くいきました。
DirectXを使う場合はApplication::Run()を使えないのがちょっと残念ですが、まあいいでしょう。
Win32APIを使うよりははるかにましというものです。


さて、うまくウィンドウを表示できたので、こんどはちょっと遊び心を加えて見ましょう。
実行結果はそのままに、XNAっぽくリファクタリング?です。
#using<System.Windows.Forms.dll>
#using<System.dll>

using namespace System::Windows::Forms;

ref class Game : Form
{
	public:
	void Run()
	{
		Show();

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

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




 

拍手[0回]