忍者ブログ

Memeplexes

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

[PR]

×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。


XNAでエリア総和テーブルの実験

XNAでエリア総和テーブルの実験をしてみました。(まだ実験段階で、未解決の問題があります)
参考にした資料はATIの2つのpdfファイル(1, 2)です。

テクスチャのあるエリアの色の平均を求めるのに、エリア総和テーブルを使うと、かなり効率的に行えます。
(この方法のおもしろい所は、10x10の領域の平均だろうが、1000x1000の領域の平均だろうが、同じ程度の計算で済んでしまうことです。一見1000x1000の方がずっと計算が多くなりそうですが、そうはならないのがエリア総和テーブルの面白いところです。これはどういうことかというと、「100時間走った車の速度の平均を求める計算は、1時間走った車の速度の平均を求める計算の100倍時間がかかるわけではない」理由と、本質的には同じです。ようするにどちらとも、始めと終わりの車の位置を出して、引き算して、時間で割ってやるだけでいいわけですからね。エリア総和テーブルを作るということは、車の速度のデータから、車の位置のデータを作り出すことと、本質的に同じです。とりあえず足しまくるのです。)

エリア総和テーブルとは、右上にあるエリアの合計を表すテーブルです。
例えばこんなテクスチャがあったとします。
2 4 1 3
1 0 2 0
0 3 0 2
2 1 0 4

すると、そのテクスチャのエリア総和テーブルはこんなんです:
2 6 7 10
3 7 10 13
3 10 13 18
5 13 16 25

テーブルの各値は、右上のエリアのテクスチャの色の合計です。

実装に関してまだ納得できないところもあるので、詳しい説明はまたの機会に一度にすることにします。
(別に説明を思いつかないわけじゃないですよ!噛み砕いた説明はもう思いついていますが、説明する本人が自信なさげだったら説得力がないじゃないですか!)

ではコードです(ちょっと引っかかるところがいくつかあるのですが・・・・・・):
MyGame.cs
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

public class MyGame : Game
{
    GraphicsDeviceManager graphics;

    Texture2D texture;
    BasicEffect basicEffect;
    VertexDeclaration vertexDeclaration;

    Effect tableCreationEffect;
    RenderTarget2D renderTarget;
   

