2023-12-22 Recalculate Normals の振り返り
This page is available in English: English
このページは AI(Claude)によって翻訳されています。翻訳に誤りが含まれる可能性があります。
選択したブレンドシェイプの法線を再計算することで、SkinnedMeshRenderer のシェーディングを改善するツールを作りました。
これを作る中で学んだことを共有します。
ドキュメント:
Recalculate Normals | Haï~
Improves the shading of SkinnedMeshRenderers by recalculating select blendshape normals (and tangents).
このツールは MIT ライセンスで公開しています。ライセンスの条件の範囲で、自由に使ってください!
法線とは?
簡単に言うと:
- 床の上に、床に対して垂直な棒を立てます。
- 床が傾いていれば、垂直な棒も傾きます。
- メッシュのすべての点に、このルールに従って垂直な棒を立てます。
この垂直な棒が法線です。その点が最も多くの光を受ける方向を割り出すために使われる情報です。太陽が棒の真上にあれば、その頂点は最大の光を受けます。
この情報は、反射(棒の真上からその点を見ると、自分自身が映り込みます)にも使われますし、シェーダーのほかの多くの処理にも使われます。
VRChat における、ブレンドシェイプの法線
ブレンドシェイプは、メッシュの点を動かすことでメッシュを変形させます。
動くのは点だけではなく、法線も動かす必要があります。点が動いたのですから、その下の面の新しい傾きに合わせて、垂直な棒をどう傾けるかという新しい情報が必要になるのです。
ところが、VRChat の文脈では(正確には Unity の側であり、VRChat に非はありません)、問題があります。
Unity には、次のような問題がありました。
- 顔のブレンドシェイプを変更すると、ブレンドシェイプ自体の差分法線によってメッシュの法線が汚染され、不正に再計算されるため、アバター全体のシェーディングが変形してしまうことがある。
- トゥーンシェーディングのアバターでは、顔の法線を再計算するのは間違いであることが多い。アバターのアーティストは、顔の曲面から通常計算されるものとは異なるように、顔の法線を描き直しているからです。
- 上記の癖の副作用として、Unity の不正な再計算により、VRAM 上でも、圧縮されたダウンロードバンドルのサイズでも、メッシュのサイズが大幅に膨れ上がる。
このため、VRChat のユーザーには、メッシュの設定を「Legacy Blendshape Normals」にするか、ブレンドシェイプの法線をインポートも再計算もしない設定にすることが推奨されがちです。
この推奨の結果として、ブレンドシェイプに差分法線がなくなります。ブレンドシェイプには、その棒をどう傾けるかを記述するデータがなくなるため、シェーディングが不正確に見えます。
体の一部を平らにしたり、盛り上げたり、大きくしたりすると、面の傾きを記述する情報は元の面のままになるため、見た目がおかしくなります。
残念ながら、これより悪い選択肢しかないので、この推奨が最善です。
本来であれば、トゥーンの顔の表情用ブレンドシェイプや、技術的なブレンドシェイプ(体の一部を隠すものなど)でない限り、ブレンドシェイプの法線はそれに応じて再計算されるべきでしょう。
さらに詳しく知りたい場合は、こちらをご覧ください。
Unityでカスタム法線メッシュにブレンドシェイプを適用した際に法線が崩れる問題の解決法 - Qiita
はじめにUTS2.0などのNPRシェーダを使ってキャラクターを可愛く見せようとした時、顔メッシュの法線編集は必須になってくると思います。しかし、Blenderなどでカスタム法線メッシュを作成し、…
OpenKnowledge/Normals, Tangents, BlendShapeNormals のUnityインポート設定の解説
この記事では、Unity のFBXインポート設定にある「Normals」等の解説を行います。 実際の画面では、以下の場所にある設定項目を主に解説します。Unity のバージョンは 2018.4.20f1 です。 # 先に結論を VRChat向けアバターとしては `Normals = Import, Tangents = Calculate Mikktspace, Blend Sh...
スクリプトによるブレンドシェイプの法線の再計算
Unity には、スタティックメッシュの法線を再計算する関数があります。
これを使って、再計算すべき各ブレンドシェイプの差分法線を再計算します。
Unity - Scripting API: Mesh.RecalculateNormals
Thank you for helping us improve the quality of Unity Documentation. Although we cannot accept all submissions, we do read each suggested change from our users and will make updates where applicable.
試している途中で、Unity の RecalculateNormals のドキュメントにも記載されている問題に遭遇しました。
インポートされたメッシュは、すべての頂点を共有しているとは限りません。たとえば、UV シームにある頂点は 2 つの頂点に分割されるため、
RecalculateNormals関数は UV シームで滑らかでない法線を作成します。
(Unity のドキュメントからの引用を翻訳したものです。)
これは実用上の問題であることがわかりました。アーティストが体の各部分を別々の UV シームに分割している場合、アバターのアーティストの作業フローでは 1 つの滑らかにシェーディングされた頂点であっても、ゲームエンジン向けのメッシュデータ表現では、UV データが異なるため、そのアーティストが作った 1 つの頂点が複数の頂点に分割されます。
UV データは、各頂点がテクスチャのどこからサンプリングするかを決めるために使われます。ゲームエンジンのレンダラーの観点では、アーティストから見れば 1 つの頂点であっても、シェーディングには複数の頂点が使われることを意味します。
この技術的な事情が、RecalculateNormals 関数の出力に問題を引き起こします。再計算によって、アーティストが作った同じ 1 つの頂点に対して、異なる角度の垂直な棒が複数立てられてしまうのです。
その結果、法線データに UV シームが見えるようになります。これはシェーディングに悪影響を与えます。光、反射、リムが突然途切れてしまい、法線を変更せずに放置するよりも悪いと言ってよいでしょう。
これに対処するため、次の戦略を採りました。
- 法線を再計算する前に、位置と法線が同じ点のすべての組のインデックスを、データ構造に保存します。
- これらの点は、実質的に UV シームです。
- タンジェントは UV データに基づいており、異なる可能性があるため、タンジェントが等しいかどうかは確認しません。
- 次に、
RecalculateNormalsを使って法線を再計算します。 - 法線を再計算した後、そのデータ構造を再利用して、それらのインデックスの法線だけを、平均して正規化することで再々計算します。
- その後、
RecalculateTangentsを使ってタンジェントを再計算します。
完璧ではありませんが、良いトレードオフです。ソースコードはこちらをご覧ください。特に ReRecalculateNormalsInUVSeams メソッドとその呼び出し元に注目してください。
全体的な戦略
全体的な戦略は次のとおりです。
- ベイク用の SkinnedMeshRenderer を作成し、すべてのブレンドシェイプのウェイトを 0 に設定します。
- BakeMesh を使って、そのスキンドメッシュからスタティックな Mesh を作ります。
- 上で説明した対処を使って、法線とタンジェントを再計算します。
- ブレンドシェイプが 1 つも有効でない場合でも、法線とタンジェントを再計算します。差分法線と差分タンジェントは、実際の法線ではなく、Unity が法線であるべきと判断したものに基づいて計算したいからです。
- このメッシュが、再計算済みのベースメッシュになります。
- 続いて、再計算すべき各ブレンドシェイプについて、個別に(すべてのブレンドシェイプをまとめて再計算するわけではありません):
- ブレンドシェイプのウェイトを最大に設定します (厳密には、元のブレンドシェイプの各フレームの各ウェイトについてこれを行います)。
- BakeMesh を使って、そのスキンドメッシュから別のスタティックな Mesh を作ります。
- 上で説明した対処を使って、法線とタンジェントを再計算します。
- この個別のブレンドシェイプのメッシュの法線と、再計算済みのベースメッシュの法線との差分を保存します。
- この個別のブレンドシェイプのメッシュのタンジェントと、再計算済みのベースメッシュのタンジェントとの差分を保存します。
- これらが、後でメッシュに書き戻す必要がある差分法線と差分タンジェントです。
- これらがすべて終わったら、変更したブレンドシェイプを(元のメッシュのコピーに)書き戻します。
- Unity の API には単一のブレンドシェイプだけを編集する方法がないため、すべてのブレンドシェイプをクリアして、すべてを書き直すことで回避する必要があります。
- 差分法線と差分タンジェントを保存しておくのはそのためで、書き直しの操作を何度もではなく 1 回で済ませられます。
その後に計算される差分は Unity の判断だけに基づいているため、Unity が法線は変わらないと判断すれば、差分はゼロのままです。
差分は、アーティストの判断と Unity の判断を比較して計算してはいけません。そうすると、ほぼすべての頂点で両者の意見が食い違い、アバター全体のシェーディングが変わってしまいます。 ブレンドシェイプが 1 つも有効でないベースメッシュでも法線を再計算することで、これを明確に避けています。
動かない頂点も再計算される
頂点の位置がブレンドシェイプによって変化しない場合でも、すべての頂点の法線とタンジェントが再計算されます。頂点の位置が変わると、その頂点とスムージングされている隣接頂点もすべて法線が変わるからです。その隣接頂点自体の位置が変わらない場合でも同様です。
再計算を動く頂点だけに限定すると、特に体の一部を平らにするブレンドシェイプで、奇妙なシェーディングの「オーラ」効果が目立つことが、実験でわかりました。そのため、この制限をなくして、シェーディングが一貫するようにするほうが良いのです。
隣接する頂点が影響を受けるため、VRAM の使用量がわずかに増えるはずですが、RecalculateNormals の安定性に頼って、影響を受けないすべての頂点の差分法線と差分タンジェントが 0 になることを期待しています。
最後に
追加するブレンドシェイプを提案する UI も加えました。顔の表情用のブレンドシェイプはおそらく再計算すべきではなく、衝突防止用のブレンドシェイプも同様なので、これはユーザーが選択する仕組みになっています。
ともあれ、このツールは MIT ライセンスで公開しています。ライセンスの条件の範囲で、自由に使ってください! VRCFury などにネイティブに組み込んでもらえるかもしれませんね。
ドキュメント:
Recalculate Normals | Haï~
Improves the shading of SkinnedMeshRenderers by recalculating select blendshape normals (and tangents).