Blender MCP × LM Studio × gpt-oss で 自然語3Dグラフィックスに挑戦!

Blender MCP × LM Studio × gpt-oss で 自然語3Dグラフィックスに挑戦! HowTo

背景と目的

近年、テキストから画像・動画を一発生成する生成AIが台頭し、従来の3DCGは「わざわざモデリングしなくてもいいのでは?」という空気さえ漂っています。
しかし発想を変えれば、AIを使って3Dソフトを操作すること自体が新しい体験になります。

今回は Blender MCP × LM Studio × gpt-oss というローカル環境を組み合わせ、自然語だけで3Dシーンを構築し、最後はカメラ周回アニメまで作る実験をしました。

筆者はBlenderで昔、ドミノ倒しの物理演算を試した程度の初心者ですが、「もし自然語で自動化できたら面白そうだ」と思い立ったのが出発点です。

環境準備

  • Blender に MCP (Model Context Protocol) を導入
  • LM Studio で gpt-oss を起動し、MCP プラグイン経由で Blender と接続
  • 以後、自然語を投げると LLM が Python コードを生成し、Blender 内で実行される

(詳細なセットアップ手順は末尾の付録にまとめています)

自然語でシーン構築

最初の試みは「25個のキューブを並べて3層に複製」。
自然語で次のように指示します:

  • 1辺2のキューブを 5×5 に並べ、隙間は1
  • これをZ方向に3層複製(層間隔3)
  • 各層を Floor1〜3 のコレクションにまとめる

さらに「全キューブにランダムな高彩度マテリアルを割り当てよ」と続けると、色とりどりのオブジェクトが並びます。
この時のコツは「RGBランダム」ではなく「HSV指定」。Hueをランダム、Saturationを0.9〜1.0、Valueを0.8〜1.0と指定すると鮮やかな色になります。

カメラやライトも自動配置。ここまで自然語で一気にシーンが整いました。

ハマりどころ(罠と対処)

もちろん順風満帆ではありません。

  • 真っ暗事件
    F11とF12を取り違え、「レンダーしても真っ暗」に。F12が正解です。
  • ライト逆向き事件
    Blenderのライト正面は -Z方向
    「原点を向けろ」と自然語で指示したのに、なぜか反対を向く──これは仕様でした。解決策は Track To 制約を使うこと。
  • 床に埋まる事件
    キューブの底面が床と干渉して真っ黒に。Z=-2 に床を置くことで回避。
  • カラーマネジメント/Eevee設定
    Cycles で重くなりすぎる、EEVEEで背景が透過になるなど、細かい調整も必要でした。
  • 結構止まる
    コンテキストの消費は大きいです。幸い、チャットを分割して作業は継続できます
コンテキスト不足に陥りやすい
カメラが原点の逆を向いてしまう

アニメはPythonで安定化

「カメラを原点の周りに回し、常に原点を見続ける」──ここで自然語指示は限界に。

自然語では

  • 「カメラ本体を回すのか?」
  • 「親のEmptyを回すのか?」
  • 「注視はTrack ToかDamped Trackか?」
    と解釈が揺れて、安定しませんでした。

最終的には Python で一発解決:

  • 原点に Empty「TrackTarget」を追加
  • カメラに Damped Track 制約で Target を注視
  • もう1つ Empty「OrbitPivot」を作ってカメラを子にし、Z軸で360°回転アニメーション
  • フレーム範囲 1〜120、補間は線形
  • 出力は FFmpeg / MPEG-4 / H.264

これで滑らかなカメラ周回動画が完成。ブログに貼れる映像ができました。

速度・運用の本音

ここで気づいたのが 速度の壁

  • ローカル gpt-oss では、25キューブ3層+マテリアル付与の処理に 約13分。しかも7回に分割してコードを投げる挙動。
  • 対して GPT-5 なら Pythonコードを即座に生成してくれる。コードをローカルで実行すれば、速度も安定性も圧倒的。

結論:
「課金が怖い勢」も GPT-5でコードだけ生成 → ローカルで実行が最速で現実的。
MCPを使って「自然語だけで完結」を狙うと時間を浪費する一方で、シーン生成のアイデア出しや数式ベース配置には使える、と言えます。

Gemma 3 の試行錯誤ループ

参考までに、Gemma 3 でも試しました。学習済みでできると本人も言っていました。

You

あなたは、この機能を使って、シーンを行使くするコードを生成できますか?

google/gemma-3n-e4b

はい、可能です。どんなシーンを作成したいのか教えていただけますでしょうか? 具体的に指示していただければ、必要な Python コードを生成します。


面白いことに「自分で出したコードを否定・修正して再実行する」ループに陥りがち。

例:Cube が存在しないのに参照 → エラー → 「修正版コード」を提案 → 再実行…の繰り返し。

結果と所感

自然語で「ここまで」できるのは面白い

ただし仕上げはやはり Python が安定

gpt-oss は「動くが遅い」、GPT-5 は「速くて安定」

