忍者ブログ

Memeplexes

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

[PR]

×

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


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

前回は背景を青一色に塗りつぶしました
今回は三角形を表示します。


三角形を一つ表示するだけだというのに今回は
やけにやることが増えます。
こういう所がDirectXよりもXNAのほうが
優れているところなのでしょうね

まずはコードから。

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 unsafe D3DBuffer CreateVertexBuffer(D3DDevice device, Vector3F[] vertices)
    {
        fixed (Vector3F* fixedVertices = vertices)
        {
            return device.CreateBuffer(
                new BufferDescription
                {
                    ByteWidth = (uint)(Marshal.SizeOf(typeof(Vector3F)) * vertices.Length),
                    BindingOptions = BindingOptions.VertexBuffer,
                },
                new SubresourceData
                {
                    SystemMemory = new System.IntPtr(fixedVertices)
                }
                );
        }
    }
}

class MyGame : Game
{
    protected override void Draw()
    {
        this.DeviceContext.ClearRenderTargetView(RenderTargetView, new ColorRgba(0, 0, 1, 1));
        this.DeviceContext.Draw(3, 0);
        this.SwapChain.Present(0, PresentOptions.None);
    }

    protected override void LoadGraphicsContent(D3DDevice device)
    {
        initShadersAndInputLayout(device);
        initTriangleToDraw(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(
            new[] {
                    new InputElementDescription
                    {
                        SemanticName = "SV_Position",
                        Format = Format.R32G32B32Float,
                    }
                },
            vertexShaderBinary
            );
    }

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

        D3DBuffer vertexBuffer = CreateVertexBuffer(device, vertices);

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

        this.DeviceContext.IA.PrimitiveTopology = PrimitiveTopology.TriangleList;
    }
}
あまりにも長いのでクラスを2分割しました。
ある程度一般的な機能(Game)と、今回のデモに特有な部分(MyGame)とにです。
使うメソッド名などもなるべくXNAに似せます。

前回から付け加わったのはおおまかにいって
1.シェーダーの作成
2.インプットレイアウトの作成
3.頂点バッファの作成
4.ビューポートの作成

です。
もちろん生成したものはセットしてから描画します。

  説明 XNAで言うと
シェーダー(頂点シェーダとピクセルシェーダ) 頂点データがどのような処理をされて描画されるかを制御します。このコードではMyShader.vsとMyShader.psというファイルから読み込んでいます。 Effect
インプットレイアウト 頂点データの意味を表します。(何バイト目が色のデータだ、など) VertexDeclaration
頂点バッファ 描画する図形の頂点データを保持します。 VertexBuffer
ビューポート 描画先の領域です。 Viewport


このうち4.ビューポートの作成以外は描画するものに影響し
あまり抽象化するとかえってまずい気がするので、
MyGameクラスの方に持ってきます。
(ビューポートは大抵のゲームでウィンドウ全体に設定することを選ぶでしょう。)

シェーダーファイル

さらに、DirectXにはXNAのBasicEffectのような気の利いたものは無いので
シェーダーファイルも必要です。

MyShader.fx

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

float4 MyPixelShader() : SV_Target
{
    return float4(1, 1, 1, 1);
}
この意味は、ポリゴンの「頂点はそのままなにも動かさず、色は白にせよ」
です

fxc.exeを使ってシェーダーをコンパイル

さて、このエフェクト(.fx)ファイルはどうやらそのままではプログラムからは使えません。
残念ながら、このファイルをコンパイルするメソッドが
まだWindows API Code Packには実装されていないようなのです。
(DirectX10のほうはまだマシなようですが)

よってDirectX SDKのツール
fxc.exe
を使って手作業でコンパイルすることにします。
最新のDirectX SDKをダウンロードしましょう。

上のMyShader.fxを適当なフォルダに置いてください。
そしてそれと同じフォルダ内に以下のような.batファイルを作ります。
"C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)\Utilities\bin\x86\fxc.exe" /T vs_4_0 /E MyVertexShader /Fo MyShader.vs MyShader.fx
@pause
"C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)\Utilities\bin\x86\fxc.exe" /T ps_4_0 /E MyPixelShader /Fo MyShader.ps MyShader.fx
@pause
意味は
「MyShader.fxをコンパイルして、頂点シェーダMyShader.vsとピクセルシェーダMyShader.psを作成せよ」
です。

   
/T 対象とするプロファイル。例えばvs_4_0やps_4_0です。これを入れ違えたり間違ってもfx_4_0などにしないでください。C#側からArgumentExceptionがスローされます。
/E エントリーポイント。頂点シェーダなら頂点シェーダのメソッド名、ピクセルシェーダならピクセルシェーダのメソッド名です。
/Fo 出力ファイル名

