カメラの操作
キー入力が出来るようになったので、キーボードでカメラを操作してみたいと思います。
キーボードの「↑↓」でカメラが上下に回転し、「←→」で左右に回転します。カメラは原点を中心に回転するようになっています。

下のリンクから今回のプロジェクトをダウンロードできます。
今回のメインコードファイルを載せます。重要なコードを赤色で表示させています。
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 float _lensPosTheta = 270.0f;
/// <summary>
/// カメラレンズの位置(φ)
/// </summary>
private float _lensPosPhi = 0.0f;
/// <summary>
///
/// </summary>
/// <param name="topLevelForm"></param>
/// <returns></returns>
/// <remarks>
///
/// </remarks>
public bool InitializeApplication(MainForm topLevelForm)
{
this._form = topLevelForm;
// 入力イベント作成
this.CreateInputEvent(topLevelForm);
try
{
this.CreateDevice(topLevelForm);
this.CreateFont();
}
catch (DirectXException ex)
{
MessageBox.Show(ex.ToString(), "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
// 四角形ポリゴン作成
this.CreateSquarePolygon();
// 射影変換を設定
this._device.Transform.Projection = Matrix.PerspectiveFovLH(
Geometry.DegreeToRadian(60.0f),
(float)this._device.Viewport.Width / (float)this._device.Viewport.Height,
1.0f, 100.0f);
this._device.RenderState.Lighting = false;
// カリングを無効にしてポリゴンの裏も描画する
//this._device.RenderState.CullMode = Cull.None;
return true;
}
/// <summary>
///
/// </summary>
public void MainLoop()
{
// キーによる移動
if (this._keys[(int)Keys.Left])
{
// ←キーが押されている場合
this._lensPosTheta -= 3.0f;
}
if (this._keys[(int)Keys.Right])
{
// →キーが押されている場合
this._lensPosTheta += 3.0f;
}
if (this._keys[(int)Keys.Up])
{
// ↑キーが押されている場合
this._lensPosPhi += 3.0f;
}
if (this._keys[(int)Keys.Down])
{
// ↓キーが押されている場合
this._lensPosPhi -= 3.0f;
}
// φに関しては制限をつける
if (this._lensPosPhi >= 90.0f)
{
this._lensPosPhi = 89.9999f;
}
else if (this._lensPosPhi <= -90.0f)
{
this._lensPosPhi = -89.9999f;
}
// レンズの位置を三次元極座標で変換
float radius = 10.0f;
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.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.DarkBlue, 1.0f, 0);
this._device.BeginScene();
// 四角形ポリゴン描画
this.RenderSquarePolygon();
this._font.DrawText(null, "[↑↓]カメラの上下回転 [←→]カメラの左右回転",
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()
{
if (this._vertexBuffer != null)
{
this._vertexBuffer.Dispose();
}
if (this._font != null)
{
this._font.Dispose();
}
if (this._device != null)
{
this._device.Dispose();
}
}
}
}
|
では、赤文字の部分を説明していきます。MainSamplePartial.cs ファイルのコードはこちらです。
/// <summary>
///
/// </summary>
private float _lensPosTheta = 270.0f;
/// <summary>
///
/// </summary>
private float _lensPosPhi = 0.0f;
|
カメラの動きですが、今回は物体(ポリゴン)を中心にしてカメラがその周りを周るようにしています。その計算として「三次元極座標」の公式を使用しています。この Tips では数学の公式を説明することを目的としているわけではないので詳しくは説明しませんが、後ほど実際に使用しているところで簡単に説明します。
その際に、現在カメラがどの位置にいるのかという情報を保持するために「θ」と「φ」というパラメータを持っておきます。とりあえずここでは、「θ」が左右移動、「φ」が上下移動だと思っておいてかまいません。半径R に関しては固定にします。

カメラの初期位置は、-Z 方向(0.0, 0.0 -x)の位置に配置して、原点(0.0, 0.0, 0.0)を見ているという感じになります。
this.CreateInputEvent(form);
|
キー入力イベントに関しては別ファイルに移動しています。
this.CreateSquarePolygon();
|
ポリゴン作成も同じなのでまとめておきます。
this._device.Transform.Projection = Matrix.PerspectiveFovLH(
Geometry.DegreeToRadian(60.0f),
(float)this._device.Viewport.Width / (float)this._device.Viewport.Height,
1.0f, 100.0f);
|
カメラの位置は毎フレーム変わりますが、射影変換に関しては固定なので先に設定しておきます。
カリングについては最後に説明します。
if (this._keys[(int)Keys.Left])
{
this._lensPosTheta -= 3.0f;
}
if (this._keys[(int)Keys.Right])
{
this._lensPosTheta += 3.0f;
}
if (this._keys[(int)Keys.Up])
{
this._lensPosPhi += 3.0f;
}
if (this._keys[(int)Keys.Down])
{
this._lensPosPhi -= 3.0f;
}
|
カメラの状態は毎フレーム変わる可能性があるので、メインループのメソッドで処理するようにします。
ここでキーの判定を行っていますが、カーソルキーのフラグが立っている場合、フィールドとして持っている「θ」と「φ」の値を増減しています。どのキーフラグが立っているかは、キーフラグ配列のインデックスに(this.keys[/*ここのこと*/])「System.Windows.Forms.Keys」列挙のいずれかを「int」でキャストして指定します。
値は「3.0f」と指定していますが、特に意味はありません。カメラの移動スピードなので、いろいろ変えてみるといいでしょう。
if (this._lensPosPhi >= 90.0f)
{
this._lensPosPhi = 89.9999f;
}
else if (this._lensPosPhi <= -90.0f)
{
this._lensPosPhi = -89.9999f;
}
|
ここでは「φ」の値を制限しています。これはカメラの性質上、φが90度を上回ったり、-90度を下回ったりすると、突然カメラの向きが反転するので、制限しています。90度ぴったりだと表示上でエラーが起こります。(強制終了するわけではありません。ポリゴンが見えなくなります。)
回避する方法もありますが、少し面倒なので今回はそういう仕様でいきます。

float radius = 10.0f;
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)));
|
さて、カメラの位置は「X」「Y」「Z」の位置に直さないといけないので、「R」「θ」「φ」から三次元極座標の公式で変換します。また、角度はすべて「Radian」で計算しなければならないので、あらかじめ「θ」「φ」を「Geometry.DegreeToRadian」メソッドで「Degree」から「Radian」に変換しています。
下がその公式になります。
X = R × cos(θ) × cos(φ)
Y = R × sin(φ)
Z = R × sin(θ) × cos(φ)
これに当てはめればカメラの位置ベクトルは求まります。
this._device.Transform.View = Matrix.LookAtLH(
lensPosition, new Vector3(0.0f, 0.0f, 0.0f), new Vector3(0.0f, 1.0f, 0.0f));
|
レンズの位置が求まれば後は今までと同じようにビュー変換行列を生成してセットするだけです。
this.RenderSquarePolygon();
|
四角形ポリゴンの描画もメソッドにまとめています。
さて、これでカメラが回転できるようになりましたが、まわしてみるとポリゴンの裏側が描画されていないことに気づくはずです。

これはカリングと言って、上記のようにポリゴンの裏側を描画しないようになっています。例えば閉じた箱をイメージしてみると分かりますが、箱の内側は見えないのでわざわざ描画する必要がありません。これによって描画のコストをいくらか減らそうというのがカリングです。
どちらがポリゴンの表でどちらが裏かというのは、頂点のならびに関係しています。ディフォルトではポリゴンの頂点が右回りになっている面が表です。

どちらの面を描画するのか、または両面描画というのを設定することが出来ます。コードでコメントアウトしている部分がそうです。
Device.RenderState.CullMode に Cull.None を指定すると両面描画できるようになります。
|