忍者ブログ

Memeplexes

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

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