広告
広告

Unity でボーンの回転量とブレンドシェイプの影響力とを対応付ける

カテゴリ:unity

Unity は回転をクォータニオンで保存しており、オイラー回転量をうまく取得できない。この記事ではその対策を解説する。

default
ブレンドシェイプ補正なし
correct
ブレンドシェイプ補正あり

回転量の計算

transform.localEulerAngles の初期回転量を保存しておき、引き算により回転量を計算する。クォータニオンは 180° までの回転しか表現できないので、回転量が 180° を超えている場合は回転量を反転させる。

var rot = Mathf.Abs(transform.localEulerAngles.x - rotation_rest);
if( rot >= 180f ){
    // 0 と 360 と間の回転ジャンプを修正する
    rot = 360f - rot;
}

回転方向の計算

クォータニオンの回転は回転量が 180° を超えるか 0° を下回るかすると値がジャンプすることがある。なので回転量の大小で回転方向を決められない。そこで回転軸でない基底ベクトルの内積で回転方向を判定する。

void Awake(){
    rotation_rest = transform.localEulerAngles.x;
    y_basis_rest = Matrix4x4.Rotate(transform.localRotation).GetColumn(1);
}
private float get_local_rotation(){
    // 回転量の計算
    var rot = Mathf.Abs(transform.localEulerAngles.x - rotation_rest);
    if( rot >= 180f ){
        // 0 と 360 と間の回転ジャンプを修正する
        rot = 360f - rot;
    }

    // 回転方向の計算
    var z_basis = Matrix4x4.Rotate(transform.localRotation).GetColumn(2);
    return (Vector4.Dot(z_basis, y_basis_rest) > 0f)?-rot:rot;
}

コード

このスクリプトはボーンにつけて使う。ボーンの回転量とブレンドシェイプの影響力の対応付けは Update で行っている。この式はメッシュやボーンごとに異なるので、数式が複雑な場合はソースを修正する必要がある。

.cs ファイルダウンロード

// BEGIN MIT LICENSE BLOCK //
//
// Copyright (c) 2019 dskjal
// This software is released under the MIT License.
// http://opensource.org/licenses/mit-license.php
//
// END MIT LICENSE BLOCK   //
using UnityEngine;

public class Shapekey : MonoBehaviour
{
    public enum Axis{
        X, Y, Z
    }
    public enum RotationOrientation{
        Clockwise = 1,
        CounterClockwise = -1
    }
    public Axis rotation_axis = Axis.X;
    public RotationOrientation rotation_orientation = RotationOrientation.Clockwise;
    public GameObject mesh;
    public int shapekey_index;
    public float equation_offset;
    public float equation_multiply = 1f;

    private SkinnedMeshRenderer smr;
    private float rotation_rest;
    private Vector4 y_basis_rest;
    private Vector2Int[] axis_table = {
        new Vector2Int(1, 2),
        new Vector2Int(2, 0),
        new Vector2Int(0, 1)
        };

    void Awake()
    {
        smr = mesh.GetComponent<SkinnedMeshRenderer>();
        rotation_rest = transform.localEulerAngles[(int)rotation_axis];
        y_basis_rest = Matrix4x4.Rotate(transform.localRotation).GetColumn(axis_table[(int)rotation_axis].x);
    }

    // 回転量を返す。逆回転の場合はマイナスが付く
    private float get_local_rotation(){
        // 回転量の計算
        var rot = Mathf.Abs(transform.localEulerAngles[(int)rotation_axis] - rotation_rest);
        if( rot >= 180f ){
            // 0 と 360 と間の回転ジャンプを修正する
            rot = 360f - rot;
        }

        // 回転方向の計算
        var z_basis = Matrix4x4.Rotate(transform.localRotation).GetColumn(axis_table[(int)rotation_axis].y);
        return (Vector4.Dot(z_basis, y_basis_rest) > 0f)?-rot:rot;
    }
    void Update()
    {
        var local_rotation = get_local_rotation();
        var weight = 0f;
        if( (int)rotation_orientation * local_rotation > 0f){
            // 回転量とシェイプキーとの対応づけ
            weight = (local_rotation + equation_offset) * equation_multiply;
            weight = Mathf.Clamp( weight, 0, 100 );
        }
        smr.SetBlendShapeWeight(shapekey_index, weight);
    }
}

広告
広告