忍者ブログ

Memeplexes

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

C#でDirectX11 SlimDXチュートリアルその14 定数バッファー

定数バッファー

ずいぶん前回と間があきましたが、SlimDXについてメモしておこうと思います。
今回は、定数バッファーです。

定数バッファーとは、CPUからGPUへのデータ転送をまとめるデータの固まりです。
どういうことでしょう?
3DCGを描くときのことを考えてください。
CPUからGPUへ、モデルの回転やライトの位置など、情報を転送しなければいけません。
そしてその転送にはオーバーヘッドがかかります。
これをいちいち別々に転送していては、オーバーヘッドが膨らむばかり。
そこでこれらを一つにまとめて一気にGPUへ転送しようということです。
そのひとつにまとめられたものを、コンスタントバッファーといいます。


定数バッファーの生成

定数バッファーは、Bufferクラスのインスタンスです。
頂点バッファやインデックスバッファと同じですね。
ただ使い方が違うのです。
そして初期化の方法も。
初期化するときには、BindFlagsにBindFlags.ConstantBufferを指定します。

    private void initConstantBuffer()
    {
        constantBuffer = new Buffer(
            GraphicsDevice,
            new BufferDescription
            {   
                //なぜかsizeof(float) * 4以上でないと例外をスローする。
                SizeInBytes = sizeof(float) * 4,  
                BindFlags = BindFlags.ConstantBuffer,
            }
            ); 
    }

ここで気をつけて欲しいのは、SizeInBytesにsizeof(float) * 4より小さな値を代入するとマズイ事になるかもしれないということです。
大してデータを使わないからと言って、もし小さな値を代入すれば、Direct3D11Exceptionをスローすることがあるので注意してください。
少なくとも私の環境ではそうでした。
すべての環境でそうであるかはわかりません。
しかし、Vector4より小さな値が禁止されているというのはおかしな話ではないでしょう。


定数バッファーの更新(CPUからGPUへのデータ転送)

定数バッファーはCPUからGPUへのデータ転送をまとめるためのバッファです。
するとデータ転送をする方法があるはずなのですが、Bufferそのものにはそんなメソッドはありません。
データ転送には、DeviceContext.UpdateSubresourceメソッドを使います。

public void UpdateSubresource(DataBox source, Resource resource, int subresource);

sourceは、GPUに転送するデータです。
resourceは転送先のGPUに存在するリソース。
subresourceはサブリソース番号です。


定数バッファをエフェクトにセット

更新した定数バッファも、少なくとも一度はエフェクトにセットしなければ描画に反映されません。
定数バッファのエフェクトへのセットには次のようにします:
 
        effect.GetConstantBufferByName("myConstantBuffer").ConstantBuffer = constantBuffer;

effectはセットするエフェクト。
"myConstantbuffer"は、HLSLファイル中の定数バッファの名前です。
constantBufferはセットする定数バッファです。


サンプルコード

Program.cs
using SlimDX;
using SlimDX.Direct3D11;
using SlimDX.DXGI;
using SlimDX.D3DCompiler;

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

struct MyConstantBuffer
{
    public float OffsetX;
    public float OffsetY;
}

class MyGame : Game
{
    Effect effect;
    InputLayout vertexLayout;
    Buffer vertexBuffer;
    Buffer constantBuffer;

    protected override void Draw()
    {
        GraphicsDevice.ImmediateContext.ClearRenderTargetView(
            RenderTarget,
            new Color4(1, 0, 0, 1)
            );

        updateConstantBuffer();
        initTriangleInputAssembler();
        drawTriangle();
        
        SwapChain.Present(0, PresentFlags.None);
    }

    private void updateConstantBuffer()
    {
        double time = System.Environment.TickCount / 500d;
        MyConstantBuffer myConstantBuffer = new MyConstantBuffer
        {
            OffsetX = (float)System.Math.Sin(time) * 0.4f,
            OffsetY = (float)System.Math.Sin(time) * 0.8f
        };
        GraphicsDevice.ImmediateContext.UpdateSubresource(
            new DataBox(0, 0, new DataStream(new[] { myConstantBuffer }, true, true)),
            constantBuffer,
            0
            );
        effect.GetConstantBufferByName("myConstantBuffer").ConstantBuffer = constantBuffer;
    }

    private void initTriangleInputAssembler()
    {
        GraphicsDevice.ImmediateContext.InputAssembler.InputLayout = vertexLayout;
        GraphicsDevice.ImmediateContext.InputAssembler.SetVertexBuffers(
            0,
            new VertexBufferBinding(vertexBuffer, sizeof(float) * 3, 0)
            );
        GraphicsDevice.ImmediateContext.InputAssembler.PrimitiveTopology
            = PrimitiveTopology.TriangleList;
    }

    private void drawTriangle()
    {
        effect.GetTechniqueByIndex(0).GetPassByIndex(0).Apply(GraphicsDevice.ImmediateContext);
        GraphicsDevice.ImmediateContext.Draw(3, 0);
    }


    protected override void LoadContent()
    {
        initEffect();
        initVertexLayout();
        initVertexBuffer();
        initConstantBuffer();
    }

    private void initEffect()
    {
        using (ShaderBytecode shaderBytecode = ShaderBytecode.CompileFromFile(
            "myEffect.fx", "fx_5_0",
            ShaderFlags.None,
            EffectFlags.None
            ))
        {
            effect = new Effect(GraphicsDevice, shaderBytecode);
        }
    }

    private void initVertexLayout()
    {
        vertexLayout = new InputLayout(
            GraphicsDevice,
            effect.GetTechniqueByIndex(0).GetPassByIndex(0).Description.Signature,
            new[] { 
                    new InputElement
                    {
                        SemanticName = "SV_Position",
                        Format = Format.R32G32B32_Float
                    }
                }
            );
    }

