忍者ブログ

Memeplexes

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

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

PR