忍者ブログ

Memeplexes

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

C#でDirectX11 SlimDXチュートリアルその10 ライト

 今回は、三角形を光で照らします。

Tutorial10Lighting01.jpg
Tutorial10Lighting02.jpg
Tutorial10Lighting03.jpg
Tutorial10Lighting04.jpg

以前とは違い、今度は本当に三角形を回転させています。
以前のようにカメラのほうを回転させ、三角形は静止状態なのでは、光の移り変わりがわかりませんからね。

Tutorial10HowTheLightIsThrown.jpg


回転マトリクス

3DCGで物をY軸方向に回転させるにはMatrix.RotationY()メソッドによって作られたマトリクスを利用します。

public static Matrix RotationY(float angle);

angleは回転角度です。単位はラジアンです。
戻り値はY軸回転を司るマトリクスです。



光の強さを計算

三角形に光を当てて、どのくらいの明るさになるかはHLSLで自分でプログラムしなければいけません。
と言っても実際には大して難しくなくて、明るさはdot()関数で計算できます。
通称、内積です。

float dot(float3 x, float3 y);

この関数は2つのベクトルが同じ向きであるほど大きく、向きが食い違うほど小さくなります。

howTheDotProductWorks.jpg

そしてまさにこの関数の戻り値こそ、光のあたたったものの明るさを計算するのに必要なものなのです。
物の明るさは、その法線ベクトル(面と垂直なベクトル)の方向と、光の方向ベクトルのなす角度で決まるからです。

relationshipBetweenLightAndAngle.jpg

-dot(法線ベクトル, 光の方向)で面の明るさが計算できます。
上の図では左が暗く、右が明るくなります。

なお、法線ベクトルは頂点座標の中に含めることになります。
そのフィールドに付けるセマンティクスはNORMALです。


コード


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

        updateCamera();
        initTriangleInputAssembler();
        initLight();
        drawRotatingTriangle();

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


    private void updateCamera()
    {
        Matrix view = Matrix.LookAtRH(
            new Vector3(0, 0, 1),
            new Vector3(),
            new Vector3(0, 1, 0)
            );
        Matrix projection = Matrix.PerspectiveFovRH(
            (float)System.Math.PI / 2,
            ClientSize.Width / ClientSize.Height,
            0.1f, 1000
            );
        effect.GetVariableByName("ViewProjection")
            .AsMatrix().SetMatrix(view * projection);

    }

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

    private void initLight()
    {
        effect.GetVariableByName("LightDirection")
            .AsVector().Set(Vector3.Normalize(new Vector3(1, 0, -1)));
    }

    private void drawRotatingTriangle()
    {
        double time = System.Environment.TickCount / 500d;
        effect.GetVariableByName("World")
            .AsMatrix().SetMatrix(Matrix.RotationY((float)time));
        drawTriangle();
    }

    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,
            VertexPositionNormal.VertexElements
            );
    }

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

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

struct VertexPositionNormal
{
    public Vector3 Position;
    public Vector3 Normal;

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

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

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
matrix World;
matrix ViewProjection;

float3 LightDirection;

struct VertexPositionNormal
{
float4 Position : SV_Position;
float4 Normal : NORMAL;
};

VertexPositionNormal MyVertexShader(VertexPositionNormal input)
{
    VertexPositionNormal output = input;
output.Position = mul(output.Position, World);
    output.Position = mul(output.Position, ViewProjection);
output.Normal = mul(output.Normal, World);
    return output;
}

float4 MyPixelShader(VertexPositionNormal input) : SV_Target
{
return -dot(LightDirection, input.Normal);
}


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




このプログラムは斜め手前から光のあたった回転する三角形を表示します。
光が垂直にあたったときは一番明るく、光が当たらなくなると暗くなります。
 
三角形の表面と光の方向を元に、三角形の明るさを計算しているのです。



















拍手[0回]

PR

C#でDirectX11 SlimDXチュートリアルその09 深度バッファ