    private void initVertexBuffer()
    {
        vertexBuffer = MyDirectXHelper.CreateVertexBuffer(
            GraphicsDevice,
            new[] {
                new SlimDX.Vector3(0, 0.5f, 0),
                new SlimDX.Vector3(0.5f, 0, 0),
                new SlimDX.Vector3(-0.5f, 0, 0),
            });
    }

    private void initConstantBuffer()
    {
        constantBuffer = new Buffer(
            GraphicsDevice,
            new BufferDescription
            {   
                //なぜかsizeof(float) * 4以上でないと例外をスローする。
                SizeInBytes = sizeof(float) * 4,  
                BindFlags = BindFlags.ConstantBuffer,
            }
            ); 
    }

    protected override void UnloadContent()
    {
        effect.Dispose();
        vertexLayout.Dispose();
        vertexBuffer.Dispose();
        constantBuffer.Dispose();
    }
}

class Game : System.Windows.Forms.Form
{
    public SlimDX.Direct3D11.Device GraphicsDevice;
    public SwapChain SwapChain;
    public RenderTargetView RenderTarget;


    public void Run()
    {
        initDevice();
        SlimDX.Windows.MessagePump.Run(this, Draw);
        disposeDevice();
    }

    private void initDevice()
    {
        MyDirectXHelper.CreateDeviceAndSwapChain(
            this, out GraphicsDevice, out SwapChain
            );

        initRenderTarget();
        initViewport();

        LoadContent();
    }

    private void initRenderTarget()
    {
        using (Texture2D backBuffer
            = SlimDX.Direct3D11.Resource.FromSwapChain<Texture2D>(SwapChain, 0)
            )
        {
            RenderTarget = new RenderTargetView(GraphicsDevice, backBuffer);
            GraphicsDevice.ImmediateContext.OutputMerger.SetTargets(RenderTarget);
        }
    }

    private void initViewport()
    {
        GraphicsDevice.ImmediateContext.Rasterizer.SetViewports(
            new Viewport
            {
                Width = ClientSize.Width,
                Height = ClientSize.Height,
            }
            );
    }

    private void disposeDevice()
    {
        UnloadContent();
        RenderTarget.Dispose();
        GraphicsDevice.Dispose();
        SwapChain.Dispose();
    }

    protected virtual void Draw() { }
    protected virtual void LoadContent() { }
    protected virtual void UnloadContent() { }
}

class MyDirectXHelper
{
    public static void CreateDeviceAndSwapChain(
        System.Windows.Forms.Form form,
        out SlimDX.Direct3D11.Device device,
        out SlimDX.DXGI.SwapChain swapChain
        )
    {
        SlimDX.Direct3D11.Device.CreateWithSwapChain(
            DriverType.Hardware,
            DeviceCreationFlags.None,
            new SwapChainDescription
            {
                BufferCount = 1,
                OutputHandle = form.Handle,
                IsWindowed = true,
                SampleDescription = new SampleDescription
                {
                    Count = 1,
                    Quality = 0
                },
                ModeDescription = new ModeDescription
                {
                    Width = form.ClientSize.Width,
                    Height = form.ClientSize.Height,
                    RefreshRate = new SlimDX.Rational(60, 1),
                    Format = Format.R8G8B8A8_UNorm
                },
                Usage = Usage.RenderTargetOutput
            },
            out device,
            out swapChain
            );
    }

    public static Buffer CreateVertexBuffer(
        SlimDX.Direct3D11.Device graphicsDevice,
        System.Array vertices
        )
    {
        using (SlimDX.DataStream vertexStream 
            = new SlimDX.DataStream(vertices, true, true))
        {
            return new Buffer(
                graphicsDevice,
                vertexStream,
                new BufferDescription
                {
                    SizeInBytes= (int)vertexStream.Length,
                    BindFlags = BindFlags.VertexBuffer,
                }
                );
        }
    }
}
 
 

myEffect.fx
cbuffer myConstantBuffer
{
float OffsetX;
float OffsetY;
}

float4 MyVertexShader(float4 position : SV_Position) : SV_Position
{
    return position + float4(OffsetX, OffsetY, 0, 0);
}

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

technique10 MyTechnique
{
pass MyPass
{
SetVertexShader( CompileShader( vs_5_0, MyVertexShader() ) );
SetPixelShader( CompileShader( ps_5_0, MyPixelShader() ) );
}
}

このプログラムは、三角形を斜めに平行移動します。
 
Tutorial14ConstantBufferTranslatingTriangle01.png
Tutorial14ConstantBufferTranslatingTriangle02.png

つまり定数バッファなど使わなくても出来るプログラムです。
というか大抵のことは定数バッファなど使わなくても出来るのです。

大切なのはパフォーマンスです。
変更点があるとすれば内部です。

このプログラムでは、三角形の平行移動に使うパラメーターを、定数バッファでまとめているのです。
平行移動に使うパラメーターは2つ有ります。
X方向のオフセットを表すOffsetX(float)と、Y方向のオフセットを表すOffsetY(float)です。
もし定数バッファを使わなければ、2回、CPUからGPUにデータを転送することになります。
しかしここでは定数バッファを使ってこの2つを一つにまとめているので、CPUからGPUへのデータ転送は、毎フレーム一回だけですんでいます。









拍手[1回]

PR

C#でDirectX11入門一覧 (SlimDX入門)

 まとめ

Direct3D 11入門

01.メッセージループ
02.デバイスの初期化
03.背景のクリア
04.三角形の表示(色なし)クラス紹介編
05.色つき三角形
06.動く三角形
07.カメラ(遠近感+回転)
08.平行移動
09.深度バッファ
10.ライト
11.テクスチャ
12.ワイヤーフレーム
13.ジオメトリシェーダー
14.定数バッファー

