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: 80448 (from 2010/06/03),
today: 1,
yesterday: 3