Silverlight 5(Beta)で Teapot + Blinn-Phong ⑤

  1. 開発環境の準備と DrawingSurface
  2. Teapot クラス
  3. Blinn-Phong シェーダー
  4. 描画
  5. トラックボール
  6. シェーダー パラメータの操作

image

ティーポットをマウスで回転・拡大縮小するためのトラックボールを実装します。まず Visual Studio の[プロジェクト]→[クラスの追加]で TrackBall.cs を追加します。

トラックボールの実装

この実装は WPF のサンプルコードにあった TrackBall.cs を Silverlight 5 (Beta) 用に改造したものです。基本的な考え方はそのままですが、WPF 版では Viewport3D をベースにしているため、回転などのデータの持ち方がWPF専用だったので、プライベート変数として回転をクォータニオンで、スケールを単精度浮動小数点で保持し、パブリック変数 Transform は行列型としました。

//private _rotation and _scale 

private Quaternion _rotation = Quaternion.Identity;

private float _scale = 1.0f;
//public Transform

public Matrix Transform

{

  get {

    Matrix transform = Matrix.CreateScale(_scale);

    transform *= Matrix.CreateFromQuaternion(_rotation);

    return transform; }

}

FrameworkElement からイベントをもらってくる方法は、ほとんど変わっていません。キャプチャの代わりに押下状態を保持したり、マウスホイール イベントを追加したくらいです。

トラックボールの基本的な考え方は、マウスの2D座標を球(ボール)上の3D座標に変換し、マウスの移動を追跡(トラック)してクォータニオン(ある軸を中心とした回転)として表現することです。前者の2Dから3Dへの変換は ProjectToTrackball メソッドで行われており、これは全く変更していません。後者のクォータニオン生成は WPF で使えたメソッドなどが使えなかったので、GetQuaternion メソッドで実装しなおしました。

ベクトル a からベクトル b への回転は、回転軸(a と b の外積)と回転角度(a と b の内積の逆余弦)で表現できます。その回転軸と回転角度から Quaternion.CreateFromAxisAngle 静的メソッドを使ってクォータニオンを生成します。ここでは最適化は全く考慮していません。クォータニオンについての詳細は拙著リアルタイム レンダリングなどを参照してください。

private Quaternion GetQuaternion(Vector3 v0, Vector3 v1)

{

  // Rotate and Normalize 

  Vector3 a = Vector3.Normalize(Vector3.Transform(v0, _rotation));

  Vector3 b = Vector3.Normalize(Vector3.Transform(v1, _rotation));

 

  // Cross product 

  Vector3 cross = Vector3.Cross(a, b);

  cross = Vector3.Normalize(cross);

  // angle 

  float dot = Vector3.Dot(a, b);

  float angle = (float)Math.Acos((double)dot);

  return Quaternion.CreateFromAxisAngle(cross, angle);

}

拡大縮小を処理するZoomでは、マウスホイールの返す値に縮小因子をかけてスケール値に加算しました。

private void Zoom(int delta)

{

  _scale += 0.001f * delta ;

}

 

トラックボールの利用

MainPage.xaml.cs の MainPage コンストラクターで TrackBall クラスをインスタンス化し、XAMLで宣言したmyElementをイベントソースとして設定します。

private Trackball trackball;

public MainPage()

{

  InitializeComponent();
  // Create Trackball 

  trackball = new Trackball();

  trackball.EventSource = myElement;

}

 

そして、描画時にティーポットのワールド行列(World プロパティ)にトラックボールの行列(Transformプロパティ)を代入します。依存プロパティにすればこれはいらないと思いますが、ここでの議論とは別の話なので、シンプルに毎回代入します。

void DrawingSurface_Draw(object sender, DrawEventArgs e)

{

  teapot.World = trackball.Transform;

  teapot.Draw(e.GraphicsDevice);

  e.InvalidateSurface();
}

 

次回はシェーダー パラメータ(レジスタ)を Silverlight コントロールで制御します。

Trackball.csを添付します。

Trackball.cs