コンピュートシェーダー

01.GPUからCPU側へのデータ転送
02.バッファのコピー
03.コンピュートシェーダー
04.複数のリソースを計算に使う
05.ShaderResourceViewを使う
06.BarrierWithGroupSync()



感想

Direct3D11入門は、XNAやDirectXに関する知識がほとんど全くない人向けに書きました。
が、しかし効果の程は怪しいですね。
三角形を描くだけでかなりの設定が必要で、XNAなどで予備知識がないと厳しい気がしなくもありません。
はじめに大きな乗り越えなければいけない壁がDirect3D9世代よりもぐんと高くなっている気がします。

最大の難関は04.三角形の表示(色なし)です。
白い三角形を描くだけなのに200行とちょっと必要というのは初学者の心をぽっきり折るのに十分だと思います。
XNAのありがたみがひしひしと感じられます。










拍手[2回]


C#でDirectX11 SlimDXチュートリアルその13 ジオメトリシェーダー

 今回のテーマはジオメトリシェーダーです。
ジオメトリシェーダーはポリゴンを分割します。

d4d15310.jpg



Tutorial13GeometryShader.jpg


上はジオメトリシェーダーなし、下はジオメトリシェーダー有りです。
上と下の頂点バッファは同じです。
違うのはHLSLのエフェクトファイルだけ。
頂点バッファの中には三角形1つ分の頂点しか入っていないのですが、ジオメトリシェーダーで分割されて2つになるのです。

こう言うことをして何が嬉しいかというと、例えば荒い3Dモデルを面の細やかなモデルに変換することが出来ます。
だったら最初から細かいモデルを用意しておけという感じですが、実際には遠くから見たときに細かいモデルを用意するのは無駄です。
近くにあるときは細かいモデル、遠くにあるときには荒いモデル、その中間ならそこそこのモデルが欲しいのです。
これら全部を用意するという方法もありますが(面倒な上容量をけっこう食いそうです)、一度に全部の距離に柔軟に対応できればそれに越したことはありません。

そういうニーズにジオメトリシェーダーは答えることが出来ます。
最初に荒いモデルを用意しておいて、必要に応じて適切な細かさのモデルを作ればいいのです。
ジオメトリシェーダーでポリゴンを分割すればそれだけ細かいモデルになります。
もちろんただ分割するだけでは意味が無いので、予めテクスチャに描いた細かい凹凸情報でポリゴンの高低を変えればいいというわけです。


ジオメトリシェーダー(HLSL)

今回ジオメトリシェーダーを使う上で必要なのはHLSL側の変更だけです。
C#側は前回のワイヤーフレームを描くコードから変える必要はありません。
今回、HLSLのジオメトリシェーダーは次のような構文となります:

[maxvertexcount(VertexCount)]
void ShaderName(
    triangle DataType vertices[3],
    inout TriangleStream<DataType> resultStream
    )


maxvertexcountはC#の属性のようなものですね。
ジオメトリシェーダーによって造られる頂点の最大数を指定します。
作られるというと語弊がありそうです。
3の頂点から6つの頂点を作るとき(1つの三角形を分割して2つの三角形にするとき)、VertexCountは6です。

 DataTypeは頂点の構造体です。

TriangleStream<T>はジオメトリシェーダーで作成される三角形を格納します。
今回は三角形なのでTriangleStreamなのですが、他にもPointStreamやLineStreamなどの仲間もあります
TriangleStreamには、ジオメトリシェーダーで作成する三角形を格納するためのメソッドが付いています。

class TriangleStream<T>
{
      void Append(T vertex);
      void RestartStrip();
}

TriangleStream<T>.Append()メソッドは頂点を追加します。
TriangleStream<T>.RestartStrip()メソッドは、Appendで3つの頂点を追加した後に呼びます。


コード

Program.cs
using SlimDX.Direct3D11;
using SlimDX.DXGI;
using SlimDX.D3DCompiler;
 
class Program
{
    static void Main()
    {
        using (Game game = new MyGame())
        {
            game.Run();
        }
    }
}
 
class MyGame : Game
{
    Effect effect;
    InputLayout vertexLayout;
    Buffer vertexBuffer;
 
    protected override void Draw()
    {
        GraphicsDevice.ImmediateContext.ClearRenderTargetView(
            RenderTarget,
            new SlimDX.Color4(1, 0, 0, 1)
            );
 
        initTriangleInputAssembler();
        drawTriangle();
        
        SwapChain.Present(0, PresentFlags.None);
    }
 
    private void drawTriangle()
    {
        effect.GetTechniqueByIndex(0).GetPassByIndex(0).Apply(GraphicsDevice.ImmediateContext);
        GraphicsDevice.ImmediateContext.Draw(3, 0);
    }
 
    private void initTriangleInputAssembler()
    {
        GraphicsDevice.ImmediateContext.InputAssembler.InputLayout = vertexLayout;
        GraphicsDevice.ImmediateContext.InputAssembler.SetVertexBuffers(
            0,
            new VertexBufferBinding(vertexBuffer, sizeof(float) * 3, 0)
            );
        GraphicsDevice.ImmediateContext.InputAssembler.PrimitiveTopology
            = PrimitiveTopology.TriangleList;
    }
 
    protected override void LoadContent()
    {
        initEffect();
        initVertexLayout();
        initVertexBuffer();
        initWireframeRasterizerState();
    }
 
    private void initEffect()
    {
        using (ShaderBytecode shaderBytecode = ShaderBytecode.CompileFromFile(
            "myEffect.fx", "fx_5_0",
            ShaderFlags.None,
            EffectFlags.None
            ))
        {
            effect = new Effect(GraphicsDevice, shaderBytecode);
        }
    }
 
