# シェーディング法線アーティファクトの発見と修正 (Shading Normal Artifact)

## 背景 — 発見の経緯 (Background)

[TEST_11](TEST_11_球体筐体実験.md) で幾何・光源を完全軸対称（球ロフト一体キャビティ＋軸上の円板単灯）にしたにもかかわらず，輝度マップが同心円にならず**横長の楕円帯**（X 方向に伸びた明帯）を示した．ユーザー指摘「3D は上下左右で違いがないのに輝度マップが円状にならないのは違和感がある」を起点に原因を切り分けた（2026-08-18）．

定量観察: 中心からの同心円環で角度別平均をとると，どの半径でも**右＝左 > 上＝下**のきれいな 2 回対称で，X/Y 比は 1.15〜1.26（浮き配置・ロフト版とも）．

## 切り分け (Diagnosis)

診断シーン（spp=256・他条件同一）による消去法:

| # | 診断シーン | X/Y 比 | 判定 |
| --- | --- | --- | --- |
| D1 | 球殻＋**円形プレート**（完全軸対称） | 1.00 | センサ・パイプラインはシロ（ortho_scale 50×52 も正しく処理） |
| D2 | 球殻＋**素の矩形プレート**（99×102，飾りなし） | 0.99 | 矩形輪郭はシロ |
| D3 | ロフト筐体＋円形プレート r=70 | 1.00 | ロフトの矩形→円スカートもシロ |
| D4 | ロフト筐体＋素の矩形プレート | 1.00 | 同上 |
| — | （両筐体＋**実物の評価板**） | 1.15〜1.26 | **評価板メッシュだけが非対称を生む** |
| D5 | ロフト筐体＋実物の評価板＋ **`face_normals=true`** | **1.000** | **原因確定＝シェーディング法線** |

補助調査: 評価板 STL の裏面（評価対象面）はレイキャストで無穴・完全平面（全ヒット z=-4.5）を確認済み．よって幾何そのものではなく描画法線の問題．

## 原因 (Root Cause)

3 つの要因の組み合わせ:

1. **builder.py が STL→PLY 変換時に法線を書き出していなかった**（`trimesh.Trimesh(...).export(ply)` は頂点座標と面のみ）
2. 法線のない PLY に対して **Mitsuba は既定でスムーズ頂点法線を自動生成**する（`face_normals` 未指定 = false）
3. 評価板の裏面は **26 枚の巨大三角形（最大 4794mm²）**で構成され，**裏面の全 28 頂点がエッジ上**（内部頂点ゼロ）にある．エッジ頂点の平均化法線は側面との混合で**約 47° 傾く**ため，見えている裏面全体がこの傾いた法線の Phong 補間で描画される

結果として，三角形分割の走り方に応じた大規模で滑らかな偽の異方性（cos 因子の歪み）が評価対象面の輝度マップに乗る．素の矩形プレート（D2/D4）で出なかったのは，トリメッシュ生成の box は分割が対称で汚染パターンが相殺されたため．

## 修正 (Fix)

`scene/builder.py` の PLY shape dict に **`face_normals: True`** を追加（2026-08-18）．機械部品 STL は平面＋シャープエッジ構成であり，面法線が物理的に正しい．曲面（球殻 icosphere subdiv=6，辺長 ≈3mm）のファセット化誤差はスムーズ法線の系統誤差より桁違いに小さい．再発防止テスト（`face_normals` キーの存在検証）を追加．

## 影響の定量化 (Requantification)

修正前後の同一シーン比較（spp=1024・d=16・blend w0.05_a0.1・mean アンカー 207.82，2026-08-18）．
再構成 full_base シーン（`configs/_tmp_fn_full_base.yaml`）は修正前コードで保存済み結果と一致
（ROI mean 5 桁一致・CoV 差 0.0001）を確認してから使用した．

| 指標 | 現行筐体 全灯 修正前 | **現行筐体 全灯 修正後** | 球ロフト円形 修正前 | **球ロフト円形 修正後** | 実機 全灯 |
| --- | --- | --- | --- | --- | --- |
| CoV | 0.1717 | **0.0610** | 0.1205 | **0.0628** | 0.0695 |
| peak/mean (ROI) | 1.491 | **1.305** | 1.393 | **1.195** | 1.155 |
| top/bot (ROI) | 0.878 | **0.962** | 1.020 | **1.007** | 0.919 |
| C/P ratio | 1.328 | **1.073** | — | **1.163** | 1.091 |
| gradient [%] | 24.72 | **6.83** | — | **14.01** | 8.35 |
| angular_ratio | 0.352 | **0.341** | 0.110 | **0.005** | — |
| radial min/max | 0.638 | **0.888** | — | **0.787** | 0.860 |
| NCC 対実機 | 0.744 | **0.616** | 0.445 | **0.436** | 1.0 |
| mean/max・min/max | 0.671・0.241 | **0.766・0.549** | 0.718・0.502 | **0.837・0.671** | 0.899・0.660 |