    public MyGame()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
    }

    protected override void LoadContent()
    {
        //このinitTextureの引数をいろいろ変えてみることで、
        //テクスチャのサイズ(横幅)が変わり、
        //画面に表示される画像の滑らかさが変わります。
        //たとえば、initTexture(8);ならカクカクしますが、
        //initTexture(128)なら滑らかです。
        initTexture(128);

        renderTarget = new RenderTarget2D(
            GraphicsDevice,
            texture.Width, texture.Height,
            texture.LevelCount,
            SurfaceFormat.Vector4
            );

        tableCreationEffect = Content.Load<Effect>("SummedAreaTableCreator");
        basicEffect = new BasicEffect(GraphicsDevice, null);



        vertexDeclaration = new VertexDeclaration(
            GraphicsDevice,
            VertexPositionTexture.VertexElements
            );
        GraphicsDevice.VertexDeclaration = vertexDeclaration;
    }

    private void initTexture(int width)
    {
        texture = new Texture2D(
            GraphicsDevice,
            width, 1,
            1,
            TextureUsage.None,
            SurfaceFormat.Vector4
            );
        Vector4[] data = new Vector4[texture.Width * texture.Height];

        for (int i = 0; i < data.Length; i++)
            data[i] = Vector4.One / texture.Width;

        texture.SetData<Vector4>(data);
    }

    protected override void UnloadContent()
    {
        texture.Dispose();
        renderTarget.Dispose();

        basicEffect.Dispose();
        vertexDeclaration.Dispose();
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.SetRenderTarget(0, renderTarget);
        GraphicsDevice.Clear(Color.CornflowerBlue);

        drawTexture(texture);

        createSummedAreaTable();

        GraphicsDevice.SetRenderTarget(0, null);
        GraphicsDevice.Clear(Color.CornflowerBlue);

        drawTexture(renderTarget.GetTexture());
    }

    private void createSummedAreaTable()
    {
        tableCreationEffect.Parameters["Width"].SetValue(renderTarget.Width);
        tableCreationEffect.Parameters["Height"].SetValue(renderTarget.Height);


        tableCreationEffect.Begin();

        for (int i = 0; i < System.Math.Log(texture.Width, 2); i++)
        {
            tableCreationEffect.CurrentTechnique.Passes[0].Begin();


            GraphicsDevice.SetRenderTarget(0, null);
            tableCreationEffect.Parameters["PreviousProduct"].SetValue(renderTarget.GetTexture());
            GraphicsDevice.SetRenderTarget(0, renderTarget);

            tableCreationEffect.Parameters["PassIndex"].SetValue(i);

            drawRect();


            tableCreationEffect.CurrentTechnique.Passes[0].End();
        }

        tableCreationEffect.End();

    }

    private void drawTexture(Texture2D texture)
    {
        basicEffect.TextureEnabled = true;
        basicEffect.Texture = texture;
        basicEffect.Begin();
        basicEffect.CurrentTechnique.Passes[0].Begin();

        drawRect();

        basicEffect.CurrentTechnique.Passes[0].End();
        basicEffect.End();
    }

    private void drawRect()
    {
        //四角形(テクスチャつき)の頂点
        VertexPositionTexture[] vertices = {
            new VertexPositionTexture(new Vector3(-1, 1, 0), new Vector2()),
            new VertexPositionTexture(new Vector3(1, 1, 0), new Vector2(1, 0)),
            new VertexPositionTexture(new Vector3(-1, -1, 0), new Vector2(0, 1)),
            
            new VertexPositionTexture(new Vector3(1, 1, 0), new Vector2(1, 0)),
            new VertexPositionTexture(new Vector3(1, -1, 0), new Vector2(1, 1)),
            new VertexPositionTexture(new Vector3(-1, -1, 0), new Vector2(0, 1))
        };

        GraphicsDevice.DrawUserPrimitives<VertexPositionTexture>(
            PrimitiveType.TriangleList,
            vertices,
            0,
            2
            );
    }

    static void Main()
    {
        using (MyGame game = new MyGame())
        {
            game.Run();
        }
    }
}


SummedAreaTableCreator.fx (Contentフォルダ内)
texture PreviousProduct;

sampler TextureSampler = sampler_state
{
    Texture = (PreviousProduct);
};

float Width;
float Height;
int PassIndex;


struct VertexPositionTexture
{
    float4 Position : POSITION;
    float2 TextureCoordinate : TEXCOORD;
};

VertexPositionTexture VertexShaderFunction(VertexPositionTexture input)
{
    return input;
}

float4 getColor(float2 texCoord)
{
    if(texCoord.x < 0 || texCoord.y < 0) return 0;
    else return tex2D(TextureSampler, texCoord + float2(0.5/Width, 0.5/Height));
}


float4 SumX(float2 textureCoordinate : TEXCOORD):COLOR
{
    return getColor(textureCoordinate + float2((-1/ Width) * exp2(PassIndex), 0))
        + getColor(textureCoordinate);
}


technique Technique1
{
    pass SumXPass
    {
        VertexShader = compile vs_1_1 VertexShaderFunction();
        PixelShader = compile ps_2_0 SumX();
    }
}


いきなり何かの画像のエリア総和テーブルを計算するのは難しいので、とりあえずは同じ色の詰まった1次元のテクスチャのエリア総和テーブルを作ってみました。(そのためY方向には足し算をしません)
もしうまくいけば、左から右に行くにしたがって色が足されていくので、徐々に明るくなっていき、きれいなグラデーションになるはずです。