    private void initVertexLayout()
    {
        vertexLayout = new InputLayout(
            GraphicsDevice,
            effect.GetTechniqueByIndex(0).GetPassByIndex(0).Description.Signature,
            new[] { 
                    new InputElement
                    {
                        SemanticName = "SV_Position",
                        Format = Format.R32G32B32_Float
                    }
                }
            );
    }
 
    private void initVertexBuffer()
    {
        vertexBuffer = MyDirectXHelper.CreateVertexBuffer(
            GraphicsDevice,
            new[] {
                new SlimDX.Vector3(0, 0.5f, 0),
                new SlimDX.Vector3(0.5f, 0, 0),
                new SlimDX.Vector3(-0.5f, 0, 0),
            });
    }
 
    private void initWireframeRasterizerState()
    {
        var desc = new RasterizerStateDescription
        {
            CullMode = CullMode.Back,
            FillMode = FillMode.Wireframe,
        };
 
        GraphicsDevice.ImmediateContext.Rasterizer.State
            = RasterizerState.FromDescription(
                GraphicsDevice,
                desc
            );
    }
 
    protected override void UnloadContent()
    {
        GraphicsDevice.ImmediateContext.Rasterizer.State.Dispose();
        effect.Dispose();
        vertexLayout.Dispose();
        vertexBuffer.Dispose();
    }
}
 
class Game : System.Windows.Forms.Form
{
    public SlimDX.Direct3D11.Device GraphicsDevice;
    public SwapChain SwapChain;
    public RenderTargetView RenderTarget;
 
 
    public void Run()
    {
        initDevice();
        SlimDX.Windows.MessagePump.Run(this, Draw);
        disposeDevice();
    }
 
    private void initDevice()
    {
        MyDirectXHelper.CreateDeviceAndSwapChain(
            this, out GraphicsDevice, out SwapChain
            );
 
        initRenderTarget();
        initViewport();
 
        LoadContent();
    }
 
    private void initRenderTarget()
    {
        using (Texture2D backBuffer
            = SlimDX.Direct3D11.Resource.FromSwapChain<Texture2D>(SwapChain, 0)
            )
        {
            RenderTarget = new RenderTargetView(GraphicsDevice, backBuffer);
            GraphicsDevice.ImmediateContext.OutputMerger.SetTargets(RenderTarget);
        }
    }
 
    private void initViewport()
    {
        GraphicsDevice.ImmediateContext.Rasterizer.SetViewports(
            new Viewport
            {
                Width = ClientSize.Width,
                Height = ClientSize.Height,
            }
            );
    }
 
    private void disposeDevice()
    {
        UnloadContent();
        RenderTarget.Dispose();
        GraphicsDevice.Dispose();
        SwapChain.Dispose();
    }
 
    protected virtual void Draw() { }
    protected virtual void LoadContent() { }
    protected virtual void UnloadContent() { }
}
 
class MyDirectXHelper
{
    public static void CreateDeviceAndSwapChain(
        System.Windows.Forms.Form form,
        out SlimDX.Direct3D11.Device device,
        out SlimDX.DXGI.SwapChain swapChain
        )
    {
        SlimDX.Direct3D11.Device.CreateWithSwapChain(
            DriverType.Hardware,
            DeviceCreationFlags.None,
            new SwapChainDescription
            {
                BufferCount = 1,
                OutputHandle = form.Handle,
                IsWindowed = true,
                SampleDescription = new SampleDescription
                {
                    Count = 1,
                    Quality = 0
                },
                ModeDescription = new ModeDescription
                {
                    Width = form.ClientSize.Width,
                    Height = form.ClientSize.Height,
                    RefreshRate = new SlimDX.Rational(60, 1),
                    Format = Format.R8G8B8A8_UNorm
                },
                Usage = Usage.RenderTargetOutput
            },
            out device,
            out swapChain
            );
    }
 
    public static Buffer CreateVertexBuffer(
        SlimDX.Direct3D11.Device graphicsDevice,
        System.Array vertices
        )
    {
        using (SlimDX.DataStream vertexStream 
            = new SlimDX.DataStream(vertices, true, true))
        {
            return new Buffer(
                graphicsDevice,
                vertexStream,
                new BufferDescription
                {
                    SizeInBytes= (int)vertexStream.Length,
                    BindFlags = BindFlags.VertexBuffer,
                }
                );
        }
    }
}
 
 

myEffect.fx
struct VertexPosition
{
float4 Position : SV_Position;
};

VertexPosition MyVertexShader(VertexPosition position)
{
    return position;
}

VertexPosition Average(VertexPosition a, VertexPosition b)
{
VertexPosition result = { (a.Position + b.Position) / 2};
return result;
}

[maxvertexcount(6)]
void MyGeometryShader(
triangle VertexPosition vertices[3],
inout TriangleStream<VertexPosition> resultStream
)
{
resultStream.Append(vertices[0]);
resultStream.Append(vertices[1]);
resultStream.Append(Average(vertices[1], vertices[2]));
resultStream.RestartStrip();

resultStream.Append(vertices[0]);
resultStream.Append(Average(vertices[1], vertices[2]));
resultStream.Append(vertices[2]);
resultStream.RestartStrip();
}

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

technique10 MyTechnique
{
pass MyPass
{
SetVertexShader( CompileShader( vs_5_0, MyVertexShader() ) );
SetGeometryShader(CompileShader(gs_5_0, MyGeometryShader()));
SetPixelShader( CompileShader( ps_5_0, MyPixelShader() ) );
}
}

実行結果はこうなります。

