【Unreal Engine】同じものを大量に置くなら:Instanced Static Meshで描画コールを減らす

作成: 2026-07-25

木を3,000本置いたらstat unitのDrawが伸びた。1個のメッシュが1回の描画命令になる仕組みと、それを1回にまとめるInstanced Static Meshを図解。ISM・HISM・Foliageの使い分け、Add Instanceでのインデックス管理、Naniteとの関係、そして3,000本をISMに置き換えてDrawを比べる実践つき。

草原に木を3,000本並べた。見た目は満足だが、stat unit を出すと Draw が伸びている。木を減らすしかないのだろうか。

減らさずに済む方法があります。同じメッシュを大量に置くとき、描画命令を1回にまとめる——これがインスタンシングです。この記事では、Drawが増える理由から、ISM・HISM・Foliageの使い分け、Blueprintからの操作、そしてNaniteを使っているなら要らないのかという疑問までを扱います。

3,000本の木がバラバラに描かれる状態と、1回の命令でまとめて描かれる状態を見比べるソフトブルーのクレイ人形

この記事でわかること

  • 1個のメッシュ=1回の描画命令(Drawが伸びる正体)
  • インスタンシングは 位置だけを配列で渡す
  • ISM / HISM / Foliage の使い分け(迷ったらISM
  • 距離の間引きは Cull Distance を自分で入れる
  • Add Instanceインデックス管理の注意
  • Naniteを使っているなら要るのか
  • 実践: 3,000本をISMに置き換えてDrawを比べる

Sponsored

Drawが増える理由

statコマンドstat unit に出る Draw は、CPUがGPUへ「これを描いて」と命令を出す作業にかかった時間です。

そしてざっくりした見立てとして、同じメッシュを別々のActorとして大量に出すと、命令の数が増えやすいと考えてください。

Static Mesh Actorを3000個置くと、CPUからGPUへ3000回の描画命令が飛ぶことを示した図

木を3,000本置けば、命令も3,000回に近い数になります。1回あたりは短くても、それだけ積み上がれば Draw は伸びます。

「1 Actor = 1 命令」は、あくまで入口の考え方です。 実際の数は マテリアルスロットの数・描画パス・影・カリング などにも左右されるので、ぴったり3,000回とは限りません。ここでは 「数が増えると命令も増える」 という感覚だけ掴んでください。正確な数は、あとで stat rhi で実測します。

ここで大事なのは、GPUが重いのではなくCPUが重いということです。木のポリゴン数を半分にしても、命令の回数は3,000回のままなので Draw は下がりません。

症状原因効く対策
Draw が大きい命令の回数が多いインスタンシング、カリング
GPU が大きい描く量(ポリゴン・ピクセル)が多いLOD、解像度、シェーダーの軽量化

stat unitDraw が突出しているなら、この記事の対象です。GPUが大きい場合は LODとカリングNanite の担当になります。


まとめて1回で描く

インスタンシングの考え方は、拍子抜けするほど単純です。

「この形を、この3,000箇所に描いて」と1回で伝える。

3000個の個別の命令が、1つのメッシュ情報と3000個の位置の配列にまとめられ、1回の命令になることを示した図

送るものは2つだけです。

  • メッシュの情報(形とマテリアル)を 1つ
  • 位置・回転・スケール3,000個ぶんの配列

これで命令は 1回 になります。GPUは同じ形を場所を変えて3,000回描きますが、CPUの仕事は1回ぶんです。

条件は、全部が同じメッシュで、同じマテリアルであることです。木の種類が3つあれば、3回の命令になります(それでも3,000回よりは圧倒的に少ない)。

同じ形でも見た目は変えられます。 インスタンスごとに回転とスケールを変えられるので、木を並べても機械的には見えません。色や質感を個体ごとに変えたい場合は、Material InstancePer Instance Custom Data を使う道もあります。

Sponsored

3つの選択肢

UEでインスタンシングを使う道は3つあります。内部の仕組みは繋がっています。

ISMはまず試す基本、HISMは数千規模でほぼ動かないもの、Foliageツールは中身がHISMという3つの使い分けを並べた図
方法何をするか向いているもの
Instanced Static Mesh(ISM)Componentに位置を追加して増やすまず試す基本。動かす可能性があるもの、Naniteメッシュ
Hierarchical ISM(HISM)ISM + 階層構造を作る数千規模でほぼ動かない草木や岩
Foliageツールエディタで塗って配置する。内部はHISM手で配置したい自然物

迷ったら、まずISMから試してください。 「大量ならHISM」という言い方をよく見かけますが、これは古いUEの前提です。現行のISMはGPU側でインスタンスごとのカリングとLODを行うので、「ISMは全部描き続ける」わけではありません。

HISMが効くのは、数千規模で、ほぼ動かないものです。HISMは配置を階層構造(ツリー)として持つため、広い範囲から「見えている塊」を素早く絞り込めます。ただしその構造を作るコストがかかり、インスタンスを動かすたびに作り直しが必要になります。

状況選ぶもの
数十〜数百、あるいは動かす可能性があるISM
Naniteが有効なメッシュISM(Nanite側の仕組みと素直に噛み合う)
数千規模で、置いたら動かさない草木・岩HISM
エディタで手で塗りたいFoliageツール(中身はHISM)

Foliageツールは、HISMを手で塗るためのUIです。中身は同じなので、「エディタで配置したい」ならFoliage、「Blueprintから動的に増やしたい」ならComponentを自分で置く、という使い分けになります(→ ランドスケープの基本)。

そして、どちらが速いかは実測で決めてください。 同じ数・同じ視点で置き換えて stat unit を比べる。これがこの領域の唯一の正解です。


Blueprintから使う

Blueprintでは、Instanced Static Mesh Component(または Hierarchical Instanced Static Mesh Component)を追加し、メッシュを1つ指定してから、位置を足していきます。

ノード何をするか戻り値
Add Instanceインスタンスを1つ追加する追加された番号(Index)
Remove Instance指定した番号を削除する成否
Update Instance Transform指定した番号の位置を変更する成否
Clear Instances全部消す

Add Instance に渡すのは Transform(位置・回転・スケール)です。ループで3,000回呼べば3,000本並びます。

そして、ここに定番の落とし穴があります

Remove Instanceで途中の1つを消すと、後ろのインデックスが1つずつ繰り上がり、保持していた番号が別のインスタンスを指すことを示した図

Remove Instance で途中の1つを消すと、後ろの番号が繰り上がります。

  • 削除前: 0 1 2 3 4
  • 2 を削除 → 0 1 2 3(元の 32 になり、43 になる)

「この木は3番」と変数に持っていた場合、その3番は別の木を指すようになります

対策は2つです。

  • 番号を保持しない: 消すときにその場で番号を取得して即座に消す
  • 消さない: 位置を地面の下へ動かす(Update Instance Transform)ことで「消したように見せる」

これは配列をループ中に削除するときと同じ構造の問題です。インスタンスは内部的に配列で管理されているので、配列の注意点がそのまま当てはまります。


効かない場合とNaniteとの関係

インスタンシングが向かない場面もあります。

状況理由
マテリアルが違う別のバッチになる。1回にまとまらない
個別に動かしたい動かすには Update Instance Transform が必要で、数が多いと逆に重い
個別に当たり判定やイベントが要るインスタンスはActorではないので、Overlap や個別のロジックを持てない

3つ目が実用上いちばん効きます。 インスタンスは「描画されるだけの存在」です。木を撃って倒したい、宝箱を開けたいといった個体ごとの挙動が要るものはActorのままにしてください。

そして、多くの人が気にする Naniteとの関係 です。

Naniteが有効なメッシュはNanite側でまとめて処理されるため、ISMへの置き換えによるDraw削減の効果が小さくなることを示した図

Nanite が有効なメッシュは、Nanite側でまとめて処理されます。 そのため、Naniteメッシュを大量に置いている場合、ISMへ置き換えても Draw の削減幅は小さくなります。

ただし、NaniteはすべてのメッシュにそのままON にできるわけではありません(対応状況はバージョンで前進しています)。また既存プロジェクトでNaniteを使っていないメッシュも当然あります。

判断は簡単です。stat unit の Draw を測って、置き換えて、もう一度測る。 減れば効いています。減らなければ、そのケースでは別の対策(LODとカリング)を見てください。推測ではなく数値で決めるのが、この領域の唯一の正解です(→ Unreal Insights)。

Sponsored

実践:3,000本をISMにしてDrawを比べる

オープンフィールドの森、ローグライクの瓦礫、タワーディフェンスの障害物。同じものを大量に置く場面はどのジャンルにもあります。ここでは実際に置き換えて、数値がどう動くかを見ます。

再現する状態を作る

用意するもの内容
メッシュ好きなStatic Mesh 1つ(Starter Contentの SM_Rock など)
配置数3,000個
計測環境Standalone Game で起動(エディタのPlayではない)

まず Actorとして3,000個 置きます。手で置くのは無理なので、Blueprintで並べます。

BP_ForestA(Actor)を作り、Event BeginPlayFor Loop(0〜2999)を回し、Spawn Actor from Class でStatic Mesh Actorを格子状に配置します。

Actorを3000個置いたときのDrawと、ISMに置き換えたときのDrawを並べて比較した実例図

① Actor版のDrawを測る

Standaloneで起動し、コンソールに stat unit と入力してください。Draw の数値を書き留めます(同時に stat rhiDrawPrimitive calls も見ると、命令の回数そのものが分かります)。

測るときはカメラの位置と向きを固定してください。 見える範囲が変わると数値も変わるので、前後の比較になりません。

② ISM版に置き換える

BP_ForestB(Actor)を新しく作ります。

  1. Componentに Instanced Static Mesh を追加し、名前を Trees にする
  2. 詳細パネルの Static Mesh に、①と同じメッシュを指定する
  3. Event BeginPlayFor Loop(0〜2999)を回す
  4. ループの中で、格子状の位置から Make Transform を作り、Add Instance(Target: Trees)へ渡す

Spawn Actor from ClassAdd Instance に変わっただけで、配置のロジックは①と同じです。

③ 遠くを間引く(値を明示する)

距離で間引く動きは、自分で値を入れないと起きません。Componentの詳細パネルで2つ設定します。

項目意味
Instance Start Cull Distance4000この距離から薄れ始める
Instance End Cull Distance6000この距離で完全に消える

単位はcmなので、40m〜60mで消える設定です。値を入れずに「遠くが消えるはず」と期待しても消えません。ここは自動ではなく、自分で決める数値です。

上段「Componentを1つ用意する」でInstanced Static Mesh→Static Mesh→Cull Distance(Start 4000 / End 6000)、下段「位置だけを足していく」でFor Loop→Make Transform→Add Instanceへ折り返す2段組みの完成ノードグラフ
BP_ForestB(イベントグラフ)
Event BeginPlay
  → For Loop(First = 0, Last = 2999)
      Loop Body:
        // 格子状に並べる(50列 × 60行、間隔400)
        x = (Index % 50) * 400.0
        y = (Index / 50) * 400.0
        t = MakeTransform(
              Location = (x, y, 0.0),
              Rotation = (0, 0, RandomFloatInRange(0, 360)),   // 向きを散らす
              Scale    = (1.0, 1.0, RandomFloatInRange(0.8, 1.2)))
        → Trees.AddInstance(Transform = t, bWorldSpace = false)

確かめる

BP_ForestB に差し替えて、①とまったく同じ位置・同じ向きからStandaloneで起動し、stat unit を出してください。

見た目はほぼ同じ(岩が3,000個並んでいる)のに、Draw の数値がはっきり下がっていますstat rhiDrawPrimitive calls を見れば、3,000回近くあった命令が大きく減っているのが分かります。

続いて手順③の効果を見ます。カメラを後ろへ引いて、岩の群れから離れてください。 End Cull Distance6000(60m)を越えたあたりから、遠くの岩が消えていきます。値を 6000 から 3000 に変えれば、消え始める位置が手前に寄るのが見えます。これは設定した値どおりの挙動で、自動ではありません。

うまくいかないときの切り分けです。

  • 1個も表示されないStatic Mesh の指定を忘れています。Componentの詳細パネルで確認してください
  • 全部が同じ場所に重なっているMake Transform に渡す Location が全ループで同じです。Index から座標を計算できているか確認します
  • Drawが下がらない → 元のメッシュが Nanite有効である可能性が高いです。すでにまとめて処理されているので、この置き換えの効果は出ません
  • 遠くの岩が消えないInstance Start / End Cull Distance0 のままです。0 は「間引かない」の意味なので、値を入れてください
  • 数値が毎回違う → カメラの位置か向きが変わっています。同じ場所から測り直してください

ポイントは2つです。

  • Drawが大きいときにだけ効く: stat unit で Draw が突出しているのを確認してから使ってください。GPUが原因の重さには効きません
  • 個別の挙動が要るものは置き換えない: インスタンスはActorではないので、当たり判定やイベントを個別に持てません。背景の飾りだけをISMへ移すのが基本の線引きです
  • 距離の間引きは自分で値を入れる: Instance Start / End Cull Distance は既定で 0(=間引かない)です。「置き換えたのに軽くならない」ときは、まずここを見てください

木を「エディタで手で塗りたい」なら、同じ仕組みをUIから使える Foliageツール が便利です(→ ランドスケープの基本)。


おまけ:先に知っておくと良いこと

PCGは内部でこれを使っている。 PCGStatic Mesh Spawner が置くメッシュは、内部でインスタンス化されています。「数千本の木を撒いても比較的軽い」のは、この記事の仕組みが効いているからです。PCGを使っているなら、すでにインスタンシングの恩恵を受けています

マテリアルは1つに寄せる。 木の葉と幹でマテリアルが2つあると、それだけでバッチが2つに分かれます。背景の飾りは、可能ならマテリアルを1つにまとめるとバッチ数が減ります(テクスチャをアトラス化する、という発想です)。

エディタツールと組み合わせる。 Editor Utility Widget で「選択したActorをまとめてISMに変換する」ツールを作ると、既存のレベルを一括で最適化できます。手作業で置いた大量のActorがある場合に効きます。

当たり判定は持てる。 インスタンスは個別のイベントを持てませんが、コリジョン自体はインスタンスごとに機能します(プレイヤーは木に衝突します)。「ぶつかるけれど、個別に反応はしない」——背景の障害物にはこれで十分です。


まとめ

  • stat unitDraw は命令の回数 。数が増えれば命令も増える(正確な数は実測する)
  • インスタンシングは 「この形を、この3,000箇所に」と1回で伝える
  • 迷ったらまずISM 。現行ISMもGPU側でインスタンス単位のカリングとLODを行う。 HISMは数千規模でほぼ動かないもの向け
  • 距離の間引きは Instance Start / End Cull Distance を自分で入れる (既定は0=間引かない)
  • Add Instance番号を返すRemove Instance番号が繰り上がる点に注意
  • Naniteが有効なら効果は小さい 。測って、置き換えて、また測る

3,000本の木を減らさずに Draw を下げられる、というのがこの機能の価値です。あなたのレベルで、いちばん数が多いメッシュは何でしょうか。