C#/Direct3Dを使った2D描画 の履歴(No.1)
更新Bitmap 画像をスクリーンに高速描画する†
Graphic.Draw だと速度やちらつきが問題になる場合に、 DirectX を使って高速&きれいに描画する方法を調べた。
本格的な 3D の表示は試していない。
DirectDraw はもう古い†
昔は 2D グラフィックを高速に表示させるには DirectDraw というのを使ったが、今はお勧めされないらしい。
C# から DirectDraw 関連の関数を呼ぼうとすると、 激しく Deprecated の表示が出る。
Direct3D ももう古い?†
C# を始めとする Managed な環境から呼び出せる DirectX を Managed DirectX と呼び、MDX と略すらしいんだけれど、 すでに Microsoft は MDX を見限っているらしい。
http://www.gamedev.net/community/forums/topic.asp?topic_id=541254
確かにいろいろ試していると、ドキュメントが足りなかったり、 あまつさえ仕様と異なる動きをする部分があったりで、 結構危うい。
今回はこういった情報に行き着く前に(無駄に)骨を折ってしまったので 一応ここにまとめておく。
ここで試した範囲では、それなりには使えている。
参照の追加†
Direct3D を使うには、プロジェクトの [参照設定] に、
- Microsoft.DirectX
- Microsoft.DirectX.Direct3D
- Microsoft.DirectX.Direct3DX
を入れておく。
大まかな流れ†
- Direct3D の画面を表示したいコントロール上に Direct3D.Device を作成
- Device の照明やZバッファの機能はオフにしておく
- Device 上に 2D 表示用の画面である Direct3D.Sprite を作成
- Sprite に座標変換方法を指定
- Direct3D.Texture に Bitmap をロード
- Texture から Sprite へ画像を転送
- Device 表示を更新
コード例†
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; } }
ユーティリティルーチン†
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: 78998 (from 2010/06/03),
today: 2,
yesterday: 2