Tutorial13GeometryShader.jpg

このプログラムはまず三角形を一つだけ用意しています。
 
Tutorial13HowTriangleIsDivided.jpg

それを真ん中で2等分しているのです。
 
Tutorial13HowTriangleIsDivided2.jpg

今回は単純化のため縦に2等分しましたが、実際には
 △
△▽△
というような分割方法を使うべきでしょう。




























拍手[2回]


C#でDirectX11 SlimDXチュートリアルその12 ワイヤーフレーム

 今回はワイヤーフレームです。
ワイヤーフレームとは何かというと、
こんな三角形を

Tutorial03WhiteTriangle.jpg

こう表示することです:

Tutorial12WireFrame.jpg


図形の骨組みだけ表示されるのです。
上の三角形で言うと、3つの辺だけが描かれ、その内部は描かれません。

これは一体何に使うのか?
ということですが、ポリゴンの様子がよくわかるということで、デバッグ用といいますか、DirectXのポリゴンの分割サンプルにはよく使われているようです。

SubD11WithoutWireframe.jpg

SubD11WithWireframe.jpg

上は普通のモデルだけ描画、下は普通のモデル+そのワイヤーフレームを描画しています。
こうしてみるとポリゴンがどんな様子なのか一目瞭然です。(このサンプルはポリゴンの分割のデモなので、ポリゴンが見えると嬉しいのです)

あるいはレトロSF風味でカッコイイということもあるかもしれませんね。

ラスタライザーステート

ワイヤーフレームを描くには、デバイスをワイヤーフレームがオンになった状態にしてやります。
その状態管理を行うのが、RasterizerWrapper.Stateプロパティです。
GraphicsDevice.ImmediateContext.Rasterizer.State = ~~というふうに設定します。

public RasterizerState State { get; set; }

SlimDX.Direct3D11.RasterizerStateクラスはデバイスの描画設定をつかさどります。
今回のようにワイヤーフレームをオンにしたい場合は、そういう設定をしたこのクラスのインスタンスをデバイスにセットします。
初期化にはRasterizerState.FromDescriptionメソッドを使います。

public class RasterizerState : DeviceChild
{
    public static RasterizerState FromDescription(
        Device device, 
        RasterizerStateDescription description
        );
...
}

deviceはRasterizerStateを作るデバイス。
descriptionはRasterizerStateが設定するデバイスの描画設定です。つまり今回の設定はdescriptionに対してセットします。

SlimDX.Direct3D11.RasterizerStateDescription構造体のメンバのうち、今回セットする必要があるのはCullModeFillModeプロパティです。

public struct RasterizerStateDescription
{
    public CullMode CullMode { get; set; }
    public FillMode FillMode { get; set; }
...
}

CullModeは三角形が描かれなくする方向です。
三角形ポリゴンには方向があります。
FrontとBackです(CullMode.FrontCullMode.Back)。
この方向はIsFrontCounterclockwiseプロパティ(今回は使いませんが)によって時計回りか反時計回りかがセットされるのです。
CullModeプロパティによってセットされた方向では三角形が見えなくなります。
これはより近くのポリゴンによって隠れてしまうポリゴンはどうせ描かれないので最初っから描かないことにしようというものです。

例えば地球を描く事を考えます。
日本が表を向いた地球を描くとき、地球の反対側のチリ共和国を構成しているポリゴンはどうあがいたって描かれません。
なら最初から地球の裏側は描かないことにすれば描画コストを削減できるというわけです。
地球の裏側にあるかどうかというのを、三角形の頂点が時計回りに見えるかどうかというのを基準に考えるのです。


FillModeは図形内部を色塗りする方法です。
FillMode.WireframeFillMode.Solidのどちらかをセットします。
前回までやってきた三角形内部を塗りつぶすのはSolidで、今回のワイヤーフレームはWireframeです。

本当は今回のワイヤーフレームを使うのにCullModeは関係ありません。
しかし、デフォルトの設定ではAPIがエラーを投げるのです。
これはしかたがありません。
とりあえず今回はCullModeにはBackをセットしておきます。

コード

Program.cs
using SlimDX.Direct3D11;
using SlimDX.DXGI;
using SlimDX.D3DCompiler;
 
class Program
{
    static void Main()
    {
        using (Game game = new MyGame())
        {
            game.Run();
        }
    }
}
 
class MyGame : Game
{
    Effect effect;
    InputLayout vertexLayout;
    Buffer vertexBuffer;
 
    protected override void Draw()
    {
        GraphicsDevice.ImmediateContext.ClearRenderTargetView(
            RenderTarget,
            new SlimDX.Color4(1, 0, 0, 1)
            );
 
        initTriangleInputAssembler();
        drawTriangle();
        
        SwapChain.Present(0, PresentFlags.None);
    }
 
    private void drawTriangle()
    {
        effect.GetTechniqueByIndex(0).GetPassByIndex(0).Apply(GraphicsDevice.ImmediateContext);
        GraphicsDevice.ImmediateContext.Draw(3, 0);
    }
 
    private void initTriangleInputAssembler()
    {
        GraphicsDevice.ImmediateContext.InputAssembler.InputLayout = vertexLayout;
        GraphicsDevice.ImmediateContext.InputAssembler.SetVertexBuffers(
            0,
            new VertexBufferBinding(vertexBuffer, sizeof(float) * 3, 0)
            );
        GraphicsDevice.ImmediateContext.InputAssembler.PrimitiveTopology
            = PrimitiveTopology.TriangleList;
    }
 
    protected override void LoadContent()
    {
        initEffect();
        initVertexLayout();
        initVertexBuffer();
        initWireframeRasterizerState();
    }
 