 前回のプログラムはとんだ欠陥プログラムでした。
近くのものが遠くのものの影に隠れて見えないのです。

withoutDepthBuffer.jpg

ここに表示されている2つの三角形は、元は同じ大きさの三角形でした。
右の三角形は遠くにあり、左の三角形は近くにあるので、大きさが違ってみえるのです。
普通このような状況では、右の三角形が左の三角形に隠れます。
近くにあるものが遠くのものを隠す。
当然です。

しかしここでは逆になっています。
遠くのもののせいで近くのものが見れなくなっているのです。
これはおかしいですね。

なぜこうなったのかというと、深度バッファというものを使っていないからです。
深度バッファを使わなければ、物体は単に描かれた順番によって前後関係が決まります。
後から描いたものが、すでに描かれていたものを隠すのです。
ふつうの2Dの描画と同じです。
だから上の図のようなおかしなことになっていたのですね。

今回は、深度バッファを使ってこの問題を解決します。


深度バッファ

深度バッファは、画面に存在するピクセルごとにfloat値がひとつ存在していて、このピクセルに描くべきかそうでないかを判定してくれます。
もしそのピクセルに、すでに手前の物が描かれている場合、そこには何も描きません。
逆にそのピクセルにまだ、これから描こうとしているピクセルよりも奥にしか物が描かれていないのなら、ピクセルを描き、深度バッファのfloatを更新します。
この仕組によって物の前後関係を正しく描くのです。

深度バッファを扱うには、SlimDX.Direct3D11.DepthStencilViewクラスを使います。

public class DepthStencilView : ResourceView

以前説明したとおり、~~Viewというのはリソースの使われ方を表すクラスです。
リソース自体は別に作る必要があります。
そしてそのリソースをコンストラクタ引数に取るのです。

public DepthStencilView(Device device, Resource resource);

deviceはViewをつくるデバイス。
resourceは作成するViewがその役割を指定するリソースです。このリソースは深度ステンシルバッファとして扱われます。(ここではステンシルという言葉は無視してください。ステンシルもピクセルごとに存在し、そのピクセルに描画するか否かを判定するのですが、今回は使いません)

resourceにはTexture2Dのインスタンスを使いましょう。
Texture2Dのインスタンスはここではコンストラクタから得ます。

public Texture2D(Device device, Texture2DDescription description);

deviceはテクスチャを作るデバイス。
descriptionはTexture2Dの設定を表すオブジェクトで、このメンバが真のコンストラクタ引数です。

SlimDX.Direct3D11.Texture2DDescription構造体には10個の設定可能なプロパティが存在します
このテクスチャのサイズはウィンドウのクライアント領域と同じで、BindFlagsにはBindFlags.DepthStencilを設定します。

DepthStencilViewをデバイスにセットするには、OutputMergerWrapper.SetTargetsメソッドを使います。
これはレンダーターゲットと同時にセットします。

public void SetTargets(DepthStencilView depthStencilView, RenderTargetView renderTargetView);

depthStencilViewはセットする深度ステンシルのビューです。
renderTargetViewはセットするレンダーターゲットのビューです。

今回は深度バッファを使うので、Viewport.MaxZプロパティを設定する必要があります。
これは普通1です。


コード

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)
            );
        GraphicsDevice.ImmediateContext.ClearDepthStencilView(
            DepthStencil,
            DepthStencilClearFlags.Depth,
            1,
            0
            );

        updateCamera();
        initTriangleInputAssembler();
        drawMovingTriangles();

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

    private void updateCamera()
    {
        Matrix view = Matrix.LookAtRH(
            new Vector3(0, 0, 1),
            new Vector3(),
            new Vector3(0, 1, 0)
            );
        Matrix projection = Matrix.PerspectiveFovRH(
            (float)System.Math.PI / 2,
            ClientSize.Width / ClientSize.Height,
            0.1f, 1000
            );
        effect.GetVariableByName("ViewProjection")
            .AsMatrix().SetMatrix(view * projection);

    }

    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 drawMovingTriangles()
    {
        double time = System.Environment.TickCount / 500d;

        effect.GetVariableByName("World").AsMatrix().SetMatrix(
            Matrix.Translation(
            (float)System.Math.Cos(time), 0, -1 + (float)System.Math.Sin(time)
            ));
        drawTriangle();

        effect.GetVariableByName("World").AsMatrix().SetMatrix(
            Matrix.Translation(
            -(float)System.Math.Cos(time), 0, -1 + -(float)System.Math.Sin(time)
            ));
        drawTriangle();
    }

    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 DepthStencilView DepthStencil;

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

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

        initRenderTarget();
        initDepthStencil();
        GraphicsDevice.ImmediateContext.
            OutputMerger.SetTargets(DepthStencil, RenderTarget);
        initViewport();

        LoadContent();
    }

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

    private void initDepthStencil()
    {
        Texture2DDescription depthBufferDesc = new Texture2DDescription
        {
            ArraySize = 1,
            BindFlags = BindFlags.DepthStencil,
            Format = Format.D32_Float,
            Width = ClientSize.Width,
            Height = ClientSize.Height,
            MipLevels = 1,
            SampleDescription = new SampleDescription(1, 0)
        };

        using (Texture2D depthBuffer = new Texture2D(GraphicsDevice, depthBufferDesc))
        {
            DepthStencil = new DepthStencilView(GraphicsDevice, depthBuffer);
        }
    }

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

    private void disposeDevice()
    {
        UnloadContent();
        DepthStencil.Dispose();
        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
matrix World;
matrix ViewProjection;
 
struct VertexPositionColor
{
float4 Position : SV_Position;
float4 Color : COLOR;
};
 
VertexPositionColor MyVertexShader(VertexPositionColor input)
{
    VertexPositionColor output = input;
output.Position = mul(output.Position, World);
    output.Position = mul(output.Position, ViewProjection);
    return output;
}
 
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() ) );
}
}

