# シェーディング法線アーティファクトの発見と修正 (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 注記を参照）．

## 追補 — 単灯相対結論の再検証 (Addendum: Single-Light Re-verification)

TEST_09 実験5 の中核結論「シミュは 3 灯がほぼ同形（灯間 NCC 0.95x）＝実機の非対称相補性
（0.85/0.61/0.41）を原理的に再現できない → 残差の唯一の候補は B1 非対称幾何」を，
基準単灯 3 灯（blend w0.05_a0.1・d=16・spp=1024，`configs/_tmp_fix_{bottom,left,right}.yaml`）の
修正後再レンダで再検証した（2026-08-18．ロフト w1p4 系はユーザー判断でスキップ）．

### 灯間 NCC（SIM_ROI・mean 正規化）

raw マップの NCC はノイズで希釈される（無相関ノイズが相関を 0 方向へ引っ張る．単灯
spp=1024 はノイズ 5.3% > 構造振幅 3.8〜4.4% でノイズ支配）ため，
平滑化（median 5px + gaussian σ=4）後の構造のみの NCC を正とする:

| セット | bottom-left | bottom-right | left-right |
| --- | --- | --- | --- |
| 修正前（平滑） | 0.987 | 0.992 | 0.986 |
| **修正後（平滑）** | **0.854** | **0.934** | **0.727** |
| 実機（平滑） | 0.917 | 0.662 | 0.440 |
| （参考）raw: 修正前 / 修正後 / 実機 | 0.888 / 0.363 / 0.853 | 0.891 / 0.392 / 0.606 | 0.869 / 0.329 / 0.414 |

### 単灯の形（summary.csv 抜粋）

| 灯 | peak/mean（修正前→修正後） | top/bot（修正前→修正後） | 実機 |
| --- | --- | --- | --- |
| bottom | 1.672 → 1.648 | 0.872 → 0.968 | p/m 1.218・t/b 0.931 |
| left | 1.613 → 1.473 | 0.875 → 0.956 | p/m 1.267・t/b 0.922 |
| right | 1.546 → 1.521 | 0.890 → 0.963 | p/m 1.201・t/b 0.946 |

※ 修正後の単灯マップはほぼ平坦で raw の peak/mean はノイズスパイク支配．以後の単灯実験は
spp 増強（≥4096）または平滑化後指標を使うこと．

### 判定

1. **「3 灯ほぼ同形」は撤回**: 修正前の高相関（0.99）は全マップ共通のアーティファクト構造が
   作っていた見かけだった．修正後のシミュは灯ごとに明確に異なるフットプリントを持つ．
2. **非対称相補性は部分的に再現される**: 3 ペアの NCC は 0.73〜0.93 と非均等で，
   「left-right が最も異なる」順序は実機（0.44 が最小）と一致する．ただし差の振幅は
   実機（0.92→0.44）より弱く（0.93→0.73），最類似ペアも実機 b-l vs シミュ b-r で異なる．
3. **単灯の平らさ残差は縮小して残る**: top/bot は 0.96 前後で実機（0.92〜0.95）とほぼ整合．
   peak/mean の差は残るが raw 指標がノイズ支配のため要再測定（高 spp）．
4. **含意**: TEST_09 の「等方な変更では 3 ペアを一様にしか動かせない → 非対称幾何（漏れ・開口）
   の検証が必須」という実験4 の主動機は，アーティファクト起因として大幅に弱まった．
   残る差分（相補性の振幅・最類似ペアの並び・実機の斜め構造）の切り分けを優先課題として
   再設定するのが妥当．

成果物: `output/singles_fix/{bottom,left,right}/`・`output/singles_fix/{summary,pairwise_ncc}.csv`
（raw / smooth 両方を記録，解釈は smooth を正とする）・
図 `output/_slide_panels/singles_fix_shape_roi.png`・集計 `scripts/build_singles_fix_summary.py`．

### ロフト w1.4 の再検証（TEST_10 相対結論）

続けてロフト w=1.4（`後側_w1p4.stl`）を修正版で再レンダ（全灯 1 + 単灯 3，
`configs/_tmp_fix_{full,bottom,left,right}_w1p4.yaml` → `output/full_w1p4_fix/`・
`output/singles_fix/{bottom,left,right}_w1p4/`，2026-08-18）．

全灯（`eval_baseline_dng_target_shape.yaml`）:

| 指標 | 基準 fix | w1p4 fix | 差 |
| --- | --- | --- | --- |
| CoV | 0.06103 | 0.06051 | -0.9% |
| gradient [%] | 6.83 | 6.75 | -1.2% |
| C/P | 1.0733 | 1.0723 | -0.1% |
| angular_ratio | 0.3413 | 0.3332 | -2.4% |

単灯（平滑後 peak/mean）と灯間 NCC（平滑）:

| | bottom | left | right | | b-l | b-r | l-r |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 基準 fix | 1.0692 | 1.0745 | 1.0732 | 基準 fix | 0.854 | 0.934 | 0.727 |
| w1p4 fix | 1.0600 | 1.0750 | 1.0676 | w1p4 fix | 0.909 | 0.958 | 0.844 |
| 実機 | 1.1224 | 1.1567 | 1.1122 | 実機 | 0.917 | 0.662 | 0.440 |

TEST_10 の 4 結論の判定:

1. **④「ロフトは全灯に実質不感」— 維持**（全指標差 ≈1% でノイズ同等）．実機 w1.4 測定
   `MiniTIAS_QM_20260726_183416` への予測「従来筐体とほぼ同じ」も維持される．
2. **③「対称なキャビティ拡大は 3 灯を同形化する方向」— 維持・明確化**: 灯間 NCC は
   w1p4 で +0.05〜+0.12 上昇（0.85/0.93/0.73 → 0.91/0.96/0.84）＝実機の非対称
   （0.92/0.66/0.44）から遠ざかる．NCC 対実機も 3 灯とも微減で実機方向に近づかない（②と整合）．
3. **①「bottom 単灯の明確な平ら化＝幾何レバー初実証」— 大幅に弱まる**: 平滑後 peak/mean の
   変化は bottom -0.9%・right -0.5%・left ±0 で微小．TEST_10 の raw -10%（1.672→1.508）は
   アーティファクト期の見かけが大半だった（修正後 raw の -12% はヘビーテイルノイズの
   最大値ゆらぎで信頼できない．smooth を正とする）．
4. **②「left/right 不動」— 維持**．

総括: ロフト重み変形は修正後データでは「全灯・単灯とも実質不感で，むしろ 3 灯を同形化する
（実機の非対称からは遠ざかる）小さな混合強化」と再解釈される．TEST_10 の相対結論のうち
不感性（③④）は生き残り，幾何レバーの実証（①）は撤回相当に弱まった．

## 再現コマンド (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): 再現コマンド