    private void initEffect()
    {
        using (ShaderBytecode shaderBytecode = ShaderBytecode.CompileFromFile(
            "myEffect.fx", "fx_5_0",
            ShaderFlags.None,
            EffectFlags.None
            ))
        {
            effect = new Effect(GraphicsDevice, shaderBytecode);
        }
    }
 
    private void initVertexLayout()
    {
        vertexLayout = new InputLayout(
            GraphicsDevice,
            effect.GetTechniqueByIndex(0).GetPassByIndex(0).Description.Signature,
            new[] { 
                    new InputElement
                    {
                        SemanticName = "SV_Position",
                        Format = Format.R32G32B32_Float
                    }
                }
            );
    }
 
    private void initVertexBuffer()
    {
        vertexBuffer = MyDirectXHelper.CreateVertexBuffer(
            GraphicsDevice,
            new[] {
                new SlimDX.Vector3(0, 0.5f, 0),
                new SlimDX.Vector3(0.5f, 0, 0),
                new SlimDX.Vector3(-0.5f, 0, 0),
            });
    }
 
    private void initWireframeRasterizerState()
    {
        var desc = new RasterizerStateDescription
        {
            CullMode = CullMode.Back,
            FillMode = FillMode.Wireframe,
        };
 
        GraphicsDevice.ImmediateContext.Rasterizer.State
            = RasterizerState.FromDescription(
                GraphicsDevice,
                desc
            );
    }
 
    protected override void UnloadContent()
    {
        GraphicsDevice.ImmediateContext.Rasterizer.State.Dispose();
        effect.Dispose();
        vertexLayout.Dispose();
        vertexBuffer.Dispose();
    }
}
 
class Game : System.Windows.Forms.Form
{
    public SlimDX.Direct3D11.Device GraphicsDevice;
    public SwapChain SwapChain;
    public RenderTargetView RenderTarget;
 
 
    public void Run()
    {
        initDevice();
        SlimDX.Windows.MessagePump.Run(this, Draw);
        disposeDevice();
    }
 
    private void initDevice()
    {
        MyDirectXHelper.CreateDeviceAndSwapChain(
            this, out GraphicsDevice, out SwapChain
            );
 
        initRenderTarget();
        initViewport();
 
        LoadContent();
    }
 
    private void initRenderTarget()
    {
        using (Texture2D backBuffer
            = SlimDX.Direct3D11.Resource.FromSwapChain<Texture2D>(SwapChain, 0)
            )
        {
            RenderTarget = new RenderTargetView(GraphicsDevice, backBuffer);
            GraphicsDevice.ImmediateContext.OutputMerger.SetTargets(RenderTarget);
        }
    }
 
    private void initViewport()
    {
        GraphicsDevice.ImmediateContext.Rasterizer.SetViewports(
            new Viewport
            {
                Width = ClientSize.Width,
                Height = ClientSize.Height,
            }
            );
    }
 
    private void disposeDevice()
    {
        UnloadContent();
        RenderTarget.Dispose();
        GraphicsDevice.Dispose();
        SwapChain.Dispose();
    }
 
    protected virtual void Draw() { }
    protected virtual void LoadContent() { }
    protected virtual void UnloadContent() { }
}
 
class MyDirectXHelper
{
    public static void CreateDeviceAndSwapChain(
        System.Windows.Forms.Form form,
        out SlimDX.Direct3D11.Device device,
        out SlimDX.DXGI.SwapChain swapChain
        )
    {
        SlimDX.Direct3D11.Device.CreateWithSwapChain(
            DriverType.Hardware,
            DeviceCreationFlags.None,
            new SwapChainDescription
            {
                BufferCount = 1,
                OutputHandle = form.Handle,
                IsWindowed = true,
                SampleDescription = new SampleDescription
                {
                    Count = 1,
                    Quality = 0
                },
                ModeDescription = new ModeDescription
                {
                    Width = form.ClientSize.Width,
                    Height = form.ClientSize.Height,
                    RefreshRate = new SlimDX.Rational(60, 1),
                    Format = Format.R8G8B8A8_UNorm
                },
                Usage = Usage.RenderTargetOutput
            },
            out device,
            out swapChain
            );
    }
 
    public static Buffer CreateVertexBuffer(
        SlimDX.Direct3D11.Device graphicsDevice,
        System.Array vertices
        )
    {
        using (SlimDX.DataStream vertexStream 
            = new SlimDX.DataStream(vertices, true, true))
        {
            return new Buffer(
                graphicsDevice,
                vertexStream,
                new BufferDescription
                {
                    SizeInBytes= (int)vertexStream.Length,
                    BindFlags = BindFlags.VertexBuffer,
                }
                );
        }
    }
}
 
 

myEffect.fx
float4 MyVertexShader(float4 position : SV_Position) : SV_Position
{
    return position;
}

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

technique10 MyTechnique
{
pass MyPass
{
SetVertexShader( CompileShader( vs_5_0, MyVertexShader() ) );
SetPixelShader( CompileShader( ps_5_0, MyPixelShader() ) );
}
}

このプログラムはワイヤーフレームな三角形を表示します。

d4d15310.jpg

デバイスが初期化するときに、ラスタライザーステートをデバイスにセットするのです。
そのラスタライザーステートには、ワイヤーフレームで表示せよ、ということが書いてあります。
結果として、前回まで中身がきちんと描かれていた三角形は、周りの辺だけが描かれることになったのです。





























拍手[0回]


C#でDirectX11 SlimDXチュートリアルその11 テクスチャ

 今までやってきたサンプルはこんな具合でした:

Tutorial03WhiteTriangle.jpg
Tutorial04ColoredTriangle.jpg

今回はちょっと見栄えが変わります。
三角形にテクスチャを貼ります。
Windows7についてくるPenguins.jpgです(Pictures\Sample Pictures)。