このプログラムは前回のプログラムに加え、深度ステンシルバッファをデバイスにセットしています。
前回は2つの連星のような三角形を描いていたのですが、前後関係正しく描画されない時がありました。
今回は深度バッファのおかげでそのようなことはありません。
描かれる三角形の前後関係が常に正しくなります。
冒頭のようなおかしな事にはならなくなるのです。






拍手[0回]


C#でDirectX11 SlimDXチュートリアルその08 平行移動

 今回はお互いの周りをくるくる回る2つの三角形です。

Tutorial08HowTheTrianglesRotate.jpg

Tutorial08RotatingTriangles01.jpg


平行移動

今回は、三角形を中心から平行移動します。

Tutorial08RotatingTrianglesNeedTranslation.jpg

「回転させたいのか平行移動させたいのかどっちなんだ?」
いえいえこの2つは相反するものではありません。
平行移動の方向を少しずつ変えていくことによって、回転させるのです。
平行移動する変形は、Matrix.Translationメソッドによって作ります。

public static Matrix Translation(float x, float y, float z);

x, y, zはそれぞれの軸の並行移動距離です。


コード

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

        updateCamera();
        initTriangleInputAssembler();
        drawMovingTriangles();

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

    private void updateCamera()
    {
        Matrix view = Matrix.LookAtRH(
            new Vector3(0, 0, 1),
            new Vector3(),
            new Vector3(0, 1, 0)
            );
        Matrix projection = Matrix.PerspectiveFovRH(
            (float)System.Math.PI / 2,
            ClientSize.Width / ClientSize.Height,
            0.1f, 1000
            );
        effect.GetVariableByName("ViewProjection")
            .AsMatrix().SetMatrix(view * projection);

    }

    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 drawMovingTriangles()
    {
        double time = System.Environment.TickCount / 500d;

        effect.GetVariableByName("World").AsMatrix().SetMatrix(
            Matrix.Translation(
            (float)System.Math.Cos(time), 0, -1 + (float)System.Math.Sin(time)
            ));
        drawTriangle();

        effect.GetVariableByName("World").AsMatrix().SetMatrix(
            Matrix.Translation(
            -(float)System.Math.Cos(time), 0, -1 + -(float)System.Math.Sin(time)
            ));
        drawTriangle();
    }

    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
