今回やること
視点移動を実装し、いろいろな角度・向きからモデルを表示できるようにする。
今回のソース: GitHub
視点移動
MMDでは、視点水平移動(XY軸移動)・視線の先を対象としたXY軸回転・ズームをマウスの操作によって制御できます。
座標変換行列Viewに移動・回転情報を設定することで実現します。
RawInput
マウスによる視点移動を行うために、マウスの処理を行わなければなりません。
SlimDXには、RawInputと呼ばれる、デバイス(キーボードやマウスなど)の情報を取得するためのものが付属しているので、それを使用します。
下記のコードでは、マウスとキーボードからの入力を受け取り、処理する関数を定義しています。
//using SlimDX.Multimedia;
//using Rwin = SlimDX.RawInput;
protected virtual void MouseInput(object sender, Rwin.MouseInputEventArgs e) { }
protected virtual void KeyInput(object sender, Rwin.KeyboardInputEventArgs e) { }
private void InitializeInputDevice() {
	Rwin.Device.RegisterDevice(UsagePage.Generic, UsageId.Mouse, Rwin.DeviceFlags.None);
	Rwin.Device.MouseInput += MouseInput;
	Rwin.Device.RegisterDevice(UsagePage.Generic, UsageId.Keyboard, Rwin.DeviceFlags.None);
	Rwin.Device.KeyboardInput += KeyInput;
}
protected override void MouseInput(object sender, Rwin.MouseInputEventArgs e) {
}
protected override void KeyInput(object sender, Rwin.KeyboardInputEventArgs e) {
}
RawInputでは、マウスの左/中(ホイール)/右ボタンの押下・押上、ホイールの動き、マウスの移動量を取得できます。
たとえば、左クリックされたときに何か処理をする場合は、以下のように書きます。
protected override void MouseInput(object sender, Rwin.MouseInputEventArgs e) {
	switch(e.ButtonFlags) {
	case Rwin.MouseButtonFlags.LeftDown:
		//処理
		break;
	}
}
なお、RawInputではマウスのボタンが押されているか(押しっぱなしであるか)や、マウスの位置を取得することはできません。
水平移動・ズーム
MMDでは、中クリックをしながらマウスを移動させることで、XY軸方向に移動することができます。
これはカメラ(視点)と対象の点(注視点)を一緒に移動させることで実現できます。
さらにMMDでは、ホイールを回転させることでズームイン・ズームアウトができます。
RawInputではホイールの回転量をe.WheelDataから取得できます。回転してなければ0, 奥に向けて回転させると正の値、手前に向けて回転させると負の値です。
struct MovingData {
	public int posX;
	public int posY;
	public int posZ;
	public void ResetPosXY() { posX = posY = 0; }
	public void ResetPosZ() { posZ = 0; }
	public void ResetAll() { ResetPosXY(); ResetPosZ(); }
}
bool isMiddleMoving;
MovingData movingNow;
private void UpdateCamera() {
	var world = Matrix.Identity;
	float div = 10;
	var viewEye = new Vector3(0 , 10, -10);
	var viewTarget = new Vector3(0, 10, 0);
	var view = Matrix.Multiply(
		Matrix.LookAtRH(
			viewEye, viewTarget, new Vector3(0, 1, 0)
		),
		Matrix.Translation(movingNow.posX / div, -movingNow.posY / div, movingNow.posZ * 0.2f)
	);
	//省略
}
protected override void MouseInput(object sender, Rwin.MouseInputEventArgs e) {
	switch(e.ButtonFlags) {
		case Rwin.MouseButtonFlags.MiddleDown:
			isMiddleMoving = true;
			break;
		case Rwin.MouseButtonFlags.MiddleUp:
			isMiddleMoving = false;
			break;
	}
	if(isMiddleMoving) {
		movingNow.posX += e.X;
		movingNow.posY += e.Y;
	}
	if(e.WheelDelta > 0) {
		movingNow.posZ++;
	} else if(e.WheelDelta < 0) {
		movingNow.posZ--;
	}
}
View行列に移動量を表す行列を掛けることによって、実現させています。
X, Y, Z軸方向に移動する行列はMatrix.Translation(x, y, z)で生成できます。
XY軸回転
MMDでは右クリックしながら動かすことで、回転させることができます。
水平移動のときと同じように回転を表す行列を生成し、掛ければいいのですが、行列には交換法則が適用できないため、掛ける順番に注意が必要です。
今回は、MMDでモデルを選択しているときの回転・移動を基準にしています。
(MMDの表示に近付けるため、視野角を30度に、初期のカメラの位置を変更しております)
struct MovingData {
	//省略
	public int rotX;
	public int rotY;
	//省略
	public void ResetRotXY() { rotX = rotY = 0; }
	public void ResetAll() { ResetPosXY(); ResetPosZ(); ResetRotXY(); }
}
bool isRightMoving;
protected override void MouseInput(object sender, Rwin.MouseInputEventArgs e) {
	switch(e.ButtonFlags) {
		//省略
		case Rwin.MouseButtonFlags.RightDown:
			isRightMoving = true;
			break;
		case Rwin.MouseButtonFlags.RightUp:
			isRightMoving = false;
			break;
	}
	//省略
	if(isRightMoving) {
		movingNow.rotX += e.X;
		movingNow.rotY += e.Y;
	}
}
private void UpdateCamera() {
	var world = Matrix.Identity;
	float div = 10;
	var viewEye = new Vector3(0 , 10, -45);
	var viewTarget = new Vector3(0, 10, 0);
	var view = Matrix.Multiply(
		Matrix.Multiply(
			Matrix.RotationX(-movingNow.rotY / 100.0f), 
			Matrix.RotationY(movingNow.rotX / 100.0f)
		), Matrix.Multiply(
			Matrix.LookAtRH(viewEye, viewTarget, new Vector3(0, 1, 0)),
			Matrix.Translation(movingNow.posX / div, -movingNow.posY / div, movingNow.posZ * 0.2f)
		)
	);
var projection = Matrix.PerspectiveFovRH(
	30 * (float)Math.PI / 180, ClientSize.Width / ClientSize.Height, 0.1f, 1000
);
	//省略
}
おまけ
モデルを描画した画像をいくつか掲載していますが、何かおかしいことに気が付いたでしょうか。
スカートの下の部分が黒かったり、羽が透けてなかったりしています。(右がMMDでの表示。羽が透けて腕が見えるのがわかる)


