忍者ブログ

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