忍者ブログ

Memeplexes

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

[PR]

×

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


C#でDirectX11 SlimDXチュートリアルその06 動く三角形

頂点シェーダー

 前々回は白い三角形、前回は3色三角形を描きました。


Tutorial03WhiteTriangle.jpg

Tutorial04ColoredTriangle.jpg


両方共三角形は全く動きません。
静止画でした。

今回はこの三角形を動かしてみたいと思います。
アニメーションです。

Tutorial06MovingTriangle.jpg
Tutorial06MovingTriangle2.jpg

上の静止画2つだけでは動いているように見えませんが、ともかくアニメーションするのです。
横にゆらゆら揺れます。

かんが妙に鋭い人は
「フームなるほど。ビューポートを動かして描くんだな。そうすれば三角形が描かれる場所をコントロールできる」
と思われるでしょうが、違います。

あるいは
「いやこういうことだな。頂点バッファの中に入っている頂点データを書き換えていくんだ。そうすれば三角形が動いたように見えるはず」
こちらはある意味正しくいい線行っています。

もちろんビューポートを動かしても全く同じ結果が得られるでしょうが、
ここでやる方法はそれよりはるかに柔軟性のある方法です。
頂点シェーダーを使って頂点の情報をいじくり回すのです。
この方法を応用すれば三角形を回転させたり、遠近法の効果を付け加えることも可能です。
ここでは話を簡単にするために、平行移動みたいな簡単なことをするのです。

頂点バッファの書き換えがある意味正しいといったのは、頂点シェーダーがまさにバッファのデータをいじくりまわしているからです。
「ある意味」という言い方をしたのは、実は頂点シェーダーの中に入っているデータそのものは変わらないということです。
そのものではなく、データのコピーが変えられるのです。
頂点バッファはあまり頻繁にデータを書き換えするのには向いていません。
CPU側からデバイスにあるデータ(バッファ)を書き換えるのには時間がかかるのです。

今回するのは、頂点バッファのデータを元にちょっと変更を加えて描画にのみ利用する方法です。
頂点バッファそのもののデータは変わりません。

コード

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;
 
    protected override void Draw()
    {
        GraphicsDevice.ImmediateContext.ClearRenderTargetView(
            RenderTarget,
            new SlimDX.Color4(1, 0, 0, 1)
            );
 
        initTriangleInputAssembler();
        setTriangleTranslation();
        drawTriangle();
        
        SwapChain.Present(0, PresentFlags.None);
    }
 
    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 setTriangleTranslation()
    {
        double time = System.Environment.TickCount / 500d;
 
        effect.GetVariableByName("TranslationX")
            .AsScalar().Set((float)System.Math.Cos(time));
    }
 
    private void drawTriangle()
    {
        effect.GetTechniqueByIndex(0).GetPassByIndex(0).Apply(GraphicsDevice.ImmediateContext);
        GraphicsDevice.ImmediateContext.Draw(3, 0);
    }
 
 
    protected override void LoadContent()
    {
        initEffect();
        initVertexLayout();
        initVertexBuffer();
    }
 
    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),
            });
    }
 
    protected override void UnloadContent()
    {
        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

float TranslationX;
 
float4 MyVertexShader(float4 position : SV_Position) : SV_Position
{
    return position + float4(TranslationX, 0, 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() ) );
}
}
このプログラムで一番重要なのはHLSLの変数TranslationXです。 この値が三角形を横に動かします。 TranslationXはC#側のコードによって値がセットされます。 値はMath.Sinに時間が引数になったもの。 つまりTranslationXは時とともに増えたり減ったりします。 それがHLSL側で頂点の座標に足されます。 つまりゆらゆらと横に揺れるようになるのです。 頂点バッファのデータそのものを変えなくても、違った描き方をすることができる。 それが頂点シェーダーです。
Tutorial06MovingTriangle.jpg

Tutorial06MovingTriangle2.jpg
HLSLの変数を書き換える 今回のサンプルでやったことは、C#側からHLSL側の変数の書き換えを行うというものでした。 書き換えの結果三角形が動くのです。 それにはHLSL側の変数を操作するためのクラスが必要です。 それがSlimDX.Direct3D11.EffectVariableクラスです。
public class EffectVariable

このオブジェクトはEffect.GetVariableByName()メソッドにより取得します。
public EffectVariable GetVariableByName(string name);

nameはHLSLファイルに書いた変数の名前です。
戻り値はEffectVariableのインスタンスです。
 
EffectVariableは変数を司るものです。
HLSLの変数を読み書きします。
ただ、どんな変数も読み書きしなくてはならないので、型情報がありません。
この変数がfloatなのか、Vector3なのか、テクスチャなのかマトリクスなのか…
使う前に、まずそれをはっきりさせる必要があります。
それにはEffectVariaible.AsXXX()という形式のメソッドを使います。
このメソッドに引数はありません。
戻り値は型と関連付けられたEffectVariableの派生クラスです。
 
 
メソッド名 戻り値
AsBlend EffectBlendVariable
AsClassInstance EffectClassInstanceVariable
AsConstantBuffer EffectConstantBuffer
AsDepthStencil EffectDepthStencilVariable
AsDepthStencilView EffectDepthStencilViewVariable
AsInterface EffectInterfaceVariable
AsMatrix EffectMatrixVariable
AsRasterizer EffectRasterizerVariable
AsRenderTargetView EffectRenderTargetViewVariable
AsResource EffectResourceVariable
AsSampler EffectSamplerVariable
AsScalar EffectScalarVariable
AsShader EffectShaderVariable
AsString EffectStringVariable
AsUnorderedAccessView EffectUnorderedAccessViewVariable
AsVector EffectVectorVariable

