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

More than 1 year has passed since last update.

前: その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

さいごに

次回は光を扱う予定です。

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.