Unity でボーンの回転量とブレンドシェイプの影響力とを対応付ける
Unity は回転をクォータニオンで保存しており、オイラー回転量をうまく取得できない。この記事ではその対策を解説する。
回転量の計算
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);
}
}