今回はfloatの変数を書き換えるので、AsScalar()メソッドを使います。
戻り値はSlimDX.Direct3D11.EffectScalarVariableです。

このクラスを使うとHLSL側のfloat変数に書き込みができます。
書き込みにはEffectScalarVariable.Set()メソッドを使います。

public Result Set(float value);

valueは変数にセットする値です。

こうしてC#側で用意した値をHLSL側に書き込めます。
HLSLでは描画に使われる頂点データにアクセスできるので、描くものをアニメーションすることができるのです。
















 

拍手[1回]

PR

C#でDirectX11 SlimDXチュートリアルその05 色つき三角形

前回は青を背景に白い三角形を描きました。

Tutorial03WhiteTriangle.jpg

たったこれだけですが前回はものすごい労力を必要としました。
今回は大したことありません。
見た目はだいぶ変わっていますが、労力は前回の100分1くらいです(だいたい)。

今回は三角形に色をつけます。


Tutorial04ColoredTriangle.jpg

ついでに見栄えを良くするためついでに背景の色も変えます。
青が背景のままだと青の頂点と色がかぶりますからね。



入力のレイアウト

今回は三角形に色が付いています。
3つの頂点に、それぞれ位置情報だけでなく、色が割り当てられているのです。
するとバッファに入れるデータのレイアウトが変わります。

今までは

位置

だけだったのが、

位置+色

となったのです。

頂点レイアウト中の位置とか色を表すのはInputElementクラスでした。
今回は新たに色のInputElementを付け加えるのです。
そこで、InputElement.AlignedByteOffsetプロパティを利用することになります。

public int AlignedByteOffset { get; set; }
これは要素のバイトオフセットを表します。

前回必要なかったのに今回は必要です。
なぜかというと、これは要素の位置(バイト単位)を表すからです。
前回は要素がひとつしか存在しなかったので位置は一番最初=0でよかったのですね。
今回はそうはいきません。
頂点中の色要素のバイトオフセットを指定する必要があります。

「いまいちよくわからないな。
どうしてこんなモノを指定する必要があるんだろう?
Formatプロパティですでに要素がなんなのか指定しているわけだからデバイスはすでにオフセット情報を知ってるはず。
こんなの必要ないんじゃないかな?」
という声が聞こえてきそうです。

実際そのとおりで、このプロパティの値は自動的に計算可能です。
ただしそうするにはこのプロパティに-1を指定しなければいけません。
-1をそのまま代入するのはコードが汚いので、-1を返すプロパティが用意されています。
InputElement.AppendAlignedプロパティです。

public static int AppendAligned { get; }

このプロパティの値をAlignedByteOffsetに代入すると自動計算してくれます。


HLSLの構造体

今回ひとつの頂点は位置と色を持ちます。
HLSL側ではこの2つを合わせて頂点構造体を作る必要があります。
HLSLの構造体の宣言の仕方は、C++のものと似ています。 (最後のセミコロン;を忘れないでください)

struct VertexName
{
    Type MemberName : Semantics
...
};

C++の構造体と違うのは、セマンティクスをフィールドに付けられることです。
頂点を表す構造体は、フィールドの扱われ方をセマンティクスで指定する必要があります。
位置か、色か。

逆に、頂点として使わない構造体はセマンティクスを付ける必要はありません。
シェーダー関数のシグネチャのようなものにセマンティクスは影響するのです。
シェーダー関数が利用するユーザー定義の関数にはセマンティクスは必要ありませんからね。

コード

今回のコードは新たに頂点を表す構造体を作っています。
C#でもHLSLでも。

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;
 
    protected override void Draw()
    {
        GraphicsDevice.ImmediateContext.ClearRenderTargetView(
            RenderTarget,
            new SlimDX.Color4(1, 0.39f, 0.58f, 0.93f)
            );
 
        initTriangleInputAssembler();
        drawTriangle();
 
        SwapChain.Present(0, PresentFlags.None);
    }
 
    private void initTriangleInputAssembler()
    {
        GraphicsDevice.ImmediateContext.InputAssembler.InputLayout = vertexLayout;
        GraphicsDevice.ImmediateContext.InputAssembler.SetVertexBuffers(
            0,
            new VertexBufferBinding(vertexBuffer, VertexPositionColor.SizeInBytes, 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();
    }
 
    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,
            VertexPositionColor.VertexElements
            );
    }
 
    private void initVertexBuffer()
    {
        vertexBuffer = MyDirectXHelper.CreateVertexBuffer(
            GraphicsDevice,
            new[] {
                new VertexPositionColor
                {
                    Position = new Vector3(0, 0.5f, 0),
                    Color = new Vector3(1, 1, 1) 
                },
                new VertexPositionColor
                {
                    Position = new Vector3(0.5f, 0, 0),
                    Color = new Vector3(0, 0, 1)
                },
                new VertexPositionColor
                {
                    Position = new Vector3(-0.5f, 0, 0),
                    Color = new Vector3(1, 0, 0)
                },
            });
    }
 
    protected override void UnloadContent()
    {
        effect.Dispose();
        vertexLayout.Dispose();
        vertexBuffer.Dispose();
    }
}
 
struct VertexPositionColor
{
    public Vector3 Position;
    public Vector3 Color;
 
