
2026/07/21 0:07
隅がそのような外観にならない理由:スクリーンスペース環境閉鎖に関する議論(2012)
RSS: https://news.ycombinator.com/rss
要約▶
日本語訳:
Sean Barrett は、広範なスクリーン空間環境遮蔽(SSAO)の使用が物理的な現実と比較して部屋隅の暗さを誇張することで不自然な視覚効果を生み出すと論じます。彼はこれらの失敗要因を以下の 3 つの問題に起因すると指摘しています:放射輝度計算(radiosity)を用いずに SSAO を使用することによる近似誤差、環境成分のみを対象とするのではなく全ての照明に対して暗化を適用すること、そして実際の光の挙動を無視する過剰な調整ウェイトです。
実世界の外観を検証するために、Barrett は Canon 5D Mk II カメラを用いた写真撮影テストを実施しました(露光時間は 1/80 秒から 1/8 秒)、JPEG を 4 倍ダウンサンプリングし、クロマチャンネルを幅 16 のガウシアンフィルタでぼかす処理を行いました。分析結果として、劇的な隅の暗さは真の環境遮蔽によるものではなく、柔らかい影や埋め込み照明によって引き起こされていることが判明しました。ピクセル値グラフは凹み境界におけるわずかな暗さを示しており、マッヒバンドリングによってさらに軽減されることが多く、暗いバスルームテストでは強い視覚的印象にもかかわらず実際の端の暗化は最小限であることが確認されました。光源付近でのテストでは、観測された減衰が追加の AO 成分によるものではなく、直接的な照明の減衰に起因していることが示されました。
Barrett は、凹み境界にはある程度の端部の暗化が存在するものの、それは微妙であり、面朝着けされている表面そのものが既に大幅に暗い場合にのみ顕著になるだけであると結論付けました。彼は SSAO が現実世界の隅の暗化を正確にモデル化していないと主張しており、その主要なメカニズムは微小な実効効果を生成する条件とは異なるためです。Barrett はコミュニティに対してより良いデータを収集して彼の主張を検証することを呼びかけ、支持する検証データのリンクを提供しました。開発者の観点からは、レンダリングパイプラインを調整して人工的な端部の暗化を削減し、より物理的に正確な照明モデルへ移行することを目指すべきであると示唆されています。
本文
シアン・バーレット「Cornell Box の SSAO(環境奥度)に関する考察」
著者: シアン・バーレット (Chien-Barrett)
日付: 2012 年 12 月 13 日
参考文献: Goral ら、「光の拡散表面間の相互作用のモデル化」(1984 年)
概要と問題提起
ゲーム開発者は、スクリーンスペース環境奥度(SSAO)を強く求めている。しかし、その実装には多くの欠陥が存在する。
- アマチュア開発者: 凸角付近で不自然に明るくなる画像を投稿。
- プロフェッショナル開発者: ハロー効果やアーチファクトを含んだゲームをリリース。
これらの欠陥を回避しても、SSAO を用いるゲームは依然として正しく見えません。 ここで学ぶべき簡単な教訓は:部屋の角が実際には不自然に暗く見えることです。
筆者は、この現象の背景に以下の 4 つの要因の組み合わせがあると考えています。
- SSAO の近似誤差(正確な AO を用いない場合と比較)
- AO の近似誤差(正確なラジオシティを用いない場合と比較)
- 環境光への適用誤差(照明全体に暗化を適用する場合の誤差)
- 重み調整のヒューリスティック誤差(AO 効果のウェイトが強すぎるため)
※ 筆者は能動的な研究者ではないため、体系的な検証フレームワークの構築までは行いません。
角はそのように見えません(大部分の場合)
以下の画像はすべて**実写撮影(Canon 5D Mark II)**であり、レンダリング結果ではありません。
- 画質処理: 1/80 秒〜1/8 秒の露光時間。元の JPEG を Adobe Photoshop で約 4 倍に縮小し、クロマノイズをガウスフィルタで軽減。
- 注意点: 天井が壁より明るい色調(白っぽさ)のため、照明の影響と誤解しないように注意してください。
劇的に暗く見せる角
典型的な SSAO レンダリングのように見える角を探しましたが、問題の本質はそこにありませんでした。 以下に示す現象は「画面上部の左上から見える柔らかいシャドウ」や「光源からの距離が近いことによる広がり影」などが原因です。
具体的な要因の解析
- 壁(d)の頂端: 明るい壁の頂端に見える影は、埋め込みタイプ光源の**リブ(唇)**によって直接光が届いていないためです。ここでのすべての光は間接光であり、天井に落ちる光は壁・床・他の物体からの反射光です。
- ランバertian 式の影響: 壁(c)の右端や天井(a)のエッジへの暗化は、主に間接照明効果(表面積と法線の内積)によるものです。
- エッジに近づくほど側面として見え、明度が低下します。
- ただし、これが AO が捕捉すべき要素であり、ここでは過大評価されています。
- マッハ帯(Mach banding)と錯覚:
- エッジの暗化効果の多くは、実際にはマッハ帯による知覚的な錯覚である可能性があります。
- 周囲の壁が明るい場合、背面壁への効果は非常に穏やかになります。
カメラは知覚的ではないが、グラフ化可能
写真は sRGB で出力されていますが、厳密な線形光の値ではありません(トーンマッピングが行われている可能性あり)。 しかし、人間の知覚される明るさの傾向は以下のグラフから把握可能です。
グラフ分析の結果
- 左側(影の影響がある部分): エッジに近づくにつれて照明が急速に暗くなる。
- 右側(明るい部分): 線形的な上昇を示す(柔らかいシャドウの影響)。
- 中央部: 顕著なディップ(明度低下)が見られるが、これは照明効果か汚れによるかの区別が難しい。
エッジごとの比較
| エッジ | 特徴 | AO 的な減衰? |
|---|---|---|
| (a) と (d) | 類似したグラフだが、中央のディップは明瞭 | やや誇張されている可能性あり |
| (a) と (c) | 「リアルワールド AO」に近い、最も間接的な照明を持つエッジ | 減衰規模が有意に減少。中央部への下降がない。 |
注意: グラフは単一ラインで比較しており、遠近縮小の影響によりスケーリングが一貫していない可能性があります。また、壁(c)の幅は 13.5 インチであり、グラフ左半分(緑色線)は約 4 インチ分の明度変化を表しています。
家の中で最も暗い部屋
暗いバスルームでの検証も行われました。
- 直感: エッジに沿って暗化があるように見える。
- 実測結果: 数値を確認すると、暗化はほとんど観測されない(またはマッハ帯による錯覚)。
- 結論: 世界空間での明暗変化は、視覚的に想像するほど顕著ではありません。
さらに多くの角
光源に近い場所や、複数の照明が存在する環境でも検証されました。
重要な発見
- 結論: 「追加的な」SSAO によるエッジへの減衰は存在しない。
- 理由:
- モニター境界線の上の左側の壁に見られる暗化は、別の光源からの影です。
- 天井は右側の壁に近づくにつれて明るくなる(直接照明の影響)。
- AO を適用すると、本来の非現実的な部分だけが強調され、全体として不自然に見せてしまいます。
マッハ帯効果の確認方法
- エッジの一方側を覆って暗化効果を消すと、知覚アーチファクトであることが確認できます(背面壁で実際に発生)。
- 5×5 ボックスフィルタではなく 3×3 ボックスフィルタでグラフ化した際も、結果は似ていました。
私の意見
凹曲率の境界線に沿ったエッジ暗化効果は存在しますが、繊細で常には起こりません。
- 顕著なエッジ暗化が発生するのは、向かい側の表面が別の理由(例:柔らかいシャドウ)により既に暗化されている場合だけです。
- 現在の SSAO が行っているのは、これではなさそうです。
付録 A: グラフ生成用コード
以下の C 言語のコードは、画像ファイル内のラインセグメントをサンプリングし、明度グラフを生成するためのものです。
#define STB_IMAGE_WRITE_IMPLEMENTATION #include "stb_image_write.h" #define STBI_NO_WRITE #include "stb_image.c" #define STB_PLOT_IMPLEMENTATION #include "stb_plot.h" #define STB_DEFINE #include "stb.h" int main(int argc, char **argv) { int i,n; char **data = stb_stringfile("c:/imv_log.txt", &n); for (i=0; i < n; i += 2) { int w,h; int x0,y0,x1,y1,j,len; uint8 *pixels; char file1[999], file2[999], name[999]; stbplot_dataset *ds = stbplot_dataset_create(); stbplot_variable *v = stbplot_dependent_variable(ds, "brightness"); // データラインの解析 if ( sscanf(data[i ], "%d%d%s", &x0,&y0, file1) != 3 || sscanf(data[i+1], "%d%d%s", &x1,&y1, file2) != 3) stb_fatal("Error on line %d\n", i+1); if (strcmp(file1, file2)) stb_fatal("Mismatch %1 vs %2\n", i+1, i+2); pixels = stbi_load(file1, &w, &h, NULL, 3); // バリデーション... (省略) len = max(abs(x1-x0),abs(y1-y0)); for (j=0; j <= len; ++j) { int dx,dy,c,sum=0; int x = (int) stb_linear_remap(j,0,len,x0,x1); int y = (int) stb_linear_remap(j,0,len,y0,y1); // 5x5 ボックスフィルタで平均化 for (dx = -2; dx <= 2; ++dx) for (dy = -2; dy <= 2; ++dy) for (c=0; c < 3; ++c) sum += pixels[(y+dy)*3*w + (x+dx)*3 + c]; stbplot_add_value(j, v, (double) sum / (9*3)); } // グラフ保存 stb_splitpath(name, file1, STB_FILE); stbplot_plot(stb_sprintf("c:/temp2/graph_%s_%d.bmp", name, i/2+1), ds, "Pixel brightness", 600, 400, 1); stbplot_dataset_destroy(ds); // ハイライト処理... (省略) } return 0; }
備考: このプログラムは小規模で単発使用のため、エラーケースへの対応を省略しています。最初の成功したコンパイル後には問題なく動作しました。