図: `output/_slide_panels/sphere_fix_{shape_roi,relative}.png`（修正後のみの 1×3 比較）．
出力: `output/full_base_fix/`・`output/sphere_r100_loft_disk_fix/`．

## 読み取れること (Findings)

1. **アーティファクトはシミュの不均一性の支配的成分だった**．現行筐体全灯の CoV 0.1717 のうち
   実に 6 割超がシェーディング法線起因で，修正後は **0.0610 と実機の 0.0695 を下回る**．
   Step 8-13 以降「実機との残差 ≈2.4×」としてきたものの大半は物理モデルの誤差ではなく
   本アーティファクトだった．
2. **現行筐体シミュは修正後，実機と数値レベルでほぼ整合する**: 勾配 6.83% vs 実機 8.35%，
   C/P 1.073 vs 1.091，radial min/max 0.888 vs 0.860．長年の「Y 軸単調勾配」の見かけの強さも
   大幅に減った（top/bot 0.878 → 0.962，実機 0.919）．
3. **球体筐体の優位性は消滅**: 修正後は現行筐体 0.0610 vs 球ロフト 0.0628 でほぼ同等＝
   **現行筐体はすでに理想積分球に近い混合性能を持っている**．TEST_11 の「幾何レバーで
   CoV -30%」という差分もほぼ全てアーティファクトの差だった．
4. 球ロフトの angular_ratio は 0.005（完全な同心円）となり，軸対称セットアップの
   検証としても閉じた．
5. NCC 対実機は 0.744 → 0.616 と低下しており，**残る差分は「形」**（実機は右下明・左上暗の
   斜め構造，シミュは上端暗）に移った．振幅系の指標が揃った今，形の残差が次の主題になる．

## 過去結論への影響 (Impact on Past Conclusions)

本アーティファクトは評価板メッシュを使う**全過去レンダ**（Step 8 キャリブ履歴・TEST_09/10 の全実験）
に共通して乗っている．取り扱い:

- **絶対値・形の結論は要再検証**: 「残差 2.63×/2.4×」「Y 軸単調勾配」「単灯 peak/mean 1.5〜1.7」
  等の絶対量はアーティファクト込みの数値であり，修正後の再測定で再基準化が必要．
- **相対比較の結論は多くが生き残る見込み**: 比較対象同士が同じアーティファクトを共有するため，
  「ロフト重みに不感（TEST_10）」「材料・光源レバーの序列（TEST_09）」等の相対結論は
  方向性としては保持されると考えられる．ただし灯間 NCC 等の形ベース指標は感度が変わりうるため，
  結論を引用する際は「修正前データに基づく」ことを明記する．
- `output/` の修正前出力は歴史的記録として保持（[TEST_02](TEST_02_シミュレーション実行記録.md)
  冒頭の BEFORE FIX 注記を参照）．

## 再現コマンド (Reproduction)

```powershell
# 修正版レンダ（builder が face_normals=true を自動適用）
$env:DRJIT_LIBLLVM_PATH = "C:\Program Files\LLVM\bin\LLVM-C.dll"
uv run sm-render --scene configs/_tmp_fn_full_base.yaml --output output/full_base_fix/
uv run sm-render --scene configs/scene_sphere_r100_loft_disk.yaml --output output/sphere_r100_loft_disk_fix/
uv run sm-evaluate --image output/full_base_fix/luminance.npy --config configs/eval_baseline_dng_target_shape.yaml --output output/full_base_fix/
uv run sm-evaluate --image output/sphere_r100_loft_disk_fix/luminance.npy --config configs/eval_baseline_dng_target_shape.yaml --output output/sphere_r100_loft_disk_fix/
uv run python scripts/build_sphere_panel.py
```

## 参照 (References)

- [TEST_11_球体筐体実験](TEST_11_球体筐体実験.md): 発見の起点となった実験（結論の改訂を含む）
- [TEST_08_面光源スケール軸バグの発見と修正](TEST_08_面光源スケール軸バグの発見と修正.md): 同種の系統誤差発見の前例（実測グラウンドトゥルースとの突合・消去法診断の有効性）
- [TEST_02_シミュレーション実行記録](TEST_02_シミュレーション実行記録.md): 再現コマンド
