4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

MMDモデルを使用したデスクトップユーティリティの開発 その6 視点移動

Last updated at Posted at 2016-09-30

前: その5
次: その7

##今回やること
視点移動を実装し、いろいろな角度・向きからモデルを表示できるようにする。
今回のソース: GitHub

##視点移動
MMDでは、視点水平移動(XY軸移動)・視線の先を対象としたXY軸回転・ズームをマウスの操作によって制御できます。
座標変換行列Viewに移動・回転情報を設定することで実現します。

###RawInput
マウスによる視点移動を行うために、マウスの処理を行わなければなりません。
SlimDXには、RawInputと呼ばれる、デバイス(キーボードやマウスなど)の情報を取得するためのものが付属しているので、それを使用します。
下記のコードでは、マウスとキーボードからの入力を受け取り、処理する関数を定義しています。

Core.cs
//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;
}
DrawPmdModel.cs
protected override void MouseInput(object sender, Rwin.MouseInputEventArgs e) {
}

protected override void KeyInput(object sender, Rwin.KeyboardInputEventArgs e) {
}

RawInputでは、マウスの左/中(ホイール)/右ボタンの押下・押上、ホイールの動き、マウスの移動量を取得できます。
たとえば、左クリックされたときに何か処理をする場合は、以下のように書きます。

DrawPmdModel.cs
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, 奥に向けて回転させると正の値、手前に向けて回転させると負の値です。

DrawPmdModel.cs
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度に、初期のカメラの位置を変更しております)

DrawPmdModel.cs
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
);
	//省略
}

マウスでぐるぐる動かせます。
cirno_trans_rotate.png

##おまけ
モデルを描画した画像をいくつか掲載していますが、何かおかしいことに気が付いたでしょうか。
スカートの下の部分が黒かったり、羽が透けてなかったりしています。(右がMMDでの表示。羽が透けて腕が見えるのがわかる)
cirno_no_alpha.pngcirno_alphaed_mmd.png

アルファブレンドを設定することで解決できます。

Core.cs
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);
}

cirno_alphaed_pmv.png

##さいごに
次回は光を扱う予定です。

4
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?