    public static readonly InputElement[] VertexElements = new[]
    {
        new InputElement
        {
            SemanticName = "SV_Position",
            Format = Format.R32G32B32_Float
        },
        new InputElement
        {
            SemanticName = "COLOR",
            Format = Format.R32G32B32_Float,
            AlignedByteOffset = InputElement.AppendAligned//自動的にオフセット決定
        }
    };
 
    public static int SizeInBytes
    {
        get
        {
            return System.Runtime.InteropServices.
                Marshal.SizeOf(typeof(VertexPositionColor));
        }
    }
}
 
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 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 MyPass
{
SetVertexShader( CompileShader( vs_5_0, MyVertexShader() ) );
SetPixelShader( CompileShader( ps_5_0, MyPixelShader() ) );
}
}

このプログラムは3頂点それぞれに色をわりあて表示しています。
最初の頂点には白、次に青、最後に赤です。
それぞれの色の情報は表示されるときには徐々に混じっています。
白と青の頂点の中間は水色です。
赤と青の頂点の中間は紫です。
混ざるのはデバイスの仕様です。
ピクセルシェーダーにやってくる頂点の情報は位置も色も徐々に混ざるようになっているのです。

C#側では色情報を頂点に追加すると同時に、頂点レイアウトも変更しています。
頂点に色が追加されたことをデバイスが理解できるようにするのです。

HLSLでは、ピクセルシェーダーに入ってきた情報の中から色を取り出してそのまま返しています。

Tutorial04ColoredTriangle.jpg












拍手[0回]


C#でDirectX11 SlimDXチュートリアルその04 三角形の表示(色なし) クラス紹介編

前回使用したクラスを解説します。
三角形を描きたいだけなのにこのクラスの多さといったら異常です。
XNAがいかにありがたいかわかりますね。


Bufferクラス

頂点バッファを使うにはSlimDX.Direct3D11.Bufferクラスを使います。
public class Buffer : Resource

頂点バッファ(VertexBuffer)という名前のクラスはありません。
XNAにはそういう名前のクラスが存在していて、用途別に一つずつクラスが用意されて簡単です。
しかしDirect3D11にはバッファはバッファでクラスはひとつだけ。
Bufferは頂点バッファにも、並列計算するときに使う長いベクトルにも使うことができるのです。
簡単さを取るか柔軟さを取るか。
Direct3D11では後者を取ったようです。

柔軟さと引換にコンストラクタは少し厄介です。

public Buffer(Device device, DataStream data, BufferDescription description);

deviceはバッファーをつくるデバイス。
dataはバッファの初期データ。
descriptionはどういうバッファを作るかを表します。


DataStreamクラス

Bufferのコンストラクタでは初期データとして配列を渡せてもよさそうなものですが(XNAは渡せる設計ですね)、残念ながら渡せません。
かわりにSlimDX.DataStreamクラスを使います。
これはSystem.IO.Streamを継承しています。
配列をストリームとして扱うわけですね。

public class DataStream : Stream, IDisposable
コンストラクタは3種類用意されていますが、
今回使うのは次の初期値の配列を引数に取るコンストラクタです。
public DataStream(Array userBuffer, bool canRead, bool canWrite);

userBufferはこのインスタンスが初期値に使う配列です。
canReadはこのストリームから読み込むことができるかどうか、です。(Stream.CanReadと関係があるのでしょうか)
canWriteはこのストリームに書き込むことができるかどうか、です。(Stream.CanWriteと関係があるのでしょうか)


BufferDescriptionクラス

Bufferクラスにはコンストラクタに大量の引数を用意したくないためか
たくさんのオプションをメンバーに持ったSlimDX.Direct3D11.BufferDescriptionを引数に取ります。
BufferDescriptionはBufferのインスタンスを生成するのに必要ないろいろな情報を格納します。

プロパティ名 説明
BindFlags BindFlags バッファがどういう役割を持っているかを表します。 たとえば頂点バッファとして使われるか(VertexBuffer)?インデックスバッファとして使われるか(IndexBuffer)?定数バッファとして使われるのか(ConstantBuffer)?シェーダーのリソースとして使われるのか(ShaderResource)?ストリームの出力として使われるのか(StreamOutput)?レンダーターゲットとして使われるのか(RenderTarget)?デプスステンシルバッファとして使われるのか(DepthStencil)?アンオーダードアクセス(複数のスレッドから同時に読み書き可能な)リソースとして使われるのか(UnorderedAccess)? ・・・・・・などです
CpuAccessFlags CpuAccessFlags このバッファにCPUがいかほどのアクセス権限を持っているかを表します。None, Write, Readの3種類があります。
OptionFlags ResourceOptionFlags その他のオプションです。 たとえば、MipMapを生成するか?複数のデバイス間で共有されるか?TextureCubeか?インスタンシングを有効にするか?RawBufferか?StructuredBufferか?深度バイアスはどうするか?KeyedMutexか?GDIと互換性があるか? ・・・・・・などです。
SizeInBytes int バッファが何バイトか、を表します。
StructureByteStride int StructuredBufferとして使用する場合(OptionFlagsでそのように指定しなければいけません)に使います。ひとつの構造体のサイズが何バイトかを表します。
Usage ResourceUsage バッファの使われ方を表します。GPUが読み書きのできるDefault、GPUで読み取りのみ可能なImmutable、GPUからは読み取りのみCPUからは書き込みのみのDynamic、GPUからCPUへデータを転送できるStagingの4つです。

今回使うのはこのうちBindFlagsとSizeInBytesです。



Effectの生成

SlimDX.Direct3D11.Effectクラスは頂点バッファのデータの変換され方を司ります。
public class Effect : ComObject

