MayaのBifrostを使用した曲げ捻り補助骨の実装例

[Maya Advent Calendar 2024](https://qiita.com/advent-calendar/2024/maya) の16日目の記事になります。

# はじめに
MayaのBifrostを使用した補助骨作成を行います。

補助骨とは何かの説明や、標準ノードを使用した手首/肘補助骨の一例を以下の記事に記載しています。
[Mayaによる補助骨作成の基本](https://qiita.com/salt-k2t/items/e253725214d771b9c579)

前回に引き続き、本記事はテクニカルアーティストやリガーを対象とした初学者向けの内容となっています。
作例もシステマチックな内容になっており、見た目の良さを追求したものでは無い点をご了承ください。

# Bifrostとは
ノードベースで様々な処理を行うためのプラグインです。
標準ノードと比べて数学/物理系のノードが強化されており、VFXなどの分野でも使われます。
今回は汎用の計算ノードを組み合わせて簡単な補助骨リグを作るために使用します。
[Bifrost 2.0 for Maya](https://area.autodesk.jp/product/maya/bifrost2/)

また作成したノードの書き出し/読み込みができるため、配布しやすい点も利点として挙げられます。
本記事で制作した内容は公開していますので、まずは読み込んで使ってみてください。
普段テクニカルな内容を扱わないキャラクタモデラーの方でも簡単に導入できると思います。
[公開しているグラフ](https://github.com/salt-k2t/MayaHelperBifrostGraph)

# 本記事で実現したいこと
肩と前腕に鎧のような硬い装飾がついているモデルを想定します。

肩鎧はY軸とZ軸の曲げの動きには完全追従しますが、X軸の捻りの動きにはゆるく追従させたいです。
前腕鎧はX軸の捻りの動きにはゆるく追従しますが、Y軸とZ軸の曲げの動きには全く追従させません。

| バインドポーズ  | 稼働 |
| --- | --- |
| <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878081/81fd1233-2810-2fea-0871-1f521fce91d2.png" width="512" alt="肩鎧モデル"> | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878081/2bf2b822-85b2-65a5-11fe-9c59ac042008.png" width="512" alt="肩鎧稼働"> |
| <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878081/dbf7cea7-29b4-fab5-d12a-9c19e3abc5c3.png" width="512" alt="前腕鎧モデル"> | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878081/8fc9657c-4cb9-6e63-b521-6aa98cd8add2.png" width="512" alt="前腕鎧稼働"> |

# 計算
今回の補助骨で実現したい計算は以下の流れになります。

```mermaid
flowchart TB
    A(基礎骨の回転) -->
    B[回転の曲げ,捻り分解] -- 曲げ回転 --> C[任意の曲げ重みをかける]
    B[回転の曲げ,捻り分解] -- 捻り回転 --> D[任意の捻り重みをかける]
    C[任意の曲げ重みをかける] -- 重み付き曲げ回転 --> E[回転合成]
    D[任意の捻り重みをかける] -- 重み付き捻り回転 --> E[回転合成]
    E[回転合成] --> G(補助骨の回転)
```

## 回転の曲げ、捻り分解
基礎骨の回転を、曲げと捻りの2つに分解します。
### 曲げの取得
基準となるベクトルを用意します。
基準ベクトルを基礎骨の回転角度で回転します。
基準ベクトルを回転後ベクトルに向ける回転を得ます。
ここで得た回転角度は2つのベクトルのなす角にあたり、元の基礎骨の回転から捻り成分を除いた曲げ回転になります。

いわゆるエイムを実装であり、別の計算方法でも構いません。
以下にプログラムで実装する場合の例を記載します。

```math
\displaylines{
defaultVec = om.MVector(1, 0, 0) \\
rotateVec = defaultVec.rotateBy(baseQuat) \\
bendQuat = om.MQuaternion(defaultVec, rotateVec)
}
```

### 捻りの取得
基礎骨の回転と、前項で取得した曲げ回転を使用します。
捻り回転は、基礎骨の回転から曲げ回転を除いたものになります。

以下にプログラムで実装する場合の例を記載します。

```math
\displaylines{
rollQuat = bendQuat.inverse() * baseQuat
}
```

## 任意の曲げ重みをかける/任意の捻り重みをかける
対象の回転を軸ベクトルとその軸周りの角度として得ます。
その軸周りの角度に重みをかけます。
軸ベクトルと重みを考慮した軸周りの角度から、重みを考慮した回転を得ます。

以下にプログラムで実装する場合の例を記載します。

```math
\displaylines{
axis, angle = originalQuat.asAxisAngle() \\
weightedAngle = angle * weight \\
weightedQuat = om.MQuaternion(axis, weightedAngle)
}
```

## 回転合成
重みをかけた回転を合成します。
シンプルにQuaternionを合成するだけですが、合成する順番によって結果が異なります。
詳細は割愛しますが、両方試して見た目が良いほうを使用すればとりあえずOKです。

以下にプログラムで実装する場合の例を記載します。
```math
\displaylines{
resultQuat = weightedBendQuat * weightedRollQuat \\
or \\
resultQuat = weightedRollQuat * weightedBendQuat \\
}
```

# ノード接続
これまで上げた計算を、Bifrostで実装します。
Bifrostの使い方は省略し、該当部分のみ画像で添付します。
もっと短く作れる便利ノードもあると思いますが、簡単のために汎用の計算ノードで作成します。

まずはジョイントとの接続部分です。

オイラー角とクオータニオンの変換、弧度法と度数法の変換を行っています。
| 基礎骨との接続(入力) | 補助骨との接続(入力) |
| --- | --- |
| <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878081/5ac0822d-e0df-bd6b-acbf-5328a78e20e8.png" width="512" alt="bifrost_入力"> | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878081/b644fe4c-fdb2-ce63-047d-6fde50302744.png" width="512" alt="bifrost_出力"> |

## 回転の曲げ、捻り分解
Bifrostのクオータニオン系の計算ノードを使用しています。
このあたりの便利ノードがありがたいです。
| 曲げを得る | 捻りを得る |
| --- | --- |
| <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878081/99375cfb-ab80-c56f-2be7-5c02a000fe2b.png" width="512" alt="bifrost_曲げを得る"> | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878081/9cd3305b-d4b0-2e0a-0beb-9de8e0797f1f.png" width="512" alt="bifrost_捻りを得る"> |

## 任意の曲げ重みをかける/任意の捻り重みをかける
quaternion_to_axis_angleとaxis_angle_to_quaternionを使用しています。
このあたりの便利ノードがありがたいです。(2回目)
| 曲げに重みをかける | 捻りに重みをかける |
| --- | --- |
| <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878081/7a7c0e36-121d-1684-73f9-79fb417b10bc.png" width="512" alt="bifrost_曲げに重みをかける"> | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878081/5255a98c-ec01-594c-24c4-43364812bd4c.png" width="512" alt="bifrost_捻りに重みをかける"> |

## 回転合成
曲げ捻り、捻り曲げそれぞれの順番で回転を合成します。
その後どちらを使用するか分岐しています。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878081/24682102-d954-86e0-fe49-edeadeeed4c0.png" width="512" alt="bifrost">

# 書き出し、読み込み
グラフを作成できたので、右肩、左肩、右前腕、左前腕の4つに複製します。
グラフメニューからPublish、ビューワーから読み込みを行います。
この機能を使用してシーン内で複製することもできますし、異なるシーンで使用することもできます。
再利用しやすくてありがたいです。
| Export | Import |
| --- | --- |
| <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878081/6b68d3a1-4ce0-30f8-5653-2c95d36a207f.png" width="512" alt="bifrost_曲げに重みをかける"> | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878081/0d20d01c-cd0a-1c3c-d907-39b6b8dbbdec.png" width="512" alt="bifrost_捻りに重みをかける"> |

# 結果
肩鎧、前腕鎧にそれぞれ設定して動かします。
よさそうですね、軸ごとにウェイトが異なりつつ、変なフリップもなさそうです。
| 肩鎧駆動 | 前腕鎧駆動 |
| --- | --- |
| <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878081/1b2c4d2d-c6ab-00c4-d785-b6a18129d6f2.gif" width="512" alt="肩鎧駆動"> | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878081/48f8722e-61b4-dc40-5e40-9971c41f9106.gif" width="512" alt="前腕鎧駆動"> |

# まとめ
本記事ではBifrostを使用した曲げ/捻り分解補助骨を作成しました。

今回作成したグラフは以下のリンクから配布します。
自由に使っていただいて構いません。
曲げ重みや合成順番を調整しつつ、ご自身の制作にご活用ください。
[公開しているグラフ](https://github.com/salt-k2t/MayaHelperBifrostGraph)

曲げ重みじゃなくて、縦曲げ横曲げそれぞれで分けたい...
可動域を制限したい...
もっと数式ベースでの解説が読みたい...
などの要望も出てきそうですが、それらはまた別の機会にとっておこうと思います。

個人的な感想としては、今回初めてBifrostに触れてみました。
噂に聞いていた計算系ノードの充実が素晴らしく、また設定ファイルとして共有しやすい点もありがたかったです。
普段はカスタムノードで独自の処理を書いているのですが、一部の用途ではBifrostに乗り換えてもよいかもしれません。

本記事が、3Dゲーム開発の助けになっていれば幸いです。

# おまけ
Unityでの実現方法を、以下の記事で公開しました。
[UnityのAnimationC#Jobsを使用した曲げ捻り補助骨の実装例](https://qiita.com/salt-k2t/items/beb26c884e25d0c9c6a1)