Tutorial11TextureMappedTriangle.jpg

三角形にテクスチャを貼るのは物体をリアルに見せる一般的な技法です。
たとえばDirectXに次のような人を表示するサンプルがあるのですが:

BasicHLSL10.jpg

実は次のようなテクスチャを表面に貼っているのです。
(ちょっと怖いですが)

Tiny_skin.JPG

単なる三角形ではいかにも無機質なCGという感じですが、テクスチャを貼ると少しましになります。

テクスチャ

テクスチャを三角形に貼ると一言で言っても、具体的に何をするのでしょうか?
テクスチャを貼る作業は、ピクセルシェーダーの中で行われます。
ピクセルシェーダーはお絵かきで例えると色塗り。
三角形を構成するピクセルそれぞれが具体的にどんな色になるか決定する関数です。
ピクセルシェーダーでテクスチャにアクセスし、そのピクセルの色を決めるのです。
全体としてテクスチャがマップされているように見えるように。

このように、シェーダーの中で使うリソースのことを、シェーダーリソースといいます。
今回テクスチャはシェーダーリソースなのです。
シェーダーリソースとしてリソースを使う場合、SlimDX.Direct3D11.ShaderResourceViewクラスを利用します。

public class ShaderResourceView : ResourceView

このクラスが今回三角形に貼り付けるテクスチャをつかさどります。
テクスチャのロードにはShaderResourceView.FromFile()メソッドを使います。

public static ShaderResourceView FromFile(Device device, string fileName);

deviceはこのシェーダーリソースを作るデバイス。
fileNameはロードするテクスチャの画像ファイル名です。

このメソッドの便利なところは、Texture2Dクラスなんかを経由せずに直接ShaderResourceViewを生成できるところです。
ふつう~~Viewクラスは、リソースを作ってからそれと関連付けるように生成するものです。
ですがそのリソースを~~View以外の用途で使わないのならそれは二度手間です。
シェーダーリソース以外にも使いたい用途があるのならこのメソッドはまずいのですが、今回の用途ではこれで十分です。

こうして生成したテクスチャのビューは、EffectResourceVariable.SetResource()メソッドでエフェクトにセットし、シェーダーから使えるようにします。

public Result SetResource(ShaderResourceView view);

viewはセットするビューです。

HLSL側でのテクスチャの扱い

貼り付けるテクスチャは、C#ではシェーダーリソースとしてセットしますが、HLSL側ではTexture2D変数です。
HLSLでテクスチャのある点の色を取得するには、Texture2D.Sampleメソッドを使います。

float4 Texture2D.Sample(
    SamplerState samplerState,
    float2 location
);
samplerStateはテクスチャから色を取ってくるときにのやり方です。
locationはテクスチャ内の座標です。

戻り値はテクスチャのlocation地点の色です。

SamplerStateはテクスチャのピクセルとピクセルの間の色を取ってくるときに、どのような色の取り方をするかを表します。
が、今回は難しい事はなしで、次のように空の定義でいいでしょう。

SamplerState mySampler {};

locationに渡すテクスチャ座標は、頂点の中に含めておきます。
テクスチャ座標とはテクスチャ中の位置を表す縦1,横1の座標空間です。

Tutorial11TextureCoordinate.jpg

頂点中のテクスチャ座標データには、何かセマンティクスを付ける必要があります。
それにはTEXCOORDを使うといいでしょう。
TEXtureCOORDinate(テクスチャ座標)の略です。


コード

Program.cs
using SlimDX;
using SlimDX.Direct3D11;
using SlimDX.DXGI;
using SlimDX.D3DCompiler;

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

class MyGame : Game
{
    Effect effect;
    InputLayout vertexLayout;
    Buffer vertexBuffer;
    ShaderResourceView texture;

    protected override void Draw()
    {
        GraphicsDevice.ImmediateContext.ClearRenderTargetView(
            RenderTarget,
            new SlimDX.Color4(1, 0.39f, 0.58f, 0.93f)
            );

        effect.GetVariableByName("diffuseTexture")
            .AsResource().SetResource(texture);
        initTriangleInputAssembler();
        drawTriangle();

        SwapChain.Present(0, PresentFlags.None);
    }

    private void drawTriangle()
    {
        effect.GetTechniqueByIndex(0).GetPassByIndex(0).Apply(GraphicsDevice.ImmediateContext);
        GraphicsDevice.ImmediateContext.Draw(3, 0);
    }

    private void initTriangleInputAssembler()
    {
        GraphicsDevice.ImmediateContext.InputAssembler.InputLayout = vertexLayout;
        GraphicsDevice.ImmediateContext.InputAssembler.SetVertexBuffers(
            0,
            new VertexBufferBinding(vertexBuffer, VertexPositionTexture.SizeInBytes, 0)
            );
        GraphicsDevice.ImmediateContext.InputAssembler.PrimitiveTopology
            = PrimitiveTopology.TriangleList;
    }

    protected override void LoadContent()
    {
        initEffect();
        initVertexLayout();
        initVertexBuffer();
        initTexture();
    }

    private void initEffect()
    {
        using (ShaderBytecode shaderBytecode = ShaderBytecode.CompileFromFile(
            "myEffect.fx", "fx_5_0",
            ShaderFlags.None,
            EffectFlags.None
            ))
        {
            effect = new Effect(GraphicsDevice, shaderBytecode);
        }
    }

    private void initVertexLayout()
    {
        vertexLayout = new InputLayout(
            GraphicsDevice,
            effect.GetTechniqueByIndex(0).GetPassByIndex(0).Description.Signature,
            VertexPositionTexture.VertexElements
            );
    }

