シミュレーションとコンピュータグラフィックス

シミュレーションは「状態遷移の正しさ」、CGは「観測の見え方」を担います。世界モデルではこの2層を分けて考えることが実装上の要点です。

動きと見え方は、別の層で作られている

物体がどう動くかを決めるのはシミュレーションで、その結果がセンサーや画像としてどう見えるかを決めるのは描画側です。世界モデルを作るとき、この二つを混ぜると原因の切り分けが難しくなります。

ここでは放物運動のごく小さな例を使って、状態更新と 2D グリッドへの描画を分けて見ます。やりたいのは CG を学ぶことそのものではなく、『物理の間違い』と『見え方の間違い』を別々に扱う感覚を持つことです。

反発係数ひとつでも、物理と観測は役割が違う

vy *= -0.4 は床にぶつかったあとの跳ね返り方を決める物理側の設定です。一方で canvas* を置く処理は、その状態をどんな観測へ変換するかという表示側の設定です。二つは同じ誤差に見えても、直す場所が違います。

この notebook の見どころ

最初の状態列、グリッド上の軌跡、床反射の挙動を順に見ながら、どこまでが遷移でどこからがレンダリングかを切り分けます。

現実へ持っていくときに効くのも、この分離です。sim-to-real でズレたとき、『世界の動きが違うのか、見え方が違うのか』をまず切り分けないと調整方針が立ちません。

丸めが生む別種の誤差

連続座標をグリッドに落とすと、物理は合っていても見た目が粗くなります。これは遷移モデルの失敗ではなく観測化の副作用で、学習器には別種のノイズとして入ります。

読み方の軸

この notebook は物理エンジンや本格レンダラの代わりではありません。状態更新と観測生成を分けて設計する理由を、最小例で確かめるためのものです。

状態を時間発展させる

まずは位置と速度を更新し続けて、世界の側で何が起きているかを数値として追います。

import numpy as np

dt = 0.1
g = -9.8
x, y = 0.0, 0.0
vx, vy = 2.0, 6.0
traj = []
for _ in range(25):
    x += vx * dt
    y += vy * dt
    vy += g * dt
    if y < 0:
        y = 0
        vy *= -0.4
    traj.append((x, y))
print('first 5 simulated states =', [(round(a,2), round(b,2)) for a,b in traj[:5]])

数値の軌跡を見える形にする

次に、その状態列を粗いキャンバスへ写して、観測として何が見えているかを確かめます。

# CG: 連続状態をグリッドに投影して可視化
W, H = 40, 12
canvas = [['.' for _ in range(W)] for _ in range(H)]

for x, y in traj:
    px = min(W - 1, max(0, int(x * 3)))
    py = min(H - 1, max(0, int(y * 1.5)))
    canvas[H - 1 - py][px] = '*'

for row in canvas:
    print(''.join(row))

この分離を保っておくと、ズレが出たときに『物理を直すのか、描画を直すのか』を判断しやすくなります。世界モデルの実装では、この切り分け自体がかなり重要です。