3DCGと生成AIは競合ではなく、「3DCG × 生成AI」として組み合わせると面白い未来が見える

付録:セットアップとプロンプト集

Blender MCP の導入

Blender 公式アドオンから MCP (Model Context Protocol) を有効化

「編集 → プリファレンス → アドオン」から MCP を検索し、チェックを入れる

有効化後、MCP が待ち受けできるようになる

LM Studio 側の設定

LM Studio に gpt-oss をロード(ローカル LLM 環境)

MCP プラグインを有効にして Blender と接続

以降、自然語プロンプトを投げると Python コードが生成され、Blender 内で実行される

成功した自然語プロンプト例

紆余曲折の上、動くプロンプトが作れました。

シーン生成

新規シーンにしてください。

1辺2のキューブを5×5で並べ、隙間は1にしてください。
原点中心に配置し、これを「BaseCubes」コレクションにまとめてください。

この25個のキューブをZ方向に複製して3層にし、層間隔は3にしてください。
各層を Floor1, Floor2, Floor3 というコレクションに分けてください。
BaseCubes は不要なら削除してください。

マテリアル付与

すべての Floor コレクションのキューブに高彩度ランダム色のマテリアルを割り当て直してください。

- 既存の Principled BSDF ノードを再利用してください。
- Base Color は HSV でランダムにしてください。
  Hue = 0〜1、Saturation = 0.9〜1.0、Value = 0.8〜1.0
- Metallic = 0.0、Roughness = 0.4 にしてください。
- 各キューブにはユニークなマテリアルを割り当ててください。

カメラとライト

カメラを追加してください。
位置は (25, -25, 20)、原点を向け、焦点距離は28mmにしてください。
このカメラをアクティブにしてください。

スポットライトを3つ追加してください。

Spot.001: 位置 (15, -10, 10)、強さ 5000、スポットサイズ 65°、ブレンド0.35、原点を向く。
Spot.002: 位置 (-15, -10, 8)、強さ 2500、スポットサイズ 65°、ブレンド0.35、原点を向く。
Spot.003: 位置 (0, 15, 15)、強さ 7000、スポットサイズ 60°、ブレンド0.35、原点を向く。

Eevee 設定

レンダーエンジンを EEVEE にしてください。
Ambient Occlusion と Bloom を有効にしてください。
ワールド背景色を #202020 にしてください。

最終的に使った Python コード(カメラ周回アニメ)

import bpy, math

scene = bpy.context.scene
scene.frame_start = 1
scene.frame_end = 120

# カメラ取得または作成
cam = scene.camera
if cam is None:
    bpy.ops.object.camera_add(location=(25,-25,20))
    cam = bpy.context.active_object
    scene.camera = cam
cam.rotation_mode = 'XYZ'

# 原点ターゲット
target = bpy.data.objects.get("TrackTarget")
if target is None:
    bpy.ops.object.empty_add(type='PLAIN_AXES', location=(0,0,0))
    target = bpy.context.active_object
    target.name = "TrackTarget"

# カメラに注視制約
if not any(c.type == 'DAMPED_TRACK' for c in cam.constraints):
    ct = cam.constraints.new('DAMPED_TRACK')
    ct.target = target
    ct.track_axis = 'TRACK_NEGATIVE_Z'
    ct.influence = 1.0

# 周回用ピボット
pivot = bpy.data.objects.get("OrbitPivot")
if pivot is None:
    bpy.ops.object.empty_add(type='PLAIN_AXES', location=(0,0,0))
    pivot = bpy.context.active_object
    pivot.name = "OrbitPivot"

# 親子付け
cam.parent = pivot
cam.matrix_parent_inverse = pivot.matrix_world.inverted()

# キーフレーム設定
pivot.rotation_mode = 'XYZ'
pivot.keyframe_insert("rotation_euler", frame=scene.frame_start)
pivot.rotation_euler.z = 2*math.pi
pivot.keyframe_insert("rotation_euler", frame=scene.frame_end)

# 線形補間
for fc in pivot.animation_data.action.fcurves:
    for kp in fc.keyframe_points:
        kp.interpolation = 'LINEAR'

# 出力設定
scene.render.filepath = "//camera_orbit.mp4"
scene.render.image_settings.file_format = 'FFMPEG'
scene.render.ffmpeg.format = 'MPEG4'
scene.render.ffmpeg.codec = 'H264'
scene.render.resolution_x = 1920
scene.render.resolution_y = 1080
scene.render.fps = 24

print("カメラ周回アニメ設定完了")

運用Tips:まず軽いpingでMCPの生存確認→本命リクエスト

MCP は死活監視が弱い。軽いping(例:シーン一覧取得)を先に投げ、応答確認してから本命リクエストを送ると安定。

繰り返しリクエストを投げると応答が途切れる場合があるので、長い処理はPython一発コードにまとめて実行した方が良い。

「自然語でのお膳立て」「Pythonでの仕上げ」の役割分担がベストバランス。