Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
アニメーションとスキニングをBurstで独自実装する
Search
Infiniteloop
October 18, 2023
Programming
0
230
アニメーションとスキニングをBurstで独自実装する
【タガヤス その27】わくわくUnity! ~CPUとGPUを酷使しよう~【仙台発信の定期勉強会】
https://tagayas.connpass.com/event/255830/
Infiniteloop
October 18, 2023
Tweet
Share
More Decks by Infiniteloop
See All by Infiniteloop
俺の PHP プロファイラの話 PHP スクリプトで PHP 処理系のメモリをのぞき込む
infiniteloop_inc
0
270
心理的安全性を学び直し、 「いい組織とは何か?」を考えてみる
infiniteloop_inc
0
340
ゼロからつくる 2D物理シミュレーション ~物理現象をコードに落とし込む方法~
infiniteloop_inc
0
410
詫び石の裏側
infiniteloop_inc
0
370
[新卒向け研修資料] テスト文字列に「うんこ」と入れるな(2024年版)
infiniteloop_inc
6
25k
リファクタリングで実装が○○分短縮した話
infiniteloop_inc
0
140
ADRという考えを取り入れてみて
infiniteloop_inc
0
130
500万行のPHPプロジェクトにおけるログ出力の歩み
infiniteloop_inc
0
110
I ❤ Virtual Machines 仮想環境をより便利に使うツールたち
infiniteloop_inc
0
83
Other Decks in Programming
See All in Programming
flutterkaigi_2024.pdf
kyoheig3
0
150
Laravel や Symfony で手っ取り早く OpenAPI のドキュメントを作成する
azuki
2
120
2024/11/8 関西Kaggler会 2024 #3 / Kaggle Kernel で Gemma 2 × vLLM を動かす。
kohecchi
5
930
タクシーアプリ『GO』のリアルタイムデータ分析基盤における機械学習サービスの活用
mot_techtalk
4
1.4k
役立つログに取り組もう
irof
28
9.6k
WebフロントエンドにおけるGraphQL(あるいはバックエンドのAPI)との向き合い方 / #241106_plk_frontend
izumin5210
4
1.4k
A Journey of Contribution and Collaboration in Open Source
ivargrimstad
0
960
3 Effective Rules for Using Signals in Angular
manfredsteyer
PRO
0
120
AWS Lambdaから始まった Serverlessの「熱」とキャリアパス / It started with AWS Lambda Serverless “fever” and career path
seike460
PRO
1
260
PHP でアセンブリ言語のように書く技術
memory1994
PRO
1
170
Make Impossible States Impossibleを 意識してReactのPropsを設計しよう
ikumatadokoro
0
230
受け取る人から提供する人になるということ
little_rubyist
0
250
Featured
See All Featured
What’s in a name? Adding method to the madness
productmarketing
PRO
22
3.1k
Measuring & Analyzing Core Web Vitals
bluesmoon
4
130
Optimizing for Happiness
mojombo
376
70k
Unsuck your backbone
ammeep
668
57k
The Invisible Side of Design
smashingmag
298
50k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
8
900
Automating Front-end Workflow
addyosmani
1366
200k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
27
840
Rebuilding a faster, lazier Slack
samanthasiow
79
8.7k
The Pragmatic Product Professional
lauravandoore
31
6.3k
Thoughts on Productivity
jonyablonski
67
4.3k
Music & Morning Musume
bryan
46
6.2k
Transcript
【Unity/Burst】 CPUだけでペンギン一万体 アニメさせてみた
誰 村上 善紀 仙台高専(11年~) DeNA(16年4月~) アカツキ(19年4月~) IL仙台(21年12月~)
視聴者の想定 難易度:Advanced ・コンシューマーの3Dゲーム開発の経験がある ・Unityでの3Dゲーム開発に詳しい ・Burstが何か知っている
ペンギンのハドル 皇帝ペンギンは寒さに対抗するために、ハドルと呼ばれる集まりを作る。ハドル には数百ほどの数が集まるらしい
それはともかくとして、
ペンギンは一万体くらい 60 30FPSで 元気に動いていてほしい
一万体動かしたいペンギン 頂点1076 ボーン6
今回の実行環境 CPU:Ryzen 5950x (Stock) GPU:Radeon 6800XT (Stock) RAM:DDR4-3200 (CL14) 64GB
Unity:2022.2b3 Development Build(Faster Runtime) / IL2CPP Release DX12 / Graphics Job Enabled / HDRP 3820x2160
そもそも標準のUnityではペンギン一万体動かないの?
Unityの標準実装ではどうなるか? CPUスキニングとGPUスキニングについて 一万体全員が映るケースで検証してみる 1mおきにx軸とz軸について100x100で配置。カメラはこれらを収める位置に。 最良の結果を得るため、AnimatorのボーンOptimizeも利用する。
Unityの標準実装ではどうなるか? ・Animator+CPUスキニング 1フレームに掛かる時間 140-160ms Animator関連 35ms程度 Skinning関連 105ms程度 (Render+Mainスレッド の重ならない部分の合計)
Unityの標準実装ではどうなるか? ・Animator+GPUスキニング 1フレームに掛かる時間 55ms~60ms程度 Animator関連 35ms程度 Skinning関連 40ms程度 (Render+Mainスレッド の重ならない部分の合計)
30FPSは、1フレームが33msに収まる必要がある
アニメーション~スキニングを 前述した問題を解決できるよう、Burstで実装してみよう。 ということで
そもそも。
アニメーションとスキニングとは ポリゴンで表現される生物について、本来生物が骨の動作によって行う筋肉等 の移動を、簡易的にリアルタイムCGで行うためのもの。 アニメーションでは、ボーンという骨を表す表現の、回転値と位置を決定する。 スキニングでは、ボーンの位置に合わせて、ポリゴンの頂点を移動する。
Unityでのアニメーションとスキニングの具体的な仕様 ・ボーン Transformで表される。スキニングのロジック内では座標変換行列として扱わ れる。 ・アニメーション Animatorによってボーンが挙動することで実現される。AnimationClipが、具体 的なアニメーション内容を持つ。 ・スキニング Skinned Meshという種類のポリゴン描画で実行する。
Unityのアニメーションとスキニングについて 動作が重い理由について分析する
動作が重い理由:アニメーション
アニメーションの問題は主に二つある 1.Transformが邪魔 2.Animatorが単純に重い
アニメーションの問題1. Transformが邪魔
Transformはパフォーマンス的には邪魔者 単純にオブジェクト指向的メモリ配置なため、重い。(連続していない) また、階層構造で処理の並列化の可否が決定する。 https://forum.unity.com/threads/ijobparallelfortransform-15000-transforms-executed-on-single-job-thread-a ny-hints.537723/ https://blog.unity.com/technology/best-practices-from-the-spotlight-team-optimizing-the-hierarchy
Transformはパフォーマンス的には邪魔者 一方で、作業上重要な役割も担っている。 AnimationClipを介したアニメーション再生で、Transformは使われることが前 提。
アニメーションの問題2. Animatorが単純に重い
2.Animatorが単純に重い 見た感じ、やってることに対して重すぎる。 (Unityの内部実装は確認できないため、Burstで一から実装したらもう少しパ フォーマンス出るだろうという推測です。)
動作が重い理由:スキニング編
動作が重い理由:スキニング編 ・そもそも、スキニングがロジックとして重い! Unityは別に何か悪いことをしているわけではない。 強いて言えば、Skinned Meshでは16bitのメッシュをスキニングできない。今回 は精度的に16bitで十分なため、これはUnityの問題と言える?
これらをふまえて
独自実装の指針
独自実装の指針 ・アニメーション AnimationClipを利用できるようにしながら、Burstで一から PlayableGraph(Animator)に相当する物をBurstで実装する。 ・スキニング 16bitスキニングに対応して、Burstで実装する。
独自実装する:アニメーション編
独自実装する:アニメーション編 ・AnimationClipの利用 ・PlayableGraph(Animator)相当のもの これらが必要
AnimationClip
AnimationClipの詳細 内部表現をエディタースクリプティングで確認することが出来る。
AnimationClipの詳細 AnimationClipの内部表現は、文字列とAnimationCurveで出来ている。 AnimationCurveとして時間軸上におけるアニメーションの値が表現され、文 字列で、そのCurveがどのプロパティに関連するか示されている。 ※今回はボーンに対する挙動なので移動回転スケールができればいい
AnimationClipの評価をBurst化 ・だがAnimationCurveはメインスレッドでしか評価できない。 https://forum.unity.com/threads/animationcurve-evaluate-can-only-be-calle d-from-main-thread.531614/ 移植するためには、内部ロジックが必要。
AnimationClipの評価をBurst化 UnityのCsReference内にAnimationCurveの図的表示に使われている部分の ロジックがあったので、これをベースにスレッドセーフなものを実装する。 https://github.com/Unity-Technologies/UnityCsReference/blob/master/Edit or/Mono/Animation/AnimationWindow/CurveEditor.cs
PlayableGraph相当の表現
PlayableGraph相当の表現 Clipをどのように評価し、最終的なボーンの出力にするかは、決して素朴なも のではない。ブレンディングやレイヤーマスクなど、何をどのようにどの程度実 装するかはゲームに応じて決めていい。 その為、詳細は割愛します。 (自由に実装できるということ自体が 重要な個所)
スキニングの実装指針詳細
スキニング in 16bit 頂点バッファを16bit化して、それに合わせたロジックを書く。 今回はランタイムでUnityの通常のメッシュアセットを変換し、16bitになるように 調整する。 ※UnityではBurstをつかって、VertexBufferのレイアウト等に対して細かく指示 しながらランタイムでメッシュを構築できる。 https://github.com/Unity-Technologies/MeshApiExamples
Unityでのアセット
Burstで利用するランタイム表現
実際に動かした
Unityエディターに移動します
パフォーマンス確認・改善
ボーンアニメ+スキニング ペンギン1000体のパフォーマンス ・Burst実装 ・Animator+Unity CPU ・Animator+Unity GPU
ボーンアニメ+スキニング ペンギン1000体のパフォーマンス ※スキニングとAnimation関連部分のみ ・Burst実装 約4.6ms ・Animator+Unity CPU 約13ms ・Animator+Unity GPU
約5ms
1000体でこれじゃあ、10000体むり
改善1 Burst Jobのスケジュールパフォーマンス
改善1 UnityのJobスケジュールパフォーマンス スケジュールに掛かってる時間が長い(2ms以上)
改善1 UnityのJobスケジュールパフォーマンス 例えばスキニングの処理は、1メッシュの頂点バッファが一つのNativeArrayで 表される。 安全システムによってコレクションのコレクションは許可されておらず、通常では 複数の頂点バッファを一つのJobに対してまとめることができない。 つまりメッシュ数分のJobがスケジュールされる。これが重い。
UnityのJobスケジュールパフォーマンス 安全システムの回避方法として、ポインタのコレクションを扱うことができる。 NativeArrayはGetUnsafePtr()で要素先頭へのポインタを取得することができ る。つまり「配列の先頭ポインタ」の「配列」は作れる。 これによって、複数の頂点バッファへの処理を単一のIJobParallelForで行うこ とが出来る。
UnityのJobスケジュールパフォーマンス 2msほど要していたものが、0.3ms程度まで落ちた
改善2 ボーンアニメーションの評価速度
改善2 ボーンアニメーションの評価速度 ボーンAnimationの評価自体にも時間が掛かっている。(1msが31スレッド)
改善2 ボーンアニメーションの評価速度 直接の原因としてはAnimationCurve相当の処理評価がそれなりに重いこと。
改善2 ボーンアニメーションの評価速度 そもそもクリップがオーサリングされている以上の分解能で評価される必要が あるのか? ない。指定された分解能以上の評価が行われないようにしちゃおう! モーションの評価が決まった回数ならば、ボーンアニメーション程度の情報量 はキャッシュできるのでは? あるモーションのある特定のフレームは、初回の評価の結果をキャッシュして、 一度だけしか評価されないように実装してみる。
UnityのJobスケジュールパフォーマンス 1msほどの処理が0.1msほどに
ボーンアニメ+スキニング ペンギン1000体のパフォーマンス 計測環境:Ryzen 9 5950x / Radeon 6800XT / DX12
/ IL2CPP DEBUG ・Burst All実装改善後 ・Burst実装(さっきの) 2.3ms 4.6ms
改善3 スキニングをキャッシュ
改善3 スキニングをキャッシュ AnimationClipが固定の分解能を持てるなら、スキニングもキャッシュを持て る。 1フレーム当たりの情報量は 16bit x 4 x 3 =
192bit (24byte) 頂点数1076の場合約25KB …まあ対象や条件を限定するなら。
ボーンアニメ+スキニング 約1000頂点ペンギン1001体のパフォーマンス ・Burst All実装(改善3あり) ・Burst実装(改善3なし) 2.3ms 2.0ms
計算0なのに思ったより早くなってないのね スキニングの処理自体は1.8ms -> 1.1ms 帯域の方が問題か。コア数が少ないCPUなら有意な差が出るかも。(ちゃんと 調べていない) Jobスケジュールの複雑度が上がったオーバーヘッドもある。(0.4ms増) 追記:ポインタをもっと駆使していればもうちょい何とかなった。 尚、今回は各Mesh Rendererの利用するMeshの頂点バッファが、毎回CPU
側の処理としてキャッシュから値を受け取っていて、それぞれがGPUへのアッ プロードをしている。 単体のアニメーションを再生するだけにしては無駄な処理で、重い。
メッシュ共通化によるスキニングキャッシュ 今回は行わないが、キャッシュ戦略を使うなら、常時、全てのメッシュが固有の 頂点バッファを使う必要はあまりない。 Batch Renderer Group 2022を利用することで、メッシュをintのIDで指定する 描画発行が行える。特定のアニメーションのあるフレームをメッシュとして保存 し、それをIDによって指定することで最速のスキニングが可能。 (ただし、モーフやブレンディング等の処理が追加で行われることが頻繁なら、
この指針ではつど明示的なインスタンス化の解決が必要になるため、実装は複 雑になるかもしれない。)
いよいよ一万体で勝負
ボーンアニメ+スキニング ペンギン10000体のパフォーマンス ・Burst実装 ・Animator+Unity CPU ・Animator+Unity GPU
ボーンアニメ+スキニング ペンギン10000体のパフォーマンス フレーム時間トータル ・Burst実装 約65ms ・Animator+Unity CPU 約140-160ms ・Animator+Unity GPU
約55-60ms
標準のGPU実装に負けてるやん!
どういうこと? スキニング+アニメーションの時間だけならGPU実装より早い UnityのGPUスキニング Animator関連 35ms程度 Skinning関連 40ms程度 Burstアニメ+スキニング Animator相当 3ms程度
Skinning関連 25ms程度
どういうこと? GPUとの情報同期にやたらとオーバーヘッドがある UnityのAPIの構造上、GPU上のバッファに一度頂点相当情報を転送してから さらにGPU内でVertexBufferにコピーする必要があり、一度余計なコピーが 走っている。これによって11ms程度のロスが発生… https://forum.unity.com/threads/feature-request-vertex-buffer-with-lockbufferforwrite-when-po ssible.1294395/#post-8387988
回避できないオーバーヘッドは仕方がない フルコントロールしてる恩恵を使う
間引きをやる 登場しているキャラが多い場面などで、遠距離のキャラクターや動きの少ない キャラクターについてはより少ない頻度でスキニング更新する。割と古くからあ る最適化方法。 本来Unityではスキニングを一時停止させることは出来ず、この方法をパ フォーマンス改善で利用できない。独自実装ではスキニングを明示的にコント ロールしているため可能。
間引きをやる 動画 動画に移動します
間引きをやる 動画
間引きをやる 動画
間引きをやる 動画
間引きをやる 動画
間引きをやる 今回は高度な条件を入れず、1フレームごとに全体の半分ずつを更新してい る。29-30FPSを達成
今後の展望・まとめ
今後の展望 ・残念なオーバーヘッド含め、頂点アップロードの時間が掛かりすぎている。 Batch Renderer Group(BRG)によるMeshIDの指定によるスキニング結果の ルックアップ方式を、使える場面では使った方がよさそう。 余談だがBRGで沢山の異なるメッシュを使った時に、パフォーマンスが過剰に 低くなる特性を発見し、公式と連携してバグ提出した。BRGは2022で大幅にリ ニューアルされたホットなAPIなので注視必須。 https://forum.unity.com/threads/new-batchrenderergroup-api-for-2022-1.1230669/
まとめ ・Burstの登場でアセットの低レベルな表現を割と高速かつ自由にCPU側で扱 えるようになっている。 >CPUは最低物理8コアが常識な時代。酷使していこう。 ・ゲームにあったソリューション(間引き等)は汎用ゲームエンジン時代に取りづ らいようだが、今はBurstやコンピュートシェーダがあり、1から実装すればビル トインより高品質なものが割と作れる。検討すべきときはする。