生成にはコンストラクタを使います。
エフェクトの実際の振る舞いはHLSLで記述されています。
そのためHLSLファイルをコンパイルしたものを引数に取るのです。

public Effect(Device device, ShaderBytecode data);

deviceはエフェクトをつくるデバイス。
dataはHLSLで記述されたファイルをコンパイルしたものです。

SlimDX.D3DCompiler.ShaderBytecodeクラスは、ShaderBytecode.CompileFromFile()メソッドを使って生成します。

public static ShaderBytecode CompileFromFile(
    string fileName, 
    string profile, 
    ShaderFlags shaderFlags, 
    EffectFlags effectFlags
    );
fileNameはエフェクトを記述したHLSLファイルの名前。 profileはシェーダーのバージョン(シェーダープロファイル)です。この値によってシェーダー中で使える機能が変わってきます。Direct3D11では5.0です。
shaderFlagsはシェーダーのコンパイルオプションです。今回はNoneです。
effectFlagsはエフェクトのコンパイルオプションです。今回はNoneです。

Effectの構成要素 : EffectTechniqueクラス、EffectPassクラス

さてHLSLでデータの描かれ方を記述したファイルは、
次のような構成になっています。

CompositionOfEffect.jpg

ひとつのEffectにはひとつ以上のTechniqueがあります。
ひとつのTechniqueにはひとつ以上のPassがあります。

ではTechniqueやPassとはなんだ?
とうことですが、Techniqueは一回なにかを描くときに使うモードのようなもの(違うモードで描きたいならテクニックを切り替える)。
Passは描画の一部ということだと思われます。

しかし実際のところTechniqueやPassは1つだけしか無いことがそこそこあったりするので、
Pass=描画方法と考えて問題ないと思います。
Passが頂点バッファを元に描画する方法を規定します。

図形を描く前に、まずPassをオンにする必要があります。
そのためにはPassをTechniqueから、TechniqueをEffectから得る必要があります。
Techniqueを得るにはEffect.GetTechniqueByIndex()メソッドを使います。

public EffectTechnique GetTechniqueByIndex(int index);

indexは取得するテクニックのインデックスです。このインデックスはHLSLのファイルに書いた順番で決まります。最初に描いたテクニックは0です。
戻り値がテクニックです。

テクニックからパスを得るにはEffectTechnique.GetPassByIndex()メソッドを使います。

public EffectPass GetPassByIndex(int index);

indexは取得するパスのインデックスです。このインデックスはHLSLのファイルに描いた順番で決まります。最初に書いたパスは0です。 戻り値は取得するパスです。

こうして得たパスを、描画メソッドを呼ぶ前に使うことをデバイスに教えてあげる必要があります。
「これからこのパスを使って三角形を描きますよ」ということをはっきりさせる必要があります。
それがEffectPass.Apply()メソッドです。

public Result Apply(DeviceContext context);

contextはセットするデバイスのImmediateContextプロパティの値です。

他にも描画を行う上でEffectPassクラスには使うメンバがあります。
インプットレイアウトを初期化するときにパスのシグネイチャが必要なのです。
それにはEffectPass.Descriptionプロパティと、EffectPassDescription.Signatureプロパティを使います。
public EffectPassDescription Description { get; }

public ShaderSignature Signature { get; }
おそらくインプットレイアウトは、シグネイチャのなかの、頂点情報が必要なのだと思われます。


InputLayoutクラスとその構成要素

Bufferは基本的に型情報のないメモリの塊のようなものでした。
Bufferの構造をデバイスに教えるにはSlimDX.Direct3D11.InputLayoutクラスを使います。
public class InputLayout : DeviceChild

コンストラクタは3つの引数を取ります。
頂点バッファのひとつの頂点を構成する要素を引数に取ります。

public InputLayout(Device device, ShaderSignature shaderSignature, InputElement[] elements);

deviceはインプットレイアウトを作るデバイス。 shaderSignatureは使うパスのシグネイチャ。
elementsはひとつの頂点を構成する要素を示す構造体の配列です。

elementsはSlimDX.Direct3D11.InputElement構造体の配列です。
一つ一つが頂点を構成する要素(位置、色など)を表します。

public struct InputElement : IEquatable<InputElement>


この構造体にはプロパティが7つあります。 が、今回使うのはそのうち2つです。

InputElement.SemanticNameプロパティは、頂点を構成する要素の役割を表す文字列です。

public string SemanticName { get; set; }
この文字列はなんでもいいというわけではありません。
すでに決まっているいくつかの中から選びます。
たとえば位置なら"SV_Position"、色なら"COLOR"か"SV_Target"です。

InputElement.Formatプロパティは、頂点を構成する要素のフォーマットを表します。

public Format Format { get; set; }
SlimDX.DXGI.Format列挙型はフォーマットを表します。
これほどの値が用意されています。
今回は32bitのfloatが3つ連なったものを位置として使うので、R32G32B32_Floatを使います。

InputAssembler

描画を行うメソッドの引数は少なく、描くのに使うパラメーターは引数として渡すのではなく、予めセットしておく必要があります。
作ったBufferやInputLayoutはデバイスにセットしなければ描画されません。
その設定を行うのが、SlimDX.Direct3D11.InputAssemblerWrapperクラスです。

public class InputAssemblerWrapper

このクラスのインスタンスは、DeviceContext.InputAssemblerプロパティを通じて入手します。

public InputAssemblerWrapper InputAssembler { get; }

インプットレイアウトをセットするのはInputAssemblerWrapper.InputLayoutプロパティです。

