忍者ブログ

Memeplexes

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

[Windows® API Code Pack for Microsoft® .NET Framework] C#でDirectX11をつかう その5 三角形を回転



 前回は色のついた三角形を表示しました。
ここでは、その三角形を回転させてみましょう。

三角形を回転させるためにカメラの方を回転させます。
つまり今回はカメラの情報を新たに扱うことになります。


定数バッファー

DirectX10の頃からそうだったと思いますが、
GPU側にConstantBufferという物を作り、
それにカメラの情報をセットしてやります。
GPUはその情報を利用して三角形を表示します。

ですから今回の主役はConstantBufferです。
ConstantBufferは頂点バッファやインデックスバッファのような
GPUのバッファリソースの一つで、
シェーダー内で使われる変数群です。
生成も頂点バッファと同じ、D3DDevice.CreateBuffer()メソッドを使います。

違うのはそのメソッドに使われるパラメーターで、
BindingOptionsBindingOptions.ConstantBufferに指定してやります。

そして一旦生成した後、その中身を変更するには
DeviceContext.UpdateSubresource()メソッドを使います。
このメソッドを使ってカメラ情報を毎回描画前にアップデートしてやります。

生成、データをセット、だけでは定数バッファは使えるようになりません。
定数バッファをデバイスにセットしてやる必要があります。
これを行うのはVS.SetConstantBuffers()メソッドです。


Program.cs

using Microsoft.WindowsAPICodePack.DirectX.Direct3D;
using Microsoft.WindowsAPICodePack.DirectX.Direct3D11;
using Microsoft.WindowsAPICodePack.DirectX.Graphics;

using System.IO;
using System.Runtime.InteropServices;

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

class Game : System.Windows.Forms.Form
{
    protected DeviceContext DeviceContext;
    protected SwapChain SwapChain;
    protected RenderTargetView RenderTargetView;
    
    public void Run()
    {
        D3DDevice device = initDevice();
        LoadGraphicsContent(device);
        Show();

        while (Created)
        {
            Draw();
            System.Windows.Forms.Application.DoEvents();
        }
    }

    private D3DDevice initDevice()
    {
        D3DDevice device = D3DDevice.CreateDeviceAndSwapChain(this.Handle);
        this.SwapChain = device.SwapChain;
        this.DeviceContext = device.ImmediateContext;

        using (Texture2D texture2D = SwapChain.GetBuffer<Texture2D>(0))
        {
            this.RenderTargetView = device.CreateRenderTargetView(texture2D);
            DeviceContext.OM.RenderTargets = new OutputMergerRenderTargets(new[] { RenderTargetView });
        }

        DeviceContext.RS.Viewports = new[]
        {
            new Viewport
            {
                Width = ClientSize.Width,
                Height = ClientSize.Height,
                MaxDepth = 1
            }
        };
        return device;
    }

    protected virtual void LoadGraphicsContent(D3DDevice device) { }
    protected virtual void Draw() { }

    protected D3DBuffer CreateVertexBuffer<T>(D3DDevice device, T[] vertices) where T : struct
    {
        int vertexSize = Marshal.SizeOf(typeof(T));
        System.IntPtr verticesPointer = Marshal.AllocCoTaskMem(vertices.Length * vertexSize);

        for (int i = 0; i < vertices.Length; i++)
        {
            Marshal.StructureToPtr(vertices[i], verticesPointer + vertexSize * i, false);
        }

        D3DBuffer result = device.CreateBuffer(
                new BufferDescription
                {
                    ByteWidth = (uint)(vertexSize * vertices.Length),
                    BindingOptions = BindingOptions.VertexBuffer,
                },
                new SubresourceData
                {
                    SystemMemory = verticesPointer
                }
                );

        Marshal.FreeCoTaskMem(verticesPointer);

        return result;
    }

    protected void ToNative(object structure, System.Action<System.IntPtr> nativeAction)
    {
        System.IntPtr nativeConstantBuffer = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(MyCamera)));
        Marshal.StructureToPtr(structure, nativeConstantBuffer, false);
        nativeAction(nativeConstantBuffer);
        Marshal.FreeHGlobal(nativeConstantBuffer);
    }

}

