Newer
Older
SmTIAS-LightSim / docs / 06_TEST / TEST_08_面光源スケール軸バグの発見と修正.md

面光源スケール軸バグの発見と修正 (Area-Light Scale-Axis Bug)

本ドキュメントは,実機の単灯(下/左/右)実測データとシミュレーションを 1 灯ずつ突き合わせた結果,シミュ側の面光源(area emitter)変換に座標軸の取り違えバグが見つかり,これが「下灯がほかの 2 倍強い」という長年の不一致の主因だったことの発見・原因・修正・検証を記録する.

⚠️ 本件により TEST_07 LED グループ別動作確認 の結論「下灯 2.1× は物理的に正しい/意図どおり」は誤りであったことが判明した(TEST_07 はバグ込みの出力を物理的に正当化してしまっていた).TEST_07 冒頭にも訂正注記を追加済み.

発端 — 実機単灯データとの突き合わせ

実機(SmTIAS-Evaluation)で 3 灯を 1 灯ずつ単独点灯して撮影した白板 ROI 輝度マップ一式を入力として受領した(lightsim/:luminance_maps/{bottom,left,right}_*.npy,0〜255 linear / Rec.709 luma / LSC 補正済 / portrait).

ユーザー観察:「シミュでは下側がほかの約 2 倍になっているが,実機では 3 灯ほぼ均等」.これを灯ごとに分解して原因を特定するのが目的.

灯ごとの白板 ROI 平均輝度(実機)と,既存シミュ単灯出力(output/led_group_sanity/,spp=1024)を比較:

灯実機 mean実機 比シミュ(バグ) meanシミュ(バグ) 比
bottom57.751.00(最暗)98.822.10
left61.461.0647.081.00
right63.551.1047.101.00

→ 実機は 3 灯ほぼ均等で下灯が最暗.シミュは下灯だけ 2.10×で,しかも左右が 47.08 vs 47.10 と完全対称.傾向が逆転している.

LED 定義(configs/leds_extracted.yaml)は全 18 灯が direction:(0,0,-1)・radiance:2807.25 で完全同一.LED の位置・向き・強度は Fusion 360 STL 由来で実機どおりであり,radiance 差では説明できない.したがって原因は LED 以外(周囲幾何・変換処理)にあると絞り込んだ.

原因 — 面光源スケールの軸取り違え

Mitsuba の rectangle 仕様(実機検証)

Mitsuba 3 の rectangle 形状を実際に生成して確認した:

中心法線 n = (0, 0, 1)        # 面は +Z を向く
面内範囲 = X∈[-1,1], Y∈[-1,1]  # XY 平面に寝た 1 辺 2 の正方形
Z 方向 = 厚みゼロ(潰れている)

→ rectangle は「XY 平面・法線 +Z」.サイズを与えるには 横 = X 軸,高さ = Y 軸をスケールしなければならない(Z は潰れているのでスケールしても無意味).

バグ箇所

src/sm_tias_lightsim/scene/lights.py _build_area_light_transform:

# 修正前(バグ)
scale = mi.ScalarTransform4f().scale([sw / 2.0, 1.0, sh / 2.0])
#                                       横X    縦Y     奥Z
#   横(X) = sw/2     … OK
#   縦(Y) = 1.0      … ★ スケールされず基準のまま = ±1 m(2000 mm)
#   奥(Z) = sh/2     … 高さを“潰れた厚み方向”に入れてしまい無効

コード上のコメントは「rectangle は XZ 平面上の正方形(Y 軸正方向が前面)」と書かれていたが,実体は XY 平面・+Z 前面.前提を 1 軸勘違いしたまま実装されており,高さ sh を縦(Y) ではなく奥行き(Z) に入れ,縦(Y) は無補正のまま放置されていた.

単位はメートル系(mm→m に position_scale=0.001 済)なので,縦が無補正 = ±1 m.結果:

実物 2.8 × 3.5 mm の小さな LED が,シミュ内では 「幅 ~3 mm × 高さ 2000 mm」の細長い巨大ストリップになっていた.

法線(発光方向)は look_at で正しく direction を向くため,発光の向き自体は正しい(このバグでは形状サイズだけが壊れる).

なぜ「下灯だけ 2 倍」になったか

全 18 灯が同一の look_at(direction=(0,0,-1), up=(0,1,0))を使うため local Y→world Y.つまり全 LED が world Y 方向に 2 m 伸びた縦ストリップになる.各灯列の横位置(X)と評価板の関係:

灯LED 横位置 X評価板 X 範囲 [-49.5, +49.5] との関係
bottomX∈[-10, +18]板中央の真裏(中央に密集)
leftX = -66板の外(左端より 16.5 mm 外側)+側壁/カバー脇
rightX = +66板の外(右端より 16.5 mm 外側)+側壁/カバー脇

