C#/Direct3Dを使った2D描画 のバックアップ差分(No.1)

更新


  • 追加された行はこの色です。
  • 削除された行はこの色です。
[[公開メモ]]

#contents

* Bitmap 画像をスクリーンに高速描画する [#of3fe191]

Graphic.Draw だと速度やちらつきが問題になる場合に、
DirectX を使って高速&きれいに描画する方法を調べた。

本格的な 3D の表示は試していない。

* DirectDraw はもう古い [#m9531726]

昔は 2D グラフィックを高速に表示させるには DirectDraw 
というのを使ったが、今はお勧めされないらしい。

C# から DirectDraw 関連の関数を呼ぼうとすると、
激しく Deprecated の表示が出る。

* Direct3D ももう古い? [#uf4f3143]

C# を始めとする Managed な環境から呼び出せる DirectX を
Managed DirectX と呼び、MDX と略すらしいんだけれど、
すでに Microsoft は MDX を見限っているらしい。

http://www.gamedev.net/community/forums/topic.asp?topic_id=541254

確かにいろいろ試していると、ドキュメントが足りなかったり、
あまつさえ仕様と異なる動きをする部分があったりで、
結構危うい。

今回はこういった情報に行き着く前に(無駄に)骨を折ってしまったので
一応ここにまとめておく。

ここで試した範囲では、それなりには使えている。

* 参照の追加 [#a7321230]

Direct3D を使うには、プロジェクトの [参照設定] に、
- Microsoft.DirectX
- Microsoft.DirectX.Direct3D
- Microsoft.DirectX.Direct3DX

を入れておく。

* 大まかな流れ [#pefee44f]

- Direct3D の画面を表示したいコントロール上に Direct3D.Device を作成
- Device の照明やZバッファの機能はオフにしておく
- Device 上に 2D 表示用の画面である Direct3D.Sprite を作成
- Sprite に座標変換方法を指定
- Direct3D.Texture に Bitmap をロード
- Texture から Sprite へ画像を転送
- Device 表示を更新

* コード例 [#t64a35a4]

 LANG:C#
 using Direct3D = Microsoft.DirectX.Direct3D;
 using System.Drawing.Imaging;
  
 Direct3D.Device device = null;
 Direct3D.Sprite sprite;
 Direct3D.Texture texture;
 
 void InitializeDirect3D()
 {
     // デバイスの作成
     device = Direct3DUtils.CreateDevice(panel1);
 
     // スプライトの作成
     sprite = new Direct3D.Sprite(device);
     sprite.Transform = Microsoft.DirectX.Matrix.Identity;
 
     // テクスチャの読み込み
     var bmp = new Bitmap("some.jpg");
     texture = Direct3DUtils.CreateTexture(sprite, bmp.Size);
     Direct3DUtils.CopyBitmapToTexture(fore, bmp);
 
     // フレームレートの設定
     const int FrameRate = 30;
     timer1.Interval = 1000 / FrameRate;
 
     // 描画の開始
     timer1.Enabled = true;
 }
 
 void FinalizeDirect3D()
 {
     timerFrame.Enabled = false;
     if ( device!=null ) {
         fore.Dispose();
         back.Dispose();
         sprite.Dispose();
         device.Dispose();
         iter = null;
         device = null;
     }
 }
 
 bool working = false;
 private void timerFrame_Tick(object sender, EventArgs e)
 {
     if ( working )
         return;
     try {
         working = true;
 
         // デバイスの描画開始
         device.BeginScene();
 
         // 背景を塗りつぶす
         device.Clear(Direct3D.ClearFlags.Target, Color.Black, 1.0f, 0);
 
         // スプライトの描画開始
         sprite.Begin(Direct3D.SpriteFlags.AlphaBlend);
 
         // ビットマップの (0, 0, 100, 100) の部分を、
         // ビットマップ上の (0, 0) が (50, 20) に来る位置に、
         // 半透明(Transparency = 0.5) で描画
         var sourceRect = new Rectangle(0, 0, 100, 100);
         var center = Microsoft.DirectX.Vector3(0, 0, 0);
         var position = Microsoft.DirectX.Vector3(50, 20, 0);
         var Transparency = 0.5;
         var color = Color.FromArgb((int)Math.Round(Transparency*255), Color.White);
         sprite.Draw(texture, sourceRect, center, position, color);
 
         // 描画の終了
         sprite.End();
         device.EndScene();
 
         // 画面に表示
         device.Present();
     } finally {
         working = false;
     }
 }

* ユーティリティルーチン [#de760199]

 LANG:C#
 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 
 using System.Windows.Forms;
 using System.Drawing;
 using System.Drawing.Imaging;
 using Direct3D = Microsoft.DirectX.Direct3D;
 using System.Runtime.Serialization;
 
 namespace PictureSaver
 {
     class Direct3DUtils
     {
         public static Direct3D.Device CreateDevice(Control control)
         {
             // デバイス作成用パラメータ
             var param = new Direct3D.PresentParameters[] { new Direct3D.PresentParameters() };
             param[0].AutoDepthStencilFormat = Direct3D.DepthFormat.D15S1;
             param[0].BackBufferCount = 1;
             param[0].BackBufferFormat = Direct3D.Manager.Adapters.Default.CurrentDisplayMode.Format;
             param[0].BackBufferHeight = control.ClientRectangle.Height;
             param[0].BackBufferWidth = control.ClientRectangle.Width;
             param[0].DeviceWindow = control;
             param[0].EnableAutoDepthStencil = false;
             param[0].MultiSample = Direct3D.MultiSampleType.None;
             param[0].MultiSampleQuality = 0;
             param[0].PresentationInterval = Direct3D.PresentInterval.Immediate;
             param[0].PresentFlag = Direct3D.PresentFlag.None;
             param[0].SwapEffect = Direct3D.SwapEffect.Discard;
             param[0].Windowed = true;
 
             // デバイスを作成し、照明とZバッファを無効にする
             var device = new Direct3D.Device(Direct3D.Manager.Adapters.Default.Adapter, Direct3D.DeviceType.Hardware,
                                     control, Direct3D.CreateFlags.HardwareVertexProcessing, param);
             device.RenderState.ZBufferEnable = false;
             device.RenderState.Lighting = false;
 
             return device;
         }
 
         public static Microsoft.DirectX.Direct3D.Texture CreateTexture(Direct3D.Sprite sprite, Size size)
         {
             var textureSize = CalcTextureSize(size);
             return new Direct3D.Texture(sprite.Device, textureSize.Width, textureSize.Height,
                 0, Direct3D.Usage.Dynamic, Direct3D.Format.A8R8G8B8, Direct3D.Pool.Default);
         }
 
         public static Size CalcTextureSize(Size size)
         {
             // Direct3D のテクスチャの幅と高さは2の累乗でないといけない
             int width, height;
             for ( width = 1; width < size.Width; width *= 2 )
                 ;
             for ( height = 1; height < size.Height; height *= 2 )
                 ;
             return new Size(width, height);
         }
 
         public static void CopyBitmapToTexture(Direct3D.Texture texture, Bitmap bmp)
         {
             Size size;
             int[] pixels = GetPixelsFromBitmap(bmp, out size);
 
             // テクスチャをロック
             // Bitmap のデータとタテヨコが逆になってるので Height、Width の順
             Direct3D.SurfaceDescription desc = texture.GetLevelDescription(0);
             UInt32[,] buf = (UInt32[,])texture.LockRectangle(
                 typeof(UInt32), 0, Direct3D.LockFlags.None, desc.Height, desc.Width);
             try {
                 // タテ・ヨコを逆にしてコピー
                 // ビットマップからはみ出たところは Transparent にしておく
                 for ( int y = 0; y < desc.Height; y++ )
                     for ( int x = 0; x < desc.Width; x++ )
                         if ( x < size.Width && y < size.Height ) {
                             buf[y, x] = (UInt32)pixels[y * size.Width + x];
                         } else {
                             buf[y, x] = (UInt32)Color.Transparent.ToArgb();
                         }
             } finally {
                 // テクスチャをアンロック
                 texture.UnlockRectangle(0);
             }
         }
 
         public static int[] GetPixelsFromBitmap(Bitmap bmp, out Size size)
         {
             // Bitmap からピクセル情報を抜き出し int の配列に移して返す
             // pixels[y * size.Width + x] の形で ARGB データにアクセスできる
             int[] pixels;
             BitmapData bmpdata = bmp.LockBits(
                 new Rectangle(0, 0, bmp.Width, bmp.Height),
                 ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
             try {
                 size = new Size(bmpdata.Width, bmpdata.Height);
                 pixels = new int[size.Width * size.Height];
                 IntPtr ptr = bmpdata.Scan0;
                 System.Runtime.InteropServices.Marshal.Copy(ptr, pixels, 0, size.Width * size.Height);
             } finally {
                 bmp.UnlockBits(bmpdata);
             }
             return pixels;
         }
     }
 }


Counter: 70904 (from 2010/06/03), today: 7, yesterday: 0