ゆるふわフィジカルAI
パソコンで作業中のゆるロボ

MuJoCoのビューアが開かない・固まるときの原因と直し方

2026-10-16 ・ 実践

#MuJoCo#トラブルシューティング#シミュレーション#実践

MuJoCoのビューアが開かない・固まるときの原因は、ほぼ次の3つに絞られます。「表示先がない(クラウドやSSH先にDISPLAYがない)」「macOSでlaunch_passiveをpythonから直接呼んでいる」「自分で書いたループでsync()を呼んでいない」。**ビューア(Viewer)**とは、MuJoCoに標準搭載された、物理シミュレーションの様子をリアルタイムで3D表示する可視化ウィンドウのことです。この記事では、症状別の原因と直し方を、公式ドキュメントで確認した内容と筆者が実際に再現した結果で整理します。

この記事で分かること

  • 「DISPLAY environment variable is missing」エラーは、クラウドやSSH先などGUIの表示先がない環境で起きる。Xvfbで仮想ディスプレイを用意すれば解決する(本記事で実際に再現・確認済み)
  • macOSで自作スクリプトから launch_passive を呼ぶ場合、python ではなく mjpython で実行する必要がある(公式ドキュメント)
  • 画面が固まったまま動かないときは、自作ループ内で viewer.sync() を呼び忘れているケースが多い

対象読者:MuJoCo入門を終えて、自分のPC・サーバーでビューアが動かず困っている人

執筆:ゆるふわフィジカルAI編集部 最終更新日:2026年10月16日

まず症状を確認する

症状多い原因対処
エラーで即終了するDISPLAYがない(クラウド・SSH先)Xvfbで仮想ディスプレイ/ローカルPCで実行
macOSで何も起きない・落ちるpythonからlaunch_passiveを呼んでいるmjpythonで実行する
ウィンドウは開くが真っ暗・固まるsync()を呼んでいない/ロック漏れループ内で毎回sync()を呼ぶ

次の章から、それぞれの原因と直し方を詳しく見ていきます。

症状1: 「DISPLAY environment variable is missing」で終了する

これは、ビューア用のウィンドウを表示する画面(ディスプレイ)が、今のマシンにないことが原因です。クラウドの仮想マシンやコンテナ、SSH接続先のLinuxサーバーでMuJoCo入門のコードをそのまま動かすと、この状態になります。

筆者も、GUIのないLinuxコンテナで次のコードを実行し、実際にこのエラーを再現しました。

Python
import mujoco
import mujoco.viewer
 
model = mujoco.MjModel.from_xml_string("<mujoco/>")
data = mujoco.MjData(model)
mujoco.viewer.launch_passive(model, data)
GLFWError: (65550) b'X11: The DISPLAY environment variable is missing'
ERROR: could not initialize GLFW

MuJoCoのビューアはGLFWというライブラリでウィンドウを開きますが、GLFWはLinux上ではX11(画面を表示する仕組み)を前提にしているため、DISPLAYがないと初期化自体に失敗します。

🌱

直し方: Xvfbで仮想ディスプレイを用意する

Linuxサーバーやコンテナで「画面はいらないが、ビューアのコードだけ動かしたい」場合は、仮想ディスプレイソフトXvfbを使います。xvfb-runで包むだけです。

ターミナル
xvfb-run -a python your_script.py

実際にこの方法で、上と同じコードのlaunch_passiveが正常に起動することを確認しました(viewer launched OK)。ただし実機の画面には何も映らないので、見た目を確認したいなら後述のオフスクリーン描画か、ディスプレイのあるPCで動かす方法を選んでください。

💡

見るためではなく記録するためなら、ビューアを使わない選択もある

動画や画像として結果を残したいだけなら、ウィンドウを開くviewerではなく、mujoco.Rendererによるオフスクリーン描画が向いています。環境変数MUJOCO_GLにegl(GPUがある場合)やosmesa(CPUのみ)を設定して使います。筆者の手元のGPUなし環境では、対応するグラフィックライブラリ(EGL/OSMesa)自体が入っていなかったため実行までは確認できませんでしたが、該当ライブラリを導入した環境向けの一般的な方法として紹介します。

症状2: macOSで実行しても何も起きない・すぐ終わる