.batを実行すると
MyShader.vsとMyShader.psが出来ます。

後はそれをVisual C#のプロジェクトに追加して、
それぞれのプロパティを[Copy if newer]にします。

実行

実行するとこうなります。

directX11TutorialDrawWhiteTriangle.jpg


























拍手[1回]

PR

[Windows® API Code Pack for Microsoft® .NET Framework] C#でDirectX11をつかう その2 背景のクリア

前回作った雛形にコードを追加していきます。 

今回はDirect3Dのデバイスを作成して
ウィンドウの背景をクリアします。


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

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

class Game : System.Windows.Forms.Form
{
    SwapChain swapChain;
    DeviceContext deviceContext;
    RenderTargetView renderTargetView;

    public void Run()
    {
        initDevice();
        Show();

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

    private void Draw()
    {
        deviceContext.ClearRenderTargetView(renderTargetView, new ColorRgba(0, 0, 1, 1));
        swapChain.Present(0, PresentOptions.None);
    }

    private void 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);
            this.deviceContext.OM.RenderTargets = new OutputMergerRenderTargets(new[] { renderTargetView });
        }
    }
}
 これは背景を青でクリアしています。

directX11TutorialClearRenderTarget.jpg

最初にDirect3Dに関連したオブジェクトを初期化し
それを使ってwhileループの中で
延々と背景を青にクリアし続けています。

大まかに説明すると
こうなります:
クラス名 説明 XNAで言うと
SwapChain
 
 
 
これはダブルバッファリングを行うための2つのバッファを持っています。
 
(片方が描画対象、もう片方はディスプレイに表示されるバッファ)
 
この2つのバッファは、描画が終わって実際にディスプレイに表示するときに
 
役割が交代(スワップ)します
GraphicsDeviceがこれの機能を持っています
DeviceContext
描画を行うオブジェクトです。
これを使ってポリゴンとかいろいろなものを描画します。
GraphicsDeviceがこれに近いです。
RenderTargetView 描画する対象です。 RenderTarget2Dでしょうか

これら3つのオブジェクトを手に入れるためにD3DDeviceをつくっています。
SwapChainとIntermediateContextプロパティを通じて2つのオブジェクトを手に入れています。

「プロパティがあるんだからわざわざ2つもメンバ変数にしなくていいじゃない?
必要に応じてプロパティにアクセスすればメンバ変数は一つ減らせるし」
、と思われるかもしれません。

が、Windows API Code Packのソースを読めばわかりますが、
実はこのプロパティは内部で新たに生成して返しています。
今後実装が変わる可能性もありますが、ここでは念のためメンバ変数として確保しておきます。


エラーが起きる場合

64bit OSを使っている場合、
Windows API Code Packに含まれるx64のアセンブリを使いたくなるかもしれません。

しかしx86ではなくそっちを使うと、
BadImageFormatExceptionがスローされると思います。

しかしx86のアセンブリに切り替えて実行すると
今度はFileLoadExceptionがスローされるかもしれません。
にっちもさっちもいきません。
この原因は、アセンブリのランタイムバージョンが2.0だからです。

これはプロジェクトに.configファイルを次のように作れば解決します。
要はランタイムのバージョンが2.0であるアセンブリを使うことを示してやれば良いのです。

App.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup useLegacyV2RuntimeActivationPolicy="true">
    <supportedRuntime version="v4.0"/>
  </startup>
</configuration>



 

拍手[1回]


[Windows® API Code Pack for Microsoft® .NET Framework] C#でDirectX11をつかう その1 下準備

(対象読者は「XNAが使えるけどDirectXはちょっとめんどくさいなぁ」と感じている人を想定しています)

XNAではDirectX11世代の技術は使えない

簡単に3Dゲームを作れるXNAですが、
限界もあります。

XNAはDirectX9世代ですが、
現在DirectXの最新世代は11なのです。
つまりXNAでは最新技術を使えません

ではXNAがDirectX11以上に将来対応するかというと、
おそらくいつかはするのでしょうが、
Xbox360との兼ね合いですぐには難しいでしょう。

MicrosoftとしてはXNAで作ったゲームはXbox360
で動いて欲しいはずですが、
肝心のXbox360がDirectX9固定です。
XNAが11に対応してしまえば、XNAで作ったゲームが
Xbox360で動かせないケースが出てくるはずです。

ですから、XNAが11移行に対応するとすれば
それはXbox360の次の世代のゲーム機が発売された時だと思われます。
しかし残念なことにそれは、まだまだ先のことのようです。



Windows API Code Pack for Microsoft .NET Framework

つまりC#で最新技術に追いつくのはやや面倒なことになっているのですが、
一方で救いもあります。
 Windows® API Code Pack for Microsoft® .NET Frameworkというものがあるのです。