public InputLayout InputLayout { get; set; }
BufferをセットするのはInputAssemberWrapper.SetVertexBuffers()メソッドです。

public void SetVertexBuffers(int slot, VertexBufferBinding vertexBufferBinding);

SetVertexBuffersというふうに複数形の名前ですが、セットするバッファはひとつだけです。
実はSetVertexBuffersという名前のメソッドはもうひとつあり、そちらには複数のバッファをセット出来るのです。

slotはバッファをセットするインデックス。(特殊な用途のため、デバイスには複数のバッファをセット出来ます。今回は1つしかセットしませんが)
vertexBufferBindingはセットするバッファの情報です。

SlimDX.Direct3D11.VertexBufferBindingクラスには3つのプロパティがあります。

プロパティ名 説明
Buffer Buffer セットするバッファです
Offset int バッファの中の最初の頂点のインデックスです。普通0でいいでしょう。
Stride int ひとつの頂点のバイトサイズです

最後に、描画するバッファが三角形の頂点のリストなのか、線分の点のリストなのかということも設定する必要があります。
それにはInputAssemblerWrapper.PrimitiveTopologyプロパティを使います。

public PrimitiveTopology PrimitiveTopology { get; set; }

今回は三角形を描くのでPrimitiveTopology.TriangleListをセットします。
(殆どの場合でそうでしょう)


Drawメソッド

デバイスに、これからどんな図形を描くかという情報を全てセットしたら、
あとはImmediateContext.Drawメソッドを呼びます。
このメソッド自体にはほとんど引数は存在しません。
どんな図形を描くかを示す情報のほとんどはInputAsseblerでセットするためです。

public void Draw(int vertexCount, int startVertexLocation);

vertexCountは描くのに使う頂点の数です。今回は三角形を描くので3です。
startVertexLocationはバッファ中のインデックスで、描く図形の頂点として使い始める位置を表します。これはバッファに三角形一つの座標しか入っていない場合は0です。

四角を描きたい時には四角を2つの三角が合体したものとみなします。
vertexCountは6です。
もちろん同時にバッファに6つの頂点を書きこまなければいけません。
 
もっと複雑な図形を書こうとしたら、vertexCountはもっと大きくなります。


HLSL :設定編

HLSLはC言語ライクな言語です。
といってもなんとなく似ているだけでC言語と互換性はありません。
C#がC言語ライクというのと同程度の妥当性においてHLSLはC言語ライクです。
HLSLで書いたコードは基本、デバイスでしか動きません。
CPUで動くC#とはまた違った思想で動きます。

HLSLにもMainメソッドのようなものはあります。
それがpassです。
passはどのような関数を使って図形を描くかを指定します。
C#側でPass.Apply()を呼ぶとHLSLでpass内に書いた設定で初期化されます。
Mainメソッドと違うところはそのなかで計算をするのではなく、初期化設定だけを行うということです。
ですから本当はMainメソッドというよりコンストラクタに近いと言えるかもしれません。

複数のpassはひとつのtechnique10によってまとめられます。
technique10の「10」はなんでしょうか?
これはDirect3D10の10だと思われます。
Direct3D9世代ではtechniqueだけで10は付いていませんでした。
10世代以降には10が付いているようです

passとtechnique10は次のような構文で書きます。
technique10 TechniqueName < Annotations >
{
    pass PassName 
    { 
       [ SetStateGroup; ]
       [ SetStateGroup; ]
       ...
       [ SetStateGroup; ]
    } 
}

TechniqueNameは省略可能です。
Annotationsは省略可能です。
PassNameは省略可能です。
SetStateGroupでは初期化設定を行います。

passの中では頂点シェーダーピクセルシェーダーを指定しなければいけません。
頂点シェーダーって?ピクセルシェーダーって?
お絵かきに例えると、頂点シェーダーは輪郭線やアタリ、ピクセルシェーダーは色塗りに相当します。

頂点シェーダーは遠近法を司る関数です。
近くにある三角形は大きく、遠くにある三角形は小さくなるよう頂点の位置座標を操作します。
(これはデバイスが自動でやってくれるのではありません。頂点シェーダーの関数の中でプログラムしなくてはいけないのです)
ただ、今回はこれはめんどくさいのでやりません。
三角形はそのままの大きさで表示します。

ピクセルシェーダーは影や質感を司る関数です。
ピクセル一つ一つがどんな色になるかを決定するのです。
ピクセルシェーダーの関数はピクセル一つにつき一回実行されます。
ただ、面倒なので今回は三角形を単に真っ白に塗るだけです。

プログラマは頂点シェーダー関数と、ピクセルシェーダー関数を書きます。
それをpassのなかで「これを使いますよ」とセットするのです。
次のような構文を使います。

SetXXXShader( CompileShader( shader_profile, ShaderFunction( args ) ) );

XXXは、シェーダーの種類です。頂点シェーダーならVertex、ピクセルシェーダーならPixelです。
shader_profileはシェーダーのバージョンです。この値によってシェーダーの中で使える機能が決まります。Direct3D11では5.0がマックスです。
ShaderFunctionはシェーダーの関数名。
argsはそのシェーダー全部で使うパラメーター。省略可能です。


HLSL:シェーダー関数編

シェーダーは関数です。
関数の形は、目的にそったものなら自由です。 最低限の複雑さのシェーダー関数(サンプルがそれです)を考えてみましょう。

頂点シェーダーは頂点を受け取り、頂点を返します。
引数1つ、戻り値一つです。