なお、テクスチャに一番最初に入る色は、エリア総和テーブルで一番明るい色が白になるように決めました(もちろん実用では、複数のピクセルの色を足しまくるため、白より明るい色になることはしょっちゅうです。しかしそれではうまくいっているかどうか目で確認できません。テストファースト、テストファーストです)
テクスチャのサイズはいろいろ変更することができ、大きくなればなるほど、テクスチャの最初の色は暗くなります。
これはエリア総和テーブルを作った時に、色があふれださないようにする工夫です。

ではinitTextureの引数をいろいろ変えていき、上手くいっているかのテストです。
結論を先に言うと、ビミョーですが、まあ見ていきましょう。

まずは2x1のテクスチャのエリア総和テーブルです。
satFrom2x1.jpg

なるほどうまくいっているようです。
ここでは
0.5 0.5
のテクスチャから、
0.5 1.0
のエリア総和テーブルが作られているのでしょう(多分)。
正しく実装で来ているのなら、こんな風に、これ以後も全部右端は白くなるはずです。


ここではうまくいっているようですが、もう少し大きくしてみるとどうでしょうか?
4x1を試してみます:
satFrom4x1.jpg

いやいや、おかしいよ!
どうなっているのでしょう?
グラデーションにならなきゃいけないのに、真ん中の2つが同じ色になってしまっています。
なにより右端は白じゃなくちゃいけません。

この問題はこの記事を書いている今のところ未解決です(わかる人教えてください!)。
さらに大きくすると、それほど深刻には見えなかったからです。

8x1:
satFrom8x1.jpg
左端と右端を除いた真ん中全部が同じ色になりはしないかとヒヤヒヤしましたが、杞憂でしたね。もっとも問題は全然よくなっていませんが。

16x1:
satFrom16x1.jpg
もしかしておかしくなる条件は「左にある」こと?
でもどうしてそんな・・・・・・

32x1:
satFrom32x1.jpg
右端はほとんど白です。
これを見た瞬間問題を解決しようというモチベーションがガタ落ちしました。

64x1:
satFrom64x1.jpg
こまかくなってきました。
しかし「左側問題」は相変わらず解決しません。

128x1:
satFrom128x1.jpg
いいでしょう、もうたくさんです。
これ以上大きくしても結果は変わらないでしょう。

でもいったいどうしてこんな問題が起きたのでしょう?
たぶん何かのインデックスが1つずれているとかそんなのだと思いますが・・・





拍手[0回]

PR

[XNA] SpriteBatch + Effect

SpriteBatchEffectを組み合わせる方法に手こずったのでメモしておきますね。

SpriteBatchはXNAの2D機能(といってもテクスチャの描画くらいしかできませんが)をカプセル化したクラスで、それに対してEffectは頂点データをピクセルの色に変換する方法を表すクラスで、どちらかというと3Dです。
もしこの2つを組み合わせることができるのなら(実際できるのですが)、テクスチャにピクセルシェーダーを簡単に適用することができます(たとえば、水面がゆらゆらと揺れている様子がかんたんに表現できるでしょう)

しかし困ったことに、SpriteBatchにはEffectを使うメンバが見当たりませんでした。
ではこの2つは一緒に使うことができないのかというと、そうではありません。
きちんと一緒に使うことができます。
で、ぐだぐだ説明を続けてもいいですが、ここはコードを見たほうが早いでしょう。とくに難しいところはありませんからね。


MyGame.cs
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

public class MyGame : Game
{
    GraphicsDeviceManager graphics;

    SpriteBatch spriteBatch;
    Texture2D texture;
    Effect effect;

    public MyGame()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
    }

    protected override void LoadContent()
    {
        texture = Content.Load("henohenomoheji");
        effect = Content.Load("2DEffect");

        spriteBatch = new SpriteBatch(GraphicsDevice);
    }

    protected override void UnloadContent()
    {
        spriteBatch.Dispose();
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.CornflowerBlue);


        spriteBatch.Begin(
            SpriteBlendMode.None,
            SpriteSortMode.Immediate,
            SaveStateMode.None
            );

            effect.Begin();
            effect.CurrentTechnique.Passes[0].Begin();

                spriteBatch.Draw(texture, new Vector2(), Color.White);

            effect.CurrentTechnique.Passes[0].End();
            effect.End();

        spriteBatch.End();
    }


    static void Main()
    {
        using (MyGame game = new MyGame())
        {
            game.Run();
        }
    }
}


