本ドキュメントは,実機の単灯(下/左/右)実測データとシミュレーションを 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 | シミュ(バグ) 比 |
|---|---|---|---|---|
| bottom | 57.75 | 1.00(最暗) | 98.82 | 2.10 |
| left | 61.46 | 1.06 | 47.08 | 1.00 |
| right | 63.55 | 1.10 | 47.10 | 1.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 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 を向くため,発光の向き自体は正しい(このバグでは形状サイズだけが壊れる).
全 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] との関係 |
|---|---|---|
| bottom | X∈[-10, +18] | 板中央の真裏(中央に密集) |
| left | X = -66 | 板の外(左端より 16.5 mm 外側)+側壁/カバー脇 |
| right | X = +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 に戻る.
修正後に 3 単灯+全灯合成を再レンダ(単灯 output/sanity_fix_1024/,全灯 output/baseline_fix_1024/,いずれも spp=1024 / max_depth=16).暫定 spp=256 出力は output/led_group_sanity_fix/.
| 灯 | バグ 比 | 修正後 比 | 実機 比 |
|---|---|---|---|
| bottom | 2.099 | 1.000(最暗) | 1.000(最暗) |
| left | 1.000 | 1.028 | 1.064 |
| right | 1.000 | 1.026 | 1.100 |
→ 下灯 2 倍は消滅し,3 灯が ±3% で均等化.さらに**「下灯が最暗」という実機の順序まで一致**した.灯間の振幅バランスは実機パターンに乗った.
| 灯 | バグ peak/mean | 修正後 peak/mean | 実機 peak/mean |
|---|---|---|---|
| bottom | 1.47 | 1.64 | 1.22 |
| left | 1.63 | 1.56 | 1.27 |
| right | 1.58 | 1.61 | 1.20 |
単灯 3 本の和と全灯合成の ROI mean を比較:相対誤差 0.34%.Mitsuba の理論線形性どおりで,レンダの健全性を確認.
| シーン | linear CoV | 対実機残差 | center/周辺比 | gradient |
|---|---|---|---|---|
修正前(baseline_v3_leds,バグ) | 0.1815 | 2.61× | 1.318 | 24.13 |
修正後(baseline_fix_1024) | 0.1641 | 2.36× | 1.311 | 23.71 |
| 実機 | 0.0695 | — | — | — |
(参考:OETF/sRGB ドメインでは修正後 CoV=0.0790)
→ 合成 CoV は 0.1815→0.1641(残差 2.63×→2.36×)と小改善にとどまる.center/周辺比・勾配はほぼ不変.灯間バランスは直ったが,合成の主な不均一(中央明・縦勾配)は灯間バランスではなく各単灯フットプリントの拡散不足に由来するため,本バグ修正だけでは合成 CoV は大きく動かない(ユーザーの事前直感どおり).
「全 LED が高さ 2m のストリップ化していた」という主張を,可視化マーカーの再現に頼らず修正前の実コードと Mitsuba 自身の測定で独立に裏付けた.3 重の証拠:
修正コミット 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 に誤って入れていた
修正前の式をそのまま使って実 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 mm | 2000.00 mm | 0 | 5600 mm² |
修正後 scale([sw/2, sh/2, 1.0]) | 2.80 mm | 3.50 mm | 0 | 9.8 mm²(実物一致) |
修正前の bbox は **Y[-1.071m, +0.929m]**=LED 中心 y=-71mm を中心に ±1000mm,高さちょうど 2000mm.実物 9.8mm² に対し 約 570 倍の面積(5600mm²)の 2m ストリップであったことが,Mitsuba 自身の測定で確定.
可視化専用ではなく,本線のレンダリングで使われていたことを確認:
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 注記参照)が,結論は不変.
各マップを自身の mean で割って形だけを比べる(図 output/_slide_panels/before_after_shape_roi.png,ROI 揃え):
| 灯 | 修正前 peak/mean | 修正後 peak/mean |
|---|---|---|
| bottom | 1.47 | 1.64 |
| left | 1.63 | 1.56 |
| right | 1.58 | 1.61 |
| combined | 1.45 | 1.47 |
→ 正規化した「形」は修正前後でほとんど変わらない(合成は 1.45→1.47 でほぼ不変).バグの主効果は形ではなかった.
3 単灯を「left LED の mean = 1.0」基準の共通スケールで並べる(図 output/_slide_panels/before_after_amplitude_roi.png,ROI 揃え):
| 灯 | 修正前(left=1.0) | 修正後(left=1.0) |
|---|---|---|
| bottom | 2.10 | 0.97 |
| left | 1.00 | 1.00 |
| right | 1.00 | 1.00 |
→ バグが実際に壊していたのはこれ.修正前は下灯だけ 2.10× 突出,修正後は 3 灯均等(下灯やや最暗で実機順序一致).
バグ修正で直ったのは「下灯が 2 倍明るい」という灯間バランスであり,各灯の分布の“形”自体は元々あまり変わっていない(だから合成 CoV も 0.1815→0.1641 の小改善にとどまった).「カラーマップの形状」という観点では正規化すると修正前後で大差なく,違いは明るさの偏りに出ていた.形の残差は別問題(TEST_09 で B1 筐体幾何に絞り込み中).
output/led_group_sanity{,_fix}/ の索引src/sm_tias_lightsim/scene/lights.py _build_area_light_transformlightsim/(単灯実測.出典 SmTIAS-Evaluation TEST_04)scripts/show_led_geometry.py(LED を実サイズ長方形+発光方向矢印で表示.--buggy で修正前の 2m ストリップ形を再現,--screenshot で 4 アングル保存).修正前後の比較図 output/_slide_panels/led_geom{,_buggy}_{iso,front_XY,side_XZ,top_YZ}.png