float4 MyVertexShader(float4 position : SV_Position) : SV_Position
{
    return position;
}

本来は頂点シェーダーの中で頂点情報をいじくりまわします。
いじくりまわして遠近感を出すのです。
が、話を簡単にするためここではpositionをそのまま返しています。
この場合頂点バッファにある三角形がそのままの大きさで描かれます。

一方、ピクセルシェーダーはとりあえず色を返します。
引数はなくて構いません。

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

本当はもっと引数をとったりして複雑な色の計算をするのですが、
一番シンプルなのは上のような関数です。

「なるほどこの2つはたしかに関数のようだけど見慣れないものが付いているな
SV_PositionとかSV_Targetとはなんだろう。
あとfloat4も」
と思われる方もいるでしょう。

SV_PositionSV_Targetというのは変数や関数の返す値の使い道を表します。
これをセマンティクスといいます。
たとえば

float4 position : SV_Position;

とあるとき、positionは位置情報として扱われることをデバイスに教えます。

float4 MyPixelShader() : SV_Target {..}

は、この関数の戻り値が"SV_Target"(=色)として扱われるということです。

このSV_PositionはC#のコードInputElement.SemanticNameプロパティで指定したものと同じです。
セマンティクスはC#とHLSLを結びつける役割もあるのです。

float4というのはfloatの4次元ベクトルです。
float3にすると3次元ベクトルになります。
int4だとintのベクトルになります。






拍手[0回]


C#でDirectX11 SlimDXチュートリアルその04 三角形の表示(色なし)


 前回はウィンドウを青色一色で塗りつぶしました。
Tutorial02ClearRenderTarget.jpg


今回はこれに白い三角形を一つ追加します。


Tutorial03WhiteTriangle.jpg

なぜ三角形なのか?
というと3DCGで表示する複雑な図形は実は三角形の集合体として表せるからです。
ポリゴンというやつです。
三角形がすべての基本なのですね。

追加するもの

今回は白い三角形をひとつ描くだけとはいえ、
たくさんのことをしなければいけません。

次の4つをプログラムに追加しなければいけないのです。

1.Buffer
2.InputLayout
3.Effect
4.Viewport

それぞれ単語が抽象的すぎてよくわかりませんね
一体何を意味しているのでしょうか?

ざっと説明すると「Bufferは三角形の頂点、InputLayoutは頂点情報の解釈方法、Effectは頂点情報の変換のされ方、Viewportはウィンドウのどこに絵を描くか」となります。
が、もう少していねいに説明しましょう。


頂点バッファ

Bufferは描く三角形の頂点情報を格納します。

これは今回やることを整理してみると分かりやすくなります。
今回やること、それは三角形の表示です。

Tutorial03WhiteTriangle.jpg

三角形を表示するには頂点の座標が必要です。

Tutorial03WhiteTriangleWithCoordinate2D.jpg

ところが実際には3DCGをやるので、頂点座標はZ軸方向も含めて3次元が必要です。
(※Z座標は0とします。)

ec2b0c9c.jpg

さて、この3つの頂点の情報はデバイス内に格納しておきます。 これがBufferです。
デバイスはバッファの中の情報を解釈し三角形を描くのです。

