CPU Skinning (CPUスキニング)は、ゲームグラフィックスのレンダリングや最適化(テクニカルアート)において極めて重要となる技術用語です。キャラクターのボーンアニメーションによるメッシュの変形を、CPUのメインスレッドまたはマルチスレッド(Job System)で安全に事前計算する互換性重視の方式。
スキニング処理の基本概念とCPU/GPUの役割の違い
ゲーム内でキャラクターが動く際、内部的には「ボーン(骨格)」のアニメーションデータに合わせて、3Dモデルの外殻である「メッシュの頂点」の位置を引っ張って変形させる計算が毎フレーム行われています。この、骨の動きに合わせて皮膚メッシュを変形させる処理を **スキニング(Skinning)** と呼びます。
このスキニング計算は通常、並列計算が得意なグラフィックスプロセッサ(GPU)で行われることが多いですが(GPUスキニング)、特定の最適化目的やハードウェアの制約、またはプログラム的な都合により、メインプロセッサである **CPU** 側で計算を行ってから結果をGPUに送って描画する方式があります。これが **CPU Skinning (CPUスキニング)** です。
現実世界での例え:監督が全員のポーズを細かく手書きで指示する劇団
この処理方式の違いを、演劇の舞台に例えてみましょう。
舞台の上に何十人ものダンサー(キャラクターのメッシュ頂点)を登場させ、全員に複雑なダンス(ポーズの変形)をさせようとしています。
通常の「GPUスキニング」は、ダンサーたち各自が自分の頭の中で「自分はこの骨に追従して動く」と並列で超高速に判断して動くスタイルです。監督(CPU)は指示を出すだけで楽なので非常に高速ですが、監督の手元には「今、誰が正確にどこの座標に立っているか」のメモがありません。
一方の「CPUスキニング」は、劇団の総監督(CPU)が、舞台裏の楽屋でダンサー全員を1人ずつ呼び出し、「君のポーズはこう、君の位置はここ」と手元のノート(メモリ)に細かく計算・記録し、ポーズを固定した状態にしてから舞台へ送り出す厳密な管理スタイルです。
この方法は、監督が全員分をノートに書くため、非常に時間がかかります。しかし、大きな強みがあります。監督の手元のノートには、全員のポーズと詳細な座標が完璧に記録されているため、「ダンサーの手がマントのどの位置に触れているか」の正確な 当たり判定(衝突計算) を行ったり、「マントをつかんで別の形に書き換える」といった、細かな修正や物理シミュレーションをCPU側で直接行うことができます。
CPU Skinning のメリットとデメリット
| メリット(得意なこと) | デメリット(苦手なこと) |
|---|---|
| 変形後の正確な頂点座標をC#スクリプトで取得でき、当たり判定やRaycastに利用可能 | メインCPUスレッドに計算負荷がかかり、FPS低下の原因になる |
| 古いGPUや、WebGLなどのシェーダー演算が制限された環境での動作互換性 | 毎フレーム、計算した頂点データをVRAMへ転送するため、バス帯域を圧迫する |
| 衣類(Cloth)シミュレーションなど、物理演算エンジンとの連携がスムーズ | ポリゴン数や数が多いほど、計算時間が等比級数的に増大する |
実務における選定基準と現代の最適化(Job System)
かつてCPUスキニングは遅い方式とみなされがちでしたが、現代 of Unityにおいては、**`C# Job System`** や **`Burst Compiler`** と呼ばれるマルチスレッド並列処理技術を組み合わせることで、CPUスキニングを驚異的な速度で実行可能になりました。そのため、以下のような実務シナリオで選択されます。
- キャラクターの体に衣服やロープが正確に衝突・追従する物理挙動が必要なタイトル。
- アニメーション変形後のキャラクターメッシュの表面から、正確にパーティクル(血飛沫など)を発生させるエフェクトシステム。
- 古いモバイル端末やブラウザゲーム(WebGL)などで、GPU側でのボーン制限数を超えて複雑なキャラクターを動かしたい場合。
以下は、Unityの `SkinnedMeshRenderer` を制御し、動的にCPUスキニングとGPUスキニングの設定を切り替えたり、物理的な判定用に変形後の最新のメッシュ頂点位置を安全にBake(抽出)して利用するためのC#コード例です。
using UnityEngine;
[RequireComponent(typeof(SkinnedMeshRenderer))]
public class SkinningController : MonoBehaviour
{
private SkinnedMeshRenderer skinnedMeshRenderer;
private Mesh bakedMesh;
[SerializeField] private bool forceCPUSkinning = false;
void Start()
{
skinnedMeshRenderer = GetComponent<SkinnedMeshRenderer>();
bakedMesh = new Mesh();
if (forceCPUSkinning)
{
skinnedMeshRenderer.forceMatrixRecalculationPerFrame = true;
}
}
public void GetCurrentPhysicalMesh()
{
if (skinnedMeshRenderer != null && bakedMesh != null)
{
skinnedMeshRenderer.BakeMesh(bakedMesh);
if (bakedMesh.vertexCount > 0)
{
Vector3 currentVertexZero = bakedMesh.vertices[0];
Vector3 worldPos = transform.TransformPoint(currentVertexZero);
Debug.Log($"Vertex 0 position: {worldPos}");
}
}
}
void OnDestroy()
{
if (bakedMesh != null)
{
Destroy(bakedMesh);
}
}
}
CPUスキニングの性質と、Unityの近代的なマルチスレッド技術による最適化の組み合わせを正しく理解することで、グラフィックスと高度なゲーム物理・当たり判定を両立させた、極めてリアルでインタラクティブなゲームを開発することが可能になります。