2 m 縦長化により,板中央の真裏にある下灯だけが板中央を効率よく照らし,板の外側にある左右灯は伸びた光の多くが壁/カバーに当たって板に届きにくい.本来の小さな ~3.5 mm 面光源ならこの縦長効果は生じず偏らない.バグによる巨大化が,たまたま中央裏にある下灯を過剰に効かせて 2.1× を生んでいた.

TEST_07 はこの 2.1× を「直接光効率の幾何的優位=意図した設計」と解釈してしまったが,実際は変換バグによるアーティファクトだった.

修正

スケールの軸を 1 か所入れ替えるのみ:

# 修正後
scale = mi.ScalarTransform4f().scale([sw / 2.0, sh / 2.0, 1.0])
#                                       横X    縦Y     奥Z
#   横(X) = sw/2  縦(Y) = sh/2  奥(Z) = 1.0(潰れた厚みは無補正でよい)

高さ sh を,誤って入れていた奥行き(Z) から本来の縦(Y) へ移しただけ.これで LED が実物どおりの ~2.8 × 3.5 mm に戻る.

検証結果(spp=1024, max_depth=16 確定)

修正後に 3 単灯+全灯合成を再レンダ(単灯 output/sanity_fix_1024/,全灯 output/baseline_fix_1024/,いずれも spp=1024 / max_depth=16).暫定 spp=256 出力は output/led_group_sanity_fix/.

① 灯間バランス(mean 比,spp=1024)

灯バグ 比修正後 比実機 比
bottom2.0991.000(最暗)1.000(最暗)
left1.0001.0281.064
right1.0001.0261.100

→ 下灯 2 倍は消滅し,3 灯が ±3% で均等化.さらに**「下灯が最暗」という実機の順序まで一致**した.灯間の振幅バランスは実機パターンに乗った.

② 各灯の形(peak/mean,spp=1024,ROI 揃え)

灯バグ peak/mean修正後 peak/mean実機 peak/mean
bottom1.471.641.22
left1.631.561.27
right1.581.611.20
  • 絶対 mean は ~98/47 → ~4.5 へ低下:発光面積が 2.8mm×2000mm → 2.8mm×3.5mm へ約 570 倍縮小したため.絶対スケールは下流の露出ソルバ / mean anchor 正規化で吸収されるため灯間比・形が本質.
  • peak/mean はなお実機(1.2 台)に届かず(修正後 1.6 台)=各単灯フットプリントの拡散不足が残る.形の残差は TEST_09 で B1 筐体幾何に絞り込み中.

③ 線形性(単灯和 ≈ 全灯)

単灯 3 本の和と全灯合成の ROI mean を比較:相対誤差 0.34%.Mitsuba の理論線形性どおりで,レンダの健全性を確認.

④ 全灯合成 CoV(公式 sm-evaluate, ROI image_pixel 464×450)

シーンlinear CoV対実機残差center/周辺比gradient
修正前(baseline_v3_leds,バグ)0.18152.61×1.31824.13
修正後(baseline_fix_1024)0.16412.36×1.31123.71
実機0.0695———

(参考:OETF/sRGB ドメインでは修正後 CoV=0.0790)

→ 合成 CoV は 0.1815→0.1641(残差 2.63×→2.36×)と小改善にとどまる.center/周辺比・勾配はほぼ不変.灯間バランスは直ったが,合成の主な不均一(中央明・縦勾配)は灯間バランスではなく各単灯フットプリントの拡散不足に由来するため,本バグ修正だけでは合成 CoV は大きく動かない(ユーザーの事前直感どおり).

バグ実在の独立検証(報告用の一次証拠)

「全 LED が高さ 2m のストリップ化していた」という主張を,可視化マーカーの再現に頼らず修正前の実コードと Mitsuba 自身の測定で独立に裏付けた.3 重の証拠:

証拠1 — 修正前の実コード(git で確定)

修正コミット 84b0ee5 の diff より,修正前の実行は文字どおり以下だった:

# src/sm_tias_lightsim/scene/lights.py  _build_area_light_transform(修正前)
scale = mi.ScalarTransform4f().scale([sw / 2.0, 1.0, sh / 2.0])
#                                       横X    縦Y     奥Z
#   縦 Y を 1.0 のまま(無補正)=基準矩形 ±1(単位 m)のまま,高さ sh を潰れた Z に誤って入れていた

証拠2 — Mitsuba 自身が報告する矩形サイズ

修正前の式をそのまま使って実 LED(led_bottom_0, center=(-10,-71,-66.1)mm, size=2.8×3.5mm, dir=(0,0,-1))の rectangle を生成し,Mitsuba の shape.bbox() に世界サイズを報告させた結果:

X 幅Y 高さZ面積
修正前 scale([sw/2, 1.0, sh/2])2.80 mm2000.00 mm05600 mm²
修正後 scale([sw/2, sh/2, 1.0])2.80 mm3.50 mm09.8 mm²(実物一致)