Tutorial03VertexBuffer.jpg
Bufferはデバイス内に確保されたメモリです。
(これはC#のオブジェクトのような型情報を持つ高級なものよりもC言語のmallocで確保されるような低レベルなメモリの塊に似ています。)
頂点の情報を格納しているので、これを特に「頂点バッファ」といいます。
頂点以外のバッファもあり、それは違った呼び方をしますが、ここでは扱いません。

「なぜBufferはデバイスの中にあるのか?デバイスの中にあるということにどんな意味があるのか?」
デバイスが使う情報は(なるべく)デバイスの中になければいけません。
CPUからデバイスへはあまりデータを大量に送れないからです。
ここでは三角形一つだけなのでそう負担はないのですが、
3DCGでは大量のポリゴンでできたモデルを描く場合が多いのです。
ポリゴンの情報はCPUの管理するシステムメモリ上ではなく、デバイスの中になければいけないのです。

なお、複雑な図形、例えば四角形を描こうと思ったら、
四角形を2つに分割して2つの三角形にします。
三角形の座標を続けて2つバッファに格納するのです。
つまり合計6つの頂点をバッファに書き込むことになります。

InputLayout

(InputLayoutは一つ一つの頂点にどのような情報が含まれているかを表します。)

さて、今回はこれで話は終わるのですが、場合によっては三角形に色をつけたい場合があります。


Tutorial04ColoredTriangle.jpg

このケースでは頂点バッファの中に位置座標だけではなく、色情報も格納しなければいけません。


Tutorial03VertexBufferColored.jpg


この図では「位置」とか「色」とか描いてありますが現実はそう甘くありません。
頂点バッファには、そのデータをどう解釈すればいいのかは含まれないのです。
単に{0, 0.5, 0, 1, 1, 1, 0.5, 0, 0, 0, 0, 1, -0.5, 0, 0, 1, 0, 0}というデータの列のみが存在します。
型情報のないメモリの塊のようなものです。

これではデバイスはどんな図形を描けばいいのかわかりません。
データはあるけれど、その解釈の方法がわからないのです。
解釈の方法はプログラマが明示的に指定してやる必要があります。


それがInputLayoutです。
InputLayoutには頂点情報のレイアウトが書いてあり、それにしたがってデバイスは頂点バッファを解釈し、図形を描くのです。

Tutorial03VertexBufferColoredWithInputLayout.jpg



Effect

Effectは、これは頂点バッファ内のデータの変換され方を表します。
変換とは何かというと、たとえば遠近法のようなものです。
3DCGでは近くにあるものは大きく、遠くにあるものは小さくしなければいけません。
そういった座標の変換を施して初めて立体的な3DCGになります。
絵を描く時に必要な頂点データの変換方法を指定するのです。
(ただ、今回はそれはちょっと大変なので遠近法なしのそのままのサイズで描きますが)

また、Effectは他のこともやります。
遠近法がデッサンでいうアタリなら、色塗り、質感の表現に相当することもEffectの責任です。
上では三角形に色がついたりしていますが、この色の指定もEffectがやるのです。
Effectのやることを一言で言うと「頂点バッファを使って絵を描く方法を指定している」といったところでしょうか?

このようにEffectは複雑なことをやってくれますが、これをC#のオブジェクトの設定などでやろうとするとやや難しいです。
Direct3D11では、これはC++とかC#ではなく、ひとつの独立した言語で行います。
HLSL(Hight Level Shader Language)という言語です。
プログラマはHLSLでエフェクトを記述し、そのソースコードをコンパイルしてC#でEffectクラスのオブジェクトを初期化します。

つまり、3DCGを表示しようとしたら、C#とHLSLの2つの言語を使わなければいけないということです。
めんどうくさいですね。

Viewport

最後に残ったViewportは三角形をウィンドウのどこに描くかを表しています。
もうちょっと正確に(でも分かりにくく)言うと描画先の範囲といったところでしょうか。
図で表すと次のような情報を持ちます。

viewport.jpg  つまり3次元の箱を表す情報を持っています。
合計6つのプロパティです。
どこから始まって(3つ)どこで終わるか(3つ)という情報です。

この箱が一体何を表しているのかということですが、
ウィンドウ先の、3DCGが描かれる領域です。
普通はWidth, Heightをウィンドウのクライアント領域のサイズと同じにしておきます。
つまりこのように表示されるのです。

Tutorial03WhiteTriangle.jpg

たとえばここでWidthを半分にすると、
こうなります。

Tutorial03WhiteTriangleViewportWidthHalfed.jpg

ウィンドウ上の描かれる領域の横幅が半分になったのです。

Tutorial03WhiteTriangleViewportWidthHalfedWithVisibleViewport.jpg

さてここでXをWidthと同じにしてみましょう。
するとこうなります:

b96b25a7.jpg

Xを大きくすると、三角形は右にずれていきます。
ここではX = Widthなので、ちょうど右半分に表示されているのですね。

では「MaxZとMinZはなんなのか。ウィンドウ内にZ座標なんて無いじゃないか」ということですが、
これは深度バッファという、「複数の三角形を重ねて表示するとき、どちらが前でどちらか後ろか判定して正しく表示するバッファ」を使ったときに意味を持ちます。
今回は深度バッファを使わないので両方共指定しなくてかまいません。
普通はMinZ = 0, MaxZ = 1でしょう。

withoutDepthBuffer.jpg

今回は問題ありませんが、深度バッファがないとおかしなことが起きることもあります。
上の2つは同じ大きさの三角形です。
片方の三角形は近くにあり、もう片方は遠くにあります。
深度バッファを使っていないので、遠くにある三角形が手前の三角形より前に表示されてしまっています。
しかし深度バッファを使えば、きちんと遠くにある三角形が手前の三角形に隠れます。

コード

今回、コードはC#とHLSLの2種類必要です。 (Program.csとmyEffect.fx)


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();
    }
 
    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),
            });
    }
 
    protected override void UnloadContent()
    {
        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 (HLSLのコード)
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() ) );
}
}


このプログラムを実行すると、こうなります。

Tutorial03WhiteTriangle.jpg

白い三角形が表示されました!

このプログラムは、まず頂点バッファに三角形の頂点である{{0, 0.5, 0}, {0.5, 0, 0}, {-0.5, 0, 0}}を格納します。
そしてインプットレイアウトに頂点バッファの解釈方法をセットします。
「頂点はFloatが3つ集まって、位置情報を表している」と教えてやるのです。
エフェクトは、myEffect.fxを読み込んでコンパイルし、頂点バッファの情報を加工してウィンドウに出力します。
myEffect.fxにはなんと書いてあるかというと、「頂点バッファからの位置データは何もいじらず、そのまま白い三角形を描け」です。
最後には無事白い三角形が表示されました。


APIの紹介は次回に続きます。

拍手[1回]


C#でDirectX11 SlimDXチュートリアルその03 背景のクリア

 レンダーターゲットのクリア

前回まではただ単にウィンドウを表示しているのと見た目は変わりませんでした。
slimDXWindow00.jpg

今回はこのように背景を(青で)クリアする方法をメモしておきます。

Tutorial02ClearRenderTarget.jpg

「なぜ青色で塗る必要があるのか?」と思う方もいらっしゃるでしょう。
その理由は、以前描画した内容を消すためです。
ゲームでは一秒間に何十回も違った絵を表示してアニメーションにしています。
その時、以前描いた内容が残っていてはおかしなことになる場合があるのです。
(全てのケースにおいて、ではありませんが)

なお、ここでのこの背景クリア方法は、背景クリアにだけ使える方法です。
今回使った方法の応用で絵を描くことはできないので注意してください。
絵の描き方は次回以降です。


クリアするAPI

背景を一色でクリアするには、DeviceContext.ClearRenderTargetView()メソッドを使います。
public void ClearRenderTargetView(RenderTargetView view, Color4 color);
viewはクリアする対象。レンダーターゲットといいます。
colorはクリアする色です。例えばここに青を指定すれば画面が全て青になります。