自分で書いたPythonスクリプトの中でmujoco.viewer.launch_passive()を呼んでいる場合、macOSではpythonコマンドの代わりにmjpythonコマンドで実行する必要があります。これは公式ドキュメントに明記されているmacOS特有の制約で、理由は「ウィンドウの描画はメインスレッドで行う必要があるが、通常のpythonでは別スレッドになってしまう」ためです1。

ターミナル
# macOSでlaunch_passiveを使うスクリプトを動かすとき
mjpython your_script.py

mjpythonはmujocoパッケージに同梱されていて、公式ドキュメントでは「pythonコマンドの完全な代替として使え、同じコマンドライン引数がそのまま使える」と説明されています1。ターミナルで直接python -m mujoco.viewerを実行して様子がおかしいときも、mjpython -m mujoco.viewerに置き換えて試してみてください。

症状3: ウィンドウは開くが真っ暗・固まったまま動かない

MuJoCo入門のようにビューアへドラッグ&ドロップするだけなら自動で描画されますが、自分でシミュレーションループを書いてlaunch_passiveに渡している場合、ループの中でviewer.sync()を呼んでいないと画面が更新されません。公式ドキュメントは次のように説明しています。

ユーザースクリプトは、ビューアが物理状態の変化を反映できるようにsync()を呼び出す必要がある2

正しい最小ループは次の形です。

Python
import mujoco
import mujoco.viewer
 
model = mujoco.MjModel.from_xml_string("""
<mujoco>
  <worldbody>
    <light pos="0 0 3"/>
    <geom type="plane" size="2 2 0.1"/>
    <body pos="0 0 1"><freejoint/><geom type="box" size="0.1 0.1 0.1"/></body>
  </worldbody>
</mujoco>
""")
data = mujoco.MjData(model)
 
with mujoco.viewer.launch_passive(model, data) as viewer:
    while viewer.is_running():
        mujoco.mj_step(model, data)
        viewer.sync()  # これを忘れると画面が固まったまま

さらに、カメラの向きや表示オプション(viewer.camやviewer.opt)を自分のスクリプトから直接書き換える場合は、sync()の外側でviewer.lock()を使ってロックを取ってから変更する必要があります。ビューアは専用スレッドで動いているため、ロックなしで触ると競合が起きます3。

python -m mujoco.viewer と launch_passive の違い

どちらも同じビューアですが、使い方の目的が違います。

🖱️

python -m mujoco.viewer(ターミナルから直接)

  • 引数なしなら空のウィンドウが開く
  • MJCFファイルをドラッグ&ドロップして確認
  • `--mjcf=path`で直接開くことも可能
  • 手早く1つのモデルを見るのに向く
VS
🧩

launch_passive()(スクリプトの中から)

  • 自分のシミュレーションループに組み込める
  • `sync()`で毎ステップ描画を更新
  • カメラ操作等は`lock()`で保護
  • 強化学習やデータ収集スクリプトに向く

「とりあえずモデルの見た目を確認したい」なら前者、「自分のコードでステップを進めながら可視化したい」なら後者、というのが基本的な使い分けです。

よくある質問

Q. 「DISPLAY environment variable is missing」エラーが出ました。どうすればいいですか?

A. GUIを表示できる画面がない環境(クラウドのVM、コンテナ、SSH接続先など)でビューアを開こうとしたことが原因です。手元にディスプレイのあるPCで実行するか、仮想ディスプレイのXvfbを使って「xvfb-run -a python your_script.py」のように実行してください。

Q. macOSで python -m mujoco.viewer や自作スクリプトを実行しても何も起きません。

A. launch_passiveを使うスクリプトは、macOSではpythonではなくmjpythonコマンドで実行する必要があります。mjpythonはmujocoパッケージに同梱されており、pythonの完全な代替として同じ引数で使えます。

Q. ビューアのウィンドウは開くのに画面が固まったまま動きません。

A. 自分で書いたシミュレーションループの中でviewer.sync()を呼んでいないことが多いです。mj_step()で物理を進めたら、毎ステップ必ずviewer.sync()を呼んで画面に反映させてください。

まとめ

  • 「DISPLAY environment variable is missing」は表示先がない環境特有のエラー。Xvfbで仮想ディスプレイを用意すれば起動できる(実際に確認済み)
  • macOSで自作スクリプトからlaunch_passiveを使うときはpythonではなくmjpythonで実行する(公式ドキュメント)
  • 画面が固まるときは、ループ内のsync()忘れをまず疑う
  • 「見た目を素早く確認したい」ならpython -m mujoco.viewerへのドラッグ&ドロップ、「自分のコードに組み込みたい」ならlaunch_passive