ここでは最新APIの数々がC#で使えるようにラッパーが書かれています。
DirectX11もその中に含まれています。

C#でDirectX11が使えるわけです

しかし見たところすべての機能がラップされているわけではありません。
たとえばMatrix4x4Fにファクトリメソッドは一つもありません
XNAの豊富なメソッド群と比べるとまだまだ貧相です。
今すぐ使うのはまだ難しいけれど、将来に期待といったところでしょうか。
ただ動作を確認するだけなら今でもできるはずです。

使ってみましょう。


Windows API Code Pack for Microsoft .NET Frameworkのインストール

インストールはここ

Windows API Code Pack v1.1 (Binaries, Source Docs)

というリンクをクリックします。
現在バージョンは1.1で、2010年9月のもののようです。

クリックすると

Windows API Code Pack Self Extractor.exe

というファイルがダウンロードされますので、実行します。
すると同じディレクトリに

Windows API Code Pack 1.1.zip

というファイルが展開されます。
この中に.netのアセンブリが含まれています。
Visual StudioのProjectフォルダかどこかに移動して展開しましょう。


出来たフォルダの中を見ると、

binaries > DirectX > x86

にDirectXのラッパーアセンブリがあります。

Visual C#のプロジェクトを作ってこれを参照追加してみましょう。


コードの雛形

いきなりDirectXを使ってみるというのは
その複雑さから言って無謀です!

順序良く行きましょう。
シンプルなものから少しずつ発展させていくのです。

まずはWindows Formsを使って
ウィンドウを表示します。

Program.cs

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

class Game : System.Windows.Forms.Form
{
    public void Run()
    {
        Show();

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



このコードはウィンドウを一つ表示します。

directX11TutorialCreateWindow.jpg

Application.Run()ではなくwhileループを使っているのは
そのほうが後に書くことになるプログラムの構造に近いからです。

ちなみにどことなくクラス名メソッド名などが
XNAに似ているのはわざとです。

今後もこの調子で
続けていきます。




拍手[1回]


C#でRubyっぽいループを書く

(この記事を書き終わってもしやと思いググッてみると
どうやら同じことを考えている人がすでに居たようです。残念)


4,5年前Rubyを使ったことがあったのですが、
ループがけっこう簡単です。
5.times { |i|
	puts i
}
Rubyを使ったことのない方のために言うと、
上の実行結果は
0
1
2
3
4

です。

これはたぶんC#よりも初学者にとって分かりやすいと思います。
なにせ「5回繰り返せ!」というのが
5.timesからぐっと伝わってくるからです。
(そう思いませんか!?)

私が初めて覚えたプログラミング言語はCでしたが、
当時中学生の身としてforはいささか複雑すぎました。
結果forに引っかかったことを覚えています。

C#でクロージャを使ってループ

しかしここで言いたいのはC#はダメだということではありません!
C#でもRubyのような書き方ができるからです。

まずC#で普通にループを書こうと思えば
以下のようにforを使います。
class Program
{
    static void Main(string[] args)
    {
        for (int i = 0; i < 5; i++)
        {
            System.Console.WriteLine(i);
        }
    }
}


結果は、上に書いたrubyのコードと同じです。
数字を0から4まで出力します。

C#でRubyのように書くにはどうすればいいでしょう?
ここで拡張メソッドの登場です。

static class Int32Extension
{
    public static void Times(this int loopCount, System.Action<int> loop)
    {
        for (int i = 0; i < loopCount; i++)
        {
            loop(i);
        }
    }
}


皆さんご存知のとおり上のように書くと、
System.Int32構造体に拡張メソッドを追加できます。
つまり以下のようにかけるのです。
class Program
{
    static void Main(string[] args)
    {
        5.Times(i => System.Console.WriteLine(i));
    }
}

いかがでしょうか。
forを使うよりシンプルになっているのは確かです。

ただ・・・これは良いものなのでしょうか悪いものなのでしょうか?
なるほどシンプルで書きやすいというのはそのとおりでしょうが、
forより明らかにパフォーマンスが落ちそうです。
もっともパフォーマンスはネックになるところ以外では気にするべきではありません。

では読みやすさはどうでしょうか?
個人的にこれを使ってみて、
「書きやすいけれどももしかするとほんの少し読みにくいかもしれない」
といった感想です。
読みにくいかもしれないというのはデリゲートに{}を使ったとき);と重なって
ちょっと汚く見えるかもしれないということです。

ただ、Timesの他にもUpToメソッドなどを書いて使ってみたのですが
そちらは});がごちゃごちゃしているのを差し引いても
かなり読みやすくなったと思います。