struct VertexPositionColor
{
    public Vector3F Position;
    public Vector3F Color;

    public static readonly InputElementDescription[] VertexElements = new[]
    {
         new InputElementDescription
         {
             SemanticName = "SV_Position",
             Format = Format.R32G32B32Float,
         },
         new InputElementDescription
         {
             SemanticName = "COLOR",
             Format = Format.R32G32B32Float,
             AlignedByteOffset = uint.MaxValue //MaxValueにするとオフセットを自動的に決定
         }
    };

}

struct MyCamera
{
    public Matrix4x4F View;
    public Matrix4x4F Projection;

    public static int SizeInBytes
    {
        get
        {
            return Marshal.SizeOf(typeof(MyCamera));
        }
    }
}

class MyGame : Game
{
    D3DBuffer cameraConstantBuffer;

    protected override void Draw()
    {
        this.DeviceContext.ClearRenderTargetView(RenderTargetView, new ColorRgba(0.39f, 0.58f, 0.93f, 1));

        updateCamera();

        this.DeviceContext.Draw(3, 0);
        this.SwapChain.Present(0, PresentOptions.None);
    }

    private void updateCamera()
    {
        double time = System.Environment.TickCount / 100d;

        Matrix4x4F view = MatrixHelper.CreateLookAt(
                new Vector3F((float)System.Math.Cos(time), 0, (float)System.Math.Sin(time)),
                new Vector3F(0, 0, 0),
                new Vector3F(0, 1, 0)
                );
        Matrix4x4F projection = MatrixHelper.CreatePerspectiveFieldOfView(
                (float)System.Math.PI / 2,
                (float)ClientSize.Width / ClientSize.Height,
                0.1f, 1000);

        ToNative(
            new MyCamera
            {
                View =  MatrixHelper.Transpose(view),
                Projection = MatrixHelper.Transpose(projection)
            }, 
            nativeCamera => 
            {
                DeviceContext.UpdateSubresource(
                    cameraConstantBuffer,
                    0,
                    nativeCamera,
                    0,
                    0
                    );
            }
            );

        DeviceContext.VS.SetConstantBuffers(0, new[] { cameraConstantBuffer });
    }

    protected override void LoadGraphicsContent(D3DDevice device)
    {
        initShadersAndInputLayout(device);
        initTriangleToDraw(device);
        initCamera(device);
    }

    private void initShadersAndInputLayout(D3DDevice device)
    {
        using (Stream vertexShaderBinary = File.Open("MyShader.vs", FileMode.Open))
        {
            this.DeviceContext.VS.Shader = device.CreateVertexShader(vertexShaderBinary);
            vertexShaderBinary.Position = 0;
            this.DeviceContext.IA.InputLayout = createInputLayout(device, vertexShaderBinary);
        }

        using (Stream pixelShaderBinary = System.IO.File.Open("MyShader.ps", FileMode.Open))
        {
            this.DeviceContext.PS.Shader = device.CreatePixelShader(pixelShaderBinary);
        }
    }

    private InputLayout createInputLayout(D3DDevice device, Stream vertexShaderBinary)
    {
        return device.CreateInputLayout(
            VertexPositionColor.VertexElements,
            vertexShaderBinary
            );
    }

    private void initTriangleToDraw(D3DDevice device)
    {
        VertexPositionColor[] vertices = new []
        {
            new VertexPositionColor{ Position = new Vector3F(0, 0.5f, 0), Color = new Vector3F(1, 1, 1) },
            new VertexPositionColor{ Position = new Vector3F(0.5f, 0, 0), Color = new Vector3F(0, 0, 1) },
            new VertexPositionColor{ Position = new Vector3F(-0.5f, 0, 0), Color = new Vector3F(1, 0, 0) },
        };

        D3DBuffer vertexBuffer = CreateVertexBuffer(device, vertices);

        this.DeviceContext.IA.SetVertexBuffers(
            0,
            new D3DBuffer[] { vertexBuffer },
            new uint[] { (uint)Marshal.SizeOf(typeof(VertexPositionColor)) },
            new uint[] { 0 }
            );

        this.DeviceContext.IA.PrimitiveTopology = PrimitiveTopology.TriangleList;
    }