さてここで聞きなれないクラスが2つ現れました。
DeviceContextクラスとはなんでしょう?
RenderTargetViewクラスとは?

順番に説明しましょう。
SlimDX.Direct3D11.DeviceContextクラスはデバイスの描画機能をカプセル化したクラスです。
デバイスは描画を司ると説明しておきながら実はDeviceクラスそのものには描画機能はありませんでした。
このクラスに全てまとめられていたのです。

これはコンストラクタで生成して使うようなタイプのクラスではありません。
Deviceのプロパティを通じてアクセスするクラスです。
このクラスを使うときにはいつもDevice.ImmediateContextプロパティを使うことになるでしょう。

public DeviceContext ImmediateContext { get; }

たとえばdevice.ImmediateContext.ClearRenderTargetView(view, new Color4(1, 1, 1, 1));という具合です。

RenderTargetView

SlimDX.Direct3D11.RenderTargetViewクラスは描画対象を表すクラスです。
3DCGを描いたり今回のように青でクリアしたりするときはこのオブジェクトが変わります。
レンダー(描画)ターゲット(対象)ビューです。
?「ビュー」とはなんでしょう?

ビューというのはDirect3D11におけるデータの使われ方を意味するオブジェクトです。
RenderTargetViewの他にも~~Viewというクラスが幾つかあります。
Direct3D11では、デバイスで使われるデータ(リソース)と、その使われ方(ビュー)を切り離しています。
これは謎ですね。
オブジェクト指向の原則「データとそれに対する操作を一緒にせよ」に反しているようです。

どうしてこういうことをするのかというと、ひとつのデータに複数の役割を割り当てられるようにするためです。
「このデータはあるときにはこういう使い方をするけど別の時にはああいう使い方をしたいなあ」というとき、
この2つが一体化しているとクラスの数が膨大になってしまうでしょう。
そのため一見めんどくさいようでありますが、データとその使われ方を分離しているのですね。

RenderTargetViewクラスを使うにはコンストラクタでインスタンスを生成してやります。

public RenderTargetView(Device device, Resource resource);

deviceはレンダーターゲットビューを作るデバイスです。
resourceはレンダーターゲットビューを関連付けるリソースです。「このリソースをレンダーターゲットとして使う」ということですね。

deviceは前回初期化したデバイスを使います。
resourceには何を使えばいいのでしょう?
それはスワップチェーンのバックバッファーです。
スワップチェーンのバックバッファーがレンダーターゲットになるのです。
具体的にはどうすればいいのかというと、Resource.FromSwapChain()メソッドの戻り値を使います。
Resourceという名前のクラスはたくさんあるのですが、ここではSlimDX.Direct3D11.Resourceクラスのことです。

public static T FromSwapChain(SwapChain swapChain, int index) where T : class, Resource; 

このメソッドはスワップチェーンのバックバッファーを取得します。
swapChainは取得するバックバッファーを持つスワップチェーン。
indexは欲しいバッファのインデックスです。通常0です。
Tは取得するリソースの型です。Resourceでもいいでしょうが、Resourceは似たような名前が多くて紛らわしいのでサンプルではTexture2Dにしておきます。


SwapChain.Present

以上のクラスを使って青にした後、しなければならないことがまだ残っています。
それはSwapChain.Present()を呼ぶことです。
これを呼ばなければクリアの結果が画面に反映されません。
これは描画されたイメージをウィンドウにプレゼントするメソッドです。
参考:IDXGISwapChain::Present

public Result Present(int syncInterval, PresentFlags flags);
syncIntervalは垂直同期とフレーム表示の同期方法を表します。0ならば同期せずに即座にプレゼントします。その他の値ならその分の垂直同期の後に表示を同期します。0でいいでしょう。
flagsはプレゼント方法を表します。
 
参考:DXGI_PRESENT
  数値  
None 0  
Test 1  
DoNotSequence 2  
 

コード

コードは前回のを少し変えてこうなります。


	
using SlimDX.Direct3D11;
using SlimDX.DXGI;
 
class Program
{
    static void Main()
    {
        using (Game game = new MyGame())
        {
            game.Run();
        }
    }
}
 
class MyGame : Game
{
    protected override void Draw()
    {
        GraphicsDevice.ImmediateContext.ClearRenderTargetView(
            RenderTarget,
            new SlimDX.Color4(1, 0, 0, 1)
            );
 
        SwapChain.Present(0, PresentFlags.None);
    }
}
 
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();
    }
 
    private void initRenderTarget()
    {
        using (Texture2D backBuffer
            = SlimDX.Direct3D11.Resource.FromSwapChain<Texture2D>(SwapChain, 0)
            )
        {
            RenderTarget = new RenderTargetView(GraphicsDevice, backBuffer);
        }
    }
 
 
    private void disposeDevice()
    {
        RenderTarget.Dispose();
        GraphicsDevice.Dispose();
        SwapChain.Dispose();
    }
 
    protected virtual void Draw() { }
}
 
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
            );
    }
}
 
まず、デバイスとスワップチェーンを作成します。 そしてスワップチェーン→リソース→レンダーターゲットという流れでオブジェクトを作成し、 レンダーターゲットを青でクリアします。 そして仕上げとしてスワップチェーンでプレゼントします。 そうするとウィンドウに青が反映されます。 結果はこうなります。
Tutorial02ClearRenderTarget.jpg

拍手[3回]