dskjal
広告
広告

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

カテゴリ: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);

}

}


広告
広告

カテゴリ