    private void initCamera(D3DDevice device)
    {
        ToNative(
            new MyCamera {},
            nativeCamera => 
            {
                this.cameraConstantBuffer = device.CreateBuffer(
                    new BufferDescription
                    {
                        ByteWidth = (uint)MyCamera.SizeInBytes,
                        BindingOptions = BindingOptions.ConstantBuffer
                    }, 

                    //nativeCameraの中身はカラなので
                    //これはいらないように一見見えますが、
                    //これがないと何故か例外をスローします。
                    //今後改善なりなんなりがあるのでしょうか?
                    new SubresourceData
                    {
                        SystemMemory = nativeCamera
                    }
                    );
            }
            );
    }
}
 

カメラの情報とは何かというと、
2つのマトリクスです。
この2つのマトリクスに従って頂点情報を変形させてやると
実際にウィンドウに表示される三角形の形になるのですが、
困ったことにマトリクスを生成するためのメソッドが
Windows API Code Packにはありません

厳密に言うと無いのではなく、
付属のサンプルプログラムの中には存在するのですが、
その数はXNAより豊富ではありませんし
サンプルのものをコピーしたり参照したりすることになるので
どのみち以下のようなプログラムを書くことになると思います。

MatrixHelper.cs
using Microsoft.WindowsAPICodePack.DirectX.Direct3D;

class MatrixHelper
{
    public static Matrix4x4F CreateLookAt(
        Vector3F cameraPosition, Vector3F cameraTarget, Vector3F cameraUpVector
        )
    {
        Vector3F newZ = (cameraPosition - cameraTarget).Normalize();
        Vector3F newX = Vector3F.Cross(cameraUpVector, newZ).Normalize();
        Vector3F newY = Vector3F.Cross(newZ, newX);
        return new Matrix4x4F(
            newX.X,
            newY.X,
            newZ.X,
            0,
            newX.Y,
            newY.Y,
            newZ.Y,
            0,
            newX.Z,
            newY.Z,
            newZ.Z,
            0,
            -Vector3F.Dot(newX, cameraPosition),
            -Vector3F.Dot(newY, cameraPosition),
            -Vector3F.Dot(newZ, cameraPosition),
            1f
            );
    }

    public static Matrix4x4F CreatePerspectiveFieldOfView(
        float fieldOfView, float aspectRatio,
        float nearPlaneDistance, float farPlaneDistance
        )
    {
        return new Matrix4x4F
        {
            M11 = (float)(1 / (aspectRatio * System.Math.Tan(fieldOfView / 2))),
            M22 = (float)(1 / System.Math.Tan(fieldOfView / 2)),
            M33 = farPlaneDistance / (nearPlaneDistance - farPlaneDistance),
            M34 = -1,
            M43 = (nearPlaneDistance * farPlaneDistance) / (nearPlaneDistance - farPlaneDistance),
        };
    }

    public static Matrix4x4F Transpose(Matrix4x4F matrix)
    {
        return new Matrix4x4F(
            matrix.M11, matrix.M21, matrix.M31, matrix.M41,
            matrix.M12, matrix.M22, matrix.M32, matrix.M42,
            matrix.M13, matrix.M23, matrix.M33, matrix.M43,
            matrix.M14, matrix.M24, matrix.M34, matrix.M44
            );
    }
}

そしてGPU側のコードにも
以下のようにカメラの情報を考慮して三角形を表示するよう
書き加えます。

2つのマトリクスを使って位置ベクトルを変形させるのです。

cbuffer MyCamera
{
    matrix View;
    matrix Projection;
}

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

VertexPositionColor MyVertexShader(VertexPositionColor input)
{
    VertexPositionColor output = input;
    output.Position = mul(output.Position, View);
    output.Position = mul(output.Position, Projection);
    return output;
}

float4 MyPixelShader(VertexPositionColor input) : SV_Target
{
    return input.Color;
}
 

例によってこのファイルはfxc.exeを使ってコンパイルしてください。

結果

三角形がY軸を回転軸として回転します。

directX11RotatingTriangle1.jpg
directX11RotatingTriangle2.jpg
directX11RotatingTriangle3.jpg


三角形を逆方向からみると表示されないので、
回転しては
消えて
回転しては
消えて
を繰り返します。

拍手[0回]

PR