matrix World;
matrix ViewProjection;

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

VertexPositionColor MyVertexShader(VertexPositionColor input)
{
    VertexPositionColor output = input;
output.Position = mul(output.Position, World);
    output.Position = mul(output.Position, ViewProjection);
    return output;
}

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

このプログラムは2つの三角形を描いています。 描く上で、それぞれ反対方向に平行移動しています。
平行移動の変換マトリクスはWorldという名前の変数に格納されています。
まず平行移動をし、カメラから見たらどう映るかという変換を行っているわけですね。

Worldの値は時間と共にすこしずつ変わっているので、2つの三角形はお互いの周りを回ります。

Tutorial08RotatingTriangles01.jpg

前後関係がおかしい!!

さて、このプログラムにはひとつおかしなところがあります。
それは、遠くのものに近くのものが隠れることがあるということです。

withoutDepthBuffer.jpg

この2つの三角形のもとの大きさは同じです。
違うのは、カメラからの距離で、左の三角形は遠くに、右の三角形は近くにあります。

明らかにおかしいですね。
本来、遠くのものは近くのものに隠れます。
逆ではありません。

こんなおかしな結果になってしまった理由は、
現在このサンプルは単に描画する順番で隠す隠さないを決めているからです。
後から描かれた三角形が、その前に描かれた三角形を隠します。

この解決には深度バッファが必要です。
これは次回にしましょう。


拍手[0回]


C#でDirectX11 SlimDXチュートリアルその07 カメラ(遠近感+回転)

 このチュートリアルは3DCGを想定しておきながら、前回までは2Dと大差ありませんでした。
話を簡単にするために三角形をそのままの大きさで描いていたのです。
これではいけません。
近くにあるものは大きく、遠くにあるものは小さく表示されるのが自然です。

今回はそのように遠近感を出してみましょう。

Tutorial07RotatingTriangle01.jpg

Tutorial07RotatingTriangle01WithDescription.jpg

遠くにあるものは小さく、近くにあるものは大きく。

あとついでに回転させてみましょう。

972835a4.jpg


マトリクス

今回やることは、頂点データを変更するという意味で前回と同じです。
前回はfloatの変数を用意して、その値の分だけX軸方向に三角形を平行移動しました。

今回が前回と違うのは、頂点データの変換が複雑だということです。
今回は回転と遠近法するデータの変換です。
「こうするとたくさん変数が必要そうだし、計算もなんだかめんどくさくなりそうだ」と思われるかもしれません。

しかし実際は簡単です。
変数は1つでいいですし、ベクトルの変換も1行で住みます。
マトリクスという、ベクトルデータの変換方法を示した変数を使うのです。
マトリクスはC#側では構造体で、16個のフィールドを持ち、回転だとか平行移動だとか遠近法だとかいった変換を全て司ります。
おもしろいのは、「回転してから平行移動して遠近法を適用したい」という複数の変換も1つのマトリクスだけですんでしまうということです。
16個のフィールドがその変換を全部表現してくれるのです。
ですから、今回HLSL側に必要な変数はmatrix型の変数1つだけです。
変数の数は前回から増えません。
マトリクス様様です。



マトリクスの生成

マトリクスはベクトルの変換方法を表すデータです。
C#ではSlimDX.Matrix構造体を使います。

public struct Matrix : IEquatable<Matrix>

一口にベクトルの変換と言っても様々です。
回転、平行移動、遠近法…
それぞれに、対応するマトリクスを生成するためのファクトリメソッドが付いています。