もう少し詳しく(背景)

MuJoCoの標準ビューアはGLFWでウィンドウを作成しますが、GLFWはLinux上ではX11プロトコルに依存するため、X11サーバーに接続できない(=DISPLAYがない)環境では初期化できません4。これはMuJoCo固有の制約ではなく、GLFWを使うアプリ全般に共通する挙動です。macOSのmjpython要件は、Apple製OS特有のGUIイベント処理の都合(描画はメインスレッドでなければならない)に対応するためのものです1。launch_passiveが別スレッドでビューアを動かす設計になっているのは、ユーザーのPythonコード(強化学習の学習ループなど)をブロックせずに裏側で可視化を続けられるようにするためで、その代わりにsync()とlock()でメインのシミュレーションコードとの整合性を取る仕組みになっています23。

次の一歩 🌸

ビューアが動くようになったら、MuJoCo入門で振り子のシミュレーションから練習しましょう。Python環境そのものが不安ならPython環境構築の第一歩へ。シミュレーションでの練習を実機につなげる考え方はSim-to-Realの実践、本格的なGPUシミュレータが必要になったらNVIDIA Isaac Sim入門も合わせてどうぞ。

参考資料・出典

ゆるふわポータルでは、一次情報・公的資料にもとづき、専門用語をできるだけやさしく翻訳することを心がけています。内容に誤りや古い情報がある場合は、お問い合わせからご連絡ください。

執筆:ゆるふわフィジカルAI編集部 最終更新日:2026年10月16日

Footnotes

  1. MuJoCo公式Pythonドキュメント。macOSではlaunch_passiveを使うユーザースクリプトをmjpythonで実行する必要があると明記。mjpythonはpythonコマンドの完全な代替として同じ引数で使える。 ↩ ↩2 ↩3

  2. MuJoCo公式Pythonドキュメント。「ユーザースクリプトは、ビューアが物理状態の変化を反映できるようにsync()を呼び出す必要がある」と説明。 ↩ ↩2

  3. MuJoCo公式Pythonドキュメント。ビューアは専用スレッドで動作するため、物理・可視化状態(cam, opt, pert等)を変更する前にユーザースクリプト側でlock()によるロックを取る必要があると説明。 ↩ ↩2

  4. GLFW(ウィンドウ作成ライブラリ)はLinux上でX11プロトコルに依存する。X11サーバーに接続できない環境(DISPLAY未設定のクラウドVM・コンテナ等)では初期化に失敗する。 ↩

この記事をシェア:𝕏 で共有LINEで送る

🔥 この分野の最新トレンドをチェック →

あわせて読みたい

Gymnasium入門|CartPoleで強化学習を体験する

Gymnasiumとは、強化学習の環境をPythonの共通インターフェースで扱えるFarama Foundation製のオープンソースライブラリです。CartPole-v1でreset/stepの基本ループを動かし、ランダム行動と自分で書いた方策で結果がどれだけ変わるかを比較しながら体験します。

IMUセンサーフュージョンをPythonで実装する|加速度計とジャイロから傾きを求める(相補フィルタ)

IMU(加速度計+ジャイロ)から姿勢の傾きを求める相補フィルタをPythonでゼロから実装。加速度計だけだとノイズだらけ、ジャイロだけだとドリフトで増え続ける様子を数値で確認し、2つを混ぜるとなぜ安定するのかを手を動かして理解します。

A*経路探索をPythonで実装する|ロボットの「最短ルート」を見つける

ロボットや自律移動の経路計画で定番のA*(エースター)アルゴリズムをPythonでゼロから実装。ヒューリスティックがなぜ探索を速くするのか、グリッド上で障害物を避けて最短経路を見つける流れを、動くコードで理解します。

SLAM(自己位置推定と地図作成)をPythonで実装する|占有格子地図とパーティクルフィルタ

「地図が無いと自分の位置が分からず、位置が分からないと地図が作れない」というSLAMの鶏と卵問題を、占有格子地図とパーティクルフィルタでPython実装。ロボットが自分の位置と周囲の地図を同時に組み立てる仕組みをコードで理解します。