2DEffect.fx
sampler TextureSampler : register(s0);

float4 PixelShaderFunction(float2 textureCoordinate : TEXCOORD) : COLOR0
{
    float2 texOffset = {sin(textureCoordinate.x * 30) / 20 , 0};
    
    return tex2D(
        TextureSampler,
        textureCoordinate + texOffset
        );
}

technique Technique1
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}



spriteBatchWithPixelShader.jpg

なんだかよくわからないものが左上に表示されていますが、これは「へのへのもへじ」をピクセルシェーダーで変形したものです。

henohenomoheji.jpghenohenomoheji.jpg


ピクセルシェーダー内で、適当にテクスチャ座標をいじってやると、水面が揺らいでいるようにみえなくもないというわけです。









拍手[0回]


[XNA] GPUを使って計算した結果をCPUに戻す

なんとなくGPUを使った計算結果をCPU側でも使ってみたくなったので、やってみました。

ただ、これはXbox360ならともかくパソコンではパフォーマンスが悪くなるそうですね。
グラフィックスメモリをCPUからは読むのはあまり速くないとかだそうです。
ですからよほど面白いことができない限り、控えるべきでしょう。

実際に何かに使うとしたら、並列的な計算でしょうね。
GPUは、CPUと違って並列的な計算が得意ですから、そこら辺で面白いことができるに違いありません。
ある研究室でGPUを使ってニューラルネットワークを動かし、人の顔を判別している研究を見たことがありますが、まあそんな感じでしょうか。
その研究では、CPUを使った時よりも、何倍も計算速度が速くなったそうです。
こういうのはCPUよりもGPUのほうが向いているんですね。

もちろんニューラルネットワークは単なる一例で、「この手があったか!」みたいな応用方法が他にもあるに違いありません。
そしてその応用方法の中には、ゲームをもっと面白くする何かがあるにちがいありません!(たぶん)

というわけでXNAを使ってやってみました。

かといって、いきなりGPUでニューラルネットワークを動かしたりというのはあまりにハードルが高すぎるので、まずは一番簡単な計算をしてみたいと思います。

つまり、1 + 1です。
たぶん人が一番簡単だと考える計算ではないでしょうか!
たぶん幼稚園児でもできます。
これをGPUに計算させて、CPU側に戻し、ウィンドウに「2」と表示するのです。
「1 + 1なんてGPU使わなくたってわかるよ!」って感じですが、話をシンプルにするにはこれが一番です。

MyGame.cs


using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

public class MyGame : Game
{
    GraphicsDeviceManager graphics;

    Texture2D inputTexture;
    RenderTarget2D result;
    Effect additionCalculator;
    VertexDeclaration vertexDeclaration;

    //GPUから計算結果を受け取るバッファ
    float[] resultBuffer = new float[2];

    VertexPositionTexture[] vertices = {
            new VertexPositionTexture(new Vector3(-1, 1, 0), new Vector2()),
            new VertexPositionTexture(new Vector3(1, 1, 0), new Vector2(1, 0)),
            new VertexPositionTexture(new Vector3(-1, -1, 0), new Vector2(0, 1)),
            
            new VertexPositionTexture(new Vector3(1, 1, 0), new Vector2(1, 0)),
            new VertexPositionTexture(new Vector3(1, -1, 0), new Vector2(1, 1)),
            new VertexPositionTexture(new Vector3(-1, -1, 0), new Vector2(0, 1))
        };