今回使うのは2つです。
物体をある方向から見たときに、物体の相対的な座標がどう見えるかは、Matrix.LookAtRHメソッドを使います。 このメソッドはカメラから見たものの見え方にするようなマトリックスを作り出します。

public static Matrix LookAtRH(Vector3 eye, Vector3 target, Vector3 up);

eyeはカメラの位置を表します。
targetはカメラの向いている対象の位置です。target - eyeで、カメラの向いている方向になります。
upはカメラの上方向です。これは普通y方向、new Vector3(0, 1, 0)です。これをnew Vector3(0, -1, 0)にすると画面は上下ひっくり返ります。


プレイヤーが迷路の中をすすんでいくとき、引数eyeを変えていくことになるでしょう。
プレイヤーがその場であたりを見まわし回転したら引数targetの値が変わります。

遠近法を司るマトリクスはMatrix.PerspectiveFovRHメソッドによって作り出します。

public static Matrix PerspectiveFovRH(float fov, float aspect, float znear, float zfar);
CreatePerspectiveFieldOfView.jpg

fovはfieldOfViewの略です。縦の視野角を表します。
aspectはアスペクト比です。つまり横幅 / 縦幅です。
znearは画面のz座標。
zfarは最も遠い地点のz座標です。


こうして作ったマトリクスは、*演算子で合成することができます。
A * Bは「ベクトルをAで変換してからBで変換せよ」というマトリクスを新たに作り出します。
こうして合成しても必要なメモリは全く変わらないというのだから驚きです。

public static Matrix operator *(Matrix left, Matrix right);

leftははじめにベクトルを変換するマトリクス。
rightは次にベクトルを変換するマトリクスです。

マトリクスのセット

C#側からHLSLの変数にマトリクスをセットするには、EffectMatrixVariable.SetMatrix()メソッドを使います。

public Result SetMatrix(Matrix matrix);

matrixはセットするマトリクスです。



マトリクスでベクトルを変換する(HLSL)

今回、HLSL側のコードにはマトリクス(matrix)型の変数がひとつあります。
この変数を使って、頂点位置情報を変換します。
変換にはmul関数使います
float4 mul(float4 position, matrix transform)

positionは変換前の位置ベクトル。
transformはベクトルの変換方法を表すマトリクスです。



コード

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)
            );
 
        updateCamera();
        initTriangleInputAssembler();
        drawTriangle();
 
        SwapChain.Present(0, PresentFlags.None);
    }
 
    private void updateCamera()
    {
        double time = System.Environment.TickCount / 500d;
 
        Matrix view = Matrix.LookAtRH(
            new Vector3((float)System.Math.Cos(time), 0, (float)System.Math.Sin(time)),
            new Vector3(),
            new Vector3(0, 1, 0)
            );
        Matrix projection = Matrix.PerspectiveFovRH(
            (float)System.Math.PI / 2,
            ClientSize.Width / ClientSize.Height,
            0.1f, 1000
            );
        effect.GetVariableByName("ViewProjection")
            .AsMatrix().SetMatrix(view * projection);
 
    }
 
    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, VertexPositionColor.SizeInBytes, 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,
            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

matrix ViewProjection;
 
struct VertexPositionColor
{
float4 Position : SV_Position;
float4 Color : COLOR;
};
 
VertexPositionColor MyVertexShader(VertexPositionColor input)
{
    VertexPositionColor output = input;
    output.Position = mul(output.Position, ViewProjection);
    return output;
}
 
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() ) );
}
}
このプログラムは回転する三角形を描きます。 ただ正確に言うと、回転しているのは三角形の方ではなくてカメラなのですが。
 
Tutorial07HowTheCameraRotates.jpg

結果、三角形が回転しているように見えるのです。

Tutorial07RotatingTriangle01.jpg

Tutorial07RotatingTriangle03.jpg

Tutorial07RotatingTriangle02.jpg

拍手[1回]


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