様々な 2D ラインの描画
當前顯示的頁面不支援所選的顯示語言。
スクリーン座標(2D)でラインを表示させるようにしています。
座標変換済み頂点を使用してもラインは描画できるのですが、SDK の「Line」クラスを使用することにより、いくつかのパターンでラインを描画することが出来ます。
今回のメインコードファイルを載せます。
MainSample.cs
>using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
namespace MDXSample
{
<summary>
メインサンプルクラス
</summary>
public partial class MainSample : IDisposable
{
<summary>
ラインクラス
</summary>
private Line _line = null;
<summary>
アプリケーションの初期化
</summary>
<param name="topLevelForm">トップレベルウインドウ</param>
<returns>全ての初期化がOKなら true, ひとつでも失敗したら false を返すようにする</returns>
<remarks>
false を返した場合は、自動的にアプリケーションが終了するようになっている
</remarks>
public bool InitializeApplication(MainForm topLevelForm)
{
// フォームの参照を保持
this._form = topLevelForm;
// 入力イベント作成
this.CreateInputEvent(topLevelForm);
try
{
// Direct3D デバイス作成
this.CreateDevice(topLevelForm);
// フォントの作成
this.CreateFont();
}
catch (DirectXException ex)
{
// 例外発生
MessageBox.Show(ex.ToString(), "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
// XYZライン作成
this.CreateXYZLine();
// ラインクラスの作成
this._line = new Line(this._device);
return true;
}
<summary>
メインループ処理
</summary>
public void MainLoop()
{
// アプリケーションの終了操作
if (this._keys[(int)Keys.Escape])
{
this._form.Close();
return;
}
// カメラの設定
this.SettingCamera();
// 描画内容を単色でクリアし、Zバッファもクリア
this._device.Clear(ClearFlags.ZBuffer | ClearFlags.Target, Color.DarkBlue, 1.0f, 0);
// 「BeginScene」と「EndScene」の間に描画内容を記述する
this._device.BeginScene();
// ライトを無効
this._device.RenderState.Lighting = false;
// テクスチャー無効
this._device.SetTexture(0, null);
// 原点に配置
this._device.SetTransform(TransformType.World, Matrix.Identity);
// XYZラインを描画
this.RenderXYZLine();
// ラインの描画
// 本来は「Line.Begin」メソッドと「LineEnd」メソッドの間に
// ライン描画コードを入れるが
// ラインクラスのパラメータを常に変更しているので使わない
//this._line.Begin();
// ラインの位置を保持するためのベクトル配列
Vector2[] positions = new Vector2[3];
// 初期化
this._line.Width = 1.0f;
this._line.Antialias = false;
this._line.Pattern = -1;
this._line.PatternScale = 1.0f;
// 通常のライン
this._font.DrawText(null, "通常のライン", 20, 120, Color.White);
positions[0] = new Vector2(50.0f, 150.0f);
positions[1] = new Vector2(250.0f, 150.0f);
positions[2] = new Vector2(450.0f, 100.0f);
this._line.Draw(positions, Color.White);
// 太いライン
this._font.DrawText(null, "太いライン", 20, 170, Color.White);
positions[0] = new Vector2(50.0f, 200.0f);
positions[1] = new Vector2(250.0f, 200.0f);
positions[2] = new Vector2(450.0f, 150.0f);
this._line.Width = 10.0f;
this._line.Draw(positions, Color.Yellow);
// アンチエイリアス
this._font.DrawText(null, "アンチエイリアス", 20, 220, Color.White);
positions[0] = new Vector2(50.0f, 250.0f);
positions[1] = new Vector2(250.0f, 250.0f);
positions[2] = new Vector2(450.0f, 200.0f);
this._line.Antialias = true;
this._line.Draw(positions, Color.Yellow);
// パターン通常
this._font.DrawText(null, "パターン通常", 20, 270, Color.White);
positions[0] = new Vector2(50.0f, 300.0f);
positions[1] = new Vector2(250.0f, 300.0f);
positions[2] = new Vector2(450.0f, 250.0f);
this._line.Pattern = 1;
this._line.PatternScale = 1.0f;
this._line.Draw(positions, Color.LightBlue);
// パターン間隔0.5倍
this._font.DrawText(null, "パターン間隔 0.5 倍", 20, 320, Color.White);
positions[0] = new Vector2(50.0f, 350.0f);
positions[1] = new Vector2(250.0f, 350.0f);
positions[2] = new Vector2(450.0f, 300.0f);
this._line.PatternScale = 0.5f;
this._line.Draw(positions, Color.LightPink);
// 「Line.Begin」を使用していないのでコメントアウト
//this._line.End();
// 文字列の描画
this._font.DrawText(null, "[Escape]終了", 0, 0, Color.White);
this._font.DrawText(null, "θ:" + this._lensPosTheta, 0, 12, Color.White);
this._font.DrawText(null, "φ:" + this._lensPosPhi, 0, 24, Color.White);
// 描画はここまで
this._device.EndScene();
// 実際のディスプレイに描画
this._device.Present();
}
<summary>
リソースの破棄をするために呼ばれる
</summary>
public void Dispose()
{
// ラインクラスの解放
this.SafeDispose(this._line);
// リソースの破棄
this.DisposeResource();
}
}
}
MainSamplePartial.cs ファイルのコードはこちらです。
MainSamplePartial.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
namespace MDXSample
{
public partial class MainSample
{
<summary>
メインフォーム
</summary>
private MainForm _form = null;
<summary>
Direct3D デバイス
</summary>
private Device _device = null;
<summary>
Direct3D 用フォント
</summary>
private Microsoft.DirectX.Direct3D.Font _font = null;
<summary>
キーのプレス判定
</summary>
private bool[] _keys = new bool[256];
<summary>
1つ前のマウスの位置
</summary>
private Point _oldMousePoint = Point.Empty;
<summary>
カメラレンズの位置(R)
</summary>
private float _lensPosRadius = 5.0f;
<summary>
カメラレンズの位置(θ)
</summary>
private float _lensPosTheta = 300.0f;
<summary>
カメラレンズの位置(φ)
</summary>
private float _lensPosPhi = 30.0f;
<summary>
XYZライン用頂点バッファ
</summary>
private VertexBuffer _xyzLineVertexBuffer = null;
<summary>
入力イベント作成
</summary>
<param name="topLevelForm">トップレベルウインドウ</param>
private void CreateInputEvent(MainForm topLevelForm)
{
// キーイベント作成
topLevelForm.KeyDown += new KeyEventHandler(this.form_KeyDown);
topLevelForm.KeyUp += new KeyEventHandler(this.form_KeyUp);
// マウス移動イベント
topLevelForm.MouseMove += new MouseEventHandler(this.form_MouseMove);
}
<summary>
キーボードのキーを押した瞬間
</summary>
<param name="sender"></param>
<param name="e"></param>
private void form_KeyDown(object sender, KeyEventArgs e)
{
// 押されたキーコードのフラグを立てる
if ((int)e.KeyCode < this._keys.Length)
{
this._keys[(int)e.KeyCode] = true;
}
}
<summary>
キーボードのキーを放した瞬間
</summary>
<param name="sender"></param>
<param name="e"></param>
private void form_KeyUp(object sender, KeyEventArgs e)
{
// 放したキーコードのフラグを下ろす
if ((int)e.KeyCode < this._keys.Length)
{
this._keys[(int)e.KeyCode] = false;
}
}
<summary>
マウス移動イベント
</summary>
<param name="sender"></param>
<param name="e"></param>
private void form_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
// 回転
this._lensPosTheta -= e.Location.X - this._oldMousePoint.X;
this._lensPosPhi += e.Location.Y - this._oldMousePoint.Y;
// φに関しては制限をつける
if (this._lensPosPhi >= 90.0f)
{
this._lensPosPhi = 89.9999f;
}
else if (this._lensPosPhi <= -90.0f)
{
this._lensPosPhi = -89.9999f;
}
}
// マウスの位置を???憶
this._oldMousePoint = e.Location;
}
<summary>
Direct3D デバイスの作成
</summary>
<param name="topLevelForm">トップレベルウインドウ</param>
private void CreateDevice(MainForm topLevelForm)
{
// PresentParameters。デバイスを作成する際に必須
// どのような環境でデバイスを使用するかを設定する
PresentParameters pp = new PresentParameters();
// ウインドウモードなら true、フルスクリーンモードなら false を指定
pp.Windowed = true;
// スワップ効果。とりあえず「Discard」を指定。
pp.SwapEffect = SwapEffect.Discard;
// 深度ステンシルバッファ。3Dでは前後関係があるので通常 true
pp.EnableAutoDepthStencil = true;
// 自動深度ステンシル サーフェイスのフォーマット。
// 「D16」に対応しているビデオカードは多いが、前後関係の精度があまりよくない。
// できれば「D24S8」を指定したいところ。
pp.AutoDepthStencilFormat = DepthFormat.D16;
try
{
// デバイスの作成
this.CreateDevice(topLevelForm, pp);
}
catch (DirectXException ex)
{
// 例外発生
throw ex;
}
}
<summary>
Direct3D デバイスの作成
</summary>
<param name="topLevelForm">トップレベルウインドウ</param>
<param name="presentationParameters">PresentParameters 構造体</param>
private void CreateDevice(MainForm topLevelForm, PresentParameters presentationParameters)
{
// 実際にデバイスを作成します。
// 常に最高のパフォーマンスで作成を試み、
// 失敗したら下位パフォーマンスで作成するようにしている。
try
{
// ハードウェアによる頂点処理、ラスタライズを行う
// 最高のパフォーマンスで処理を行えます。
// ビデオカードによっては実装できない処理が存在します。
this._device = new Device(0, DeviceType.Hardware, topLevelForm.Handle,
CreateFlags.HardwareVertexProcessing, presentationParameters);
}
catch (DirectXException ex1)
{
// 作成に失敗
Debug.WriteLine(ex1.ToString());
try
{
// ソフトウェアによる頂点処理、ハードウェアによるラスタライズを行う
this._device = new Device(0, DeviceType.Hardware, topLevelForm.Handle,
CreateFlags.SoftwareVertexProcessing, presentationParameters);
}
catch (DirectXException ex2)
{
// 作成に失敗
Debug.WriteLine(ex2.ToString());
try
{
// ソフトウェアによる頂点処理、ラスタライズを行う
// パフォーマンスはとても低いです。
// その代わり、ほとんどの処理を制限なく行えます。
this._device = new Device(0, DeviceType.Reference, topLevelForm.Handle,
CreateFlags.SoftwareVertexProcessing, presentationParameters);
}
catch (DirectXException ex3)
{
// 作成に失敗
// 事実上デバイスは作??できません。
throw ex3;
}
}
}
}
<summary>
フォントの作成
</summary>
private void CreateFont()
{
try
{
// フォントデータの構造体を作成
FontDescription fd = new FontDescription();
// 構造体に必要なデータをセット
fd.Height = 12;
fd.FaceName = "MS ゴシック";
// フォントを作成
this._font = new Microsoft.DirectX.Direct3D.Font(this._device, fd);
}
catch (DirectXException ex)
{
// 例外発生
throw ex;
}
}
<summary>
XYZライン作成
</summary>
private void CreateXYZLine()
{
// 6つ分の頂点を作成
this._xyzLineVertexBuffer = new VertexBuffer(typeof(CustomVertex.PositionColored),
6, this._device, Usage.None, CustomVertex.PositionColored.Format, Pool.Managed);
// 頂点バッファをロックして、位置、色情報を書き込む
using (GraphicsStream data = this._xyzLineVertexBuffer.Lock(0, 0, LockFlags.None))
{
// 今回は各XYZのラインを原点(0.0f, 0.0f, 0.0f)からプラス方向に 10.0f 伸びた線を作成
data.Write(new CustomVertex.PositionColored(0.0f, 0.0f, 0.0f, Color.Red.ToArgb()));
data.Write(new CustomVertex.PositionColored(10.0f, 0.0f, 0.0f, Color.Red.ToArgb()));
data.Write(new CustomVertex.PositionColored(0.0f, 0.0f, 0.0f, Color.Green.ToArgb()));
data.Write(new CustomVertex.PositionColored(0.0f, 10.0f, 0.0f, Color.Green.ToArgb()));
data.Write(new CustomVertex.PositionColored(0.0f, 0.0f, 0.0f, Color.Blue.ToArgb()));
data.Write(new CustomVertex.PositionColored(0.0f, 0.0f, 10.0f, Color.Blue.ToArgb()));
this._xyzLineVertexBuffer.Unlock();
}
}
<summary>
ライトの設定
</summary>
private void SettingLight()
{
// 平行光線を使用
this._device.Lights[0].Type = LightType.Directional;
// ライトの方向
this._device.Lights[0].Direction = new Vector3(1.0f, -1.5f, 2.0f);
// 光の色は白
this._device.Lights[0].Diffuse = Color.White;
// 環境光
this._device.Lights[0].Ambient = Color.FromArgb(255, 128, 128, 128);
// 0 番のライトを有効
this._device.Lights[0].Enabled = true;
// 0 番のライトを更新
this._device.Lights[0].Update();
}
<summary>
カメラの設定
</summary>
private void SettingCamera()
{
// レンズの位置を三次元極座標で変換
float radius = this._lensPosRadius;
float theta = Geometry.DegreeToRadian(this._lensPosTheta);
float phi = Geometry.DegreeToRadian(this._lensPosPhi);
Vector3 lensPosition = new Vector3(
(float)(radius * Math.Cos(theta) * Math.Cos(phi)),
(float)(radius * Math.Sin(phi)),
(float)(radius * Math.Sin(theta) * Math.Cos(phi)));
// ビュー変換行列を設定
this._device.Transform.View = Matrix.LookAtLH(
lensPosition, new Vector3(0.0f, 0.0f, 0.0f), new Vector3(0.0f, 1.0f, 0.0f));
// 射影変換を設定
this._device.Transform.Projection = Matrix.PerspectiveFovLH(
Geometry.DegreeToRadian(60.0f),
(float)this._device.Viewport.Width / (float)this._device.Viewport.Height,
1.0f, 100.0f);
}
<summary>
XYZライン描画
</summary>
private void RenderXYZLine()
{
this._device.SetStreamSource(0, this._xyzLineVertexBuffer, 0);
this._device.VertexFormat = CustomVertex.PositionColored.Format;
this._device.DrawPrimitives(PrimitiveType.LineList, 0, 3);
}
<summary>
リソースの破棄
</summary>
private void DisposeResource()
{
// XYZラインの破棄
this.SafeDispose(this._xyzLineVertexBuffer);
// フォントのリソース解放
this.SafeDispose(this._font);
// Direct3D デバイスのリソース解放
this.SafeDispose(this._device);
}
<summary>
安全なリソース破棄
</summary>
<param name="resource">破棄するリソース</param>
private void SafeDispose(IDisposable resource)
{
if (resource != null)
{
resource.Dispose();
}
}
}
}
<summary>
ラインクラス
</summary>
private Line _line = null;
今回2Dラインを描画するのに「Line」クラスを使用します。座標変換済み頂点をライン描画に置き換えてしまうことも出来るのですが、描画パターンが限られています。Line クラスを使用することにより、線を太くしたり、アンチエイリアスを掛けたりするのが非常に簡単なので、こちらを使ってみたいと思います。
// ラインクラスの作成
this._line = new Line(this._device);
Direct3D デバイスを渡せば Line クラスを作成することが出来ます。今回メインループでパラメータを変更させているので、初期化メソッドで設定するものは特にありません。
// ラインの描画
// 本来は「Line.Begin」メソッドと「LineEnd」メソッドの間に
// ライン描画コードを入れるが
// ラインクラスのパラメータを常に変更しているので使わない
//this._line.Begin();
ラインを描画する場合は「Line.Begin」メソッドと「Line.End」メソッドの間に記述しないといけないのですが、その2つのメソッドの間で Line クラスのプロパティを変更するとエラーが発生するようなので今回は使用しないことにします。
「LineBegin」と「Line.End」を使用しなくでもライン描画時に内部で呼んでいるので実際には問題ありません。ただ、複数のライン描画を行う場合は「LineBegin」と「Line.End」を使用しないと処理速度が低下する場合があります。(今回はサンプルなので処理速度にはこだわっていません)
// ラインの位置を保持するためのベクトル配列
Vector2[] positions = new Vector2[3];
ライン描画時には各頂点の位置の配列を渡す必要があるので、「Vector2」の配列を作成します。別なラインを描画するたびに配列を作成するのも面倒なので、一律下のようなラインを描画することにします。
// 初期化
this._line.Width = 1.0f;
this._line.Antialias = false;
this._line.Pattern = -1;
this._line.PatternScale = 1.0f;
描画毎に違う設定をしているので最初に初期化するようにしています。各パラメータに関しては後のセクションで説明します。
// 通常のライン
this._font.DrawText(null, "通常のライン", 20, 120, Color.White);
positions[0] = new Vector2(50.0f, 150.0f);
positions[1] = new Vector2(250.0f, 150.0f);
positions[2] = new Vector2(450.0f, 100.0f);
this._line.Draw(positions, Color.White);
設定を何も変更していない状態のラインを描画しています。描画するには「Line.Draw」メソッドにスクリーン座標での頂点位置の配列と線の色を渡します。
頂点の配列の数に応じて繋がった(折れ曲がった)線を描画することが出来ます。
// 太いライン
this._font.DrawText(null, "太いライン", 20, 170, Color.White);
positions[0] = new Vector2(50.0f, 200.0f);
positions[1] = new Vector2(250.0f, 200.0f);
positions[2] = new Vector2(450.0f, 150.0f);
this._line.Width = 10.0f;
this._line.Draw(positions, Color.Yellow);
「Line.Width」プロパティを変更することにより、線の太さを変更することが出来ます。float で設定しますが、スクリーン座標での設定なので 1.0f が太さ1ドットになります。
よく見ると線が曲がっているところで微妙に切れていることが分かります。実はこの Line クラスは線を描画しているのではなく、四角形ポリゴンを変換させて描画しているのです。そのため、下の図のように描画しているのでこのようになってしまうのです。
太いラインを曲げるときは、繋げて描画するのではなく、個別に描画したほうがいいかもしれません。
// アンチエイリアス
this._font.DrawText(null, "アンチエイリアス", 20, 220, Color.White);
positions[0] = new Vector2(50.0f, 250.0f);
positions[1] = new Vector2(250.0f, 250.0f);
positions[2] = new Vector2(450.0f, 200.0f);
this._line.Antialias = true;
this._line.Draw(positions, Color.Yellow);
「Line.Antialias」プロパティを true にすることにより、アンチエイリアスが掛かり、線のギザギザが滑らかに見えるようになります。
下の図では直線はギザギザが無いのであまり分からないですが、斜めの線が滑らかになっていることが分かります。
// パターン通常
this._font.DrawText(null, "パターン通常", 20, 270, Color.White);
positions[0] = new Vector2(50.0f, 300.0f);
positions[1] = new Vector2(250.0f, 300.0f);
positions[2] = new Vector2(450.0f, 250.0f);
this._line.Pattern = 1;
this._line.PatternScale = 1.0f;
this._line.Draw(positions, Color.LightBlue);
「Line.Pattern」プロパティを 1 に設定するとラインが点描みたいに描画されます。1 以上も設定できるようですが、どのような法則で描画されているかは不明です。(ヘルプにも 0 と 1 のことしか書かれていません)
わかりやすいように線を太くしています。
// パターン間隔0.5倍
this._font.DrawText(null, "パターン間隔 0.5 倍", 20, 320, Color.White);
positions[0] = new Vector2(50.0f, 350.0f);
positions[1] = new Vector2(250.0f, 350.0f);
positions[2] = new Vector2(450.0f, 300.0f);
this._line.PatternScale = 0.5f;
this._line.Draw(positions, Color.LightPink);
「Line.PatternScale」プロパティを変更すると点描の間隔を変更することが出来ます。1.0 を標準として、1.0 以下を指定すると間隔が短くなり、1.0 以上の場合は間隔が広くなります。
// 「Line.Begin」を使用していないのでコメントアウト
//this._line.End();
「Line.Begin」を使用している場合はライン描画後に「Line.End」を使用する必要がありますが、今回は使用していません。
// ラインクラスの解放
this.SafeDispose(this._line);
Line クラスを使用し終わったら解放します。