    private void initVertexBuffer()
    {
        vertexBuffer = MyDirectXHelper.CreateVertexBuffer(
            GraphicsDevice,
            new[] {
                new VertexPositionTexture
                {
                    Position = new Vector3(0, 0.5f, 0),
                    TextureCoordinate = new Vector2(0.5f, 0) 
                },
                new VertexPositionTexture
                {
                    Position = new Vector3(0.5f, 0, 0),
                    TextureCoordinate = new Vector2(1, 1)
                },
                new VertexPositionTexture
                {
                    Position = new Vector3(-0.5f, 0, 0),
                    TextureCoordinate = new Vector2(0, 1)
                },
            });
    }

    private void initTexture()
    {
        texture = ShaderResourceView.FromFile(GraphicsDevice, "Penguins.jpg");
    }

    protected override void UnloadContent()
    {
        effect.Dispose();
        vertexLayout.Dispose();
        vertexBuffer.Dispose();
        texture.Dispose();
    }
}

struct VertexPositionTexture
{
    public Vector3 Position;
    public Vector2 TextureCoordinate;

    public static readonly InputElement[] VertexElements = new[]
    {
        new InputElement
        {
            SemanticName = "SV_Position",
            Format = Format.R32G32B32_Float
        },
        new InputElement
        {
            SemanticName = "TEXCOORD",
            Format = Format.R32G32_Float,
            AlignedByteOffset = InputElement.AppendAligned//自動的にオフセット決定
        }
    };

    public static int SizeInBytes
    {
        get
        {
            return System.Runtime.InteropServices.
                Marshal.SizeOf(typeof(VertexPositionTexture));
        }
    }
}

class Game : System.Windows.Forms.Form
{
    public SlimDX.Direct3D11.Device GraphicsDevice;
    public SwapChain SwapChain;
    public RenderTargetView RenderTarget;


    public void Run()
    {
        initDevice();
        SlimDX.Windows.MessagePump.Run(this, Draw);
        disposeDevice();
    }

    private void initDevice()
    {
        MyDirectXHelper.CreateDeviceAndSwapChain(
            this, out GraphicsDevice, out SwapChain
            );

        initRenderTarget();
        initViewport();

        LoadContent();
    }

    private void initRenderTarget()
    {
        using (Texture2D backBuffer
            = SlimDX.Direct3D11.Resource.FromSwapChain<Texture2D>(SwapChain, 0)
            )
        {
            RenderTarget = new RenderTargetView(GraphicsDevice, backBuffer);
            GraphicsDevice.ImmediateContext.OutputMerger.SetTargets(RenderTarget);
        }
    }

    private void initViewport()
    {
        GraphicsDevice.ImmediateContext.Rasterizer.SetViewports(
            new Viewport
            {
                Width = ClientSize.Width,
                Height = ClientSize.Height,
            }
            );
    }

    private void disposeDevice()
    {
        UnloadContent();
        RenderTarget.Dispose();
        GraphicsDevice.Dispose();
        SwapChain.Dispose();
    }

    protected virtual void Draw() { }
    protected virtual void LoadContent() { }
    protected virtual void UnloadContent() { }
}

class MyDirectXHelper
{
    public static void CreateDeviceAndSwapChain(
        System.Windows.Forms.Form form,
        out SlimDX.Direct3D11.Device device,
        out SlimDX.DXGI.SwapChain swapChain
        )
    {
        SlimDX.Direct3D11.Device.CreateWithSwapChain(
            DriverType.Hardware,
            DeviceCreationFlags.None,
            new SwapChainDescription
            {
                BufferCount = 1,
                OutputHandle = form.Handle,
                IsWindowed = true,
                SampleDescription = new SampleDescription
                {
                    Count = 1,
                    Quality = 0
                },
                ModeDescription = new ModeDescription
                {
                    Width = form.ClientSize.Width,
                    Height = form.ClientSize.Height,
                    RefreshRate = new SlimDX.Rational(60, 1),
                    Format = Format.R8G8B8A8_UNorm
                },
                Usage = Usage.RenderTargetOutput
            },
            out device,
            out swapChain
            );
    }

    public static Buffer CreateVertexBuffer(
        SlimDX.Direct3D11.Device graphicsDevice,
        System.Array vertices
        )
    {
        using (SlimDX.DataStream vertexStream 
            = new SlimDX.DataStream(vertices, true, true))
        {
            return new Buffer(
                graphicsDevice,
                vertexStream,
                new BufferDescription
                {
                    SizeInBytes= (int)vertexStream.Length,
                    BindFlags = BindFlags.VertexBuffer,
                }
                );
        }
    }
}

myEffect.fx
Texture2D diffuseTexture;

SamplerState mySampler
{
};

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

VertexPositionTexture MyVertexShader(VertexPositionTexture input)
{
return input;
}

float4 MyPixelShader(VertexPositionTexture input) : SV_Target
{
    return diffuseTexture.Sample(mySampler, input.TextureCoordinate);
}


technique10 MyTechnique
{
pass MyPass
{
SetVertexShader( CompileShader( vs_5_0, MyVertexShader() ) );
SetPixelShader( CompileShader( ps_5_0, MyPixelShader() ) );
}
}

実行結果はこうなります。
Tutorial11TextureMappedTriangle.jpg

このプログラムは、まず三角形にテクスチャ座標を割り当てます。

Tutorial11HowTextureCoordinatesAreSet.jpg

左上が(0, 0)、右下が(1, 1)な座標です。
これにピクセルシェーダーの中で、テクスチャを貼り付けるのです。

Tutorial11TextureCoordinate.jpg

この三角形のテクスチャ座標の指定のしかたは横に眺めになっているので、少し縦に潰れてテクスチャが貼られるのです。

拍手[0回]