    public MyGame()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
    }

    protected override void LoadContent()
    {
        inputTexture = new Texture2D(GraphicsDevice, 2, 1, 0, TextureUsage.None, SurfaceFormat.Single);
        inputTexture.SetData<float>(new float[] { 1, 1 });

        result = new RenderTarget2D(GraphicsDevice, 2, 1, 0, SurfaceFormat.Single);

        additionCalculator = Content.Load<Effect>("AdditionCalculator");


        GraphicsDevice.VertexDeclaration = new VertexDeclaration(
            GraphicsDevice,
            VertexPositionTexture.VertexElements
            );
        vertexDeclaration = GraphicsDevice.VertexDeclaration;
    }

    protected override void UnloadContent()
    {
        inputTexture.Dispose();
        result.Dispose();
        vertexDeclaration.Dispose();
    }

    protected override void Update(GameTime gameTime)
    {
        Window.Title = resultBuffer[1].ToString();
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.SetRenderTarget(0, result);

        additionCalculator.Parameters["InputTexture"].SetValue(inputTexture);
        additionCalculator.Parameters["InputWidth"].SetValue(2);
        additionCalculator.Begin();
        additionCalculator.CurrentTechnique.Passes[0].Begin();

        GraphicsDevice.DrawUserPrimitives<VertexPositionTexture>(
            PrimitiveType.TriangleList,
            vertices,
            0, 2
            );

        additionCalculator.CurrentTechnique.Passes[0].End();
        additionCalculator.End();

        //GetDataを呼ぶには、レンダーターゲットはいったん
         //デバイスから外されなければいけません。
        GraphicsDevice.SetRenderTarget(0, null);

        result.GetTexture().GetData<float>(resultBuffer);
    }

    static void Main()
    {
        using (MyGame game = new MyGame())
        {
            game.Run();
        }
    }
}


AdditionCalculator.fx(Contentフォルダ内)
float InputWidth;
texture InputTexture;

sampler InputSampler = sampler_state
{
    Texture = (InputTexture);
};

struct VertexPositionTexture
{
    float4 Position : POSITION;
    float2 TextureCoordinate : TEXCOORD;
};

VertexPositionTexture VertexShaderFunction(VertexPositionTexture input)
{
    return input;
}

float4 PixelShaderFunction(VertexPositionTexture input) : COLOR0
{
    return tex2D(InputSampler, input.TextureCoordinate + float2(-1/InputWidth, 0))
		+ tex2D(InputSampler, input.TextureCoordinate);
}

technique Technique1
{
    pass Pass1
    {
        VertexShader = compile vs_1_1 VertexShaderFunction();
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}

AdditionByGPU.jpg

できました。
やったー!

きちんと、ウィンドウのタイトルに"2"と表示されているのがわかると思います(もちろん1 + 1の結果ですよ)。
GPUに、{1, 1}という配列をテクスチャとして送り込んで、ピクセルシェーダーで足し算しているのです。
そして、描画結果を2x1のテクスチャとしてCPU側に戻します。
そのデータをGetDataメソッドで読み込み、ウィンドウのTitleプロパティにセットすれば完了です。



additionByGPUDiagram.jpg

これを1秒間に60回繰り返しています。
最初の{1, 1}を送り込むのは一度だけですが、1 + 1は何度も何度も、60ヘルツで計算しています。
なんせDrawメソッド内でやっていますからね。

念のため、SetDataメソッドの引数をいろいろ変えて検証してみました。

inputTexture.SetData<float>(new float[] { 1, 2 });

a6d637c6.jpg

3か・・・・・・1 + 2は3ですから、うまくいっているようです。
つぎは2 + 5くらいをやってみましょう

inputTexture.SetData<float>(new float[] { 2, 5 });

9b78bdf9.jpg

2 + 5は7ですから、大丈夫ですね。

どうやらこれでうまくいっているようです。
ここでは単なる足し算の計算をしましたが、現実にはもっとエキサイティングでファンタスティックなことをやることができるでしょう。

拍手[2回]


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


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