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: 70904 (from 2010/06/03), today: 7, yesterday: 0