アルファブレンドを設定することで解決できます。
private void InitializeBlend() {
	var blend = new Dx11.BlendStateDescription() {
		AlphaToCoverageEnable = false, IndependentBlendEnable = false
	};
	for(int i = 0;i < 8; i++) {
		blend.RenderTargets[i] = new Dx11.RenderTargetBlendDescription();
		blend.RenderTargets[i].BlendEnable = true;
		blend.RenderTargets[i].BlendOperation = Dx11.BlendOperation.Add;
		blend.RenderTargets[i].BlendOperationAlpha = Dx11.BlendOperation.Add;
		blend.RenderTargets[i].DestinationBlend = Dx11.BlendOption.InverseSourceAlpha;
		blend.RenderTargets[i].DestinationBlendAlpha = Dx11.BlendOption.Zero;
		blend.RenderTargets[i].RenderTargetWriteMask = Dx11.ColorWriteMaskFlags.All;
		blend.RenderTargets[i].SourceBlend = Dx11.BlendOption.SourceAlpha;
		blend.RenderTargets[i].SourceBlendAlpha = Dx11.BlendOption.One;
	}
	device.ImmediateContext.OutputMerger.BlendFactor = new Color4(1, 1, 1, 1);
	device.ImmediateContext.OutputMerger.BlendSampleMask = 0xffffff;
	device.ImmediateContext.OutputMerger.BlendState = Dx11.BlendState.FromDescription(device, blend);
}
さいごに
次回は光を扱う予定です。