個人的見解としては、今後もループをたくさん使わなければいけないときにはこれを使うと思います、
といったところでしょうか。

拍手[0回]


WPFで列挙型の表示(DataTemplateSelector)

 WPFの話です。

さて以下のような列挙型があり、
それをWPFで表示したくなったとします。

public enum FigureType
{
    Ellipse,
    Rectangle,
    Triangle
}
3つの値はそれぞれ図形のタイプを表しています。
丸、四角、三角です。

例えば「この中から一つ図形をユーザーに選ばせたい」という状況を考えてください。

すると、WPFでは全体として次のようになるでしょう。
(全体として、と言いましたがMainWindow.csとMainWindow.xamlだけです。
App.xaml、App.csはデフォルトのままなので割愛します。)


C#
using System.Windows;

namespace DataTemplateSelectorDemo
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            figuresView.ItemsSource = System.Enum.GetValues(typeof(FigureType));
        }
    }

    public enum FigureType
    {
        Ellipse,
        Rectangle,
        Triangle
    }
}

XAML
<Window x:Class="DataTemplateSelectorDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">

    <ListBox x:Name="figuresView"/>
    
</Window>


この結果がどうなるかというと、以下のようなぐあいです。
dataTemplateSelectorWithoutSelector.png

リストボックス中に3つの選択肢が出てきます!
リストボックスなのでマウスでクリックするとそれ相応のイベントが発生し
選択した図形をもとにプログラムを書くことができます。

しかしここで表示されるのはあくまでも文字なので、
ちょっと使いにくいと考える人もいるでしょう。

Ellipse, Rectangle, Triangleよりも
○、□、△のほうが直感的です。

DataTemplateSelectorを使う

では列挙型をもとに図形を表示するにはどうすればいいでしょうか?
WPFではSystem.Windows.Controls.DataTemplateSelectorを利用するとできます。

(別の方法もあるのですが)
これを使うと以下のようになります。

C#
using System.Windows;
using System.Windows.Controls;

namespace DataTemplateSelectorDemo
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            figuresView.ItemsSource = System.Enum.GetValues(typeof(FigureType));
        }
    }

    public enum FigureType
    {
        Ellipse,
        Rectangle,
        Triangle
    }


    public class FigureTypeDataTemplateSelector : DataTemplateSelector     {
public DataTemplate EllipseDataTemplate { get; set; }
public DataTemplate RectangleDataTemplate { get; set; }
public DataTemplate TriangleDataTemplate { get; set; }

public override DataTemplate SelectTemplate(
object item,
DependencyObject container
)
{
if (!(item is FigureType)) return null;

switch ((FigureType)item)
{
case FigureType.Ellipse:
return EllipseDataTemplate;
case FigureType.Rectangle:
return RectangleDataTemplate;
case FigureType.Triangle:
return TriangleDataTemplate;
default: return null;
}
}
}
}

XAML
<Window x:Class="DataTemplateSelectorDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:model="clr-namespace:DataTemplateSelectorDemo"
        Title="MainWindow" Height="350" Width="525">

    <ListBox x:Name="figuresView" ItemTemplateSelector="{DynamicResource templateSelector}"/>

    <Window.Resources>
<DataTemplate x:Key="MyEllipseDataTemplate">
<Ellipse Stroke="Black" Width="100" Height="100"/>
</DataTemplate>
<DataTemplate x:Key="MyRectangleDataTemplate">
<Rectangle Stroke="Black" Width="150" Height="100"/>
</DataTemplate>
<DataTemplate x:Key="MyTriangleDataTemplate">
<Polygon Stroke="Black" Points="100,0 0,100 200,100"/>
</DataTemplate>

<model:FigureTypeDataTemplateSelector
x:Key="templateSelector"
EllipseDataTemplate="{StaticResource MyEllipseDataTemplate}"
RectangleDataTemplate="{StaticResource MyRectangleDataTemplate}"
TriangleDataTemplate="{StaticResource MyTriangleDataTemplate}"
/> </Window.Resources>
</Window>


DataTemplateSelectorは、「どの値の時にどのように表示するか」を決定することができます。
丸の時には○を表示するように、四角の時には□を表示するように、三角の時には△を表示するようにしたいものです。
そういったことを実際に決定するのがDataTemplateSelector.SelectTemplate()メソッドで、
上のコードではswitchで場合分けしてデータの表示方法(DataTemplate)をreturnしています。

結果はこうなります。

dataTemplateSelectorDemoWithFigure.jpg


リストボックス中に図形が表示されました!

ここでは列挙型を表示しましたが、
もちろん、DataTemplateSelectorは列挙体だけでなくintなど他の値であっても使えるクラスです。























拍手[2回]