修正前の bbox は **Y[-1.071m, +0.929m]**=LED 中心 y=-71mm を中心に ±1000mm,高さちょうど 2000mm.実物 9.8mm² に対し 約 570 倍の面積(5600mm²)の 2m ストリップであったことが,Mitsuba 自身の測定で確定.

証拠3 — この変換が実レンダで使われていた経路

可視化専用ではなく,本線のレンダリングで使われていたことを確認:

builder.py:139 build_light_dict() → lights.py:98 _build_area_light_transform()(バグの scale)
  → scene_dict → mi.render()

→ 結論: 「にわかに信じがたいが本当にそうなっていた」が正しい. 全 18 LED が幅 2.8mm × 高さ 2000mm の縦ストリップとしてレンダされていた.検証スクリプトは一時的に実行(mi.ScalarTransform4f で修正前後の式を適用し rectangle.bbox() を比較).

修正前後のカラーマップ比較 — 「バグが壊したのは形ではなく灯間バランス」

修正前(バグ)と修正後のレンダ結果(輝度マップ)を,単灯3+合成で並べて比較した.正規化(形)と共通スケール(灯間明るさ)の 2 視点で見ると,バグの主効果が明確になる.

以下の数値・図は ROI 揃え版(シミュを SIM_ROI=(24,37,464,450) で切り出し).旧版はフル 512² frame で算出していた(TEST_09 の ROI 注記参照)が,結論は不変.

視点 A — mean 正規化(=「形」の比較)

各マップを自身の mean で割って形だけを比べる(図 output/_slide_panels/before_after_shape_roi.png,ROI 揃え):

灯修正前 peak/mean修正後 peak/mean
bottom1.471.64
left1.631.56
right1.581.61
combined1.451.47

→ 正規化した「形」は修正前後でほとんど変わらない(合成は 1.45→1.47 でほぼ不変).バグの主効果は形ではなかった.

視点 B — 共通スケール(=「灯間の明るさ」の比較)

3 単灯を「left LED の mean = 1.0」基準の共通スケールで並べる(図 output/_slide_panels/before_after_amplitude_roi.png,ROI 揃え):

灯修正前(left=1.0)修正後(left=1.0)
bottom2.100.97
left1.001.00
right1.001.00

→ バグが実際に壊していたのはこれ.修正前は下灯だけ 2.10× 突出,修正後は 3 灯均等(下灯やや最暗で実機順序一致).

結論

バグ修正で直ったのは「下灯が 2 倍明るい」という灯間バランスであり,各灯の分布の“形”自体は元々あまり変わっていない(だから合成 CoV も 0.1815→0.1641 の小改善にとどまった).「カラーマップの形状」という観点では正規化すると修正前後で大差なく,違いは明るさの偏りに出ていた.形の残差は別問題(TEST_09 で B1 筐体幾何に絞り込み中).

学びと残課題

  1. 灯間の量的バランス問題は解決:下灯 2.1× は変換バグのアーティファクトであり,修正で実機どおり(3 灯均等・下灯最暗)になった.
  2. 論点が移動:残る不一致は「灯間バランス」ではなく各単灯フットプリントの拡散不足(peak/mean がシミュ 2.9 vs 実機 1.2).候補は LED 配光 ξ(指向の広がり)・ディフューザーのモデル化・面光源サイズの実測整合など.
  3. 最終目標(3 灯合成 CoV)への効果は小さい(確定):spp=1024 で合成 CoV は 0.1815→0.1641(残差 2.63×→2.36×)の小改善にとどまった.残差の主因は灯間バランスではなく各単灯フットプリントの拡散不足であり,本修正だけでは合成 CoV は大きく動かない.次の本筋は配光 ξ/ディフューザー拡散のモデル化.
  4. 過去の正当化に注意:TEST_07 のように「シミュ出力を物理で後付け説明」すると,変換バグを設計意図と取り違える危険がある.実機グラウンドトゥルースとの灯ごと突き合わせが有効だった.

関連ドキュメント

  • TEST_07 LED グループ別動作確認 — バグ込み出力に基づく旧結論(本件で訂正)
  • TEST_01 キャリブレーション検証履歴 — Step 8 系残差 2.63× の経緯
  • TEST_02 シミュレーション実行記録 — output/led_group_sanity{,_fix}/ の索引
  • 修正箇所: src/sm_tias_lightsim/scene/lights.py _build_area_light_transform
  • 実機入力: lightsim/(単灯実測.出典 SmTIAS-Evaluation TEST_04)
  • LED 幾何の 3D 目視確認ツール: scripts/show_led_geometry.py(LED を実サイズ長方形+発光方向矢印で表示.--buggy で修正前の 2m ストリップ形を再現,--screenshot で 4 アングル保存).修正前後の比較図 output/_slide_panels/led_geom{,_buggy}_{iso,front_XY,side_XZ,top_YZ}.png