フルスクリーン表示
เพจที่คุณกำลังดูอยู่ในขณะนี้ไม่สนับสนุนภาษาที่แสดงที่เลือก
市販PCゲームでよく見られる全画面表示を行います。それ以外の動作は前回と同じです。
実行すると下のように表示されます。わかりづらいかもしれませんが、全画面の時の画面キャプチャです。(枠がありません。)
今回のメインコードファイルを載せます。
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 Vector3 _trans = Vector3.Empty;
<summary>
モデルの回転情報(degree)
</summary>
private float _rotate = 0.0f;
<summary>
モデルの拡大縮小情報
</summary>
private Vector3 _scale = new Vector3(1.0f, 1.0f, 1.0f);
<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);
// PresentParameters。デバイスを作成する際に必須
PresentParameters pp = new PresentParameters();
// ウインドウモードなら true、フルスクリーンモードなら false を指定
pp.Windowed = false;
// スワップ効果
pp.SwapEffect = SwapEffect.Discard;
// 深度ステンシルバッファ
pp.EnableAutoDepthStencil = true;
// 自動深度ステンシル サーフェイスのフォーマット
pp.AutoDepthStencilFormat = DepthFormat.D16;
// 使用できるディスプレイモードを検索し、目的のモードを探す
bool flag = false;
// ディプレイモードを列挙し、サイズが「640×480」かつ
// リフレッシュレートが「60」のモードを探す
// (条件はアプリケーションの内容によって変えてください)
foreach (DisplayMode i in Manager.Adapters[0].SupportedDisplayModes)
{
if (i.Width == 640 && i.Height == 480 && i.RefreshRate == 60)
{
// 条件に見合えば使用する
pp.BackBufferWidth = 640;
pp.BackBufferHeight = 480;
pp.BackBufferFormat = i.Format;
pp.FullScreenRefreshRateInHz = 60;
// 見つかったことを示すフラグを立てる
flag = true;
break;
}
}
if (!flag)
{
// 目的のモードがなければそのまま終了
MessageBox.Show("指定したディプレイモードは見つかりませんでした。",
"エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
try
{
// Direct3D デバイス作成
this.CreateDevice(topLevelForm, pp);
// フォントの作成
this.CreateFont();
// Xファイルからメッシュ作成
this.LoadXFileMesh("Deruderu.x");
}
catch (DirectXException ex)
{
// 例外発生
MessageBox.Show(ex.ToString(), "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
// XYZライン作成
this.CreateXYZLine();
// ライトを設定
this.SettingLight();
// カメラ空間にトランスフォームされた後で頂点法線の正規化
this._device.RenderState.NormalizeNormals = true;
return true;
}
<summary>
メインループ処理
</summary>
public void MainLoop()
{
// カメラの設定
this.SettingCamera();
// キー操作で移動パラメータを変化
if (this._keys[(int)Keys.Down])
{
// 後へ移動
this._trans -= Vector3.TransformCoordinate(new Vector3(0.0f, 0.0f, -0.3f),
Matrix.RotationY(Geometry.DegreeToRadian(this._rotate)));
}
if (this._keys[(int)Keys.Up])
{
// 前へ移動
this._trans += Vector3.TransformCoordinate(new Vector3(0.0f, 0.0f, -0.3f),
Matrix.RotationY(Geometry.DegreeToRadian(this._rotate)));
}
if (this._keys[(int)Keys.Left])
{
// 左回転
this._rotate -= 5.0f;
}
if (this._keys[(int)Keys.Right])
{
// 右回転
this._rotate += 5.0f;
}
if (this._keys[(int)Keys.Z])
{
// 縮小
this._scale.X /= 1.01f;
this._scale.Y /= 1.01f;
this._scale.Z /= 1.01f;
//this._scale *= 1.0f / 1.01f;
}
if (this._keys[(int)Keys.A])
{
// 拡大
this._scale *= 1.01f;
}
// モデル座標変換用マトリックスを初期化
Matrix modelTransform = Matrix.Identity;
// 最初に拡大縮小
modelTransform *= Matrix.Scaling(this._scale);
// 回転
modelTransform *= Matrix.RotationY(Geometry.DegreeToRadian(this._rotate));
// 最後に移動
modelTransform *= Matrix.Translation(this._trans);
// 描画内容を単色でクリアし、Zバッファもクリア
this._device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.DarkBlue, 1.0f, 0);
// 「BeginScene」と「EndScene」の間に描画内容を記述する
this._device.BeginScene();
// ライトを無効
this._device.RenderState.Lighting = false;
// 原点に配置
this._device.SetTransform(TransformType.World, Matrix.Identity);
// XYZラインを描画
this.RenderXYZLine();
// ライトを有効
this._device.RenderState.Lighting = true;
// 計算したマトリックスで座標変換
this._device.SetTransform(TransformType.World, modelTransform);
// メッシュの描画
this.RenderMesh();
// 文字列の描画
this._font.DrawText(null, "[Escape]終了", 0, 0, Color.White);
this._font.DrawText(null, "[←→]回転 [↑↓]移動 [ZA]拡大縮小", 0, 12, Color.White);
this._font.DrawText(null, "θ:" + this._lensPosTheta, 0, 24, Color.White);
this._font.DrawText(null, "φ:" + this._lensPosPhi, 0, 36, Color.White);
this._font.DrawText(null,
"移動:"+this._trans.X+","+this._trans.Y+","+this._trans.Z, 0, 48, Color.White);
this._font.DrawText(null, "回転:" + this._rotate, 0, 60, Color.White);
this._font.DrawText(null, "拡大:" + this._scale.X, 0, 72, Color.White);
// 描画はここまで
this._device.EndScene();
// 実際のディスプレイに描画
this._device.Present();
// アプリケーションの終了操作
if (this._keys[(int)Keys.Escape])
{
this._form.Close();
}
}
<summary>
リソースの破棄をするために呼ばれる
</summary>
public void Dispose()
{
// メッシュの破棄
this.DisposeMesh();
// XYZラインの破棄
this.DisposeXYZLine();
// フォントのリソースを解放
if (this._font != null)
{
this._font.Dispose();
}
// Direct3D デバイスのリソース解放
if (this._device != null)
{
this._device.Dispose();
}
}
}
}
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 = 20.0f;
<summary>
カメラレンズの位置(θ)
</summary>
private float _lensPosTheta = 270.0f;
<summary>
カメラレンズの位置(φ)
</summary>
private float _lensPosPhi = 45.0f;
<summary>
XYZライン用頂点バッファ
</summary>
private VertexBuffer _xyzLineVertexBuffer = null;
<summary>
メッシュ
</summary>
private Mesh _mesh = null;
<summary>
マテリアル情報配列
</summary>
private ExtendedMaterial[] _materials = null;
<summary>
テクスチャー配列
</summary>
private Texture[] _textures = 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>
Xファイルからメッシュ関連データを読み込む
</summary>
<param name="xfileName">Xファイル名</param>
private bool LoadXFileMesh(string xfileName)
{
// Xファイルを読み込んでメッシュを作成する
try
{
this._mesh = Mesh.FromFile(xfileName,
MeshFlags.Managed, this._device, out this._materials);
}
catch (DirectXException ex)
{
// メッシュの作成に失敗した場合は例外が飛んでくる
throw ex;
}
// 法線情報がなければ計算して作成
if ((this._mesh.VertexFormat & VertexFormats.Normal) == 0)
{
// 法線情報を加えたメッシュを複製する
Mesh tempMesh = this._mesh.Clone(this._mesh.Options.Value,
this._mesh.VertexFormat | VertexFormats.Normal, this._device);
// 法線を計算
tempMesh.ComputeNormals();
// 古いメッシュを破棄し、置き換える
this._mesh.Dispose();
this._mesh = tempMesh;
}
// テクスチャーがあれば読み込み
if (this._materials.Length >= 1)
{
// テクスチャー用の配列を作成
this._textures = new Texture[this._materials.Length];
// 配列分テクスチャーの読み込みを試みる
for (int i = 0; i < this._materials.Length; i++)
{
// 必ず null で初期化する
this._textures[i] = null;
// テクスチャー名が登録されているか確認
if (this._materials[i].TextureFilename != null &&
this._materials[i].TextureFilename.Length >= 1)
{
try
{
// テクスチャーを読み込む
this._textures[i] = TextureLoader.FromFile(this._device,
this._materials[i].TextureFilename);
}
catch (DirectXException ex)
{
// テクスチャーの作成に失敗した場合は例外が飛んでくる
throw ex;
}
}
}
}
return true;
}
<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 RenderMesh()
{
// 属性の数だけループさせて描画
for (int i = 0; i < this._materials.Length; i++)
{
// テクスチャーのセット
this._device.SetTexture(0, this._textures[i]);
// マテリアルをセット
this._device.Material = this._materials[i].Material3D;
// 描画
this._mesh.DrawSubset(i);
}
}
<summary>
XYZラインの破棄
</summary>
private void DisposeXYZLine()
{
if (this._xyzLineVertexBuffer != null)
{
this._xyzLineVertexBuffer.Dispose();
}
}
<summary>
メッシュの破棄
</summary>
private void DisposeMesh()
{
// テクスチャーの解放
if (this._textures != null)
{
foreach (Texture i in this._textures)
{
if (i != null)
{
i.Dispose();
}
}
}
// メッシュの解放
if (this._mesh != null)
{
this._mesh.Dispose();
}
}
}
}
最初のころにやったコードを引っ張ってきました。
実は「フルスクリーン」にするには「PresentParameter」構造体のパラメータを5つ変更するだけでできてしまいます。
- Windowed を false に設定
- BackBufferFormat を Format.R5G6B5 に設定
- BackBufferWidth を 640 に設定
- BackBufferHeight を 480 に設定
- FullScreenRefreshRateInHz を 60 に設定
しかし、フルスクリーンで作成する場合は、いろいろとパラメータに制限があり、また実際作成したアプリケーションを使う人がどんなビデオカードやチップセットを使うかをプログラマが知る方法はありません(使うビデオカードを指定すれば別ですが)。
高いクオリティで設定すると、ソフトが使えないPCが出てきますし、逆に低スペックなPCでも使えるように作るとクオリティをどうしても低くしないといけません。
また、クオリティに関わらずPCの環境によって使用できる画面サイズやフォーマットなど違うこともあるので、そこら辺をうまく考えて設定しなければなりません。
そのため、今回はアダプタ(ビデオカードなどのこと)の「ディスプレイモード」を列挙し、使用できる画面サイズやフォーマットなど調べて設定するようにします。
// PresentParameters。デバイスを作成する際に必須
PresentParameters pp = new PresentParameters();
// ウインドウモードなら true、フルスクリーンモードなら false を指定
pp.Windowed = true;
// スワップ効果
pp.SwapEffect = SwapEffect.Discard;
// 深度ステンシルバッファ
pp.EnableAutoDepthStencil = true;
// 自動深度ステンシル サーフェイスのフォーマット
pp.AutoDepthStencilFormat = DepthFormat.D16;
フルスクリーンなので「Windowed」パラメータは false に設定しておきます。
// 使用できるディスプレイモードを検索し、目的のモードを探す
bool flag = false;
// ディプレイモードを列挙し、サイズが「640×480」かつ
// リフレッシュレートが「60」のモードを探す
// (条件はアプリケーションの内容によって変えてください)
foreach (DisplayMode i in Manager.Adapters[0].SupportedDisplayModes)
{
if (i.Width == 640 && i.Height == 480 && i.RefreshRate == 60)
{
// 条件に見合えば使用する
pp.BackBufferWidth = 640;
pp.BackBufferHeight = 480;
pp.BackBufferFormat = i.Format;
pp.FullScreenRefreshRateInHz = 60;
// 見つかったことを示すフラグを立てる
flag = true;
break;
}
}
「Manager」クラスにアダプタコレクションがあるので、0番のアダプタでディスプレイモードを列挙しています。
アダプタのインデックスですが、ビデオカードなどが複数ある場合、1以降のインデックスも使用できます。
逆にアダプタが1個も無いということはほとんどないので、今回は直接「0」を指定しています。実際にアプリケーションを作るときは一応アダプタの数も調べておいたほうがいいです。
今回は「幅が 640」「高さが 480」「リフレッシュレートが 60」という条件に当てはまるディスプレイモードが存在するか調べて、あればその条件で使用するようにしています。
ちなみにこれはディスプレイモードの調べ方の一例ですので、実際に作るときは、高いクオリティを使用できるように調べたり、ユーザーに選択させたりできるような形にしてみてください。これは多くのユーザーにソフトを使っていただける条件にもなります。
if (!flag)
{
// 目的のモードがなければそのまま終了
MessageBox.Show("指定したディプレイモードは見つかりませんでした。",
"エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
条件に見合わなければメッセージボックスを出して終了します。
// Direct3D デバイス作成
this.CreateDevice(topLevelForm, pp);
新しく設定した PresentParameters をデバイス作成メソッドに渡しています。
// アプリケーションの終了操作
if (this._keys[(int)Keys.Escape])
{
this._form.Close();
}
フルスクリーンではウインドウの「×」ボタンによる終了が出来ないので、「ESC」キーでフォームを閉じるようにしています。
また、他にもディフォルトで「Alt + F4」でもアプリケーションを終了させることができます。